[
  {
    "path": ".gitattributes",
    "content": "# Disable LF normalization for all files\r\n* -text\r\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: mcmonkey4eva\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug Report\nabout: Describe an issue with Denizen not working as expected. DO NOT POST HERE UNLESS SOMEONE TOLD YOU TO ON DISCORD.\n\n---\n\n<!--\nIMPORTANT: Support is on Discord @ https://discord.gg/Q6pZGSR\nYou should only post on GitHub if you've been directed here from the Discord.\n\nPlease fill in the asterisks sections below.\nUse https://paste.denizenscript.com/ to pastebin any scripts.\n -->\nThe output of `/version` on my server is: **paste here**\nThe output of `/version denizen` on my server is: **paste here**\n(If relevant) A link to a paste of a script demonstrating the issue: **paste here**\nThe issue is:\n**Describe your issue in full here**\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "content": "---\nname: Feature Request\nabout: Suggest something new to add. DO NOT POST HERE UNLESS SOMEONE TOLD YOU TO ON DISCORD.\n\n---\n\n<!--\nIMPORTANT: Support is on Discord @ https://discord.gg/Q6pZGSR\nYou should only post on GitHub if you've been directed here from the Discord.\n\nPlease ensure you're running the latest version of Denizen (new features are not backported).\nPlease also ensure that what you're asking is not already present in Denizen.\nDescribe your request below.\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/i-m-having-trouble.md",
    "content": "---\nname: I'm Having Trouble\nabout: Make an issue post that seems to be trouble on your end (not a bug in Denizen itself) - ASK ON DISCORD https://discord.gg/Q6pZGSR\n\n---\n\n**STOP**\nYou shouldn't be on GitHub issues!\nAsk for help on Discord: https://discord.gg/Q6pZGSR\n"
  },
  {
    "path": ".gitignore",
    "content": "# Maven\r\ntarget/\r\ndependency-reduced-pom.xml\r\n\r\n# IntelliJ\r\n.idea/\r\n*.iml\r\n\r\n# Temporary/etc.\r\n*.bak\r\n*.exe\r\n.DS_Store\r\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contribution to Denizen\r\n-----------------------\r\n\r\nPlease talk to us on Discord @ https://discord.gg/Q6pZGSR prior to contributing.\r\n\r\n## Signing\r\n\r\nPlease sign [the Contributor License Agreement](https://cla-assistant.io/DenizenScript/Denizen-For-Bukkit).\r\n\r\n## Pull Requests\r\n\r\nFor pull requests:\r\n- You MUST: own the code you submit OR it is free/open/safe to use, EG it is in the public domain. Code gathered from within any open source DenizenScript project is valid to include here as well.\r\n\t- If your code is under any pre-existing license, you MUST note it in the pull request.\r\n- Any and all code you submit is subject to the mini-license below.\r\n- In general, you should confirm your PR is functional in any ways that it may vary.\r\n\t- EG if your code adds an NMS call, it should be tested across multiple game versions.\r\n- USUALLY, your pull request should fix an open issue or feature request made in a Discord thread.\r\n\t- If there is no issue or thread for it, please open a thread.\r\n\t- If issues or threads for it have been closed with a refusal statement, ensure you want to be making a PR at all before bothering with it. Generally, refused requests means PRs are also refused.\r\n\t\t- The exception to this is when an issue is only partially related to what you're doing, or you believe in good faith that the issue was refused on grounds made irrelevant by your adaptation of it.\r\n\t\t\t- In these cases, please open a new issue or thread.\r\n- Do NOT click the `Resolve conversation` button on any PR comments. Ever.\r\n\r\n## Code Style / Formatting\r\n\r\nMatch the general format/style of existing code within the project. For the most part, this is standard Oracle Java code conventions. As a particular difference from some standards, there must always be a newline after `}`.\r\n\r\n## Mini-license pre-warning\r\n\r\nBy contributing to the project, you give up all rights to your contribution\\*.\r\n\r\nIf you later decide you don't want us using your code - you may make a polite request and it will be treated as such, but that is the extent of your abilities.\r\n\r\n\\* This does not include your rights to continue making use of any involved code yourself, which you of course may continue to do. You give up specifically your rights to the contribution *within this project*. Or to put it another way: your rights to the specific copy of the contribution that is now contained within this project, as specifically opposed to ideas and/or expressions thereof within the code or other work contributed, which you remain free to do with as you wish (excluding actions that conflict with our ownership of the copy given to us).\r\n\r\n### You agree by contributing:\r\n\r\n- That you have read and agree to this document.\r\n- To not attempt to \"revoke rights to your code\" or any similar action.\r\n- That you have the right to publicly contribute any assets/code given. (IE, no contributing someone else's code without their permission)\r\n- That the code submitted is either your own work, dedicated now to this project, OR it is under a license specified directly in the contribution.\r\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\r\n\r\nCopyright (c) 2019-2026 The Denizen Script Team\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
  },
  {
    "path": "README.md",
    "content": "The Denizen Scripting Language - Spigot Impl\r\n--------------------------------------------\r\n\r\nAn implementation of the Denizen Scripting Language for Spigot servers, with strong Citizens interlinks to emphasize the power of using Denizen with NPCs!\r\n\r\n**Version 1.3.2**: Compatible with Spigot 1.17.1, 1.18.2, 1.19.4, 1.20.6, 1.21.11, and 26.1.2!\r\n\r\n**Learn about Denizen from the Beginner's guide:** https://guide.denizenscript.com/guides/background/index.html\r\n\r\n#### Download Links:\r\n\r\n- **Release builds**: https://ci.citizensnpcs.co/job/Denizen/\r\n- **Developmental builds**: https://ci.citizensnpcs.co/job/Denizen_Developmental/\r\n- **SpigotMC - VERY SLOW releases**: https://www.spigotmc.org/resources/denizen.21039/\r\n\r\n#### Need help using Denizen? Try one of these places:\r\n\r\n- **Discord** - chat room (Modern, strongly recommended): https://discord.gg/Q6pZGSR\r\n- **Denizen Home Page** - a link directory (Modern): https://denizenscript.com/\r\n- **Forum and script sharing** (Modern): https://forum.denizenscript.com/\r\n- **Meta Documentation** - command/tag/event/etc. search (Modern): https://meta.denizenscript.com/\r\n- **Beginner's Guide** - text form (Modern): https://guide.denizenscript.com/\r\n\r\n#### Also check out:\r\n\r\n- **Citizens2 (NPC support)**: https://github.com/CitizensDev/Citizens2/\r\n- **Depenizen (Other plugin support)**: https://github.com/DenizenScript/Depenizen\r\n- **dDiscordBot (Adds a Discord bot to Denizen)**: https://github.com/DenizenScript/dDiscordBot\r\n- **DenizenCore (Our core, needed for building)**: https://github.com/DenizenScript/Denizen-Core\r\n- **DenizenVSCode (extension for writing Denizen scripts in VS Code)**: https://github.com/DenizenScript/DenizenVSCode\r\n\r\n### Building\r\n\r\n- Built against JDK 17, using maven `pom.xml` as project file.\r\n- Requires building all listed versions of Spigot via Spigot BuildTools: https://www.spigotmc.org/wiki/buildtools/\r\n\r\n### Maven\r\n\r\n```xml\r\n    <repository>\r\n        <id>citizens-repo</id>\r\n        <url>https://maven.citizensnpcs.co/repo</url>\r\n    </repository>\r\n    <dependencies>\r\n        <dependency>\r\n            <groupId>com.denizenscript</groupId>\r\n            <artifactId>denizen</artifactId>\r\n            <version>1.3.2-SNAPSHOT</version>\r\n            <type>jar</type>\r\n            <scope>provided</scope>\r\n            <exclusions>\r\n                <exclusion>\r\n                    <groupId>*</groupId>\r\n                    <artifactId>*</artifactId>\r\n                </exclusion>\r\n            </exclusions>\r\n        </dependency>\r\n    </dependencies>\r\n```\r\n\r\n### Licensing pre-note:\r\n\r\nThis is an open source project, provided entirely freely, for everyone to use and contribute to.\r\n\r\nIf you make any changes that could benefit the community as a whole, please contribute upstream.\r\n\r\n### The short of the license is:\r\n\r\nYou can do basically whatever you want, except you may not hold any developer liable for what you do with the software.\r\n\r\n### Previous License\r\n\r\nCopyright (C) 2012-2013 Aufdemrand, All Rights Reserved.\r\n\r\nCopyright (C) 2013-2019 The Denizen Script Team, All Rights Reserved.\r\n\r\n### The long version of the license follows:\r\n\r\nThe MIT License (MIT)\r\n\r\nCopyright (c) 2019-2026 The Denizen Script Team\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
  },
  {
    "path": "dist/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>com.denizenscript</groupId>\n        <artifactId>denizen-parent</artifactId>\n        <version>1.3.2-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>denizen-dist</artifactId>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <BUILD_NUMBER>Unknown</BUILD_NUMBER>\n        <BUILD_CLASS>CUSTOM</BUILD_CLASS>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizencore</artifactId>\n            <version>1.91.0-SNAPSHOT</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen-v1_17</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen-v1_18</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen-v1_19</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen-v1_20</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen-v1_21</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen-v26_1</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen-paper</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <sourceDirectory>src/main/java</sourceDirectory>\n        <resources>\n            <resource>\n                <filtering>false</filtering>\n                <directory>${basedir}/src/main/resources</directory>\n                <includes>\n                    <include>*.mid</include>\n                </includes>\n            </resource>\n            <resource>\n                <filtering>true</filtering>\n                <directory>${basedir}/src/main/resources</directory>\n                <includes>\n                    <include>*.yml</include>\n                    <include>*.dsc</include>\n                </includes>\n            </resource>\n        </resources>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>3.4.1</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                        <configuration>\n                            <minimizeJar>true</minimizeJar>\n                            <filters>\n                                <filter>\n                                    <artifact>com.denizenscript:denizencore</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                                <filter>\n                                    <artifact>com.denizenscript:denizen</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                                <filter>\n                                    <artifact>org.json:json</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                                <filter>\n                                    <artifact>com.denizenscript:denizen-v**</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                                <filter>\n                                    <artifact>com.denizenscript:denizen-paper</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                                <filter>\n                                    <artifact>com.denizenscript:denizen-nmshandler</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                                <filter>\n                                    <artifact>net.kyori:adventure-nbt</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                            </filters>\n                            <relocations>\n                                <relocation>\n                                    <pattern>com.freneticllc</pattern>\n                                    <shadedPattern>com.denizenscript.shaded.com.freneticllc</shadedPattern>\n                                </relocation>\n                                <relocation>\n                                    <pattern>org.json</pattern>\n                                    <shadedPattern>com.denizenscript.shaded.org.json</shadedPattern>\n                                </relocation>\n                                <relocation>\n                                    <pattern>org.ow2.asm</pattern>\n                                    <shadedPattern>com.denizenscript.shaded.org.ow2.asm</shadedPattern>\n                                </relocation>\n                                <relocation>\n                                    <pattern>org.objectweb</pattern>\n                                    <shadedPattern>com.denizenscript.shaded.org.objectweb</shadedPattern>\n                                </relocation>\n                                <relocation>\n                                    <pattern>org.apache</pattern>\n                                    <shadedPattern>com.denizenscript.shaded.org.apache</shadedPattern>\n                                </relocation>\n                                <relocation>\n                                    <pattern>net.kyori.option</pattern>\n                                    <shadedPattern>com.denizenscript.shaded.net.kyori.option</shadedPattern>\n                                </relocation>\n                                <relocation>\n                                    <pattern>net.kyori.adventure.nbt</pattern>\n                                    <shadedPattern>com.denizenscript.shaded.net.adventure.nbt</shadedPattern>\n                                </relocation>\n                            </relocations>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <version>3.3.0</version>\n                <configuration>\n                    <finalName>Denizen-${DENIZEN_VERSION}-b${BUILD_NUMBER}-${BUILD_CLASS}</finalName>\n                    <outputDirectory>../target</outputDirectory>\n                    <archive>\n                        <manifest>\n                            <addClasspath>true</addClasspath>\n                            <classpathPrefix>Denizen/lib/</classpathPrefix>\n                        </manifest>\n                    </archive>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.10.1</version>\n                <configuration>\n                    <source>16</source>\n                    <target>16</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "dist/src/main/java/com/denizenscript/denizen/dist/Dist.java",
    "content": "package com.denizenscript.denizen.dist;\r\n\r\npublic class Dist {\r\n    // Empty class just to make things work.\r\n}\r\n"
  },
  {
    "path": "paper/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.denizenscript</groupId>\n    <artifactId>denizen-paper</artifactId>\n    <version>1.3.2-SNAPSHOT</version>\n\n    <repositories>\n        <repository>\n            <id>papermc</id>\n            <url>https://repo.papermc.io/repository/maven-public/</url>\n        </repository>\n    </repositories>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen</artifactId>\n            <version>${project.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.papermc.paper</groupId>\n            <artifactId>paper-api</artifactId>\n            <version>[26.1.2.build,)</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>net.citizensnpcs</groupId>\n            <artifactId>citizens-main</artifactId>\n            <version>2.0.42-SNAPSHOT</version>\n            <type>jar</type>\n            <scope>provided</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>*</groupId>\n                    <artifactId>*</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.10.1</version>\n                <configuration>\n                    <source>16</source>\n                    <target>16</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/PaperEventHelpers.java",
    "content": "package com.denizenscript.denizen.paper;\r\n\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.InventoryScriptContainer;\r\nimport com.denizenscript.denizen.utilities.VanillaTagHelper;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryViewUtil;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent;\r\nimport io.papermc.paper.event.server.ServerResourcesReloadedEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PaperEventHelpers implements Listener {\r\n\r\n    @EventHandler\r\n    public void onRecipeBookClick(PlayerRecipeBookClickEvent event) {\r\n        InventoryTag inventory = InventoryTag.mirrorBukkitInventory(InventoryViewUtil.getTopInventory(event.getPlayer().getOpenInventory()));\r\n        if (inventory.getIdHolder() instanceof ScriptTag) {\r\n            if (((InventoryScriptContainer) ((ScriptTag) inventory.getIdHolder()).getContainer()).gui) {\r\n                event.setCancelled(true);\r\n            }\r\n        }\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.LOWEST)\r\n    public void onServerResourcesReloaded(ServerResourcesReloadedEvent event) {\r\n        VanillaTagHelper.loadTagsCache();\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/PaperModule.java",
    "content": "package com.denizenscript.denizen.paper;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.paper.datacomponents.ComponentAdaptersRegistry;\r\nimport com.denizenscript.denizen.paper.events.*;\r\nimport com.denizenscript.denizen.paper.properties.*;\r\nimport com.denizenscript.denizen.paper.tags.PaperTagBase;\r\nimport com.denizenscript.denizen.paper.utilities.PaperAPIToolsImpl;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.kyori.adventure.text.Component;\r\nimport net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport org.bukkit.Bukkit;\r\n\r\npublic class PaperModule {\r\n\r\n    public static void init() {\r\n        Debug.log(\"Loading Paper support module...\");\r\n\r\n        ScriptEvent.notNameParts.add(0, \"PaperImpl\");\r\n        // Events\r\n        ScriptEvent.registerScriptEvent(AnvilBlockDamagedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(AreaEnterExitScriptEventPaperImpl.class);\r\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) {\r\n            ScriptEvent.registerScriptEvent(BellRingScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(BlockPreDispenseScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(CreeperIgnitesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(DragonEggFormScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityAddToWorldScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityKnocksbackEntityScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityLoadCrossbowScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityPathfindScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityRemoveFromWorldScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityStepsOnScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            ScriptEvent.registerScriptEvent(EntityTeleportedByPortalScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(ExperienceOrbMergeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerAbsorbsExperienceScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerBeaconEffectScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            ScriptEvent.registerScriptEvent(PlayerChangesFramedItemScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerChoosesArrowScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerChunkUnloadScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerClicksFakeEntityScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerClicksInRecipeBookScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerClientOptionsChangeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerCompletesAdvancementScriptEventPaperImpl.class);\r\n        ScriptEvent.registerScriptEvent(PlayerDeepSleepScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerElytraBoostScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerEquipsArmorScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerGrantedAdvancementCriterionScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            ScriptEvent.registerScriptEvent(PlayerInventorySlotChangeScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerItemTakesDamageScriptEventPaperImpl.class);\r\n        ScriptEvent.registerScriptEvent(PlayerJumpsScriptEventPaperImpl.class);\r\n        ScriptEvent.registerScriptEvent(PlayerLecternPageChangeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerLoomPatternSelectScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerNamesEntityScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            ScriptEvent.registerScriptEvent(PlayerOpenSignScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerPreparesGrindstoneCraftScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerQuitsScriptEventPaperImpl.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            ScriptEvent.registerScriptEvent(PlayerRaiseLowerItemScriptEventPaperImpl.class);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            ScriptEvent.registerScriptEvent(PlayerReceivesLinksScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerSelectsStonecutterRecipeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerSetSpawnScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            ScriptEvent.registerScriptEvent(PlayerShieldDisableScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerSpectatesEntityScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerStopsSpectatingScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            ScriptEvent.registerScriptEvent(PlayerTracksEntityScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerTradesWithMerchantScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PreEntitySpawnScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            ScriptEvent.registerScriptEvent(PrePlayerAttackEntityScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(ProjectileCollideScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ServerListPingScriptEventPaperImpl.class);\r\n        ScriptEvent.registerScriptEvent(ServerResourcesReloadedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(SkeletonHorseTrapScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(TargetBlockHitScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) {\r\n            ScriptEvent.registerScriptEvent(TNTPrimesScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(UnknownCommandScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            ScriptEvent.registerScriptEvent(VaultChangesStateScriptEvent.class);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            ScriptEvent.registerScriptEvent(WardenChangesAngerLevelScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(WorldGameRuleChangeScriptEvent.class);\r\n\r\n        // Properties\r\n        PropertyParser.registerProperty(EntityArmsRaised.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            PropertyParser.registerProperty(EntityAutoExpire.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityBodyStingers.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityCarryingEgg.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityCanTick.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityDrinkingPotion.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityEggLayTime.class, EntityTag.class);\r\n            PropertyParser.registerProperty(EntityFriction.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityLeftHanded.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityReputation.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityShouldBurn.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntitySneaking.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityWitherInvulnerable.class, EntityTag.class);\r\n        PropertyParser.registerProperty(ItemArmorStand.class, ItemTag.class);\r\n\r\n        // Components system\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            PropertyParser.registerProperty(ItemRemovedComponents.class, ItemTag.class);\r\n            ComponentAdaptersRegistry.register();\r\n        }\r\n\r\n        // Paper object extensions\r\n        PaperElementExtensions.register();\r\n        PaperEntityExtensions.register();\r\n        PaperItemExtensions.register();\r\n        PaperPlayerExtensions.register();\r\n        PaperWorldExtensions.register();\r\n        // Paper Tags\r\n        new PaperTagBase();\r\n\r\n        // Other helpers\r\n        Bukkit.getPluginManager().registerEvents(new PaperEventHelpers(), Denizen.getInstance());\r\n        PaperAPITools.instance = new PaperAPIToolsImpl();\r\n        PacketOutChat.convertComponentToJsonString = (o) -> componentToJson((Component) o);\r\n    }\r\n\r\n    public static Component parseFormattedText(String text, ChatColor baseColor) {\r\n        if (text == null) {\r\n            return null;\r\n        }\r\n        try {\r\n            return jsonToComponent(FormattedTextHelper.componentToJson(FormattedTextHelper.parse(text, baseColor)));\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.verboseLog(\"Failed to parse formatted text: \" + text.replace(ChatColor.COLOR_CHAR, '&'));\r\n            throw ex;\r\n        }\r\n    }\r\n\r\n    public static String stringifyComponent(Component component) {\r\n        if (component == null) {\r\n            return null;\r\n        }\r\n        return FormattedTextHelper.stringify(FormattedTextHelper.parseJson(componentToJson(component)));\r\n    }\r\n\r\n    public static Component jsonToComponent(String json) {\r\n        if (json == null) {\r\n            return null;\r\n        }\r\n        try {\r\n            return GsonComponentSerializer.gson().deserialize(json);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.verboseLog(\"Failed to parse json to component: \" + json);\r\n            throw ex;\r\n        }\r\n    }\r\n\r\n    public static String componentToJson(Component component) {\r\n        if (component == null) {\r\n            return null;\r\n        }\r\n        return GsonComponentSerializer.gson().serialize(component);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/ComponentAdaptersRegistry.java",
    "content": "package com.denizenscript.denizen.paper.datacomponents;\n\npublic class ComponentAdaptersRegistry {\n\n    public static void register() {\n        DataComponentAdapter.register(new FoodAdapter());\n        DataComponentAdapter.register(new GliderAdapter());\n        DataComponentAdapter.register(new ItemModelAdapter());\n        DataComponentAdapter.register(new MaxDurabilityAdapter());\n        DataComponentAdapter.register(new MaxStackSizeAdapter());\n        DataComponentAdapter.register(new RarityAdapter());\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/DataComponentAdapter.java",
    "content": "package com.denizenscript.denizen.paper.datacomponents;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.properties.item.ItemComponentsPatch;\nimport com.denizenscript.denizen.objects.properties.item.ItemProperty;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport io.papermc.paper.datacomponent.DataComponentType;\nimport org.bukkit.Registry;\nimport org.bukkit.inventory.ItemStack;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\npublic abstract class DataComponentAdapter<D extends ObjectTag, C extends DataComponentType> {\n\n    // <--[language]\n    // @name Item Components\n    // @group Minecraft Logic\n    // @description\n    // Minecraft item components (see <@link url https://minecraft.wiki/w/Data_component_format>) are managed as follows:\n    // Each item type has a default set of component values; a food item will have food components by default, a tool item will have tool components by default, etc.\n    // Different items can override their type's default components, either by setting values that weren't there previously (e.g. making an inedible item edible), or by removing values that are there by default (e.g. making a shield item that can't block).\n    // Items' overrides can later be reset, making them use their type's default values again.\n    //\n    // In Denizen, different item components are represented by item properties.\n    // These properties allow both setting a component override on an item, and clearing/resetting it by providing no input.\n    // Item properties' name will generally match their respective item component's name, but not always!\n    // Due to this, features that take item component names as input (such as <@link tag ItemTag.is_overridden>) accept both Minecraft component names and Denizen property names.\n    //\n    // Here is an example of applying all of this in a script:\n    // <code>\n    // # We define a default apple item\n    // - define apple <item[apple]>\n    // # We remove the apple's \"food\" component, making eating it restore no food points (it is still consumable due to the \"consumable\" component).\n    // - adjust def:apple remove_component:food\n    // # This check will pass, as the apple's \"food\" component is overridden to have no value.\n    // - if <[apple].is_overridden[food]>:\n    //   - narrate \"The apple has a modified food component! It will behave differently to a normal apple.\"\n    // # We reset the apple item's food component by adjusting with no value, making it a normal apple.\n    // - adjust def:apple food:\n    // </code>\n    // -->\n\n    public static final Map<String, DataComponentType> COMPONENTS_BY_PROPERTY = new HashMap<>();\n    public static final String[] EMPTY_STRING_ARRAY = new String[0];\n\n    public static DataComponentType getComponentType(String name) {\n        String nameLower = CoreUtilities.toLowerCase(name);\n        DataComponentType componentType = Registry.DATA_COMPONENT_TYPE.get(Utilities.parseNamespacedKey(nameLower));\n        if (componentType == null) {\n            componentType = COMPONENTS_BY_PROPERTY.get(nameLower);\n        }\n        return componentType;\n    }\n\n    public static void register(DataComponentAdapter<?, ?> adapter) {\n        DataComponentAdapter.Property.currentlyRegisteringComponentAdapter = adapter;\n        PropertyParser.registerPropertyGetter(\n                item -> !item.getItemStack().isEmpty() ? adapter.new Property(item) : null,\n                ItemTag.class, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, DataComponentAdapter.Property.class);\n        DataComponentAdapter.Property.currentlyRegisteringComponentAdapter = null;\n        String componentName = adapter.componentType.key().asMinimalString();\n        ItemComponentsPatch.registerHandledComponent(componentName);\n        if (!adapter.name.equals(componentName)) {\n            COMPONENTS_BY_PROPERTY.put(adapter.name, adapter.componentType);\n        }\n    }\n\n    static {\n\n        // <--[tag]\n        // @attribute <ItemTag.is_overridden[<component>]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether an item has a specific item component type overridden, see <@link language Item Components>.\n        // -->\n        ItemTag.tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"is_overridden\", (attribute, object, param) -> {\n            DataComponentType componentType = getComponentType(param.asString());\n            if (componentType == null) {\n                attribute.echoError(\"Invalid type specified, must be a valid item component type or property name.\");\n                return null;\n            }\n            return new ElementTag(object.getItemStack().isDataOverridden(componentType));\n        });\n    }\n\n    public final C componentType;\n    public final Class<D> denizenType;\n    public final String name;\n\n    public DataComponentAdapter(C componentType, Class<D> denizenType, String name) {\n        this.componentType = componentType;\n        this.denizenType = denizenType;\n        this.name = name;\n    }\n\n    public abstract D getValue(ItemStack item);\n\n    public abstract void setValue(ItemStack item, D value, Mechanism mechanism);\n\n    public boolean isDefaultValue(D value) {\n        return false;\n    }\n\n    public static abstract class NonValued extends DataComponentAdapter<ElementTag, DataComponentType.NonValued> {\n\n        public NonValued(DataComponentType.NonValued componentType, String name) {\n            super(componentType, ElementTag.class, name);\n        }\n\n        @Override\n        public ElementTag getValue(ItemStack item) {\n            return new ElementTag(item.hasData(componentType));\n        }\n\n        @Override\n        public void setValue(ItemStack item, ElementTag value, Mechanism mechanism) {\n            if (!mechanism.requireBoolean()) {\n                return;\n            }\n            if (value.asBoolean()) {\n                item.setData(componentType);\n            }\n            else {\n                item.unsetData(componentType);\n            }\n        }\n\n        // Overridden and false = removed, managed by ItemTag.removed_components\n        @Override\n        public boolean isDefaultValue(ElementTag value) {\n            return !value.asBoolean();\n        }\n    }\n\n    public static abstract class Valued<D extends ObjectTag, P> extends DataComponentAdapter<D, DataComponentType.Valued<P>> {\n\n        public static <T, D extends ObjectTag> void setIfValid(Consumer<T> setter, MapTag data, String key, Class<D> objectType, Predicate<D> checker, Function<D, T> converter, String type, Mechanism mechanism) {\n            D value = data.getObjectAs(key, objectType, mechanism.context);\n            if (value == null) {\n                return;\n            }\n            T converted;\n            if ((checker != null && !checker.test(value)) || (converted = converter.apply(value)) == null) {\n                mechanism.echoError(\"Invalid '\" + key + \"' specified: must be a \" + type + '.');\n                return;\n            }\n            setter.accept(converted);\n        }\n\n        public Valued(Class<D> denizenType, DataComponentType.Valued<P> componentType, String name) {\n            super(componentType, denizenType, name);\n        }\n\n        public abstract D toDenizen(P value);\n\n        public abstract P fromDenizen(D value, Mechanism mechanism);\n\n        @Override\n        public D getValue(ItemStack item) {\n            P data = item.getData(componentType);\n            return data != null ? toDenizen(data) : null;\n        }\n\n        @Override\n        public void setValue(ItemStack item, D value, Mechanism mechanism) {\n            P converted = fromDenizen(value, mechanism);\n            if (converted != null) {\n                item.setData(componentType, converted);\n            }\n        }\n    }\n\n    public class Property extends ItemProperty<D> {\n\n        private static DataComponentAdapter<?, ?> currentlyRegisteringComponentAdapter;\n\n        public Property(ItemTag item) {\n            this.object = item;\n        }\n\n        @Override\n        public D getPropertyValue() {\n            return getValue(getItemStack());\n        }\n\n        @Override\n        public D getPropertyValueNoDefault() {\n            if (!getItemStack().isDataOverridden(componentType)) {\n                return null;\n            }\n            return super.getPropertyValueNoDefault();\n        }\n\n        @Override\n        public boolean isDefaultValue(D value) {\n            return DataComponentAdapter.this.isDefaultValue(value);\n        }\n\n        @Override\n        public void setPropertyValue(D value, Mechanism mechanism) {\n            if (value == null) {\n                getItemStack().resetData(componentType);\n                return;\n            }\n            setValue(getItemStack(), value, mechanism);\n        }\n\n        @Override\n        public String getPropertyId() {\n            return name;\n        }\n\n        public static void register() {\n            autoRegisterNullable(currentlyRegisteringComponentAdapter.name, DataComponentAdapter.Property.class, currentlyRegisteringComponentAdapter.denizenType, false);\n        }\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/FoodAdapter.java",
    "content": "package com.denizenscript.denizen.paper.datacomponents;\n\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport io.papermc.paper.datacomponent.DataComponentTypes;\nimport io.papermc.paper.datacomponent.item.FoodProperties;\n\npublic class FoodAdapter extends DataComponentAdapter.Valued<MapTag, FoodProperties> {\n\n    // <--[property]\n    // @object ItemTag\n    // @name food\n    // @input MapTag\n    // @description\n    // Controls an item's food <@link language Item Components>.\n    // The map includes keys:\n    // - \"nutrition\", ElementTag(Number) representing the amount of food points restored by this item.\n    // - \"saturation\", ElementTag(Decimal) representing the amount of saturation points restored by this item.\n    // - \"can_always_eat\", ElementTag(Boolean) controlling whether the item can always be eaten, even if the player isn't hungry.\n    // @mechanism\n    // Provide no input to reset the item to its default value.\n    // -->\n\n    public FoodAdapter() {\n        super(MapTag.class, DataComponentTypes.FOOD, \"food\");\n    }\n\n    @Override\n    public MapTag toDenizen(FoodProperties value) {\n        MapTag foodData = new MapTag();\n        foodData.putObject(\"nutrition\", new ElementTag(value.nutrition()));\n        foodData.putObject(\"saturation\", new ElementTag(value.saturation()));\n        foodData.putObject(\"can_always_eat\", new ElementTag(value.canAlwaysEat()));\n        return foodData;\n    }\n\n    @Override\n    public FoodProperties fromDenizen(MapTag value, Mechanism mechanism) {\n        FoodProperties.Builder builder = FoodProperties.food();\n        setIfValid(builder::nutrition, value, \"nutrition\", ElementTag.class, ElementTag::isInt, ElementTag::asInt, \"number\", mechanism);\n        setIfValid(builder::saturation, value, \"saturation\", ElementTag.class, ElementTag::isFloat, ElementTag::asFloat, \"decimal number\", mechanism);\n        setIfValid(builder::canAlwaysEat, value, \"can_always_eat\", ElementTag.class, ElementTag::isBoolean, ElementTag::asBoolean, \"boolean\", mechanism);\n        return builder.build();\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/GliderAdapter.java",
    "content": "package com.denizenscript.denizen.paper.datacomponents;\n\nimport io.papermc.paper.datacomponent.DataComponentTypes;\n\npublic class GliderAdapter extends DataComponentAdapter.NonValued {\n\n    // <--[property]\n    // @object ItemTag\n    // @name glider\n    // @input ElementTag(Boolean)\n    // @description\n    // Controls whether an item can be used to glide when equipped (like elytras by default), see <@link language Item Components>.\n    // @mechanism\n    // Provide no input to reset the item to its default value.\n    // -->\n\n    public GliderAdapter() {\n        super(DataComponentTypes.GLIDER, \"glider\");\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/ItemModelAdapter.java",
    "content": "package com.denizenscript.denizen.paper.datacomponents;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport io.papermc.paper.datacomponent.DataComponentTypes;\r\nimport net.kyori.adventure.key.Key;\r\n\r\npublic class ItemModelAdapter extends DataComponentAdapter.Valued<ElementTag, Key> {\r\n\r\n    // <--[property]\r\n    // @object ItemTag\r\n    // @name item_model\r\n    // @input ElementTag\r\n    // @description\r\n    // Controls an item's model <@link language Item Components> in namespaced key format.\r\n    // The default namespace is \"minecraft\", so for example an input of \"stone\" becomes \"minecraft:stone\", and will set the item model to a stone block.\r\n    // This can also be used to display item models from your own custom resource packs.\r\n    // @mechanism\r\n    // Provide no input to reset the item to its default value.\r\n    // -->\r\n\r\n    public ItemModelAdapter() {\r\n        super(ElementTag.class, DataComponentTypes.ITEM_MODEL, \"item_model\");\r\n    }\r\n\r\n    @Override\r\n    public ElementTag toDenizen(Key value) {\r\n        return new ElementTag(value.asMinimalString(), true);\r\n    }\r\n\r\n    @Override\r\n    public Key fromDenizen(ElementTag value, Mechanism mechanism) {\r\n        return Utilities.parseNamespacedKey(value.asString());\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/MaxDurabilityAdapter.java",
    "content": "package com.denizenscript.denizen.paper.datacomponents;\r\n\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport io.papermc.paper.datacomponent.DataComponentTypes;\r\n\r\npublic class MaxDurabilityAdapter extends DataComponentAdapter.Valued<ElementTag, Integer> {\r\n\r\n    // <--[property]\r\n    // @object ItemTag\r\n    // @name max_durability\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls an item's max durability <@link language Item Components>.\r\n    // @mechanism\r\n    // Provide no input to reset the item to its default value.\r\n    // -->\r\n\r\n    public MaxDurabilityAdapter() {\r\n        super(ElementTag.class, DataComponentTypes.MAX_DAMAGE, \"max_durability\");\r\n    }\r\n\r\n    @Override\r\n    public ElementTag toDenizen(Integer value) {\r\n        return new ElementTag(value);\r\n    }\r\n\r\n    @Override\r\n    public Integer fromDenizen(ElementTag value, Mechanism mechanism) {\r\n        return mechanism.requireInteger() ? value.asInt() : null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/MaxStackSizeAdapter.java",
    "content": "package com.denizenscript.denizen.paper.datacomponents;\r\n\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport io.papermc.paper.datacomponent.DataComponentTypes;\r\n\r\npublic class MaxStackSizeAdapter extends DataComponentAdapter.Valued<ElementTag, Integer> {\r\n\r\n    // <--[property]\r\n    // @object ItemTag\r\n    // @name max_stack_size\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls an item's max stack size <@link language Item Components>.\r\n    // @mechanism\r\n    // Provide no input to reset the item to its default value.\r\n    // -->\r\n\r\n    public MaxStackSizeAdapter() {\r\n        super(ElementTag.class, DataComponentTypes.MAX_STACK_SIZE, \"max_stack_size\");\r\n    }\r\n\r\n    @Override\r\n    public ElementTag toDenizen(Integer value) {\r\n        return new ElementTag(value);\r\n    }\r\n\r\n    @Override\r\n    public Integer fromDenizen(ElementTag value, Mechanism mechanism) {\r\n        return mechanism.requireInteger() ? value.asInt() : null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/datacomponents/RarityAdapter.java",
    "content": "package com.denizenscript.denizen.paper.datacomponents;\r\n\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport io.papermc.paper.datacomponent.DataComponentTypes;\r\nimport org.bukkit.inventory.ItemRarity;\r\n\r\npublic class RarityAdapter extends DataComponentAdapter.Valued<ElementTag, ItemRarity> {\r\n\r\n    // <--[property]\r\n    // @object ItemTag\r\n    // @name rarity\r\n    // @input ElementTag\r\n    // @description\r\n    // Controls an item's rarity <@link language Item Components>.\r\n    // See <@link url https://jd.papermc.io/paper/org/bukkit/inventory/ItemRarity.html> for valid rarity values.\r\n    // @mechanism\r\n    // Provide no input to reset the item to its default value.\r\n    // -->\r\n\r\n    public RarityAdapter() {\r\n        super(ElementTag.class, DataComponentTypes.RARITY, \"rarity\");\r\n    }\r\n\r\n    @Override\r\n    public ElementTag toDenizen(ItemRarity value) {\r\n        return new ElementTag(value);\r\n    }\r\n\r\n    @Override\r\n    public ItemRarity fromDenizen(ElementTag value, Mechanism mechanism) {\r\n        return mechanism.requireEnum(ItemRarity.class) ? value.asEnum(ItemRarity.class) : null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/AnvilBlockDamagedScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryViewUtil;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.block.AnvilDamagedEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class AnvilBlockDamagedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // anvil block damaged|breaks\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Switch state:<state> to only process the event if the anvil's new damage state matches the specified state.\r\n    //\r\n    // @Triggers when an anvil is damaged from being used.\r\n    //\r\n    // @Context\r\n    // <context.state> returns an ElementTag of the anvil's new damage state. Refer to <@link url https://jd.papermc.io/paper/1.19/com/destroystokyo/paper/event/block/AnvilDamagedEvent.DamageState.html>.\r\n    // <context.break> returns an ElementTag(Boolean) that signifies whether the anvil will break.\r\n    // <context.inventory> returns the InventoryTag of the anvil's inventory.\r\n    //\r\n    // @Determine\r\n    // \"STATE:<ElementTag>\" to set the anvil's new damage state.\r\n    // \"BREAK:<ElementTag(Boolean)>\" to set weather the anvil will break.\r\n    // -->\r\n\r\n    public AnvilBlockDamagedScriptEvent() {\r\n        registerCouldMatcher(\"anvil block damaged|breaks\");\r\n        registerSwitches(\"state\");\r\n        this.<AnvilBlockDamagedScriptEvent, ElementTag>registerOptionalDetermination(\"state\", ElementTag.class, (evt, context, state) -> {\r\n            if (state.matchesEnum(AnvilDamagedEvent.DamageState.class)) {\r\n                evt.event.setDamageState(state.asEnum(AnvilDamagedEvent.DamageState.class));\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n        this.<AnvilBlockDamagedScriptEvent, ElementTag>registerOptionalDetermination(\"break\", ElementTag.class, (evt, context, value) -> {\r\n            if (value.isBoolean()) {\r\n                evt.event.setBreaking(value.asBoolean());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public AnvilDamagedEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.eventArgLowerAt(2).equals(\"breaks\") && !event.isBreaking()) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getInventory().getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"state\", event.getDamageState().name())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"state\" -> new ElementTag(event.getDamageState());\r\n            case \"inventory\" -> InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n            case \"break\" -> new ElementTag(event.isBreaking());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(InventoryViewUtil.getPlayer(event.getView()));\r\n    }\r\n\r\n    @EventHandler\r\n    public void onAnvilDamaged(AnvilDamagedEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/AreaEnterExitScriptEventPaperImpl.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.entity.AreaEnterExitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport io.papermc.paper.event.entity.EntityMoveEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.entity.EntityDeathEvent;\r\n\r\npublic class AreaEnterExitScriptEventPaperImpl extends AreaEnterExitScriptEvent {\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"cause\") && currentEvent instanceof EntityMoveEvent) {\r\n            return new ElementTag(\"WALK\");\r\n        }\r\n        else if (name.equals(\"from\") && currentEvent instanceof EntityMoveEvent) {\r\n            return new LocationTag(((EntityMoveEvent) currentEvent).getFrom());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public void registerCorrectClass() {\r\n        if (onlyTrackPlayers) {\r\n            initListener(new SpigotListeners());\r\n        }\r\n        else {\r\n            initListener(new PaperListeners());\r\n        }\r\n    }\r\n\r\n    public class PaperListeners extends SpigotListeners {\r\n\r\n        @EventHandler\r\n        public void onEntityMove(EntityMoveEvent event) {\r\n            if (event.getEntity().isValid()) {\r\n                processNewPosition(new EntityTag(event.getEntity()), event.getTo(), event);\r\n            }\r\n        }\r\n\r\n        @EventHandler\r\n        public void onEntityDeath(EntityDeathEvent event) {\r\n            processNewPosition(new EntityTag(event.getEntity()), null, event);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/BellRingScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.block.BellRingEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class BellRingScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n\r\n    public BellRingScriptEvent() {\r\n        registerCouldMatcher(\"bell rings\");\r\n    }\r\n\r\n    public BellRingEvent event;\r\n    public LocationTag location;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getEntity());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return event.getEntity() == null ? null : new EntityTag(event.getEntity()).getDenizenObject();\r\n            case \"location\": return location;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void bellRingEvent(BellRingEvent event) {\r\n        this.event = event;\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/BlockPreDispenseScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport io.papermc.paper.event.block.BlockPreDispenseEvent;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\n\npublic class BlockPreDispenseScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // <block> tries to dispense <item>\n    //\n    // @Group Block\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers before a block dispenses an item.\n    // This event fires before the dispenser fully processes a drop, allowing access to the dispensing slot and cancellation of sound effects.\n    //\n    // @Context\n    // <context.location> returns the LocationTag of the dispenser.\n    // <context.item> returns the ItemTag of the item about to be dispensed.\n    // <context.slot> returns a ElementTag(Number) of the slot that will be dispensed from.\n    //\n    // -->\n\n    public BlockPreDispenseScriptEvent() {\n        registerCouldMatcher(\"<block> tries to dispense <item>\");\n    }\n\n    public BlockPreDispenseEvent event;\n    public ItemTag item;\n    public LocationTag location;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, location)) {\n            return false;\n        }\n        if (!path.tryArgObject(0, location)) {\n            return false;\n        }\n        if (!path.tryArgObject(4, item)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"item\" -> item;\n            case \"location\" -> location;\n            case \"slot\" -> new ElementTag(event.getSlot() + 1);\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onBlockPreDispense(BlockPreDispenseEvent event) {\n        this.event = event;\n        item = new ItemTag(event.getItemStack());\n        location = new LocationTag(event.getBlock().getLocation());\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/CreeperIgnitesScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport com.destroystokyo.paper.event.entity.CreeperIgniteEvent;\r\n\r\npublic class CreeperIgnitesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // creeper ignites\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a creeper is ignited by flint and steel, or by certain plugin-based activations.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the creeper.\r\n    // <context.ignited> returns true if the creeper is ignited, or false if not. NOTE: In most cases, this will return true.\r\n    //\r\n    // -->\r\n\r\n    public CreeperIgnitesScriptEvent() {\r\n        registerCouldMatcher(\"creeper ignites\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public CreeperIgniteEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return entity;\r\n            case \"ignited\":\r\n                return new ElementTag(event.isIgnited());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onCreeperIgnites(CreeperIgniteEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/DragonEggFormScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport io.papermc.paper.event.block.DragonEggFormEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class DragonEggFormScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // dragon egg forms\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when the ender dragon is defeated and the dragon egg forms.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the ender dragon right before it's removed.\r\n    // <context.location> returns the LocationTag of the dragon egg.\r\n    // <context.end_portal_location> returns the LocationTag of the end portal.\r\n    // <context.previously_killed> returns an ElementTag(Boolean) of whether the dragon has been previously killed.\r\n    // <context.respawn_phase> returns an ElementTag of the respawn phase. Valid values can be found at <@link url https://jd.papermc.io/paper/1.21.3/org/bukkit/boss/DragonBattle.RespawnPhase.html>.\r\n    // <context.healing_crystals> returns a ListTag(EntityTag) of the healing end crystals.\r\n    // <context.respawn_crystals> returns a ListTag(EntityTag) of the respawn end crystals.\r\n    //\r\n    // -->\r\n\r\n    public DragonEggFormScriptEvent() {\r\n        registerCouldMatcher(\"dragon egg forms\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public EntityTag entity;\r\n    public DragonEggFormEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"location\" -> location;\r\n            case \"end_portal_location\" -> new LocationTag(event.getDragonBattle().getEndPortalLocation());\r\n            case \"previously_killed\" -> new ElementTag(event.getDragonBattle().hasBeenPreviouslyKilled());\r\n            case \"respawn_phase\" -> new ElementTag(event.getDragonBattle().getRespawnPhase());\r\n            case \"healing_crystals\" -> new ListTag(event.getDragonBattle().getHealingCrystals(), EntityTag::new);\r\n            case \"respawn_crystals\" -> new ListTag(event.getDragonBattle().getRespawnCrystals(), EntityTag::new);\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onDragonEggForms(DragonEggFormEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        entity = new EntityTag(event.getDragonBattle().getEnderDragon());\r\n        this.event = event;\r\n        EntityTag.rememberEntity(entity.getBukkitEntity());\r\n        fire(event);\r\n        EntityTag.forgetEntity(entity.getBukkitEntity());\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/EntityAddToWorldScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.entity.EntityAddToWorldEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class EntityAddToWorldScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> added to world\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers any time an entity is added to the world for any reason, including chunks loading pre-existing entities.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that will be added. Note that this entity will not necessarily be fully spawned yet, so usage will be limited.\r\n    // <context.location> returns the LocationTag that the entity will be added at.\r\n    //\r\n    // -->\r\n\r\n    public EntityAddToWorldScriptEvent() {\r\n        registerCouldMatcher(\"<entity> added to world\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    public EntityAddToWorldEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(null, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\": return entity;\r\n            case \"location\": return location;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPreCreatureSpawn(EntityAddToWorldEvent event) {\r\n        this.entity = new EntityTag(event.getEntity());\r\n        this.location = new LocationTag(event.getEntity().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/EntityKnocksbackEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class EntityKnocksbackEntityScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> knocks back <entity>\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning this event may in some cases double-fire, requiring usage of the 'ratelimit' command (like 'ratelimit <player> 1t') to prevent doubling actions.\r\n    //\r\n    // @Switch with:<item> to only process the event when the item used to cause damage (in the damager's hand) is a specified item.\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity is knocked back from the hit of another entity.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that was knocked back.\r\n    // <context.damager> returns the EntityTag of the one who knocked.\r\n    // <context.acceleration> returns the knockback applied as a vector.\r\n    // <context.cause> returns the cause of the knockback (only on MC 1.20+). Causes list: <@link url https://jd.papermc.io/paper/1.21.1/io/papermc/paper/event/entity/EntityKnockbackEvent.Cause.html>\r\n    //\r\n    // @Determine\r\n    // LocationTag as a vector to change the acceleration applied.\r\n    //\r\n    // @Player when the damager or damaged entity is a player. Cannot be both.\r\n    //\r\n    // @NPC when the damager or damaged entity is an NPC. Cannot be both.\r\n    //\r\n    // -->\r\n\r\n    public EntityKnocksbackEntityScriptEvent() {\r\n        registerCouldMatcher(\"<entity> knocks back <entity>\");\r\n        registerSwitches(\"with\");\r\n    }\r\n\r\n\r\n    public EntityTag entity;\r\n    public EntityTag hitBy;\r\n    public ItemTag held;\r\n    public EntityKnockbackByEntityEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String attacker = path.eventArgLowerAt(0);\r\n        String target = path.eventArgLowerAt(3);\r\n        if (!hitBy.tryAdvancedMatcher(attacker, path.context) || (!entity.tryAdvancedMatcher(target, path.context))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, held)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj.canBeType(LocationTag.class)) {\r\n            event.getAcceleration().copy(determinationObj.asType(LocationTag.class, getTagContext(path)).toVector());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(\r\n                hitBy.isPlayer() ? hitBy.getDenizenPlayer() : entity.isPlayer() ? entity.getDenizenPlayer() : null,\r\n                hitBy.isCitizensNPC() ? hitBy.getDenizenNPC() : entity.isCitizensNPC() ? entity.getDenizenNPC() : null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"damager\" -> hitBy.getDenizenObject();\r\n            case \"acceleration\" -> new LocationTag(event.getAcceleration());\r\n            case \"cause\" -> new ElementTag(event.getCause().name(), true); // TODO: once 1.20 is the minimum supported version, use the enum constructor\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityKnockbackEntity(EntityKnockbackByEntityEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        hitBy = new EntityTag(event.getHitBy());\r\n        held = hitBy.getItemInHand();\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/EntityLoadCrossbowScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.entity.EntityLoadCrossbowEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class EntityLoadCrossbowScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> loads crossbow\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Switch crossbow:<item> to only process the event if the crossbow is a specified item.\r\n    //\r\n    // @Triggers when a living entity loads a crossbow with a projectile.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that is loading the crossbow.\r\n    // <context.crossbow> returns the ItemTag of the crossbow.\r\n    // <context.consumes> returns true if the loading will consume a projectile item, otherwise false.\r\n    // <context.hand> returns \"HAND\" or \"OFF_HAND\" depending on which hand is holding the crossbow item.\r\n    //\r\n    // @Determine\r\n    // \"KEEP_ITEM\" to keep the projectile item in the shooter's inventory.\r\n    //\r\n    // @Player when the entity is a player.\r\n    //\r\n    // @NPC when the entity is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityLoadCrossbowScriptEvent() {\r\n        registerCouldMatcher(\"<entity> loads crossbow\");\r\n        registerSwitches(\"crossbow\");\r\n        this.<EntityLoadCrossbowScriptEvent>registerTextDetermination(\"keep_item\", (evt) -> {\r\n            evt.event.setConsumeItem(false);\r\n        });\r\n    }\r\n\r\n    public EntityLoadCrossbowEvent event;\r\n    public EntityTag entity;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, new ItemTag(event.getCrossbow()), \"crossbow\")) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"crossbow\" -> new ItemTag(event.getCrossbow());\r\n            case \"hand\" -> new ElementTag(event.getHand());\r\n            case \"consumes\" -> new ElementTag(event.shouldConsumeItem());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onLoadCrossbow(EntityLoadCrossbowEvent event) {\r\n        this.event = event;\r\n        this.entity = new EntityTag(event.getEntity());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/EntityPathfindScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.entity.EntityPathfindEvent;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class EntityPathfindScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> pathfinds\r\n    //\r\n    // @Location true\r\n    // @Switch to:<area> to only process the event if the entity is pathfinding into a specified area.\r\n    // @Switch at:<entity> to only process the event when the entity is pathfinding at a specified entity.\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity starts pathfinding towards a location or entity.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that is pathfinding.\r\n    // <context.location> returns the LocationTag that is being pathfound to.\r\n    // <context.target> returns the EntityTag that is being targeted, if any.\r\n    //\r\n    // @Player when the target entity is a player.\r\n    //\r\n    // @NPC when the target entity is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityPathfindScriptEvent() {\r\n        registerCouldMatcher(\"<entity> pathfinds\");\r\n        registerSwitches(\"to\", \"at\");\r\n    }\r\n\r\n\r\n    public EntityTag entity;\r\n    public EntityTag target;\r\n    public EntityPathfindEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getLoc(), \"to\")) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"at\", target)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(target);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\")) {\r\n            return entity.getDenizenObject();\r\n        }\r\n        else if (name.equals(\"target\") && target != null) {\r\n            return target.getDenizenObject();\r\n        }\r\n        else if (name.equals(\"location\")) {\r\n            return new LocationTag(event.getLoc());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityPathfind(EntityPathfindEvent event) {\r\n        this.event = event;\r\n        this.entity = new EntityTag(event.getEntity());\r\n        Entity target = event.getTargetEntity();\r\n        this.target = target != null ? new EntityTag(target) : null;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/EntityRemoveFromWorldScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class EntityRemoveFromWorldScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> removed from world\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers any time an entity is removed from the world for any reason, including chunks unloading.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that will be removed. Note that this entity will not necessarily be fully spawned at time of firing, so usage will be limited.\r\n    //\r\n    // -->\r\n\r\n    public EntityRemoveFromWorldScriptEvent() {\r\n        registerCouldMatcher(\"<entity> removed from world\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public EntityRemoveFromWorldEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(null, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPreCreatureSpawn(EntityRemoveFromWorldEvent event) {\r\n        this.entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/EntityStepsOnScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport io.papermc.paper.event.entity.EntityMoveEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class EntityStepsOnScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // entity steps on block\r\n    // <entity> steps on <material>\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Warning This event may fire very rapidly.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a non-player entity steps onto a specific block material. For players, use <@link event player steps on block>.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns an EntityTag of the entity stepping onto the block.\r\n    // <context.location> returns a LocationTag of the block the entity is stepping on.\r\n    // <context.previous_location> returns a LocationTag of where the entity was before stepping onto the block.\r\n    // <context.new_location> returns a LocationTag of where the entity is now.\r\n    //\r\n    // @Example\r\n    // # Announce the name of the entity stepping on the block and the material of block.\r\n    // on entity steps on block:\r\n    // - announce \"<context.entity.name> stepped on a <context.location.material.name>!\"\r\n    //\r\n    // @Example\r\n    // # Announce the material of the block a sheep has stepped on.\r\n    // on sheep steps on block:\r\n    // - announce \"A sheep has stepped on a <context.location.material.name>!\"\r\n    //\r\n    // @Example\r\n    // # Announce that a sheep has stepped on a diamond block.\r\n    // on sheep steps on diamond_block:\r\n    // - announce \"A sheep has stepped on a diamond block! Must be a wealthy sheep!\"\r\n    // -->\r\n\r\n    public EntityStepsOnScriptEvent() {\r\n        registerCouldMatcher(\"<entity> steps on <block>\");\r\n    }\r\n\r\n    public EntityMoveEvent event;\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (path.eventLower.startsWith(\"player\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(3, material)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"location\" -> location;\r\n            case \"previous_location\" -> new LocationTag(event.getFrom());\r\n            case \"new_location\" -> new LocationTag(event.getTo());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void entityStepsOnBlockEvent(EntityMoveEvent event) {\r\n        if (!event.hasChangedBlock()) {\r\n            return;\r\n        }\r\n        location = new LocationTag(event.getTo().clone().subtract(0.0, 0.05, 0.0));\r\n        if (!Utilities.isLocationYSafe(location)) {\r\n            return;\r\n        }\r\n        material = new MaterialTag(location.getBlock());\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/EntityTeleportedByPortalScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport io.papermc.paper.event.entity.EntityPortalReadyEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class EntityTeleportedByPortalScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> teleported by portal\r\n    //\r\n    // @Switch to:<world> to only process the event if the world the entity is being teleported to matches the specified WorldTag matcher.\r\n    // @Switch portal_type:<type> to only process the event if the portal's type matches the specified portal type.\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers When an entity is about to be teleported by a portal (currently only fires for nether portals).\r\n    //\r\n    // @Context\r\n    // <context.entity> returns an EntityTag of the entity being teleported.\r\n    // <context.target_world> returns a WorldTag of the world the entity is being teleported to.\r\n    // <context.portal_type> returns an ElementTag of the portal's type. Will be one of <@link url https://jd.papermc.io/paper/1.19/org/bukkit/PortalType.html>.\r\n    //\r\n    // @Determine\r\n    // \"TARGET_WORLD:<WorldTag>\" to set the world the entity will be teleported to.\r\n    // \"REMOVE_TARGET_WORLD\" to remove the target world. Should usually cancel the event instead of using this.\r\n    //\r\n    // -->\r\n\r\n    public EntityTeleportedByPortalScriptEvent() {\r\n        registerCouldMatcher(\"<entity> teleported by portal\");\r\n        registerSwitches(\"to\", \"portal_type\");\r\n        this.<EntityTeleportedByPortalScriptEvent, WorldTag>registerDetermination(\"target_world\", WorldTag.class, (evt, context, targetWorld) -> {\r\n            evt.event.setTargetWorld(targetWorld.getWorld());\r\n        });\r\n        this.<EntityTeleportedByPortalScriptEvent>registerTextDetermination(\"remove_target_world\", (evt) -> {\r\n            evt.event.setTargetWorld(null);\r\n        });\r\n    }\r\n\r\n    EntityPortalReadyEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryObjectSwitch(\"to\", event.getTargetWorld() == null ? null : new WorldTag(event.getTargetWorld()))) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"portal_type\", event.getPortalType().name())) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, new EntityTag(event.getEntity()))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getEntity().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> new EntityTag(event.getEntity());\r\n            case \"target_world\" -> event.getTargetWorld() != null ? new WorldTag(event.getTargetWorld()) : null;\r\n            case \"portal_type\" -> new ElementTag(event.getPortalType());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityPortalReady(EntityPortalReadyEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/ExperienceOrbMergeScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class ExperienceOrbMergeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // experience orbs merge\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when two experience orbs are about to merge.\r\n    //\r\n    // @Context\r\n    // <context.target> returns the EntityTag of the orb that will absorb the other experience orb.\r\n    // <context.source> returns the EntityTag of the orb that will be removed and merged into the target.\r\n    //\r\n    // -->\r\n\r\n    public ExperienceOrbMergeScriptEvent() {\r\n        registerCouldMatcher(\"experience orbs merge\");\r\n    }\r\n\r\n    public ExperienceOrbMergeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getMergeTarget().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(null, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"target\")) {\r\n            return new EntityTag(event.getMergeTarget()).getDenizenObject();\r\n        }\r\n        else if (name.equals(\"source\")) {\r\n            return new EntityTag(event.getMergeSource()).getDenizenObject();\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void experienceOrbsMerge(ExperienceOrbMergeEvent event) {\r\n        this.event = event;\r\n        Entity target = event.getMergeTarget();\r\n        EntityTag.rememberEntity(target);\r\n        fire(event);\r\n        EntityTag.forgetEntity(target);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerAbsorbsExperienceScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerAbsorbsExperienceScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player absorbs experience\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player is absorbing an experience orb.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the experience orb.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerAbsorbsExperienceScriptEvent() {\r\n        registerCouldMatcher(\"player absorbs experience\");\r\n    }\r\n\r\n    public PlayerPickupExperienceEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptEvent.ScriptPath path) {\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\")) {\r\n            return new EntityTag(event.getExperienceOrb());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void absorbsExperience(PlayerPickupExperienceEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerBeaconEffectScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemPotion;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.block.BeaconEffectEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerBeaconEffectScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player beacon effect applied\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a beacon applies an effect to a player.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the beacon applying an effect.\r\n    // <context.effect_data> returns a MapTag of the potion effect in <@link language Potion Effect Format>.\r\n    // <context.effect_type> returns an ElementTag of the effect type.\r\n    // <context.is_primary> returns an ElementTag(Boolean) of whether the beacon effect is the primary effect.\r\n    //\r\n    // @Determine\r\n    // ElementTag to change the applied potion effect (in the same format as <@link tag EntityTag.list_effects>).\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerBeaconEffectScriptEvent() {\r\n        registerCouldMatcher(\"player beacon effect applied\");\r\n    }\r\n\r\n    public BeaconEffectEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        try {\r\n            event.setEffect(ItemPotion.parseLegacyEffectString(determinationObj.toString(), getTagContext(path)));\r\n            return true;\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> new LocationTag(event.getBlock().getLocation());\r\n            case \"effect\" -> new ElementTag(ItemPotion.effectToLegacyString(event.getEffect(), null));\r\n            case \"effect_data\" -> ItemPotion.effectToMap(event.getEffect(), true);\r\n            case \"effect_type\" -> new ElementTag(event.getEffect().getType().getName());\r\n            case \"is_primary\" -> new ElementTag(event.isPrimary());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void beaconEffectEvent(BeaconEffectEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerChangesFramedItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.player.PlayerItemFrameChangeEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerChangesFramedItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player changes framed <item>\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player interacts with an item frame by adding, removing, or rotating the item held in it.\r\n    //\r\n    // @Switch frame:<entity> to only process the event if the item frame entity being matches the input.\r\n    // @Switch action:<action> to only process the event if the change matches the input.\r\n    //\r\n    // @Context\r\n    // <context.frame> returns the EntityTag of the item frame.\r\n    // <context.item> returns the ItemTag of the item held in the item frame.\r\n    // <context.action> returns the ElementTag of the action being performed, based on <@link url https://jd.papermc.io/paper/1.20/io/papermc/paper/event/player/PlayerItemFrameChangeEvent.ItemFrameChangeAction.html>\r\n    //\r\n    // @Determine\r\n    // \"ITEM:<ItemTag>\" to change the item held by the item frame. If there is an item already in the frame, it will be replaced. To remove the item, set it to air.\r\n    //\r\n    // @Player Always.\r\n    // -->\r\n\r\n    public PlayerChangesFramedItemScriptEvent() {\r\n        registerCouldMatcher(\"player changes framed <item>\");\r\n        registerSwitches(\"frame\", \"action\");\r\n        this.<PlayerChangesFramedItemScriptEvent, ItemTag>registerDetermination(\"item\", ItemTag.class, (evt, context, item) -> {\r\n            evt.event.setItemStack(item.getItemStack());\r\n        });\r\n    }\r\n\r\n    public ItemTag item;\r\n    public EntityTag frame;\r\n    public ElementTag action;\r\n    public PlayerItemFrameChangeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(3, item)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"frame\", frame)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"action\", action)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, frame.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"frame\" -> frame;\r\n            case \"item\" -> new ItemTag(event.getItemStack());\r\n            case \"action\" -> action;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerChangesFramedItem(PlayerItemFrameChangeEvent event) {\r\n        item = new ItemTag(event.getItemStack());\r\n        frame = new EntityTag(event.getItemFrame());\r\n        action = new ElementTag(event.getAction());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerChoosesArrowScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.player.PlayerReadyArrowEvent;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerChoosesArrowScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player chooses arrow\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Triggers when a player chooses an arrow to load a bow/crossbow.\r\n    //\r\n    // @Switch arrow:<item> to only process the event when the players chosen arrow matches the input.\r\n    // @Switch bow:<item> to only process the event when the players bow matches the input.\r\n    //\r\n    // @Context\r\n    // <context.arrow> returns the ItemTag of the arrow that was chosen.\r\n    // <context.bow> returns the ItemTag of the bow that is about to get loaded.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Example\r\n    // # This example prevents using any arrow but spectral_arrows.\r\n    // on player chooses arrow arrow:!spectral_arrow:\r\n    // - determine cancelled\r\n    // -->\r\n\r\n    public PlayerChoosesArrowScriptEvent() {\r\n        registerCouldMatcher(\"player chooses arrow\");\r\n        registerSwitches(\"arrow\", \"bow\");\r\n    }\r\n\r\n    public PlayerReadyArrowEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryObjectSwitch(\"arrow\", new ItemTag(event.getArrow()))) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"bow\", new ItemTag(event.getBow()))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"arrow\": return new ItemTag(event.getArrow());\r\n            case \"bow\": return new ItemTag(event.getBow());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(new PlayerTag(event.getPlayer()), null);\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled) {\r\n            final Player p = event.getPlayer();\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), p::updateInventory, 1);\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerChoosesArrow(PlayerReadyArrowEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerChunkUnloadScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.ChunkTag;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport io.papermc.paper.event.packet.PlayerChunkUnloadEvent;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\n\npublic class PlayerChunkUnloadScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // player receives chunk unload\n    //\n    // @Group Paper\n    //\n    // @Location true\n    //\n    // @Plugin Paper\n    //\n    // @Warning This event will fire *extremely* rapidly and almost guarantees lag. Use with maximum caution.\n    //\n    // @Triggers when a Player receives a chunk unload packet.\n    // Should only be used for packet/clientside related stuff. Not intended for modifying server side.\n    // Generally prefer <@link event chunk unloads> in most cases.\n    //\n    // @Context\n    // <context.chunk> returns a ChunkTag of the chunk being unloaded.\n    //\n    // @Player Always.\n    // -->\n\n    public PlayerChunkUnloadScriptEvent() {\n        registerCouldMatcher(\"player receives chunk unload\");\n    }\n\n    public PlayerChunkUnloadEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getPlayer());\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"chunk\" -> new ChunkTag(event.getChunk());\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void playerChunkUnloadEvent(PlayerChunkUnloadEvent event) {\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerClicksFakeEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.player.PlayerUseUnknownEntityEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerClicksFakeEntityScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player (right|left) clicks fake entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Triggers when a player clicks a fake entity, one that is only shown to the player and not tracked by the server.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the entity that was clicked. Note that this entity is not being tracked by the server, so many operations may not be possible on it.\r\n    // This will return null if the player clicks a fake entity that was not spawned via <@link command fakespawn>.\r\n    // <context.hand> returns an ElementTag of the hand used to click.\r\n    // <context.click_type> returns an ElementTag of the click type (left/right).\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerClicksFakeEntityScriptEvent() {\r\n        registerCouldMatcher(\"player (right|left) clicks fake entity\");\r\n    }\r\n\r\n    public PlayerUseUnknownEntityEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptEvent.ScriptPath path) {\r\n        if (path.eventArgLowerAt(1).equals(\"left\") && !event.isAttack()) {\r\n            return false;\r\n        }\r\n        else if (path.eventArgLowerAt(1).equals(\"right\") && event.isAttack()) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                FakeEntity fakeEntity = FakeEntity.getFakeEntityFor(event.getPlayer().getUniqueId(), event.getEntityId());\r\n                if (fakeEntity != null) {\r\n                    return fakeEntity.entity;\r\n                }\r\n                break;\r\n            case \"hand\":\r\n                return new ElementTag(event.getHand());\r\n            case \"click_type\":\r\n                return new ElementTag(event.isAttack() ? \"left\" : \"right\");\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void clickFakeEntity(PlayerUseUnknownEntityEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerClicksInRecipeBookScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerClicksInRecipeBookScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player uses recipe book\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player interacts with their recipe book.\r\n    //\r\n    // @Context\r\n    // <context.recipe> returns the key of the recipe that was clicked.\r\n    // <context.is_all> returns 'true' if the player is trying to make the maximum amount of items from the recipe, otherwise 'false'.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerClicksInRecipeBookScriptEvent() {\r\n        registerCouldMatcher(\"player uses recipe book\");\r\n    }\r\n\r\n    public PlayerRecipeBookClickEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"recipe\":\r\n                return new ElementTag(event.getRecipe().toString());\r\n            case \"is_all\":\r\n                return new ElementTag(event.isMakeAll());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerUsesRecipeBook(PlayerRecipeBookClickEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerClientOptionsChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.SkinParts;\r\nimport com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerClientOptionsChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player client options change\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Triggers when a player changes their client options.\r\n    //\r\n    // @Context\r\n    // <context.server_listings_enabled> returns a ElementTag(Boolean) of whether the player has server listings enabled. Available only on MC 1.19+.\r\n    // <context.chat_visibility> returns an ElementTag of the player's chat visibility. Valid chat visibility options can be found at <@link url https://jd.papermc.io/paper/1.20/com/destroystokyo/paper/ClientOption.ChatVisibility.html>\r\n    // <context.locale> returns an ElementTag of the player's current locale.\r\n    // <context.main_hand> returns a ElementTag of the player's main hand. Can either be LEFT or RIGHT.\r\n    // <context.skin_parts> returns a MapTag of whether the player's skin parts are enabled or not. For example: [cape=true;jacket=false;left_sleeve=true;right_sleeve=false;left_pants=true;right_pants=false;hat=true]\r\n    // <context.view_distance> returns a ElementTag(Number) of the player's view distance.\r\n    // <context.server_listings_changed> returns a ElementTag(Boolean) of whether the player's server listings have changed. Available only on MC 1.19+.\r\n    // <context.chat_colors> returns a ElementTag(Boolean) of whether the player has chat colors enabled.\r\n    // <context.chat_colors_changed> returns a ElementTag(Boolean) of whether the player has toggled their chat colors.\r\n    // <context.chat_visibility_changed> returns a ElementTag(Boolean) of whether the player's chat visibility has changed. Available only on MC 1.19+.\r\n    // <context.locale_changed> returns a ElementTag(Boolean) of whether the player's locale has changed.\r\n    // <context.main_hand_changed> returns a ElementTag(Boolean) of whether the player's main hand has changed.\r\n    // <context.skin_parts_changed> returns a ElementTag(Boolean) of whether the player's skin parts have changed.\r\n    // <context.text_filtering_changed> returns a ElementTag(Boolean) of whether the player's text filtering has changed. Available only on MC 1.19+.\r\n    // <context.text_filtering_enabled> returns a ElementTag(Boolean) of whether the player has text filtering enabled. Available only on MC 1.19+.\r\n    // <context.view_distance_changed> returns a ElementTag(Boolean) of whether the player's view distance has changed.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerClientOptionsChangeScriptEvent() {\r\n        registerCouldMatcher(\"player client options change\");\r\n    }\r\n\r\n    public PlayerClientOptionsChangeEvent event;\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"server_listings_enabled\" -> NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) ? new ElementTag(event.allowsServerListings()) : null;\r\n            case \"chat_visibility\" -> new ElementTag(event.getChatVisibility());\r\n            case \"locale\" -> new ElementTag(event.getLocale());\r\n            case \"main_hand\" -> new ElementTag(event.getMainHand());\r\n            case \"skin_parts\" -> {\r\n                MapTag map = new MapTag();\r\n                SkinParts skinParts = event.getSkinParts();\r\n                map.putObject(\"cape\", new ElementTag(skinParts.hasCapeEnabled()));\r\n                map.putObject(\"jacket\", new ElementTag(skinParts.hasJacketEnabled()));\r\n                map.putObject(\"left_sleeve\", new ElementTag(skinParts.hasLeftSleeveEnabled()));\r\n                map.putObject(\"right_sleeve\", new ElementTag(skinParts.hasRightSleeveEnabled()));\r\n                map.putObject(\"left_pants\", new ElementTag(skinParts.hasLeftPantsEnabled()));\r\n                map.putObject(\"right_pants\", new ElementTag(skinParts.hasRightPantsEnabled()));\r\n                map.putObject(\"hat\", new ElementTag(skinParts.hasHatsEnabled()));\r\n                yield map;\r\n            }\r\n            case \"view_distance\" -> new ElementTag(event.getViewDistance());\r\n            case \"server_listings_changed\" -> NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) ? new ElementTag(event.hasAllowServerListingsChanged()) : null;\r\n            case \"chat_colors_enabled\" -> new ElementTag(event.hasChatColorsEnabled());\r\n            case \"chat_colors_changed\" -> new ElementTag(event.hasChatColorsEnabledChanged());\r\n            case \"chat_visibility_changed\" -> NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) ? new ElementTag(event.hasChatVisibilityChanged()) : null;\r\n            case \"locale_changed\" -> new ElementTag(event.hasLocaleChanged());\r\n            case \"main_hand_changed\" -> new ElementTag(event.hasMainHandChanged());\r\n            case \"skin_parts_changed\" -> new ElementTag(event.hasSkinPartsChanged());\r\n            case \"text_filtering_changed\" -> NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) ? new ElementTag(event.hasTextFilteringChanged()) : null;\r\n            case \"text_filtering_enabled\" -> NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) ? new ElementTag(event.hasTextFilteringEnabled()) : null;\r\n            case \"view_distance_changed\" -> new ElementTag(event.hasViewDistanceChanged());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerClientOptionsChange(PlayerClientOptionsChangeEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerCompletesAdvancementScriptEventPaperImpl.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerCompletesAdvancementScriptEvent;\r\nimport com.denizenscript.denizen.paper.PaperModule;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.md_5.bungee.api.ChatColor;\r\n\r\npublic class PlayerCompletesAdvancementScriptEventPaperImpl extends PlayerCompletesAdvancementScriptEvent {\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"message\": return new ElementTag(PaperModule.stringifyComponent(event.message()));\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determination = determinationObj.toString();\r\n            String lower = CoreUtilities.toLowerCase(determination);\r\n            if (lower.equals(\"no_message\")) {\r\n                event.message(null);\r\n                return true;\r\n            }\r\n            event.message(PaperModule.parseFormattedText(determination, ChatColor.WHITE));\r\n            return true;\r\n        }\r\n        else {\r\n            return super.applyDetermination(path, determinationObj);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerDeepSleepScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.player.PlayerDeepSleepEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerDeepSleepScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player deep sleeps\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player has slept long enough in a bed to count as being in deep sleep and thus skip the night. Cancelling the event prevents the player from qualifying to skip the night.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerDeepSleepScriptEvent() {\r\n        registerCouldMatcher(\"player deep sleeps\");\r\n    }\r\n\r\n    public PlayerDeepSleepEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerDeepSleep(PlayerDeepSleepEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerElytraBoostScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.player.PlayerElytraBoostEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerElytraBoostScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player boosts elytra\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Switch with:<item> to only process the event if the firework item used matches the specified item.\r\n    // @Switch elytra:<item> to only process the event if the elytra used matches the specified item.\r\n    //\r\n    // @Triggers when a player boosts their elytra with a firework rocket while gliding.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the firework item used to boost.\r\n    // <context.entity> returns the firework entity that was spawned.\r\n    // <context.should_keep> returns whether the firework item gets consumed.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Determine\r\n    // \"KEEP:<ElementTag(Boolean)>\" to set whether the firework item should be kept.\r\n    //\r\n    // -->\r\n\r\n    public PlayerElytraBoostScriptEvent() {\r\n        registerCouldMatcher(\"player boosts elytra\");\r\n        registerSwitches(\"with\", \"elytra\");\r\n        this.<PlayerElytraBoostScriptEvent, ElementTag>registerOptionalDetermination(\"keep\", ElementTag.class, (evt, context, value) -> {\r\n            if (value.isBoolean()) {\r\n                evt.event.setShouldConsume(!value.asBoolean());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public PlayerElytraBoostEvent event;\r\n    public ItemTag firework;\r\n    public PlayerTag player;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, player.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, firework)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"elytra\", new ItemTag(player.getPlayerEntity().getEquipment().getChestplate()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"item\" -> firework;\r\n            case \"entity\" -> new EntityTag(event.getFirework());\r\n            case \"should_keep\" -> new ElementTag(!event.shouldConsume());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerElytraBoost(PlayerElytraBoostEvent event) {\r\n        firework = new ItemTag(event.getItemStack());\r\n        player = new PlayerTag(event.getPlayer());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerEquipsArmorScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.destroystokyo.paper.event.player.PlayerArmorChangeEvent;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.HashMap;\r\n\r\npublic class PlayerEquipsArmorScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player equips|unequips armor|helmet|chestplate|leggings|boots\r\n    // player equips|unequips <item>\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Triggers when a player (un)equips armor.\r\n    //\r\n    // @Warning This event is not reliable, and may miss some types of equipment changes or fire when equipment hasn't actually changed.\r\n    //\r\n    // @Context\r\n    // <context.new_item> returns the ItemTag that is now in the slot.\r\n    // <context.old_item> returns the ItemTag that used to be in the slot.\r\n    // <context.slot> returns the name of the slot.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public static HashMap<String, PlayerArmorChangeEvent.SlotType> slotsByName = new HashMap<>();\r\n    public static HashMap<PlayerArmorChangeEvent.SlotType, String> namesBySlot = new HashMap<>();\r\n\r\n    public static void registerSlot(String name, PlayerArmorChangeEvent.SlotType slot) {\r\n        slotsByName.put(name, slot);\r\n        namesBySlot.put(slot, name);\r\n    }\r\n\r\n    public PlayerEquipsArmorScriptEvent() {\r\n        registerSlot(\"helmet\", PlayerArmorChangeEvent.SlotType.HEAD);\r\n        registerSlot(\"chestplate\", PlayerArmorChangeEvent.SlotType.CHEST);\r\n        registerSlot(\"leggings\", PlayerArmorChangeEvent.SlotType.LEGS);\r\n        registerSlot(\"boots\", PlayerArmorChangeEvent.SlotType.FEET);\r\n        registerCouldMatcher(\"player (equips|unequips) armor|helmet|chestplate|leggings|boots\");\r\n        registerCouldMatcher(\"player (equips|unequips) <item>\");\r\n    }\r\n\r\n    public ItemTag oldItem;\r\n    public ItemTag newItem;\r\n    public PlayerArmorChangeEvent.SlotType slot;\r\n    public PlayerTag player;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String type = path.eventArgLowerAt(1);\r\n        String itemCompare = path.eventArgLowerAt(2);\r\n        PlayerArmorChangeEvent.SlotType slotType = slotsByName.get(itemCompare);\r\n        if (slotType != null && slot != slotType) {\r\n            return false;\r\n        }\r\n        if (type.equals(\"equips\")) {\r\n            if (slotType != null) {\r\n                if (newItem.getMaterial().getMaterial() == Material.AIR) {\r\n                    return false;\r\n                }\r\n            }\r\n            else if (!itemCompare.equals(\"armor\") && !newItem.tryAdvancedMatcher(itemCompare, path.context)) {\r\n                return false;\r\n            }\r\n        }\r\n        else { // unequips\r\n            if (slotType != null) {\r\n                if (oldItem.getMaterial().getMaterial() == Material.AIR) {\r\n                    return false;\r\n                }\r\n            }\r\n            else if (!itemCompare.equals(\"armor\") && !oldItem.tryAdvancedMatcher(itemCompare, path.context)) {\r\n                return false;\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"new_item\":\r\n                return newItem;\r\n            case \"old_item\":\r\n                return oldItem;\r\n            case \"slot\":\r\n                return new ElementTag(namesBySlot.get(slot));\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    public String simpleComparisonString(ItemStack stack) {\r\n        if (stack == null) {\r\n            return \"null\";\r\n        }\r\n        stack = stack.clone();\r\n        stack.setAmount(1);\r\n        stack.setDurability((short) 0);\r\n        return CoreUtilities.toLowerCase(new ItemTag(stack).identify());\r\n    }\r\n\r\n    @EventHandler\r\n    public void armorChangeEvent(PlayerArmorChangeEvent event) {\r\n        if (EntityTag.isCitizensNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        if (simpleComparisonString(event.getOldItem()).equals(simpleComparisonString(event.getNewItem()))) {\r\n            return;\r\n        }\r\n        newItem = new ItemTag(event.getNewItem());\r\n        oldItem = new ItemTag(event.getOldItem());\r\n        slot = event.getSlotType();\r\n        player = new PlayerTag(event.getPlayer());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerGrantedAdvancementCriterionScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.player.PlayerAdvancementCriterionGrantEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerGrantedAdvancementCriterionScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player granted advancement criterion\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Switch advancement:<name> to only fire if the advancement for the criterion has the specified name.\r\n    // @Switch criterion:<name> to only fire if the criterion being granted has the specified name.\r\n    //\r\n    // @Triggers when a player is granted a single criterion for an advancement.\r\n    // To fire when ALL the criteria for an advancement is met, use <@link event player completes advancement>\r\n    //\r\n    // @Context\r\n    // <context.advancement> returns the advancement's minecraft ID key.\r\n    // <context.criterion> returns the criterion minecraft ID key.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Example\r\n    // # Prevent a player from being granted an advancement criterion.\r\n    // on player granted advancement criterion:\r\n    // - determine cancelled\r\n    //\r\n    // @Example\r\n    // # This will only narrate when the player is granted the criterion for taming a Calico cat\r\n    // # for the \"A Complete Catalogue\" advancement.\r\n    // on player granted advancement criterion advancement:husbandry/complete_catalogue criterion:calico:\r\n    // - narrate \"That is a pretty cute Calico cat you have there!\"\r\n    //\r\n    // @Example\r\n    // # This will fire for a custom Denizen advancement called \"my_advancement\".\r\n    // on player granted advancement criterion advancement:denizen:my_advancement:\r\n    // - narrate \"You got the advancement!\"\r\n    // -->\r\n\r\n    public PlayerGrantedAdvancementCriterionScriptEvent() {\r\n        registerCouldMatcher(\"player granted advancement criterion\");\r\n        registerSwitches(\"advancement\", \"criterion\");\r\n    }\r\n\r\n    public ElementTag criterion;\r\n    public ElementTag advancement;\r\n    public PlayerAdvancementCriterionGrantEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryObjectSwitch(\"advancement\", advancement)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"criterion\", criterion)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"criterion\" -> criterion;\r\n            case \"advancement\" -> advancement;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerGrantedAdvancementCriterionEvent(PlayerAdvancementCriterionGrantEvent event) {\r\n        this.event = event;\r\n        criterion = new ElementTag(Utilities.namespacedKeyToString(Utilities.parseNamespacedKey(event.getCriterion())));\r\n        advancement = new ElementTag(Utilities.namespacedKeyToString(event.getAdvancement().getKey()));\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerInventorySlotChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.player.PlayerInventorySlotChangeEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerInventorySlotChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player inventory slot changes\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Switch from:<item> to only process the event if the previous item in the slot matches the specified item.\r\n    // @Switch to:<item> to only process the event if the new item in the slot matches the specified item.\r\n    // @Switch slot:<slot> to only process the event if a specific slot was clicked. For slot input options, see <@link language Slot Inputs>.\r\n    //\r\n    // @Triggers when the item in a slot of a player's inventory changes.\r\n    // Note that this fires for every item in the player's inventory when they join.\r\n    //\r\n    // @Context\r\n    // <context.new_item> returns an ItemTag of the new item in the slot.\r\n    // <context.old_item> returns an ItemTag of the previous item in the slot.\r\n    // <context.slot> returns an ElementTag(Number) of the slot that was changed.\r\n    // <context.raw_slot> returns an ElementTag(Number) of the raw number of the slot that was changed.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerInventorySlotChangeScriptEvent() {\r\n        registerCouldMatcher(\"player inventory slot changes\");\r\n        registerSwitches(\"from\", \"to\", \"slot\");\r\n    }\r\n\r\n    public PlayerInventorySlotChangeEvent event;\r\n    public ItemTag oldItem;\r\n    public ItemTag newItem;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!trySlot(path, \"slot\", event.getPlayer(), event.getSlot())) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, oldItem, \"from\")) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, newItem, \"to\")) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"new_item\": return newItem;\r\n            case \"old_item\": return oldItem;\r\n            case \"slot\": return new ElementTag(event.getSlot() + 1);\r\n            case \"raw_slot\": return new ElementTag(event.getRawSlot());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerInventorySlotChange(PlayerInventorySlotChangeEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        oldItem = new ItemTag(event.getOldItemStack());\r\n        newItem = new ItemTag(event.getNewItemStack());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerItemTakesDamageScriptEventPaperImpl.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerItemTakesDamageScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\n\r\npublic class PlayerItemTakesDamageScriptEventPaperImpl extends PlayerItemTakesDamageScriptEvent {\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"original_damage\": return NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18) ? new ElementTag(event.getOriginalDamage()) : null;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerJumpsScriptEventPaperImpl.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerJumpScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.destroystokyo.paper.event.player.PlayerJumpEvent;\r\nimport org.bukkit.event.EventHandler;\r\n\r\npublic class PlayerJumpsScriptEventPaperImpl extends PlayerJumpScriptEvent {\r\n\r\n    @EventHandler\r\n    public void onPlayerJumps(PlayerJumpEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        location = new LocationTag(event.getFrom());\r\n        player = new PlayerTag(event.getPlayer());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerLecternPageChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.player.PlayerLecternPageChangeEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerLecternPageChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player flips lectern page\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Triggers when the player flips to a page in a lectern.\r\n    //\r\n    // @Switch book:<item> to only process the event if the book on the lectern matches the given item.\r\n    //\r\n    // @Context\r\n    // <context.book> returns an ItemTag of the book in the lectern.\r\n    // <context.lectern> returns a LocationTag of the lectern.\r\n    // <context.old_page> returns an ElementTag(Number) of the last page the player was on.\r\n    // <context.new_page> returns an ElementTag(Number) of the new page that the player flipped to.\r\n    // <context.flip_direction> returns the direction in which the player flips the lectern book page, can be either LEFT or RIGHT.\r\n    //\r\n    // @Determine\r\n    // \"PAGE:<ElementTag(Number)>\" to set the page that the player will flip to.\r\n    //\r\n    // @Example\r\n    // # Announce the page the player flipped to.\r\n    // on player flips lectern page:\r\n    // - announce \"<player.name> flipped to page #<context.new_page>!\"\r\n    //\r\n    // @Example\r\n    // # Flips the player to page 5 if they are flagged with \"pancakes\".\r\n    // on player flips lectern page flagged:pancakes:\r\n    // - determine page:5\r\n    //\r\n    // -->\r\n\r\n    public PlayerLecternPageChangeScriptEvent() {\r\n        registerCouldMatcher(\"player flips lectern page\");\r\n        registerSwitches(\"book\");\r\n        this.<PlayerLecternPageChangeScriptEvent, ElementTag>registerOptionalDetermination(\"page\", ElementTag.class, (evt, context, page) -> {\r\n            if (page.isInt()) {\r\n                evt.event.setNewPage(page.asInt() - 1);\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public PlayerLecternPageChangeEvent event;\r\n    \r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getLectern().getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"book\", new ItemTag(event.getBook()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"book\" -> new ItemTag(event.getBook());\r\n            case \"lectern\" -> new LocationTag(event.getLectern().getLocation());\r\n            case \"old_page\" -> new ElementTag(event.getOldPage() + 1);\r\n            case \"new_page\" -> new ElementTag(event.getNewPage() + 1);\r\n            case \"flip_direction\" -> new ElementTag(event.getPageChangeDirection());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n    \r\n    @EventHandler\r\n    public void onPlayerFlipsLecternPage(PlayerLecternPageChangeEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerLoomPatternSelectScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.player.PlayerLoomPatternSelectEvent;\r\nimport org.bukkit.block.banner.PatternType;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerLoomPatternSelectScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player selects loom pattern\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Triggers when a player selects a loom pattern.\r\n    //\r\n    // @Switch type:<pattern> to only process the event if the specified pattern is selected.\r\n    //\r\n    // @Context\r\n    // <context.loom> returns an InventoryTag of the loom.\r\n    // <context.pattern> returns an ElementTag of the selected pattern. Valid pattern types can be found at: <@link url https://jd.papermc.io/paper/1.19/org/bukkit/block/banner/PatternType.html>\r\n    //\r\n    // @Determine\r\n    // \"PATTERN:<ElementTag>\" to set the pattern type of the loom.\r\n    //\r\n    // @Example\r\n    // # Announce the player's selected loom pattern.\r\n    // on player selects loom pattern:\r\n    // - announce \"<player.name> selected the <context.pattern> pattern!\"\r\n    //\r\n    // @Example\r\n    // # If the player selects the \"CREEPER\" pattern type, publicly shames them before setting the pattern to \"SKULL\".\r\n    // on player selects loom pattern type:CREEPER:\r\n    // - announce \"Shame on <player.name> for selecting the creeper pattern!\"\r\n    // - determine pattern:SKULL\r\n    //\r\n    // -->\r\n\r\n    public PlayerLoomPatternSelectScriptEvent() {\r\n        registerCouldMatcher(\"player selects loom pattern\");\r\n        registerSwitches(\"type\");\r\n        this.<PlayerLoomPatternSelectScriptEvent, ElementTag>registerOptionalDetermination(\"pattern\", ElementTag.class, (evt, context, pattern) -> {\r\n            if (Utilities.matchesEnumlike(pattern, PatternType.class)) {\r\n                evt.event.setPatternType(Utilities.elementToEnumlike(pattern, PatternType.class));\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public PlayerLoomPatternSelectEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getLoomInventory().getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"type\", Utilities.enumlikeToElement(event.getPatternType()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"loom\" -> InventoryTag.mirrorBukkitInventory(event.getLoomInventory());\r\n            case \"pattern\" -> Utilities.enumlikeToElement(event.getPatternType());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerSelectsLoomPattern(PlayerLoomPatternSelectEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerNamesEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.paper.PaperModule;\nimport com.denizenscript.denizen.utilities.PaperAPITools;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport io.papermc.paper.event.player.PlayerNameEntityEvent;\nimport net.md_5.bungee.api.ChatColor;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\n\npublic class PlayerNamesEntityScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // player names <entity>\n    //\n    // @Location true\n    //\n    // @Plugin Paper\n    //\n    // @Group Paper\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a player attempts to rename an entity with a name tag.\n    //\n    // @Context\n    // <context.entity> returns an EntityTag of the renamed entity.\n    // <context.old_name> returns the old name of the entity, if any.\n    // <context.name> returns the new name of the entity.\n    // <context.persistent> returns whether this will cause the entity to persist through server restarts.\n    //\n    // @Determine\n    // \"NAME:<ElementTag>\" to set a different name for the entity.\n    // \"PERSISTENT:<ElementTag(Boolean)>\" to set whether the event will cause the entity to persist through restarts. NOTE: Entities may still persist for other reasons. To ensure they do not, use <@link mechanism EntityTag.force_no_persist>.\n    //\n    // @Player Always.\n    //\n    // -->\n\n    public PlayerNamesEntityScriptEvent() {\n        registerCouldMatcher(\"player names <entity>\");\n        this.<PlayerNamesEntityScriptEvent, ElementTag>registerOptionalDetermination(\"persistent\", ElementTag.class, (evt, context, determination) -> {\n            if (determination.isBoolean()) {\n                evt.event.setPersistent(determination.asBoolean());\n                return true;\n            }\n            return false;\n        });\n        this.<PlayerNamesEntityScriptEvent, ElementTag>registerDetermination(\"name\", ElementTag.class, (evt, context, determination) -> {\n            evt.event.setName(PaperModule.parseFormattedText(determination.toString(), ChatColor.WHITE));\n        });\n    }\n\n    public PlayerNameEntityEvent event;\n    public EntityTag entity;\n    public ElementTag oldName;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, entity.getLocation())) {\n            return false;\n        }\n        if (!path.tryArgObject(2, entity)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getPlayer());\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"entity\" -> entity.getDenizenObject();\n            case \"name\" -> new ElementTag(PaperModule.stringifyComponent(event.getName()), true);\n            case \"old_name\" -> oldName;\n            case \"persistent\" -> new ElementTag(event.isPersistent());\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void playerNamesEntity(PlayerNameEntityEvent event) {\n        this.event = event;\n        entity = new EntityTag(event.getEntity());\n        String name = PaperAPITools.instance.getCustomName(entity.getBukkitEntity());\n        oldName = name == null ? null : new ElementTag(name, true);\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerOpenSignScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.player.PlayerOpenSignEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerOpenSignScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player opens sign\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers When a player opens a sign (eg after placing a sign, or by clicking on it to edit it).\r\n    //\r\n    // @Context\r\n    // <context.side> returns an ElementTag of the side of the sign that was clicked (FRONT or BACK).\r\n    // <context.cause> returns an ElementTag of reason the sign was opened - see <@link url https://jd.papermc.io/paper/1.20/io/papermc/paper/event/player/PlayerOpenSignEvent.Cause.html>.\r\n    // <context.location> returns a LocationTag of the sign's location.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerOpenSignScriptEvent() {\r\n        registerCouldMatcher(\"player opens sign\");\r\n    }\r\n\r\n    public PlayerOpenSignEvent event;\r\n    public LocationTag location;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"side\" -> new ElementTag(event.getSide());\r\n            case \"cause\" -> new ElementTag(event.getCause());\r\n            case \"location\" -> location;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerOpenSignEvent(PlayerOpenSignEvent event) {\r\n        location = new LocationTag(event.getSign().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerPreparesGrindstoneCraftScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.inventory.PrepareResultEvent;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.inventory.GrindstoneInventory;\r\n\r\npublic class PlayerPreparesGrindstoneCraftScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player prepares grindstone craft <item>\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Triggers when a player prepares to grind an item.\r\n    //\r\n    // @Context\r\n    // <context.inventory> returns the InventoryTag of the grindstone inventory.\r\n    // <context.result> returns the ItemTag to be crafted.\r\n    //\r\n    // @Determine\r\n    // \"RESULT:<ItemTag>\" to change the item that is crafted.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Warning The player doing the grinding is estimated and may be inaccurate.\r\n    //\r\n    // @Example\r\n    // # This example removes the usually not removable curse of binding enchantment.\r\n    // on player prepares grindstone craft item:\r\n    // - determine result:<context.result.with[remove_enchantments=binding_curse]>\r\n    //\r\n    // -->\r\n\r\n\r\n    public PlayerPreparesGrindstoneCraftScriptEvent() {\r\n        registerCouldMatcher(\"player prepares grindstone craft <item>\");\r\n        this.<PlayerPreparesGrindstoneCraftScriptEvent, ItemTag>registerDetermination(\"result\", ItemTag.class, (evt, context, item) -> {\r\n            evt.event.setResult(item.getItemStack());\r\n        });\r\n    }\r\n\r\n    public PrepareResultEvent event;\r\n    public PlayerTag player;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(4, new ItemTag(event.getResult()))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getInventory().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"inventory\" -> InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n            case \"result\" -> new ItemTag(event.getResult());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerPreparesGrindstoneCraft(PrepareResultEvent event) {\r\n        if (!(event.getInventory() instanceof GrindstoneInventory)) {\r\n            return;\r\n        }\r\n        if (event.getViewers().isEmpty()) {\r\n            return;\r\n        }\r\n        HumanEntity humanEntity = event.getViewers().get(0);\r\n        if (EntityTag.isNPC(humanEntity)) {\r\n            return;\r\n        }\r\n        player = EntityTag.getPlayerFrom(humanEntity);\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerQuitsScriptEventPaperImpl.java",
    "content": "package com.denizenscript.denizen.paper.events;\n\nimport com.denizenscript.denizen.events.player.PlayerQuitsScriptEvent;\nimport com.denizenscript.denizen.paper.PaperModule;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport net.md_5.bungee.api.ChatColor;\n\n\npublic class PlayerQuitsScriptEventPaperImpl extends PlayerQuitsScriptEvent {\n\n    public PlayerQuitsScriptEventPaperImpl() {\n        registerSwitches(\"cause\");\n        this.<PlayerQuitsScriptEventPaperImpl>registerTextDetermination(\"none\", (evt) -> {\n            event.quitMessage(null);\n        });\n        this.<PlayerQuitsScriptEventPaperImpl, ElementTag>registerDetermination(null, ElementTag.class, (evt, context, determination) -> {\n            event.quitMessage(PaperModule.parseFormattedText(determination.toString(), ChatColor.WHITE));\n        });\n    }\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runGenericSwitchCheck(path, \"cause\", event.getReason().name())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"message\" -> new ElementTag(PaperModule.stringifyComponent(event.quitMessage()));\n            case \"cause\" -> new ElementTag(event.getReason());\n            default -> super.getContext(name);\n        };\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerRaiseLowerItemScriptEventPaperImpl.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerRaiseLowerItemScriptEvent;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport io.papermc.paper.event.player.PlayerStopUsingItemEvent;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.player.PlayerDropItemEvent;\r\nimport org.bukkit.event.player.PlayerItemHeldEvent;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic class PlayerRaiseLowerItemScriptEventPaperImpl extends PlayerRaiseLowerItemScriptEvent {\r\n\r\n    public DurationTag heldFor;\r\n    public ElementTag hand;\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"held_for\" -> heldFor;\r\n            case \"hand\" -> hand;\r\n            case \"item\" -> item;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void run(Player pl, String reason) {\r\n        this.reason = new ElementTag(reason);\r\n        player = new PlayerTag(pl);\r\n        item = new ItemTag(pl.getActiveItem());\r\n        hand = new ElementTag(pl.getHandRaised());\r\n        if (item.getBukkitMaterial() == Material.AIR) {\r\n            item = new ItemTag(pl.getEquipment().getItemInMainHand());\r\n            hand = new ElementTag(\"HAND\");\r\n        }\r\n        if (item.getBukkitMaterial() == Material.AIR) {\r\n            item = new ItemTag(pl.getEquipment().getItemInOffHand());\r\n            hand = new ElementTag(\"OFF_HAND\");\r\n        }\r\n        if (item.getBukkitMaterial() == Material.AIR) {\r\n            return;\r\n        }\r\n        heldFor = state ? null : new DurationTag((long) pl.getHandRaisedTime());\r\n        fire();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onStopUsingItem(PlayerStopUsingItemEvent event) {\r\n        signalDidLower(event.getPlayer(), \"lower\");\r\n    }\r\n\r\n    public boolean isHandRaised(Player player, EquipmentSlot slot) {\r\n        if (player.isHandRaised()) {\r\n            return slot == player.getHandRaised();\r\n        }\r\n        return raisedItems.contains(player.getUniqueId());\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerDropItem(PlayerDropItemEvent event) {\r\n        // You can only drop items from your main hand, so if the player's main hand isn't raised, ignore\r\n        if (isHandRaised(event.getPlayer(), EquipmentSlot.HAND) && raisedItems.remove(event.getPlayer().getUniqueId())) {\r\n            cancelled = false;\r\n            state = false;\r\n            Player pl = event.getPlayer();\r\n            player = new PlayerTag(pl);\r\n            // Work around Player#getActiveItem being air in the drop item event\r\n            ItemStack loweredItem = event.getItemDrop().getItemStack();\r\n            item = new ItemTag(loweredItem);\r\n            heldFor = new DurationTag((long) loweredItem.getMaxItemUseDuration() - pl.getItemUseRemainingTime());\r\n            hand = new ElementTag(pl.getHandRaised());\r\n            reason = new ElementTag(\"drop\");\r\n            fire();\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerChangeHeldItem(PlayerItemHeldEvent event) {\r\n        if (isHandRaised(event.getPlayer(), EquipmentSlot.HAND)) {\r\n            signalDidLower(event.getPlayer(), \"hold\");\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerReceivesLinksScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.connection.PlayerConfigurationConnection;\r\nimport io.papermc.paper.connection.PlayerGameConnection;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerLinksSendEvent;\r\n\r\npublic class PlayerReceivesLinksScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player receives links\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Triggers when a player receives a list of server links.\r\n    //\r\n    // @Determine\r\n    // \"LINKS:<ListTag(MapTag)>\" to set the links sent to the player. Each item in the list must be a MapTag in <@link language Server Links Format>.\r\n    // \"ADD_LINKS:<ListTag(MapTag)>\" to send additional links to the player. Each item in the list must be a MapTag in <@link language Server Links Format>.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Warning this may fire early in the player login process, during which the linked player is essentially an offline player.\r\n    //\r\n    // -->\r\n\r\n    public PlayerLinksSendEvent event;\r\n    public PlayerTag player;\r\n\r\n    public PlayerReceivesLinksScriptEvent() {\r\n        registerCouldMatcher(\"player receives links\");\r\n        this.<PlayerReceivesLinksScriptEvent, ListTag>registerDetermination(\"links\", ListTag.class, (evt, context, value) -> {\r\n            Utilities.replaceServerLinks(evt.event.getLinks(), value, context);\r\n        });\r\n        this.<PlayerReceivesLinksScriptEvent, ListTag>registerDetermination(\"add_links\", ListTag.class, (evt, context, value) -> {\r\n            Utilities.fillServerLinks(evt.event.getLinks(), value, context);\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerLinksSend(PlayerLinksSendEvent event) {\r\n        if (event.getConnection() instanceof PlayerGameConnection gameConnection) {\r\n            player = new PlayerTag(gameConnection.getPlayer());\r\n        }\r\n        else if (event.getConnection() instanceof PlayerConfigurationConnection configConnection) {\r\n            player = new PlayerTag(configConnection.getProfile().getId());\r\n        }\r\n        else {\r\n            throw new IllegalStateException(\"Links send event fired with unknown connection type! \" + event.getConnection() + \" / \" + event.getConnection().getClass().getName());\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerSelectsStonecutterRecipeScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerSelectsStonecutterRecipeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player selects stonecutter recipe\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player selects a recipe in a stonecutter.\r\n    //\r\n    // @Switch recipe_id:<recipe_id> to only process the event if the recipe matches the recipe ID.\r\n    //\r\n    // @Context\r\n    // <context.inventory> returns the InventoryTag of the stonecutter inventory.\r\n    // <context.input> returns an ItemTag of the item in the input slot.\r\n    // <context.result> returns an ItemTag of the item in the result slot.\r\n    // <context.recipe_id> returns the ID of the recipe that was selected.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerSelectsStonecutterRecipeScriptEvent() {\r\n        registerCouldMatcher(\"player selects stonecutter recipe\");\r\n        registerSwitches(\"recipe_id\");\r\n    }\r\n\r\n    public PlayerStonecutterRecipeSelectEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getStonecutterInventory().getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"recipe_id\", event.getStonecuttingRecipe().getKey().toString())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"inventory\": return InventoryTag.mirrorBukkitInventory(event.getStonecutterInventory());\r\n            case \"input\": return new ItemTag(event.getStonecutterInventory().getInputItem());\r\n            case \"result\": return new ItemTag(event.getStonecuttingRecipe().getResult());\r\n            case \"recipe_id\": return new ElementTag(String.valueOf(event.getStonecuttingRecipe().getKey()));\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerSelectsStonecutterRecipe(PlayerStonecutterRecipeSelectEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerSetSpawnScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.paper.PaperModule;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport com.destroystokyo.paper.event.player.PlayerSetSpawnEvent;\nimport net.md_5.bungee.api.ChatColor;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\n\npublic class PlayerSetSpawnScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // player sets spawn\n    //\n    // @Cancellable true\n    //\n    // @Location true\n    //\n    // @Plugin Paper\n    //\n    // @Group Paper\n    //\n    // @Triggers when a player's spawn point changes.\n    //\n    // @Switch cause:<cause> to only process when the cause for the event matches the input.\n    //\n    // @Context\n    // <context.cause> returns the reason the player's spawn point changed. A list of causes can be found at <@link url https://jd.papermc.io/paper/1.21.5/com/destroystokyo/paper/event/player/PlayerSetSpawnEvent.Cause.html>.\n    // <context.forced> returns whether this event will persist through source block (bed or respawn anchor) removal.\n    // <context.location> returns a LocationTag of the new respawn location, if any.\n    // <context.message> returns the notification message that is sent to the player.\n    // <context.notify> returns whether the player will be notified their spawn point changed.\n    //\n    // @Determine\n    // \"FORCED:<ElementTag(Boolean)>\" to set whether the player's new spawnpoint will persist even if the bed or respawn anchor that triggered the event is removed.\n    // \"MESSAGE:<ElementTag>\" to set the notification message that is sent to the player.\n    // \"NOTIFY:<ElementTag(Boolean)>\" to set whether the player will be notified their spawnpoint changed.\n    // LocationTag to change the respawn location.\n    //\n    // @Player Always.\n    //\n    // -->\n\n    public PlayerSetSpawnScriptEvent() {\n        registerCouldMatcher(\"player sets spawn\");\n        registerSwitches(\"cause\");\n        this.<PlayerSetSpawnScriptEvent, ElementTag>registerOptionalDetermination(\"forced\", ElementTag.class, (evt, context, value) -> {\n            if (value.isBoolean()) {\n                evt.event.setForced(value.asBoolean());\n                return true;\n            }\n            return false;\n        });\n        this.<PlayerSetSpawnScriptEvent, ElementTag>registerDetermination(\"message\", ElementTag.class, (evt, context, message) -> {\n            evt.event.setNotification(PaperModule.parseFormattedText(message.toString(), ChatColor.WHITE));\n        });\n        this.<PlayerSetSpawnScriptEvent, ElementTag>registerOptionalDetermination(\"notify\", ElementTag.class, (evt, context, value) -> {\n            if (value.isBoolean()) {\n                evt.event.setNotifyPlayer(value.asBoolean());\n                return true;\n            }\n            return false;\n        });\n        this.<PlayerSetSpawnScriptEvent, LocationTag>registerDetermination(null, LocationTag.class, (evt, context, location) -> {\n            evt.event.setLocation(location);\n            evt.event.setForced(true); // required if the cause is a bed or respawn anchor\n        });\n    }\n\n    public PlayerSetSpawnEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, event.getLocation())) {\n            return false;\n        }\n        if (!runGenericSwitchCheck(path, \"cause\", event.getCause().toString())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getPlayer());\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"cause\" -> new ElementTag(event.getCause());\n            case \"forced\" -> new ElementTag(event.isForced());\n            case \"location\" -> event.getLocation() != null ? new LocationTag(event.getLocation()) : null;\n            case \"message\" -> event.getNotification() != null ? new ElementTag(PaperModule.stringifyComponent(event.getNotification()), true) : null;\n            case \"notify\" -> new ElementTag(event.willNotifyPlayer());\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onPlayerSetsSpawn(PlayerSetSpawnEvent event) {\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerShieldDisableScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport io.papermc.paper.event.player.PlayerShieldDisableEvent;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\n\npublic class PlayerShieldDisableScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // player shield disables\n    //\n    // @Plugin Paper\n    //\n    // @Group Paper\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers When a players shield is disabled.\n    //\n    // @Context\n    // <context.damager> returns an EntityTag of the attacker who disabled the shield.\n    // <context.cooldown> returns a DurationTag of the cooldown the shield is disabled for.\n    //\n    // @Determine\n    // \"COOLDOWN:<DurationTag>\" to change the cooldown.\n    //\n    // @Player Always.\n    //\n    // -->\n\n    public PlayerShieldDisableScriptEvent() {\n        registerCouldMatcher(\"player shield disables\");\n        this.<PlayerShieldDisableScriptEvent, DurationTag>registerDetermination(\"cooldown\", DurationTag.class, (evt, context, duration) -> {\n            evt.event.setCooldown(duration.getTicksAsInt());\n        });\n    }\n\n    public PlayerShieldDisableEvent event;\n    public LocationTag location;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, location)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"damager\" -> new EntityTag(event.getDamager()).getDenizenObject();\n            case \"cooldown\" -> new DurationTag((long) event.getCooldown());\n            default -> super.getContext(name);\n        };\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getPlayer());\n    }\n\n    @EventHandler\n    public void playerShieldDisableEvent(PlayerShieldDisableEvent event) {\n        location = new LocationTag(event.getPlayer().getLocation());\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerSpectatesEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerSpectatesEntityScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player spectates <entity>\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player starts spectating an entity.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the entity that is being spectated.\r\n    // <context.old_entity> returns the entity that was previously being spectated (or the player themself if they weren't spectating anything).\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerSpectatesEntityScriptEvent() {\r\n        registerCouldMatcher(\"player spectates <entity>\");\r\n    }\r\n\r\n    public PlayerStartSpectatingEntityEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, new EntityTag(event.getNewSpectatorTarget()))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\")) {\r\n            return new EntityTag(event.getNewSpectatorTarget()).getDenizenObject();\r\n        }\r\n        else if (name.equals(\"old_entity\")) {\r\n            return new EntityTag(event.getCurrentSpectatorTarget()).getDenizenObject();\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerSpectateEvent(PlayerStartSpectatingEntityEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerStopsSpectatingScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerStopsSpectatingScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player stops spectating (<entity>)\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player stops spectating an entity.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the entity that was being spectated.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerStopsSpectatingScriptEvent() {\r\n        registerCouldMatcher(\"player stops spectating (<entity>)\");\r\n    }\r\n\r\n    public PlayerStopSpectatingEntityEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.eventArgLowerAt(3).length() > 0 && !path.tryArgObject(3, new EntityTag(event.getSpectatorTarget()))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\")) {\r\n            return new EntityTag(event.getSpectatorTarget()).getDenizenObject();\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerSpectateEvent(PlayerStopSpectatingEntityEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerTracksEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.player.PlayerTrackEntityEvent;\r\nimport io.papermc.paper.event.player.PlayerUntrackEntityEvent;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerTracksEntityScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player tracks|untracks <entity>\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Warning This event may fire very rapidly.\r\n    //\r\n    // @Triggers when a player starts or stops tracking an entity. An entity is tracked/untracked by a player's client when the player moves in/out of its <@link mechanism EntityTag.tracking_range>.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns an EntityTag of the entity being tracked or untracked.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Example\r\n    // # Narrate when the player tracks any entities except for item frames.\r\n    // on player tracks !item_frame:\r\n    // - narrate \"You are now tracking <context.entity.name> at <context.entity.location.simple>\"\r\n    //\r\n    // @Example\r\n    // on player untracks chicken:\r\n    // - narrate \"CHICKEN: No! Come back! :(\"\r\n    // -->\r\n\r\n    public PlayerTracksEntityScriptEvent() {\r\n        registerCouldMatcher(\"player tracks|untracks <entity>\");\r\n    }\r\n\r\n    public String type;\r\n    public Player player;\r\n    public EntityTag entity;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.eventArgLowerAt(1).equals(type)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(2, entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerTracksEntityEvent(PlayerTrackEntityEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        player = event.getPlayer();\r\n        type = \"tracks\";\r\n        fire(event);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerUntracksEntityEvent(PlayerUntrackEntityEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        player = event.getPlayer();\r\n        type = \"untracks\";\r\n        EntityTag.rememberEntity(event.getEntity());\r\n        fire(event);\r\n        EntityTag.forgetEntity(event.getEntity());\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PlayerTradesWithMerchantScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.player.PlayerPurchaseEvent;\r\nimport io.papermc.paper.event.player.PlayerTradeEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerTradesWithMerchantScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player trades with merchant\r\n    //\r\n    // @Switch result:<result> to only process the event if the player received a specific result item.\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player trades with a merchant (villager).\r\n    //\r\n    // @Context\r\n    // <context.merchant> returns the villager that was traded with, if any (may be null for example with 'opentrades' command usage).\r\n    // <context.trade> returns a TradeTag of the trade that was done.\r\n    //\r\n    // @Determine\r\n    // TradeTag to change the trade that should be processed.\r\n    //\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerTradesWithMerchantScriptEvent() {\r\n        registerCouldMatcher(\"player trades with merchant\");\r\n        registerSwitches(\"result\");\r\n        this.<PlayerTradesWithMerchantScriptEvent, TradeTag>registerDetermination(null, TradeTag.class, (evt, context, trade) -> {\r\n            evt.event.setTrade(trade.getRecipe());\r\n        });\r\n    }\r\n\r\n    public PlayerPurchaseEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"result\", new ItemTag(event.getTrade().getResult()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"merchant\" -> event instanceof PlayerTradeEvent tradeEvent ? new EntityTag(tradeEvent.getVillager()) : null;\r\n            case \"trade\" -> new TradeTag(event.getTrade()).duplicate();\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerTradeEvent(PlayerPurchaseEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PreEntitySpawnScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent;\r\nimport com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PreEntitySpawnScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> prespawns (because <'cause'>)\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Warning This event may fire very rapidly, and only fires for NATURAL and SPAWNER reasons.\r\n    //\r\n    // @Triggers before a mob spawns and before the mob is created for spawning. Note that this has a limited number of use cases.\r\n    // The intent of this event is to save server resources for blanket mob banning/limiting scripts. Use the entity spawn event as a backup.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that will be spawned. Note that this entity will not be spawned yet, so usage will be limited.\r\n    // <context.location> returns the LocationTag the entity will spawn at.\r\n    // <context.reason> returns an ElementTag of the reason for spawning. Currently, this event only fires for NATURAL and SPAWNER reasons.\r\n    // <context.spawner_location> returns the LocationTag of the spawner's location if this mob is spawning from a spawner.\r\n    //\r\n    // -->\r\n\r\n    public PreEntitySpawnScriptEvent() {\r\n        registerCouldMatcher(\"<entity> prespawns (because <'cause'>)\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    public PreCreatureSpawnEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(2).equals(\"because\")\r\n                && !path.eventArgLowerAt(3).equalsIgnoreCase(event.getReason().name())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        event.setShouldAbortSpawn(cancelled);\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(null, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\")) {\r\n            return entity;\r\n        }\r\n        else if (name.equals(\"location\")) {\r\n            return location;\r\n        }\r\n        else if (name.equals(\"reason\")) {\r\n            return new ElementTag(event.getReason());\r\n        }\r\n        else if (name.equals(\"spawner_location\") && event instanceof PreSpawnerSpawnEvent) {\r\n            return new LocationTag(((PreSpawnerSpawnEvent) event).getSpawnerLocation());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPreCreatureSpawn(PreCreatureSpawnEvent event) {\r\n        this.entity = new EntityTag(event.getType());\r\n        this.location = new LocationTag(event.getSpawnLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/PrePlayerAttackEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.player.PrePlayerAttackEntityEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PrePlayerAttackEntityScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player tries to attack <entity>\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Switch with:<item> to only process the event if the player attacks the entity with the specified item.\r\n    //\r\n    // @Triggers when the player tries to attack an entity. This occurs before any of the damage logic, so cancelling this event will prevent any sort of sounds from being played when attacking.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the entity that was attacked in this event.\r\n    // <context.will_attack> returns whether this entity would be attacked normally.\r\n    // Entities like falling sand will return false because their entity type does not allow them to be attacked.\r\n    // Note: there may be other factors (invulnerability, etc.) that will prevent this entity from being attacked that this event does not cover.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PrePlayerAttackEntityScriptEvent() {\r\n        registerCouldMatcher(\"player tries to attack <entity>\");\r\n        registerSwitches(\"with\");\r\n    }\r\n\r\n    public PrePlayerAttackEntityEvent event;\r\n    public EntityTag entity;\r\n    public ItemTag item;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(4, entity)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"with\", new ItemTag(event.getPlayer().getEquipment().getItemInMainHand()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"will_attack\" -> new ElementTag(event.willAttack());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPrePlayerAttackEntity(PrePlayerAttackEntityEvent event) {\r\n        this.event = event;\r\n        entity = new EntityTag(event.getAttacked());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/ProjectileCollideScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.entity.ProjectileCollideEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class ProjectileCollideScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <projectile> collides with <entity>\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers N/A - use <@link event projectile hits> with the 'entity' switch on versions above 1.19.\r\n    //\r\n    // @Context\r\n    // <context.projectile> returns the projectile that is colliding.\r\n    // <context.entity> returns the entity that was collided with.\r\n    //\r\n    // @Player When the entity collided with is a player.\r\n    // @NPC When the entity collided with is a NPC.\r\n    //\r\n    // @deprecated Use 'projectile hits' with the 'entity' switch on versions above 1.19.\r\n    //\r\n    // -->\r\n\r\n    public ProjectileCollideScriptEvent() {\r\n        registerCouldMatcher(\"<projectile> collides with <entity>\");\r\n    }\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            BukkitImplDeprecations.projectileCollideEvent.warn(path.container);\r\n        }\r\n        return true;\r\n    }\r\n\r\n    public ProjectileCollideEvent event;\r\n    public EntityTag projectile;\r\n    public EntityTag collidedWith;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getEntity().getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, projectile)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(3, collidedWith)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(collidedWith);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> collidedWith.getDenizenObject();\r\n            case \"projectile\" -> projectile;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void projectileCollideEvent(ProjectileCollideEvent event) {\r\n        this.event = event;\r\n        collidedWith = new EntityTag(event.getCollidedWith());\r\n        projectile = new EntityTag(event.getEntity());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/ServerListPingScriptEventPaperImpl.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.server.ListPingScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.paper.PaperModule;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.destroystokyo.paper.event.server.PaperServerListPingEvent;\r\nimport com.destroystokyo.paper.profile.PlayerProfile;\r\nimport com.destroystokyo.paper.profile.ProfileProperty;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.profile.PlayerTextures;\r\nimport org.jetbrains.annotations.NotNull;\r\nimport org.jetbrains.annotations.Nullable;\r\n\r\nimport java.util.*;\r\nimport java.util.concurrent.CompletableFuture;\r\n\r\npublic class ServerListPingScriptEventPaperImpl extends ListPingScriptEvent {\r\n\r\n    public ServerListPingScriptEventPaperImpl() {\r\n        this.<ServerListPingScriptEventPaperImpl, ElementTag>registerOptionalDetermination(\"protocol_version\", ElementTag.class, (evt, context, version) -> {\r\n            if (version.isInt()) {\r\n                evt.getEvent().setProtocolVersion(version.asInt());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n        this.<ServerListPingScriptEventPaperImpl, ElementTag>registerDetermination(\"version_name\", ElementTag.class, (evt, context, name) -> {\r\n            evt.getEvent().setVersion(name.asString());\r\n        });\r\n        this.<ServerListPingScriptEventPaperImpl, ListTag>registerDetermination(\"exclude_players\", ListTag.class, (evt, context, list) -> {\r\n            HashSet<UUID> exclusions = new HashSet<>();\r\n            for (PlayerTag player : list.filter(PlayerTag.class, context)) {\r\n                exclusions.add(player.getUUID());\r\n            }\r\n            if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\r\n                Iterator<Player> players = evt.getEvent().iterator();\r\n                while (players.hasNext()) {\r\n                    if (exclusions.contains(players.next().getUniqueId())) {\r\n                        players.remove();\r\n                    }\r\n                }\r\n                return;\r\n            }\r\n            ListedPlayersEditor.excludeListedPlayers(evt.getEvent(), exclusions);\r\n        });\r\n        this.<ServerListPingScriptEventPaperImpl, ListTag>registerOptionalDetermination(\"alternate_player_text\", ListTag.class, (evt, context, text) -> {\r\n            if (!CoreConfiguration.allowRestrictedActions) {\r\n                Debug.echoError(\"Cannot use 'alternate_player_text' in list ping event: 'Allow restricted actions' is disabled in Denizen config.yml.\");\r\n                return false;\r\n            }\r\n            if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\r\n                evt.getEvent().getPlayerSample().clear();\r\n                for (String line : text) {\r\n                    FakeProfile lineProf = new FakeProfile();\r\n                    lineProf.setName(line);\r\n                    evt.getEvent().getPlayerSample().add(lineProf);\r\n                }\r\n                return true;\r\n            }\r\n            ListedPlayersEditor.setListedPlayerInfo(evt.getEvent(), text);\r\n            return true;\r\n        });\r\n    }\r\n\r\n    public PaperServerListPingEvent getEvent() {\r\n        return (PaperServerListPingEvent) event;\r\n    }\r\n\r\n    // TODO: workaround for Java trying to load ListedPlayerInfo on old versions, remove once 1.20 is the minimum supported version\r\n    public static class ListedPlayersEditor {\r\n        public static void setListedPlayerInfo(PaperServerListPingEvent event, List<String> lines) {\r\n            event.getListedPlayers().clear();\r\n            for (String line : lines) {\r\n                event.getListedPlayers().add(new PaperServerListPingEvent.ListedPlayerInfo(line, ProfileEditor.NIL_UUID));\r\n            }\r\n        }\r\n\r\n        public static void excludeListedPlayers(PaperServerListPingEvent event, Set<UUID> exclude) {\r\n            event.getListedPlayers().removeIf(listedPlayerInfo -> exclude.contains(listedPlayerInfo.id()));\r\n        }\r\n    }\r\n\r\n    public static class FakeProfile implements PlayerProfile {\r\n        public String name;\r\n        @Override public @Nullable String getName() {\r\n            return name;\r\n        }\r\n        @Override public @NotNull String setName(@Nullable String s) {\r\n            String old = name;\r\n            name = s;\r\n            return old;\r\n        }\r\n        @Override public @Nullable UUID getUniqueId() { return null; }\r\n        @Override public @Nullable UUID getId() { return null; }\r\n        @Override public @Nullable UUID setId(@Nullable UUID uuid) { return null; }\r\n        @Override public @NotNull PlayerTextures getTextures() { return null; }\r\n        @Override public void setTextures(@Nullable PlayerTextures playerTextures) { }\r\n        @Override public @NotNull Set<ProfileProperty> getProperties() { return null; }\r\n        @Override public boolean hasProperty(@Nullable String s) { return false; }\r\n        @Override public void setProperty(@NotNull ProfileProperty profileProperty) { }\r\n        @Override public void setProperties(@NotNull Collection<ProfileProperty> collection) { }\r\n        @Override public boolean removeProperty(@Nullable String s) { return false; }\r\n        @Override public void clearProperties() { }\r\n        @Override public boolean isComplete() { return false; }\r\n        @Override public @NotNull CompletableFuture<PlayerProfile> update() { return null; }\r\n        @Override public com.destroystokyo.paper.profile.@NotNull PlayerProfile clone() { return null; }\r\n        @Override public boolean completeFromCache() { return false; }\r\n        @Override public boolean completeFromCache(boolean b) { return false; }\r\n        @Override public boolean completeFromCache(boolean b, boolean b1) { return false; }\r\n        @Override public boolean complete(boolean b) { return false; }\r\n        @Override public boolean complete(boolean b, boolean b1) { return false; }\r\n        @Override public @NotNull Map<String, Object> serialize() { return null; }\r\n    }\r\n\r\n    @Override\r\n    public void setMotd(String text) {\r\n        event.motd(PaperModule.parseFormattedText(text, ChatColor.WHITE));\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"motd\" -> new ElementTag(PaperModule.stringifyComponent(event.motd()), true);\r\n            case \"protocol_version\" -> new ElementTag(getEvent().getProtocolVersion());\r\n            case \"version_name\" -> new ElementTag(getEvent().getVersion(), true);\r\n            case \"client_protocol_version\" -> new ElementTag(getEvent().getClient().getProtocolVersion());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onListPing(PaperServerListPingEvent event) {\r\n        syncFire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/ServerResourcesReloadedScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport io.papermc.paper.event.server.ServerResourcesReloadedEvent;\n\npublic class ServerResourcesReloadedScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // server resources reloaded\n    //\n    // @Plugin Paper\n    //\n    // @Group Paper\n    //\n    // @Switch cause:<cause> to only process the event if the cause of the resource reload matches the specified cause.\n    //\n    // @Triggers when vanilla resources (such as datapacks) are reloaded (by vanilla commands or by plugins). If you mess with datapacks often, it may be helpful to run <@link command reload> in this event.\n    //\n    // @Context\n    // <context.cause> Returns the cause of the resource reload. Refer to <@link url https://jd.papermc.io/paper/1.19/io/papermc/paper/event/server/ServerResourcesReloadedEvent.Cause.html>\n    //\n    // -->\n\n    public ServerResourcesReloadedScriptEvent() {\n        registerCouldMatcher(\"server resources reloaded\");\n        registerSwitches(\"cause\");\n    }\n\n    public ElementTag cause;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runGenericSwitchCheck(path, \"cause\", cause.asString())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            case \"cause\": return cause;\n        }\n        return super.getContext(name);\n    }\n\n    @EventHandler\n    public void onServerResourcesReloaded(ServerResourcesReloadedEvent event) {\n        cause = new ElementTag(event.getCause());\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/SkeletonHorseTrapScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.destroystokyo.paper.event.entity.SkeletonHorseTrapEvent;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class SkeletonHorseTrapScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // skeleton horse trap\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Triggers when a player gets too close to a trapped skeleton horse and triggers the trap.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns an EntityTag of the skeleton horse.\r\n    // <context.players> returns a ListTag(PlayerTag) of the players involved in the trap.\r\n    // -->\r\n\r\n    public SkeletonHorseTrapScriptEvent() {\r\n        registerCouldMatcher(\"skeleton horse trap\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public SkeletonHorseTrapEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"players\" -> {\r\n                ListTag players = new ListTag();\r\n                for (HumanEntity human : event.getEligibleHumans()) {\r\n                    if (!EntityTag.isNPC(human) && human instanceof Player player) {\r\n                        players.addObject(new PlayerTag(player));\r\n                    }\r\n                }\r\n                yield players;\r\n            }\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onSkeletonHorseTrap(SkeletonHorseTrapEvent event) {\r\n        this.event = event;\r\n        entity = new EntityTag(event.getEntity());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/TNTPrimesScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.destroystokyo.paper.event.block.TNTPrimeEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class TNTPrimesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    public TNTPrimesScriptEvent() {\r\n        registerCouldMatcher(\"tnt primes\");\r\n    }\r\n\r\n    public TNTPrimeEvent event;\r\n    public LocationTag location;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPrimerEntity());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\") && event.getPrimerEntity() != null) {\r\n            return new EntityTag(event.getPrimerEntity()).getDenizenObject();\r\n        }\r\n        else if (name.equals(\"location\")) {\r\n            return location;\r\n        }\r\n        else if (name.equals(\"reason\")) {\r\n            return new ElementTag(event.getReason());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void tntPrimeEvent(TNTPrimeEvent event) {\r\n        this.event = event;\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/TargetBlockHitScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport io.papermc.paper.event.block.TargetHitEvent;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\n\n\npublic class TargetBlockHitScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // target block hit\n    //\n    // @Location true\n    //\n    // @Group Paper\n    //\n    // @Plugin Paper\n    //\n    // @Triggers when a target block is hit by a projectile such as an arrow.\n    //\n    // @Context\n    // <context.projectile> returns an EntityTag of the projectile.\n    // <context.hit_block> returns a LocationTag of the block that was hit.\n    // <context.hit_face> returns a LocationTag vector of the hit normal (like '0,1,0' if the projectile hit the top of a block).\n    // <context.shooter> returns an EntityTag of the entity that shot the projectile, if any.\n    // <context.strength> returns a ElementTag of the emitted redstone strength.\n    //\n    // @Player when the shooter is a player.\n    //\n    // @NPC when the shooter is a npc.\n    // -->\n\n    public TargetBlockHitScriptEvent() {\n        registerCouldMatcher(\"target block hit\");\n    }\n\n    public TargetHitEvent event;\n    public LocationTag hitBlock;\n    public EntityTag projectile;\n    public EntityTag shooter;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, hitBlock)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"projectile\" -> projectile.getDenizenObject();\n            case \"hit_block\" -> hitBlock;\n            case \"hit_face\" -> event.getHitBlockFace() != null ? new LocationTag(event.getHitBlockFace().getDirection()) : null;\n            case \"shooter\" -> shooter != null ? shooter.getDenizenObject() : null;\n            case \"strength\" -> new ElementTag(event.getSignalStrength());\n            default -> super.getContext(name);\n        };\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(shooter);\n    }\n\n    @EventHandler\n    public void onProjectileHit(TargetHitEvent event) {\n        this.event = event;\n        projectile = new EntityTag(event.getEntity());\n        hitBlock = new LocationTag(event.getHitBlock().getLocation());\n        shooter = projectile.getShooter();\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/UnknownCommandScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.paper.PaperModule;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport org.bukkit.command.BlockCommandSender;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.entity.minecart.CommandMinecart;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.command.UnknownCommandEvent;\r\n\r\nimport java.util.Arrays;\r\n\r\npublic class UnknownCommandScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // command unknown\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an unknown command is processed by the server.\r\n    //\r\n    // @Context\r\n    // <context.message> returns an ElementTag of the message to be shown to the command sender.\r\n    // <context.command> returns the command name as an Element.\r\n    // <context.raw_args> returns any args used as an Element.\r\n    // <context.args> returns a ListTag of the arguments.\r\n    // <context.source_type> returns the source of the command. Can be: PLAYER, SERVER, COMMAND_BLOCK, or COMMAND_MINECART.\r\n    // <context.command_block_location> returns the command block's location (if the command was run from one).\r\n    // <context.command_minecart> returns the EntityTag of the command minecart (if the command was run from one).\r\n    //\r\n    // @Determine\r\n    // ElementTag to change the message returned to the command sender.\r\n    // \"NONE\" to cancel the message.\r\n    //\r\n    // @Player when source_type is player.\r\n    //\r\n    // -->\r\n\r\n    public UnknownCommandScriptEvent() {\r\n        registerCouldMatcher(\"command unknown\");\r\n        this.<UnknownCommandScriptEvent, ElementTag>registerDetermination(null, ElementTag.class, (evt, context, text) -> {\r\n            evt.event.message(PaperModule.parseFormattedText(text.toString(), ChatColor.WHITE));\r\n        });\r\n        this.<UnknownCommandScriptEvent>registerTextDetermination(\"none\", (evt) -> {\r\n            evt.event.message(null);\r\n        });\r\n    }\r\n\r\n    public UnknownCommandEvent event;\r\n    public String command;\r\n    public String rawArgs;\r\n    public String sourceType;\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getSender() instanceof Player player ? new PlayerTag(player) : null, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"command\" -> new ElementTag(command, true);\r\n            case \"raw_args\" -> new ElementTag(rawArgs, true);\r\n            case \"args\" -> new ListTag(Arrays.asList(ArgumentHelper.buildArgs(rawArgs, false)), true);\r\n            case \"server\" -> new ElementTag(sourceType.equals(\"server\"));\r\n            case \"source_type\" -> new ElementTag(sourceType, true);\r\n            case \"command_block_location\" -> sourceType.equals(\"command_block\") ? new LocationTag(((BlockCommandSender) event.getSender()).getBlock().getLocation()) : null;\r\n            case \"command_minecart\" -> sourceType.equals(\"command_minecart\") ? new EntityTag((CommandMinecart) event.getSender()) : null;\r\n            case \"message\" -> new ElementTag(PaperModule.stringifyComponent(event.message()), true);\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void unknownCommandEvent(UnknownCommandEvent event) {\r\n        this.event = event;\r\n        String[] splitCommand = event.getCommandLine().split(\" \", 2);\r\n        this.command = splitCommand[0];\r\n        this.rawArgs = splitCommand.length > 1 ? splitCommand[1] : \"\";\r\n        if (event.getSender() instanceof Player) {\r\n            this.sourceType = \"player\";\r\n        }\r\n        else if (event.getSender() instanceof BlockCommandSender) {\r\n            this.sourceType = \"command_block\";\r\n        }\r\n        else if (event.getSender() instanceof CommandMinecart) {\r\n            this.sourceType = \"command_minecart\";\r\n        }\r\n        else {\r\n            this.sourceType = \"server\";\r\n        }\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/VaultChangesStateScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport io.papermc.paper.event.block.VaultChangeStateEvent;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\n\npublic class VaultChangesStateScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // vault changes state\n    //\n    // @Plugin Paper\n    //\n    // @Group Block\n    //\n    // @Cancellable true\n    //\n    // @Location true\n    //\n    // @Triggers when a vault block's state changes. A list of states can be found at <@link url https://jd.papermc.io/paper/org/bukkit/block/data/type/Vault.State.html>.\n    //\n    // @Context\n    // <context.location> returns the LocationTag of the vault block.\n    // <context.old_state> returns the vault state before the change.\n    // <context.new_state> returns the vault state after the change.\n    //\n    // @Player when the change is triggered by a player.\n    //\n    // -->\n\n    public VaultChangesStateScriptEvent() {\n        registerCouldMatcher(\"vault changes state\");\n    }\n\n    public LocationTag location;\n    public VaultChangeStateEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, location)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getPlayer());\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"old_state\" -> new ElementTag(event.getCurrentState());\n            case \"new_state\" -> new ElementTag(event.getNewState());\n            case \"location\" -> location;\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onVaultChangesStateEvent(VaultChangeStateEvent event) {\n        location = new LocationTag(event.getBlock().getLocation());\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/WardenChangesAngerLevelScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.entity.WardenAngerChangeEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class WardenChangesAngerLevelScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // warden changes anger level\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a warden changes its anger level. (In practice, only fires when increasing).\r\n    //\r\n    // @Player when the entity who triggered the change is a player.\r\n    //\r\n    // @NPC when the entity who triggered the change is an NPC.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the warden which changed its anger level.\r\n    // <context.new_anger> returns an ElementTag(Number) of the new anger level.\r\n    // <context.old_anger> returns an ElementTag(Number) of the old anger level.\r\n    // <context.target> returns the EntityTag who triggered the change (if any). (In practice, always present).\r\n    //\r\n    // @Determine\r\n    // \"ANGER:<ElementTag(Number)>\" to set the value of the anger level. Value must be between 0 and 150.\r\n    //\r\n    // @Example\r\n    // on warden changes anger level:\r\n    // - if <context.new_anger> >= 40 && <context.new_anger> < 80:\r\n    //     - announce \"Careful, the warden is agitated!\"\r\n    //\r\n    // -->\r\n\r\n    public WardenChangesAngerLevelScriptEvent() {\r\n        registerCouldMatcher(\"warden changes anger level\");\r\n        this.<WardenChangesAngerLevelScriptEvent, ElementTag>registerOptionalDetermination(\"anger\", ElementTag.class, (evt, context, anger) -> {\r\n            if (anger.isInt()) {\r\n                evt.event.setNewAnger(anger.asInt());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public WardenAngerChangeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getEntity().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> new EntityTag(event.getEntity());\r\n            case \"new_anger\" -> new ElementTag(event.getNewAnger());\r\n            case \"old_anger\" -> new ElementTag(event.getOldAnger());\r\n            case \"target\" -> event.getTarget() != null ? new EntityTag(event.getTarget()).getDenizenObject() : null;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getTarget());\r\n    }\r\n\r\n    @EventHandler\r\n    public void onWardenAngerChange(WardenAngerChangeEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/events/WorldGameRuleChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.paper.events;\r\n\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.world.GameRuleReflect;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport io.papermc.paper.event.world.WorldGameRuleChangeEvent;\r\nimport org.bukkit.command.BlockCommandSender;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.entity.minecart.CommandMinecart;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class WorldGameRuleChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // gamerule changes (in <world>)\r\n    //\r\n    // @Plugin Paper\r\n    //\r\n    // @Group Paper\r\n    //\r\n    // @Triggers when a gamerule changes.\r\n    //\r\n    // @Switch gamerule:<gamerule> to only process the event if the gamerule matches a specific gamerule.\r\n    //\r\n    // @Context\r\n    // <context.gamerule> returns the name of the GameRule which was changed. Refer to <@link url https://jd.papermc.io/paper/1.19/org/bukkit/GameRule.html>.\r\n    // <context.value> returns the new value of the GameRule.\r\n    // <context.world> returns the world where the GameRule is applied.\r\n    // <context.source_type> returns type of source. Can be: PLAYER, COMMAND_BLOCK, COMMAND_MINECART, SERVER.\r\n    // <context.command_block_location> returns the command block's location (if the command was run from one).\r\n    // <context.command_minecart> returns the EntityTag of the command minecart (if the command was run from one).\r\n    //\r\n    // @Determine\r\n    // \"VALUE:<ElementTag(Number)>\" or ElementTag(Boolean) to set the value of the GameRule.\r\n    //\r\n    // @Player when the sender of the command is a player.\r\n    //\r\n    // -->\r\n\r\n    public WorldGameRuleChangeScriptEvent() {\r\n        registerCouldMatcher(\"gamerule changes (in <world>)\");\r\n        registerSwitches(\"gamerule\");\r\n        this.<WorldGameRuleChangeScriptEvent, ElementTag>registerOptionalDetermination(\"value\", ElementTag.class, (evt, context, value) -> {\r\n            if (value.isBoolean() || value.isInt()) {\r\n                evt.event.setValue(value.toString());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public WorldGameRuleChangeEvent event;\r\n\r\n    public WorldTag world;\r\n    public CommandSender source;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.eventArgLowerAt(2).equals(\"in\") && !path.tryArgObject(3, world)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"gamerule\", GameRuleReflect.getName(event.getGameRule()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"gamerule\" -> new ElementTag(GameRuleReflect.getName(event.getGameRule()), true);\r\n            case \"value\" -> new ElementTag(event.getValue(), true);\r\n            case \"source_type\" -> getSourceType();\r\n            case \"command_block_location\" -> getCommandBlock();\r\n            case \"command_minecart\" -> getCommandMinecart();\r\n            case \"world\" -> world;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        PlayerTag player = null;\r\n        if (source instanceof Player) {\r\n            player = new PlayerTag((Player) source);\r\n        }\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onGameRuleChanged(WorldGameRuleChangeEvent event) {\r\n        this.source = event.getCommandSender();\r\n        this.world = new WorldTag(event.getWorld());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n\r\n    public LocationTag getCommandBlock() {\r\n        if (source instanceof BlockCommandSender)\r\n            return new LocationTag(((BlockCommandSender) source).getBlock().getLocation());\r\n        return null;\r\n    }\r\n\r\n    public EntityTag getCommandMinecart() {\r\n        if (source instanceof CommandMinecart)\r\n            return new EntityTag(((CommandMinecart) source));\r\n        return null;\r\n    }\r\n\r\n    public ElementTag getSourceType() {\r\n        if (source instanceof Player) {\r\n            return new ElementTag(\"player\");\r\n        }\r\n        else if (source instanceof BlockCommandSender) {\r\n            return new ElementTag(\"command_block\");\r\n        }\r\n        else if (source instanceof CommandMinecart) {\r\n            return new ElementTag(\"command_minecart\");\r\n        }\r\n        else {\r\n            return new ElementTag(\"server\");\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityArmsRaised.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.destroystokyo.paper.entity.RangedEntity;\r\n\r\n@Deprecated\r\npublic class EntityArmsRaised implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof RangedEntity;\r\n    }\r\n\r\n    public static EntityArmsRaised getFrom(ObjectTag _entity) {\r\n        if (!describes(_entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityArmsRaised((EntityTag) _entity);\r\n        }\r\n    }\r\n\r\n    public EntityArmsRaised(EntityTag _entity) {\r\n        entity = _entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"arms_raised\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.arms_raised>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.arms_raised\r\n        // @group properties\r\n        // @Plugin Paper\r\n        // @deprecated use 'aggressive'\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.aggressive>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityArmsRaised.class, ElementTag.class, \"arms_raised\", (attribute, object) -> {\r\n            BukkitImplDeprecations.entityArmsRaised.warn(attribute.context);\r\n            return new ElementTag(object.getRanged().isChargingAttack());\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name arms_raised\r\n        // @input ElementTag(Boolean)\r\n        // @Plugin Paper\r\n        // @group properties\r\n        // @deprecated use 'aggressive'\r\n        // @description\r\n        // Deprecated in favor of <@link mechanism EntityTag.aggressive>.\r\n        // @tags\r\n        // <EntityTag.arms_raised>\r\n        // -->\r\n        PropertyParser.registerMechanism(EntityArmsRaised.class, ElementTag.class, \"arms_raised\", (object, mechanism, input) -> {\r\n            BukkitImplDeprecations.entityArmsRaised.warn(mechanism.context);\r\n            if (mechanism.requireBoolean()) {\r\n                object.getRanged().setChargingAttack(input.asBoolean());\r\n            }\r\n        });\r\n    }\r\n\r\n    public RangedEntity getRanged() {\r\n        return (RangedEntity) entity.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityAutoExpire.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.FallingBlock;\r\n\r\npublic class EntityAutoExpire implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof FallingBlock;\r\n    }\r\n\r\n    public static EntityAutoExpire getFrom(ObjectTag _entity) {\r\n        if (!describes(_entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityAutoExpire((EntityTag) _entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"auto_expire\"\r\n    };\r\n\r\n    public EntityAutoExpire(EntityTag _entity) {\r\n        entity = _entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.auto_expire>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.auto_expire\r\n        // @group properties\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns whether a falling_block will auto-expire (after 30 seconds, or 5 when outside the world).\r\n        // See also <@link tag EntityTag.time_lived>\r\n        // -->\r\n        PropertyParser.registerTag(EntityAutoExpire.class, ElementTag.class, \"auto_expire\", (attribute, object) -> {\r\n            return new ElementTag(object.doesAutoExpire());\r\n        });\r\n    }\r\n\r\n    public FallingBlock getFallingBlock() {\r\n        return (FallingBlock) entity.getBukkitEntity();\r\n    }\r\n\r\n    public boolean doesAutoExpire() {\r\n        return getFallingBlock().doesAutoExpire();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return doesAutoExpire() ? null : \"false\";\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"auto_expire\";\r\n    }\r\n\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name auto_expire\r\n        // @input ElementTag(Boolean)\r\n        // @Plugin Paper\r\n        // @group properties\r\n        // @description\r\n        // Sets whether a falling_block will auto-expire (after 30 seconds, or 5 when outside the world).\r\n        // See also <@link mechanism EntityTag.time_lived>\r\n        // @tags\r\n        // <EntityTag.auto_expire>\r\n        // -->\r\n        if (mechanism.matches(\"auto_expire\") && mechanism.requireBoolean()) {\r\n            getFallingBlock().shouldAutoExpire(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityBodyStingers.java",
    "content": "package com.denizenscript.denizen.paper.properties;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.properties.entity.EntityProperty;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\n\npublic class EntityBodyStingers extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name body_stingers\n    // @input ElementTag(Number)\n    // @plugin Paper\n    // @description\n    // Controls the number of bee stingers stuck in an entity's body.\n    // Note: Bee stingers will only be visible for players or player-type npcs.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.isLivingEntity();\n    }\n\n    @Override\n    public boolean isDefaultValue(ElementTag value) {\n        return value.asInt() == 0;\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(getLivingEntity().getBeeStingersInBody());\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"body_stingers\";\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        if (mechanism.requireInteger()) {\n            getLivingEntity().setBeeStingersInBody(param.asInt());\n        }\n    }\n\n    public static void register() {\n        autoRegister(\"body_stingers\", EntityBodyStingers.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityCanTick.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.ArmorStand;\r\n\r\npublic class EntityCanTick implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof ArmorStand;\r\n    }\r\n\r\n    public static EntityCanTick getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        return new EntityCanTick((EntityTag) entity);\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"can_tick\"\r\n    };\r\n\r\n    public EntityCanTick(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(((ArmorStand) entity.getBukkitEntity()).canTick());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"can_tick\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.can_tick>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.can_tick\r\n        // @group properties\r\n        // @Plugin Paper\r\n        // @description\r\n        // If the entity is an armor stand, returns whether the armor stand can tick.\r\n        // -->\r\n        PropertyParser.registerTag(EntityCanTick.class, ElementTag.class, \"can_tick\", (attribute, entity) -> {\r\n            return new ElementTag(((ArmorStand) entity.entity.getBukkitEntity()).canTick());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name can_tick\r\n        // @input ElementTag(Boolean)\r\n        // @Plugin Paper\r\n        // @group properties\r\n        // @description\r\n        // Changes whether an armor stand can tick.\r\n        // @tags\r\n        // <EntityTag.can_tick>\r\n        // -->\r\n        if (mechanism.matches(\"can_tick\") && mechanism.requireBoolean()) {\r\n            ((ArmorStand) entity.getBukkitEntity()).setCanTick(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityCarryingEgg.java",
    "content": "package com.denizenscript.denizen.paper.properties;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport org.bukkit.entity.Turtle;\n\npublic class EntityCarryingEgg implements Property {\n\n    public static boolean describes(ObjectTag entity) {\n        return entity instanceof EntityTag\n                && ((EntityTag) entity).getBukkitEntity() instanceof Turtle;\n    }\n\n    public static EntityCarryingEgg getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        }\n        else {\n            return new EntityCarryingEgg((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledMechs = new String[] {\n            \"carrying_egg\"\n    };\n\n    public EntityCarryingEgg(EntityTag _entity) {\n        entity = _entity;\n    }\n\n    EntityTag entity;\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <EntityTag.carrying_egg>\n        // @returns ElementTag(Boolean)\n        // @mechanism EntityTag.carrying_egg\n        // @group properties\n        // @Plugin Paper\n        // @description\n        // If the entity is a turtle, returns whether it is carrying an egg. A turtle that is carrying an egg isn't visually different, but can't breed and will eventually lay the egg.\n        // -->\n        PropertyParser.registerTag(EntityCarryingEgg.class, ElementTag.class, \"carrying_egg\", (attribute, entity) -> {\n            return new ElementTag(((Turtle) entity.entity.getBukkitEntity()).hasEgg());\n        });\n    }\n\n    @Override\n    public String getPropertyString() {\n        return ((Turtle) entity.getBukkitEntity()).hasEgg() ? \"true\" : null;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"carrying_egg\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name carrying_egg\n        // @input ElementTag(Boolean)\n        // @Plugin Paper\n        // @group properties\n        // @description\n        // If the entity is a turtle, sets whether it is carrying an egg.\n        // @tags\n        // <EntityTag.carrying_egg>\n        // -->\n        if (mechanism.matches(\"carrying_egg\") && mechanism.requireBoolean()) {\n            ((Turtle) entity.getBukkitEntity()).setHasEgg(mechanism.getValue().asBoolean());\n        }\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityDrinkingPotion.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Witch;\r\n\r\npublic class EntityDrinkingPotion implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Witch;\r\n    }\r\n\r\n    public static EntityDrinkingPotion getFrom(ObjectTag _entity) {\r\n        if (!describes(_entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityDrinkingPotion((EntityTag) _entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"drinking_potion\", \"potion_drink_duration\"\r\n    };\r\n\r\n    public EntityDrinkingPotion(EntityTag _entity) {\r\n        entity = _entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.drinking_potion>\r\n        // @returns ItemTag\r\n        // @mechanism EntityTag.drinking_potion\r\n        // @group properties\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns the potion item a witch is drinking, or air if none.\r\n        // -->\r\n        PropertyParser.registerTag(EntityDrinkingPotion.class, ItemTag.class, \"drinking_potion\", (attribute, object) -> {\r\n            return new ItemTag(object.getWitch().getDrinkingPotion());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.potion_drink_duration>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.potion_drink_duration\r\n        // @group properties\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns the duration remaining until a witch is done drinking a potion.\r\n        // -->\r\n        PropertyParser.registerTag(EntityDrinkingPotion.class, DurationTag.class, \"potion_drink_duration\", (attribute, object) -> {\r\n            return new DurationTag((long) object.getWitch().getPotionUseTimeLeft());\r\n        });\r\n    }\r\n\r\n    public Witch getWitch() {\r\n        return (Witch) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"drinking_potion\";\r\n    }\r\n\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name drinking_potion\r\n        // @input ItemTag\r\n        // @Plugin Paper\r\n        // @group properties\r\n        // @description\r\n        // Sets the potion item a witch is drinking.\r\n        // @tags\r\n        // <EntityTag.drinking_potion>\r\n        // -->\r\n        if (mechanism.matches(\"drinking_potion\") && mechanism.requireObject(ItemTag.class)) {\r\n            ItemTag potion = mechanism.valueAsType(ItemTag.class);\r\n            if (potion.getBukkitMaterial() != Material.POTION) {\r\n                mechanism.echoError(\"Invalid item input '\" + potion + \"': item must be a potion\");\r\n                return;\r\n            }\r\n            getWitch().setDrinkingPotion(potion.getItemStack());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name potion_drink_duration\r\n        // @input DurationTag\r\n        // @Plugin Paper\r\n        // @group properties\r\n        // @description\r\n        // Sets the duration remaining until a witch is done drinking a potion.\r\n        // @tags\r\n        // <EntityTag.potion_drink_duration>\r\n        // -->\r\n        if (mechanism.matches(\"potion_drink_duration\") && mechanism.requireObject(DurationTag.class)) {\r\n            getWitch().setPotionUseTimeLeft(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityEggLayTime.java",
    "content": "package com.denizenscript.denizen.paper.properties;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.properties.entity.EntityProperty;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport org.bukkit.entity.Chicken;\n\npublic class EntityEggLayTime extends EntityProperty<DurationTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name egg_lay_time\n    // @input DurationTag\n    // @plugin Paper\n    // @description\n    // If the entity is a chicken, controls the duration of time until it next lays an egg.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof Chicken;\n    }\n\n    @Override\n    public DurationTag getPropertyValue() {\n        return new DurationTag((long) as(Chicken.class).getEggLayTime());\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"egg_lay_time\";\n    }\n\n    @Override\n    public void setPropertyValue(DurationTag param, Mechanism mechanism) {\n        as(Chicken.class).setEggLayTime(param.getTicksAsInt());\n    }\n\n    public static void register() {\n        autoRegister(\"egg_lay_time\", EntityEggLayTime.class, DurationTag.class, false);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityFriction.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport io.papermc.paper.entity.Frictional;\r\nimport net.kyori.adventure.util.TriState;\r\n\r\npublic class EntityFriction implements Property {\r\n\r\n    public static boolean describes(ObjectTag object) {\r\n        return object instanceof EntityTag entityTag && entityTag.getBukkitEntity() instanceof Frictional;\r\n    }\r\n\r\n    public static EntityFriction getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        return new EntityFriction((EntityTag) entity);\r\n    }\r\n\r\n    public EntityFriction(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public Frictional getFrictional() {\r\n        return (Frictional) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        Boolean frictionState = getFrictional().getFrictionState().toBoolean();\r\n        if (frictionState == null) {\r\n            return null;\r\n        }\r\n        return String.valueOf(frictionState);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"has_friction\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.has_friction>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.has_friction\r\n        // @group properties\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns an entity's friction state if one has been set.\r\n        // -->\r\n        PropertyParser.registerTag(EntityFriction.class, ElementTag.class, \"has_friction\", (attribute, object) -> {\r\n            Boolean frictionState = object.getFrictional().getFrictionState().toBoolean();\r\n            if (frictionState == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(frictionState);\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name has_friction\r\n        // @input ElementTag(Boolean)\r\n        // @Plugin Paper\r\n        // @group properties\r\n        // @description\r\n        // Forces an entity into a friction state, so it either always or never experiences friction.\r\n        // An entity with no friction will move in a direction forever until its velocity is changed or it impacts a block.\r\n        // Does not work with players. Provide empty input to reset an entity back to its vanilla friction behavior.\r\n        // @tags\r\n        // <EntityTag.has_friction>\r\n        // -->\r\n        PropertyParser.registerMechanism(EntityFriction.class, ElementTag.class, \"has_friction\", (object, mechanism, input) -> {\r\n            if (!mechanism.hasValue()) {\r\n                object.getFrictional().setFrictionState(TriState.NOT_SET);\r\n            }\r\n            else if (mechanism.requireBoolean()) {\r\n                object.getFrictional().setFrictionState(TriState.byBoolean(input.asBoolean()));\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityLeftHanded.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityProperty;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Mob;\r\n\r\npublic class EntityLeftHanded extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name left_handed\r\n    // @input ElementTag(Boolean)\r\n    // @plugin Paper\r\n    // @description\r\n    // Whether a mob is left-handed. Mobs have a rare chance of spawning left-handed.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Mob;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(Mob.class).isLeftHanded());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"left_handed\";\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            as(Mob.class).setLeftHanded(param.asBoolean());\r\n        }\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"left_handed\", EntityLeftHanded.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityReputation.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport com.destroystokyo.paper.entity.villager.Reputation;\r\nimport com.destroystokyo.paper.entity.villager.ReputationType;\r\nimport org.bukkit.entity.Villager;\r\n\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic class EntityReputation implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Villager;\r\n    }\r\n\r\n    public static EntityReputation getFrom(ObjectTag _entity) {\r\n        if (!describes(_entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityReputation((EntityTag) _entity);\r\n        }\r\n    }\r\n\r\n    public EntityReputation(EntityTag _entity) {\r\n        entity = _entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getReputationMap().identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"reputation\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.reputation>\r\n        // @returns MapTag\r\n        // @mechanism EntityTag.reputation\r\n        // @group properties\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns a villager's reputations as a map of player UUIDs to reputation maps.\r\n        // Reputation maps are maps of reputation types to integer values, a full list of all valid reputation types can be found at <@link url https://jd.papermc.io/paper/1.19/com/destroystokyo/paper/entity/villager/ReputationType.html>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityReputation.class, MapTag.class, \"reputation\", (attribute, object) -> {\r\n            return object.getReputationMap();\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name reputation\r\n        // @input MapTag\r\n        // @Plugin Paper\r\n        // @group properties\r\n        // @description\r\n        // Sets a villager's reputations as a map of player UUIDs to reputation maps.\r\n        // Reputation maps are maps of reputation types to integer values, a full list of all valid reputation types can be found at <@link url https://jd.papermc.io/paper/1.19/com/destroystokyo/paper/entity/villager/ReputationType.html>.\r\n        // @tags\r\n        // <EntityTag.reputation>\r\n        // -->\r\n        PropertyParser.registerMechanism(EntityReputation.class, MapTag.class, \"reputation\", (object, mechanism, input) -> {\r\n            Villager villager = object.getVillager();\r\n            villager.clearReputations();\r\n            for (Map.Entry<StringHolder, ObjectTag> entry : input.entrySet()) {\r\n                UUID uuid;\r\n                try {\r\n                    uuid = UUID.fromString(entry.getKey().str);\r\n                }\r\n                catch (IllegalArgumentException exception) {\r\n                    mechanism.echoError(\"Invalid uuid specified: \" + entry.getKey().str);\r\n                    continue;\r\n                }\r\n                MapTag reputationInput = entry.getValue().asType(MapTag.class, mechanism.context);\r\n                if (reputationInput == null) {\r\n                    mechanism.echoError(\"Invalid reputation map specified: \" + entry.getValue());\r\n                    continue;\r\n                }\r\n                Reputation reputation = new Reputation();\r\n                for (Map.Entry<StringHolder, ObjectTag> reputationEntry : reputationInput.entrySet()) {\r\n                    ReputationType reputationType = new ElementTag(reputationEntry.getKey().low).asEnum(ReputationType.class);\r\n                    if (reputationType == null) {\r\n                        mechanism.echoError(\"Invalid reputation type specified: \" + reputationEntry.getKey().str);\r\n                        continue;\r\n                    }\r\n                    reputation.setReputation(reputationType, reputationEntry.getValue().asElement().asInt());\r\n                }\r\n                villager.setReputation(uuid, reputation);\r\n            }\r\n        });\r\n    }\r\n\r\n    public Villager getVillager() {\r\n        return (Villager) entity.getBukkitEntity();\r\n    }\r\n\r\n    public MapTag getReputationMap() {\r\n        MapTag result = new MapTag();\r\n        for (Map.Entry<UUID, Reputation> entry : getVillager().getReputations().entrySet()) {\r\n            MapTag reputationMap = new MapTag();\r\n            Reputation reputation = entry.getValue();\r\n            for (ReputationType reputationType : ReputationType.values()) {\r\n                reputationMap.putObject(reputationType.name(), new ElementTag(reputation.getReputation(reputationType)));\r\n            }\r\n            result.putObject(entry.getKey().toString(), reputationMap);\r\n        }\r\n        return result;\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityShouldBurn.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityProperty;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.*;\r\n\r\npublic class EntityShouldBurn extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name should_burn\r\n    // @input ElementTag(Boolean)\r\n    // @plugin Paper\r\n    // @description\r\n    // If the entity is a Zombie, Skeleton, Stray, Bogged, or Phantom, controls whether it should burn in daylight.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Zombie\r\n                || entity.getBukkitEntity() instanceof Phantom\r\n                || entity.getBukkitEntity() instanceof Skeleton\r\n                || entity.getBukkitEntity() instanceof Stray\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && entity.getBukkitEntity() instanceof Bogged);\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (getEntity() instanceof Zombie zombie) {\r\n            return new ElementTag(zombie.shouldBurnInDay());\r\n        }\r\n        else if (getEntity() instanceof Phantom phantom) {\r\n            return new ElementTag(phantom.shouldBurnInDay());\r\n        }\r\n        else if (getEntity() instanceof Skeleton skeleton) {\r\n            return new ElementTag(skeleton.shouldBurnInDay());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getEntity() instanceof Bogged bogged) {\r\n            return new ElementTag(bogged.shouldBurnInDay());\r\n        }\r\n        else { // stray\r\n            return new ElementTag(as(Stray.class).shouldBurnInDay());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"should_burn\";\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            if (getEntity() instanceof Zombie zombie) {\r\n                zombie.setShouldBurnInDay(param.asBoolean());\r\n            }\r\n            else if (getEntity() instanceof Phantom phantom) {\r\n                phantom.setShouldBurnInDay(param.asBoolean());\r\n            }\r\n            else if (getEntity() instanceof Skeleton skeleton) {\r\n                skeleton.setShouldBurnInDay(param.asBoolean());\r\n            }\r\n            else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getEntity() instanceof Bogged bogged) {\r\n                bogged.setShouldBurnInDay(param.asBoolean());\r\n            }\r\n            else { // stray\r\n                as(Stray.class).setShouldBurnInDay(param.asBoolean());\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"should_burn\", EntityShouldBurn.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntitySneaking.java",
    "content": "package com.denizenscript.denizen.paper.properties;\n\nimport com.denizenscript.denizen.npc.traits.SneakingTrait;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.properties.entity.EntityProperty;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport net.citizensnpcs.api.npc.NPC;\n\npublic class EntitySneaking extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name is_sneaking\n    // @input ElementTag(Boolean)\n    // @plugin Paper\n    // @description\n    // Whether an entity is sneaking.\n    // For most entities this just makes the name tag less visible, and doesn't actually update the pose.\n    // Note that <@link command sneak> is also available.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return true;\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(getEntity().isSneaking());\n    }\n\n    @Override\n    public boolean isDefaultValue(ElementTag value) {\n        return !value.asBoolean();\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\n        if (mechanism.requireBoolean()) {\n            boolean sneaking = value.asBoolean();\n            getEntity().setSneaking(sneaking);\n            if (object.isCitizensNPC()) {\n                NPC npc = object.getDenizenNPC().getCitizen();\n                if (sneaking) {\n                    npc.getOrAddTrait(SneakingTrait.class).sneak();\n                }\n                else if (npc.hasTrait(SneakingTrait.class)) {\n                    npc.getTraitNullable(SneakingTrait.class).stand();\n                    npc.removeTrait(SneakingTrait.class);\n                }\n            }\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"is_sneaking\";\n    }\n\n    public static void register() {\n        autoRegister(\"is_sneaking\", EntitySneaking.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/EntityWitherInvulnerable.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.Wither;\r\n\r\npublic class EntityWitherInvulnerable implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Wither;\r\n    }\r\n\r\n    public static EntityWitherInvulnerable getFrom(ObjectTag _entity) {\r\n        if (!describes(_entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityWitherInvulnerable((EntityTag) _entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[]{\r\n            \"invulnerable_duration\"\r\n    };\r\n\r\n    public EntityWitherInvulnerable(EntityTag _entity) {\r\n        entity = _entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.invulnerable_duration>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.invulnerable_duration\r\n        // @group properties\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns the duration remaining until the wither becomes vulnerable.\r\n        // -->\r\n        PropertyParser.registerTag(EntityWitherInvulnerable.class, DurationTag.class, \"invulnerable_duration\", (attribute, object) -> {\r\n            return new DurationTag((long) object.getWither().getInvulnerableTicks());\r\n        });\r\n    }\r\n\r\n    public Wither getWither() {\r\n        return (Wither) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        int ticks = getWither().getInvulnerableTicks();\r\n        if (ticks == 0) {\r\n            return null;\r\n        }\r\n        return new DurationTag((long) ticks).identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"invulnerable_duration\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name invulnerable_duration\r\n        // @input DurationTag\r\n        // @Plugin Paper\r\n        // @group properties\r\n        // @description\r\n        // Sets the duration remaining until the wither becomes vulnerable.\r\n        // @tags\r\n        // <EntityTag.invulnerable_duration>\r\n        // -->\r\n        if (mechanism.matches(\"invulnerable_duration\") && mechanism.requireObject(DurationTag.class)) {\r\n            getWither().setInvulnerableTicks(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/ItemArmorStand.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.destroystokyo.paper.inventory.meta.ArmorStandMeta;\r\nimport org.bukkit.Material;\r\n\r\npublic class ItemArmorStand implements Property {\r\n\r\n    public static boolean describes(ObjectTag item) {\r\n        return item instanceof ItemTag\r\n                && ((ItemTag) item).getBukkitMaterial() == Material.ARMOR_STAND;\r\n    }\r\n\r\n    public static ItemArmorStand getFrom(ObjectTag item) {\r\n        if (!describes(item)) {\r\n            return null;\r\n        }\r\n        return new ItemArmorStand((ItemTag) item);\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"armor_stand_data\"\r\n    };\r\n\r\n    public ItemArmorStand(ItemTag item) {\r\n        this.item = item;\r\n    }\r\n\r\n    ItemTag item;\r\n\r\n    public MapTag getDataMap() {\r\n        ArmorStandMeta meta = (ArmorStandMeta) item.getItemMeta();\r\n        if (meta == null) {\r\n            return null;\r\n        }\r\n        MapTag result = new MapTag();\r\n        result.putObject(\"base_plate\", new ElementTag(!meta.hasNoBasePlate()));\r\n        result.putObject(\"visible\", new ElementTag(!meta.isInvisible()));\r\n        result.putObject(\"marker\", new ElementTag(meta.isMarker()));\r\n        result.putObject(\"is_small\", new ElementTag(meta.isSmall()));\r\n        result.putObject(\"arms\", new ElementTag(meta.shouldShowArms()));\r\n        return result;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        MapTag result = getDataMap();\r\n        if (result == null) {\r\n            return null;\r\n        }\r\n        return result.toString();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"armor_stand_data\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.armor_stand_data>\r\n        // @returns MapTag\r\n        // @mechanism ItemTag.armor_stand_data\r\n        // @group properties\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns a map of basic armor stand data, with keys matching EntityTag property names.\r\n        // Keys: base_plate, visible, marker, is_small, arms\r\n        // -->\r\n        PropertyParser.registerTag(ItemArmorStand.class, MapTag.class, \"armor_stand_data\", (attribute, item) -> {\r\n            return item.getDataMap();\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name armor_stand_data\r\n        // @input MapTag\r\n        // @Plugin Paper\r\n        // @group properties\r\n        // @description\r\n        // Sets a map of basic armor stand data, with keys matching EntityTag property names.\r\n        // Allowed keys: base_plate, visible, marker, is_small, arms\r\n        // @tags\r\n        // <ItemTag.armor_stand_data>\r\n        // -->\r\n        if (mechanism.matches(\"armor_stand_data\") && mechanism.requireObject(MapTag.class)) {\r\n            MapTag map = mechanism.valueAsType(MapTag.class);\r\n            ArmorStandMeta meta = (ArmorStandMeta) item.getItemMeta();\r\n            ElementTag base_plate = map.getElement(\"base_plate\");\r\n            ElementTag visible = map.getElement(\"visible\");\r\n            ElementTag marker = map.getElement(\"marker\");\r\n            ElementTag is_small = map.getElement(\"is_small\");\r\n            ElementTag arms = map.getElement(\"arms\");\r\n            if (base_plate != null) {\r\n                meta.setNoBasePlate(!base_plate.asBoolean());\r\n            }\r\n            if (visible != null) {\r\n                meta.setInvisible(!visible.asBoolean());\r\n            }\r\n            if (marker != null) {\r\n                meta.setMarker(marker.asBoolean());\r\n            }\r\n            if (is_small != null) {\r\n                meta.setSmall(is_small.asBoolean());\r\n            }\r\n            if (arms != null) {\r\n                meta.setShowArms(arms.asBoolean());\r\n            }\r\n            item.setItemMeta(meta);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/ItemRemovedComponents.java",
    "content": "package com.denizenscript.denizen.paper.properties;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.properties.item.ItemProperty;\nimport com.denizenscript.denizen.paper.datacomponents.DataComponentAdapter;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport io.papermc.paper.datacomponent.DataComponentType;\n\npublic class ItemRemovedComponents extends ItemProperty<ListTag> {\n\n    // <--[property]\n    // @object ItemTag\n    // @name removed_components\n    // @input ListTag\n    // @description\n    // Controls the item components explicitly removed from an item.\n    // This can be used to remove item's default behavior, such as making consumable items non-consumable.\n    // Alternatively, use <@link mechanism ItemTag.remove_component> to remove a single component.\n    // See <@link language Item Components> for more information.\n    // -->\n\n    public static boolean describes(ItemTag item) {\n        return !item.getItemStack().isEmpty();\n    }\n\n    public boolean isRemoved(DataComponentType componentType) {\n        return getItemStack().isDataOverridden(componentType) && !getItemStack().hasData(componentType);\n    }\n\n    @Override\n    public ListTag getPropertyValue() {\n        return new ListTag(getMaterial().getDefaultDataTypes(), this::isRemoved, componentType -> new ElementTag(componentType.key().asMinimalString(), true));\n    }\n\n    @Override\n    public boolean isDefaultValue(ListTag value) {\n        return value.isEmpty();\n    }\n\n    @Override\n    public void setPropertyValue(ListTag value, Mechanism mechanism) {\n        for (DataComponentType componentType : getMaterial().getDefaultDataTypes()) {\n            if (isRemoved(componentType)) {\n                getItemStack().resetData(componentType);\n            }\n        }\n        for (String input : value) {\n            DataComponentType componentType = DataComponentAdapter.getComponentType(input);\n            if (componentType == null) {\n                mechanism.echoError(\"Invalid type to remove '\" + input + \"' specified: must be a valid property or item component name.\");\n                continue;\n            }\n            getItemStack().unsetData(componentType);\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"removed_components\";\n    }\n\n    public static void register() {\n        autoRegister(\"removed_components\", ItemRemovedComponents.class, ListTag.class, false);\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name remove_component\n        // @input ElementTag\n        // @description\n        // Removes the specified item component from the item, see <@link language Item Components> for more information.\n        // This can be used to remove item's default behavior, such as making consumable items non-consumable.\n        // See also <@link property ItemTag.removed_components>.\n        // -->\n        PropertyParser.registerMechanism(ItemRemovedComponents.class, ElementTag.class, \"remove_component\", (prop, mechanism, input) -> {\n            DataComponentType componentType = DataComponentAdapter.getComponentType(input.asString());\n            if (componentType == null) {\n                mechanism.echoError(\"Invalid type to remove specified: must be a valid property or item component name.\");\n                return;\n            }\n            prop.getItemStack().unsetData(componentType);\n        });\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperElementExtensions.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.paper.PaperModule;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport net.kyori.adventure.text.minimessage.MiniMessage;\r\n\r\npublic class PaperElementExtensions {\r\n\r\n\r\n    public static void register() {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <ElementTag.parse_minimessage>\r\n            // @returns ElementTag\r\n            // @Plugin Paper\r\n            // @group paper\r\n            // @description\r\n            // Returns the element with all MiniMessage tags parsed, see <@link url https://docs.adventure.kyori.net/minimessage/format.html> for more information.\r\n            // This may be useful for reading data from external plugins, but should not be used in normal scripts.\r\n            // -->\r\n            ElementTag.tagProcessor.registerTag(ElementTag.class, \"parse_minimessage\", (attribute, object) -> {\r\n                return new ElementTag(PaperModule.stringifyComponent(MiniMessage.miniMessage().deserialize(object.asString())));\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <ElementTag.to_minimessage>\r\n            // @returns ElementTag\r\n            // @Plugin Paper\r\n            // @group paper\r\n            // @description\r\n            // Returns the element with all text formatting parsed into MiniMessage format.\r\n            // This may be useful for sending data to external plugins, but should not be used in normal scripts.\r\n            // -->\r\n            ElementTag.tagProcessor.registerTag(ElementTag.class, \"to_minimessage\", (attribute, object) -> {\r\n                return new ElementTag(PaperAPITools.instance.convertTextToMiniMessage(object.asString(), false));\r\n            });\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperEntityExtensions.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityFormObject;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport io.papermc.paper.entity.Shearable;\r\nimport net.kyori.adventure.sound.Sound;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.ExperienceOrb;\r\nimport org.bukkit.entity.Goat;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\n\r\nimport java.util.UUID;\r\n\r\npublic class PaperEntityExtensions {\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.spawn_reason>\r\n        // @returns ElementTag\r\n        // @group paper\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns the entity's spawn reason.\r\n        // Valid spawn reasons can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/CreatureSpawnEvent.SpawnReason.html>\r\n        // -->\r\n        EntityTag.tagProcessor.registerTag(ElementTag.class, \"spawn_reason\", (attribute, entity) -> {\r\n            return new ElementTag(entity.getBukkitEntity().getEntitySpawnReason());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.xp_spawn_reason>\r\n        // @returns ElementTag\r\n        // @group paper\r\n        // @Plugin Paper\r\n        // @description\r\n        // If the entity is an experience orb, returns its spawn reason.\r\n        // Valid spawn reasons can be found at <@link url https://papermc.io/javadocs/paper/1.17/org/bukkit/entity/ExperienceOrb.SpawnReason.html>\r\n        // -->\r\n        EntityTag.tagProcessor.registerTag(ElementTag.class, \"xp_spawn_reason\", (attribute, entity) -> {\r\n            if (!(entity.getBukkitEntity() instanceof ExperienceOrb experienceOrb)) {\r\n                attribute.echoError(\"Entity \" + entity + \" is not an experience orb.\");\r\n                return null;\r\n            }\r\n            return new ElementTag(experienceOrb.getSpawnReason());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.xp_trigger>\r\n        // @returns EntityTag\r\n        // @group paper\r\n        // @Plugin Paper\r\n        // @description\r\n        // If the entity is an experience orb, returns the entity that triggered it spawning (if any).\r\n        // For example, if a player killed an entity this would return the player.\r\n        // -->\r\n        EntityTag.tagProcessor.registerTag(EntityFormObject.class, \"xp_trigger\", (attribute, entity) -> {\r\n            if (!(entity.getBukkitEntity() instanceof ExperienceOrb experienceOrb)) {\r\n                attribute.echoError(\"Entity \" + entity + \" is not an experience orb.\");\r\n                return null;\r\n            }\r\n            UUID uuid = experienceOrb.getTriggerEntityId();\r\n            if (uuid == null) {\r\n                return null;\r\n            }\r\n            Entity e = EntityTag.getEntityForID(uuid);\r\n            if (e == null) {\r\n                return null;\r\n            }\r\n            return new EntityTag(e).getDenizenObject();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.xp_source>\r\n        // @returns EntityTag\r\n        // @group paper\r\n        // @Plugin Paper\r\n        // @description\r\n        // If the entity is an experience orb, returns the entity that it was created from (if any).\r\n        // For example, if the xp orb was spawned from breeding this would return the baby.\r\n        // -->\r\n        EntityTag.registerSpawnedOnlyTag(EntityFormObject.class, \"xp_source\", (attribute, entity) -> {\r\n            if (!(entity.getBukkitEntity() instanceof ExperienceOrb experienceOrb)) {\r\n                attribute.echoError(\"Entity \" + entity + \" is not an experience orb.\");\r\n                return null;\r\n            }\r\n            UUID uuid = experienceOrb.getSourceEntityId();\r\n            if (uuid == null) {\r\n                return null;\r\n            }\r\n            Entity e = EntityTag.getEntityForID(uuid);\r\n            if (e == null) {\r\n                return null;\r\n            }\r\n            return new EntityTag(e).getDenizenObject();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.spawn_location>\r\n        // @returns LocationTag\r\n        // @group paper\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns the initial spawn location of this entity.\r\n        // -->\r\n        EntityTag.tagProcessor.registerTag(LocationTag.class, \"spawn_location\", (attribute, entity) -> {\r\n            Location loc = entity.getBukkitEntity().getOrigin();\r\n            return loc != null ? new LocationTag(loc) : null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.from_spawner>\r\n        // @returns ElementTag(Boolean)\r\n        // @group paper\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns whether the entity was spawned from a spawner.\r\n        // -->\r\n        EntityTag.tagProcessor.registerTag(ElementTag.class, \"from_spawner\", (attribute, entity) -> {\r\n            return new ElementTag(entity.getBukkitEntity().fromMobSpawner());\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name goat_ram\r\n        // @input EntityTag\r\n        // @Plugin Paper\r\n        // @group paper\r\n        // @description\r\n        // Causes a goat to ram the specified entity.\r\n        // -->\r\n        EntityTag.registerSpawnedOnlyMechanism(\"goat_ram\", false, EntityTag.class, (object, mechanism, input) -> {\r\n            if (object.getBukkitEntity() instanceof Goat goat) {\r\n                goat.ram(input.getLivingEntity());\r\n            }\r\n        });\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.collides_at[<location>]>\r\n            // @returns ElementTag(Boolean)\r\n            // @group paper\r\n            // @Plugin Paper\r\n            // @description\r\n            // Returns whether the entity's bounding box would collide if the entity was moved to the given location.\r\n            // This checks for any colliding entities (like boats and shulkers), the world border and regular blocks.\r\n            // (Note that this won't load chunks at the location.)\r\n            // -->\r\n            EntityTag.tagProcessor.registerTag(ElementTag.class, LocationTag.class, \"collides_at\", (attribute, entity, location) -> {\r\n                return new ElementTag(entity.getBukkitEntity().collidesAt(location));\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name damage_item\r\n            // @input MapTag\r\n            // @Plugin Paper\r\n            // @group paper\r\n            // @description\r\n            // Damages the given equipment slot for the given amount.\r\n            // This runs all vanilla logic associated with damaging an item like gamemode and enchantment checks, events, stat changes, advancement triggers, and notifying clients to play break animations.\r\n            // Input is a map with \"slot\" as a valid equipment slot, and \"amount\" as the damage amount to be dealt.\r\n            // Valid equipment slot values can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/EquipmentSlot.html>.\r\n            //\r\n            // @example\r\n            // # Damages your precious boots! :(\r\n            // - adjust <player> damage_item:[slot=feet;amount=45]\r\n            // -->\r\n            EntityTag.registerSpawnedOnlyMechanism(\"damage_item\", false, MapTag.class, (object, mechanism, input) -> {\r\n                ElementTag slot = input.getElement(\"slot\");\r\n                ElementTag amount = input.getElement(\"amount\");\r\n                if (slot == null || !slot.matchesEnum(EquipmentSlot.class)) {\r\n                    mechanism.echoError(\"Must specify a valid equipment slot to damage.\");\r\n                    return;\r\n                }\r\n                if (amount == null || !amount.isInt()) {\r\n                    mechanism.echoError(\"Must specify a valid amount to damage this item for.\");\r\n                    return;\r\n                }\r\n                object.getLivingEntity().damageItemStack(slot.asEnum(EquipmentSlot.class), amount.asInt());\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name shear\r\n            // @input None\r\n            // @Plugin paper\r\n            // @group paper\r\n            // @description\r\n            // Shears entities in the same way as a player can do using shears, including drops.\r\n            // If the entity is not ready to be sheared, there will be no drops but the sound will still play.\r\n            //\r\n            // This mech will:\r\n            // - Shear a sheep\r\n            // - harvest a bogged\r\n            // - harvest a mushroom cow (note: entity data will be lost as Minecraft will remove the entity and spawn an entirely new cow instead)\r\n            // - derp a snowman (i.e. remove the pumpkin)\r\n            //\r\n            // Optionally, specify a sound source to change the source of the sound.\r\n            // Valid sound sources can be found here: <@link url https://jd.advntr.dev/api/latest/net/kyori/adventure/sound/Sound.Source.html>.\r\n            //\r\n            // @example\r\n            // # Shears the entity you're looking at.\r\n            // - adjust <player.target> shear\r\n            //\r\n            // -->\r\n            EntityTag.registerSpawnedOnlyMechanism(\"shear\", false, (object, mechanism) -> {\r\n                if (!(object.getBukkitEntity() instanceof Shearable shearable)) {\r\n                    return;\r\n                }\r\n                if (!mechanism.hasValue()) {\r\n                    shearable.shear();\r\n                    return;\r\n                }\r\n                ElementTag input = mechanism.getValue();\r\n                if (!mechanism.requireEnum(Sound.Source.class)) {\r\n                    mechanism.echoError(\"Invalid sound source specified: \" + input);\r\n                    return;\r\n                }\r\n                Sound.Source source = input.asEnum(Sound.Source.class);\r\n                shearable.shear(source);\r\n            });\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperItemExtensions.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\n\r\npublic class PaperItemExtensions {\r\n\r\n    public static void register() {\r\n\r\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_20)) {\r\n            ItemTag.tagProcessor.registerTag(ElementTag.class, \"rarity\", (attribute, item) -> {\r\n                return new ElementTag(item.getItemStack().getRarity());\r\n            });\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperPlayerExtensions.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.destroystokyo.paper.ClientOption;\r\nimport com.destroystokyo.paper.SkinParts;\r\nimport net.kyori.adventure.util.TriState;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Player;\r\n\r\npublic class PaperPlayerExtensions {\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.affects_monster_spawning>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism PlayerTag.affects_monster_spawning\r\n        // @group paper\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns whether the player affects monster spawning. When false, no monsters will spawn naturally because of this player.\r\n        // -->\r\n        PlayerTag.registerOnlineOnlyTag(ElementTag.class, \"affects_monster_spawning\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().getAffectsSpawning());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.client_options>\r\n        // @returns MapTag\r\n        // @group paper\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns the player's client options.\r\n        // The output map contains the following keys:\r\n        // - 'allow_server_listings' (ElementTag(Boolean)): whether the player allows server listings. Available only on MC 1.19+.\r\n        // - 'chat_colors_enabled' (ElementTag(Boolean)): whether the player has chat colors enabled.\r\n        // - 'chat_visibility' (ElementTag): the player's current chat visibility option. Possible output values are: FULL, SYSTEM, HIDDEN, and UNKNOWN.\r\n        // - 'locale' (ElementTag): the player's current locale.\r\n        // - 'main_hand' (ElementTag): the player's main hand, either LEFT or RIGHT.\r\n        // - 'skin_parts' (MapTag): which skin parts the player has enabled. The output map contains the following keys:\r\n        //   - 'cape' (ElementTag(Boolean)): whether the player's cape is enabled.\r\n        //   - 'hat' (ElementTag(Boolean)): whether the player's hat is enabled.\r\n        //   - 'jacket' (ElementTag(Boolean)): whether the player's jacket is enabled.\r\n        //   - 'left_sleeve' (ElementTag(Boolean)): whether the player's left sleeve is enabled.\r\n        //   - 'right_sleeve' (ElementTag(Boolean)): whether the player's right sleeve is enabled.\r\n        //   - 'left_pants' (ElementTag(Boolean)): whether the player's left pants is enabled.\r\n        //   - 'right_pants' (ElementTag(Boolean)): whether the player's right pants is enabled.\r\n        // - 'text_filtering_enabled' (ElementTag(Boolean)): whether the player has text filtering enabled. Available only on MC 1.19+.\r\n        // - 'view_distance' (ElementTag(Number)): the player's current view distance.\r\n        // -->\r\n        PlayerTag.registerOnlineOnlyTag(MapTag.class, \"client_options\", (attribute, object) -> {\r\n            MapTag map = new MapTag();\r\n            Player player = object.getPlayerEntity();\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n                map.putObject(\"allow_server_listings\", new ElementTag(player.getClientOption(ClientOption.ALLOW_SERVER_LISTINGS)));\r\n                map.putObject(\"text_filtering_enabled\", new ElementTag(player.getClientOption(ClientOption.TEXT_FILTERING_ENABLED)));\r\n            }\r\n            map.putObject(\"chat_colors_enabled\", new ElementTag(player.getClientOption(ClientOption.CHAT_COLORS_ENABLED)));\r\n            map.putObject(\"chat_visibility\", new ElementTag(player.getClientOption(ClientOption.CHAT_VISIBILITY)));\r\n            map.putObject(\"locale\", new ElementTag(player.getClientOption(ClientOption.LOCALE)));\r\n            map.putObject(\"main_hand\", new ElementTag(player.getClientOption(ClientOption.MAIN_HAND)));\r\n            MapTag skinParts = new MapTag();\r\n            SkinParts parts = player.getClientOption(ClientOption.SKIN_PARTS);\r\n            skinParts.putObject(\"cape\", new ElementTag(parts.hasCapeEnabled()));\r\n            skinParts.putObject(\"hat\", new ElementTag(parts.hasHatsEnabled()));\r\n            skinParts.putObject(\"jacket\", new ElementTag(parts.hasJacketEnabled()));\r\n            skinParts.putObject(\"left_sleeve\", new ElementTag(parts.hasLeftSleeveEnabled()));\r\n            skinParts.putObject(\"right_sleeve\", new ElementTag(parts.hasRightSleeveEnabled()));\r\n            skinParts.putObject(\"left_pants\", new ElementTag(parts.hasLeftPantsEnabled()));\r\n            skinParts.putObject(\"right_pants\", new ElementTag(parts.hasRightPantsEnabled()));\r\n            map.putObject(\"skin_parts\", skinParts);\r\n            map.putObject(\"view_distance\", new ElementTag(player.getClientOption(ClientOption.VIEW_DISTANCE)));\r\n            return map;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.view_distance>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism PlayerTag.view_distance\r\n        // @group paper\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns this player's view distance, or the view distance of the world they're in if unset.\r\n        // -->\r\n        PlayerTag.registerOnlineOnlyTag(ElementTag.class, \"view_distance\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().getViewDistance());\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name affects_monster_spawning\r\n        // @input ElementTag(Boolean)\r\n        // @Plugin Paper\r\n        // @group paper\r\n        // @description\r\n        // Sets whether this player affects monster spawning. When false, no monsters will spawn naturally because of this player.\r\n        // @tags\r\n        // <PlayerTag.affects_monster_spawning>\r\n        // -->\r\n        PlayerTag.registerOnlineOnlyMechanism(\"affects_monster_spawning\", ElementTag.class, (object, mechanism, input) -> {\r\n            if (mechanism.requireBoolean()) {\r\n                object.getPlayerEntity().setAffectsSpawning(input.asBoolean());\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name firework_boost\r\n        // @input ItemTag\r\n        // @Plugin Paper\r\n        // @group paper\r\n        // @description\r\n        // Firework boosts the player with the specified firework rocket.\r\n        // The player must be gliding.\r\n        // -->\r\n        PlayerTag.registerOnlineOnlyMechanism(\"firework_boost\", ItemTag.class, (object, mechanism, input) -> {\r\n            if (!object.getPlayerEntity().isGliding()) {\r\n                mechanism.echoError(\"Cannot adjust 'firework_boost': player must be gliding.\");\r\n                return;\r\n            }\r\n            if (input.getBukkitMaterial() != Material.FIREWORK_ROCKET) {\r\n                mechanism.echoError(\"Invalid input item '\" + input + \"': must be a firework rocket.\");\r\n                return;\r\n            }\r\n            object.getPlayerEntity().boostElytra(input.getItemStack());\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name fake_op_level\r\n        // @input ElementTag(Number)\r\n        // @Plugin Paper\r\n        // @group paper\r\n        // @description\r\n        // Sends a fake operator level to the client, enabling clientside op-required features like the debug gamemode hotkey (F3+F4).\r\n        // Input should be a number from 0 to 4, 0 indicating not op and 4 indicating maximum level op.\r\n        // The input number value corresponds to \"op-permission-level\" in the server.properties. See also <@link url https://minecraft.wiki/w/Permission_level>\r\n        // This will be reset when a player rejoins, changes world, has their real op status changed, ...\r\n        // -->\r\n        PlayerTag.registerOnlineOnlyMechanism(\"fake_op_level\", ElementTag.class, (object, mechanism, input) -> {\r\n            if (mechanism.requireInteger()) {\r\n                object.getPlayerEntity().sendOpLevel((byte) input.asInt());\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name view_distance\r\n        // @input ElementTag(Number)\r\n        // @Plugin Paper\r\n        // @group paper\r\n        // @description\r\n        // Sets this player's view distance. Input must be a number between 2 and 32.\r\n        // This will be reset when a player rejoins. Provide no input to unset.\r\n        // @tags\r\n        // <PlayerTag.view_distance>\r\n        // -->\r\n        PlayerTag.registerOnlineOnlyMechanism(\"view_distance\", (object, mechanism) -> {\r\n            if (!mechanism.hasValue()) {\r\n                object.getPlayerEntity().setViewDistance(-1);\r\n            }\r\n            else if (mechanism.requireInteger()) {\r\n                int distance = mechanism.getValue().asInt();\r\n                if (distance < 2 || distance > 32) {\r\n                    mechanism.echoError(\"Invalid view distance '\" + distance + \"': must be between 2 and 32.\");\r\n                    return;\r\n                }\r\n                object.getPlayerEntity().setViewDistance(distance);\r\n            }\r\n        });\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.flying_fall_damage>\r\n            // @returns ElementTag(Boolean)\r\n            // @mechanism PlayerTag.flying_fall_damage\r\n            // @group paper\r\n            // @Plugin Paper\r\n            // @description\r\n            // Returns whether the player will take fall damage while <@link tag PlayerTag.can_fly> is true.\r\n            // -->\r\n            PlayerTag.registerOnlineOnlyTag(ElementTag.class, \"flying_fall_damage\", (attribute, object) -> {\r\n                return new ElementTag(object.getPlayerEntity().hasFlyingFallDamage().toBooleanOrElse(false));\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object PlayerTag\r\n            // @name add_tab_completions\r\n            // @input ListTag\r\n            // @Plugin Paper\r\n            // @group paper\r\n            // @description\r\n            // Adds custom tab completions that will be suggested to the player when typing in chat.\r\n            // Tab completions added by this mechanism can be removed using <@link mechanism PlayerTag.remove_tab_completions>.\r\n            // -->\r\n            PlayerTag.registerOnlineOnlyMechanism(\"add_tab_completions\", ListTag.class, (object, mechanism, input) -> {\r\n                object.getPlayerEntity().addAdditionalChatCompletions(input);\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object PlayerTag\r\n            // @name remove_tab_completions\r\n            // @input ListTag\r\n            // @Plugin Paper\r\n            // @group paper\r\n            // @description\r\n            // Removes custom tab completions added by <@link mechanism PlayerTag.add_tab_completions>.\r\n            // -->\r\n            PlayerTag.registerOnlineOnlyMechanism(\"remove_tab_completions\", ListTag.class, (object, mechanism, input) -> {\r\n                object.getPlayerEntity().removeAdditionalChatCompletions(input);\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object PlayerTag\r\n            // @name flying_fall_damage\r\n            // @input ElementTag(Boolean)\r\n            // @Plugin Paper\r\n            // @group paper\r\n            // @description\r\n            // Sets whether the player will take fall damage while <@link mechanism PlayerTag.can_fly> is true.\r\n            // @tags\r\n            // <PlayerTag.flying_fall_damage>\r\n            // <PlayerTag.can_fly>\r\n            // -->\r\n            PlayerTag.registerOnlineOnlyMechanism(\"flying_fall_damage\", ElementTag.class, (object, mechanism, input) -> {\r\n                if (mechanism.requireBoolean()) {\r\n                    object.getPlayerEntity().setFlyingFallDamage(TriState.byBoolean(input.asBoolean()));\r\n                }\r\n            });\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/properties/PaperWorldExtensions.java",
    "content": "package com.denizenscript.denizen.paper.properties;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport org.bukkit.boss.DragonBattle;\r\n\r\npublic class PaperWorldExtensions  {\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <WorldTag.no_tick_view_distance>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism WorldTag.no_tick_view_distance\r\n        // @group paper\r\n        // @deprecated replaced by Minecraft's simulation_distance and view_distance config pairing\r\n        // @Plugin Paper\r\n        // @description\r\n        // Deprecated: replaced by Minecraft's simulation_distance and view_distance config pairing\r\n        // -->\r\n        WorldTag.tagProcessor.registerTag(ElementTag.class, \"no_tick_view_distance\", (attribute, world) -> {\r\n            BukkitImplDeprecations.paperNoTickViewDistance.warn(attribute.context);\r\n            return new ElementTag(world.getWorld().getNoTickViewDistance());\r\n        });\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <WorldTag.gateway_count>\r\n            // @returns ElementTag(Number)\r\n            // @group paper\r\n            // @Plugin Paper\r\n            // @description\r\n            // Returns the number of end gateway portals.\r\n            // Only works if the world is an end world.\r\n            // -->\r\n            WorldTag.tagProcessor.registerTag(ElementTag.class, \"gateway_count\", (attribute, object) -> {\r\n                DragonBattle battle = object.getWorld().getEnderDragonBattle();\r\n                if (battle == null) {\r\n                    attribute.echoError(\"Provided world is not an end world!\");\r\n                    return null;\r\n                }\r\n                return new ElementTag(battle.getGatewayCount());\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <WorldTag.healing_crystals>\r\n            // @returns ListTag(EntityTag)\r\n            // @group paper\r\n            // @Plugin Paper\r\n            // @description\r\n            // Returns a ListTag of the healing end crystals located on top of the obsidian towers.\r\n            // Only works if the world is an end world.\r\n            // -->\r\n            WorldTag.tagProcessor.registerTag(ListTag.class, \"healing_crystals\", (attribute, object) -> {\r\n                DragonBattle battle = object.getWorld().getEnderDragonBattle();\r\n                if (battle == null) {\r\n                    attribute.echoError(\"Provided world is not an end world!\");\r\n                    return null;\r\n                }\r\n                return new ListTag(battle.getHealingCrystals(), EntityTag::new);\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <WorldTag.respawn_crystals>\r\n            // @returns ListTag(EntityTag)\r\n            // @group paper\r\n            // @Plugin Paper\r\n            // @description\r\n            // Returns a ListTag of the respawn end crystals located at the end exit portal.\r\n            // Only works if the world is an end world.\r\n            // -->\r\n            WorldTag.tagProcessor.registerTag(ListTag.class, \"respawn_crystals\", (attribute, object) -> {\r\n                DragonBattle battle = object.getWorld().getEnderDragonBattle();\r\n                if (battle == null) {\r\n                    attribute.echoError(\"Provided world is not an end world!\");\r\n                    return null;\r\n                }\r\n                return new ListTag(battle.getRespawnCrystals(), EntityTag::new);\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object WorldTag\r\n            // @name spawn_gateway\r\n            // @input LocationTag\r\n            // @group paper\r\n            // @Plugin Paper\r\n            // @description\r\n            // Spawns a new end gateway portal at the specified location.\r\n            // If no location is specified, tries to spawn a new end gateway using default game mechanics.\r\n            // Only works if the world is an end world.\r\n            // -->\r\n            WorldTag.tagProcessor.registerMechanism(\"spawn_gateway\", false, (object, mechanism) -> {\r\n                DragonBattle battle = object.getWorld().getEnderDragonBattle();\r\n                if (battle == null) {\r\n                    mechanism.echoError(\"Cannot spawn gateway in non-end world!\");\r\n                    return;\r\n                }\r\n                if (!mechanism.hasValue()) {\r\n                    battle.spawnNewGateway();\r\n                }\r\n                else if (mechanism.requireObject(LocationTag.class)) {\r\n                    battle.spawnNewGateway(mechanism.valueAsType(LocationTag.class));\r\n                }\r\n            });\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object WorldTag\r\n        // @name view_distance\r\n        // @input ElementTag(Number)\r\n        // @Plugin Paper\r\n        // @group paper\r\n        // @description\r\n        // Sets this world's view distance. All chunks within this radius of a player will be visible to that player.\r\n        // Input should be a number from 2 to 32.\r\n        // See also <@link mechanism WorldTag.simulation_distance>\r\n        // @tags\r\n        // <WorldTag.view_distance>\r\n        // <server.view_distance>\r\n        // -->\r\n        WorldTag.tagProcessor.registerMechanism(\"view_distance\", false, ElementTag.class, (object, mechanism, input) -> {\r\n            if (mechanism.requireInteger()) {\r\n                int distance = input.asInt();\r\n                if (distance < 2 || distance > 32) {\r\n                    mechanism.echoError(\"View distance must be a number from 2 to 32!\");\r\n                }\r\n                else {\r\n                    object.getWorld().setViewDistance(distance);\r\n                }\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object WorldTag\r\n        // @name simulation_distance\r\n        // @input ElementTag(Number)\r\n        // @Plugin Paper\r\n        // @group paper\r\n        // @description\r\n        // Sets this world's view distance. All chunks within this radius will be tracked by the server.\r\n        // Input should be a number from 2 to 32.\r\n        // See also <@link mechanism WorldTag.view_distance>\r\n        // @tags\r\n        // <WorldTag.view_distance>\r\n        // <server.view_distance>\r\n        // -->\r\n        WorldTag.tagProcessor.registerMechanism(\"simulation_distance\", false, ElementTag.class, (object, mechanism, input) -> {\r\n            if (mechanism.requireInteger()) {\r\n                int distance = input.asInt();\r\n                if (distance < 2 || distance > 32) {\r\n                    mechanism.echoError(\"View distance must be a number from 2 to 32!\");\r\n                }\r\n                else {\r\n                    object.getWorld().setSimulationDistance(distance);\r\n                }\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object WorldTag\r\n        // @name no_tick_view_distance\r\n        // @input ElementTag(Number)\r\n        // @Plugin Paper\r\n        // @group paper\r\n        // @deprecated replaced by Minecraft's simulation_distance and view_distance config pairing\r\n        // @description\r\n        // Deprecated: replaced by Minecraft's simulation_distance and view_distance config pairing\r\n        // @tags\r\n        // <WorldTag.no_tick_view_distance>\r\n        // -->\r\n        WorldTag.tagProcessor.registerMechanism(\"no_tick_view_distance\", false, (object, mechanism) -> {\r\n            BukkitImplDeprecations.paperNoTickViewDistance.warn(mechanism.context);\r\n            if (!mechanism.hasValue()) {\r\n                object.getWorld().setNoTickViewDistance(-1);\r\n            }\r\n            else if (mechanism.requireInteger()) {\r\n                int distance = mechanism.getValue().asInt();\r\n                if (distance < 2 || distance > 32) {\r\n                    mechanism.echoError(\"View distance must be a number from 2 to 32!\");\r\n                }\r\n                else {\r\n                    object.getWorld().setNoTickViewDistance(distance);\r\n                }\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/tags/PaperTagBase.java",
    "content": "package com.denizenscript.denizen.paper.tags;\r\n\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.tags.*;\r\nimport org.bukkit.Bukkit;\r\n\r\npublic class PaperTagBase extends PseudoObjectTagBase<PaperTagBase> {\r\n\r\n    public static PaperTagBase instance;\r\n\r\n    public PaperTagBase() {\r\n        instance = this;\r\n        TagManager.registerStaticTagBaseHandler(PaperTagBase.class, \"paper\", (t) -> instance);\r\n    }\r\n\r\n    @Override\r\n    public void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <paper.tick_times>\r\n        // @returns ListTag(DurationTag)\r\n        // @Plugin Paper\r\n        // @description\r\n        // Returns a sample of the server's last 5s of tick times as a list of durations.\r\n        // On average, a tick should take 50ms or less for a stable 20tps.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"tick_times\", (attribute, object) -> {\r\n            ListTag list = new ListTag();\r\n            for (long time : Bukkit.getServer().getTickTimes()) {\r\n                list.addObject(new DurationTag(time / 1000000000D));\r\n            }\r\n            return list;\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/utilities/BlockTagsSetter.java",
    "content": "package com.denizenscript.denizen.paper.utilities;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizen.utilities.VanillaTagHelper;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.destroystokyo.paper.event.server.ServerTickEndEvent;\nimport io.papermc.paper.plugin.bootstrap.BootstrapContext;\nimport io.papermc.paper.plugin.configuration.PluginMeta;\nimport io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;\nimport io.papermc.paper.registry.RegistryKey;\nimport io.papermc.paper.registry.TypedKey;\nimport io.papermc.paper.registry.tag.TagKey;\nimport net.kyori.adventure.text.logger.slf4j.ComponentLogger;\nimport org.bukkit.Bukkit;\nimport org.bukkit.Material;\nimport org.bukkit.NamespacedKey;\nimport org.bukkit.block.BlockType;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\n\nimport java.lang.invoke.MethodHandle;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\npublic class BlockTagsSetter implements Listener {\n\n    public static final MethodHandle BOOTSTRAP_CONTEXT_CONSTRUCTOR;\n\n    static {\n        try {\n            Class<?> bootstrapContextImplClass = Class.forName(\"io.papermc.paper.plugin.bootstrap.PluginBootstrapContextImpl\");\n            BOOTSTRAP_CONTEXT_CONSTRUCTOR = ReflectionHelper.getConstructor(bootstrapContextImplClass, PluginMeta.class, Path.class, ComponentLogger.class, Path.class);\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(\"Failed to initialize BlockTagsSetter\", e);\n        }\n    }\n\n    public static final BlockTagsSetter INSTANCE = new BlockTagsSetter(Denizen.getInstance());\n\n    public Map<TypedKey<BlockType>, Set<TagKey<BlockType>>> modifiedTags = new HashMap<>();\n    public boolean batchReloadNeeded;\n\n    public BlockTagsSetter(Denizen plugin) {\n        Bukkit.getPluginManager().registerEvents(this, plugin);\n        try {\n            BootstrapContext fakeContext = (BootstrapContext) BOOTSTRAP_CONTEXT_CONSTRUCTOR.invoke(plugin.getPluginMeta(), plugin.getDataPath(), plugin.getComponentLogger(), plugin.getFile().toPath());\n            fakeContext.getLifecycleManager().registerEventHandler(LifecycleEvents.TAGS.postFlatten(RegistryKey.BLOCK), event -> {\n                Map<TagKey<BlockType>, Collection<TypedKey<BlockType>>> allTags = event.registrar().getAllTags();\n                for (Map.Entry<TypedKey<BlockType>, Set<TagKey<BlockType>>> entry : modifiedTags.entrySet()) {\n                    TypedKey<BlockType> blockType = entry.getKey();\n                    Set<TagKey<BlockType>> tags = entry.getValue();\n                    for (Map.Entry<TagKey<BlockType>, Collection<TypedKey<BlockType>>> tagEntry : allTags.entrySet()) {\n                        TagKey<BlockType> tagKey = tagEntry.getKey();\n                        Collection<TypedKey<BlockType>> values = tagEntry.getValue();\n                        if (values.contains(blockType) && !tags.contains(tagKey)) {\n                            List<TypedKey<BlockType>> modifiedValues = new ArrayList<>(values);\n                            modifiedValues.remove(blockType);\n                            event.registrar().setTag(tagKey, modifiedValues);\n                        }\n                    }\n                    for (TagKey<BlockType> tag : tags) {\n                        event.registrar().addToTag(tag, List.of(blockType));\n                    }\n                }\n            });\n        }\n        catch (Throwable e) {\n            Debug.echoError(e);\n        }\n    }\n\n    @EventHandler\n    public void onServerTickEnd(ServerTickEndEvent event) {\n        if (batchReloadNeeded) {\n            batchReloadNeeded = false;\n            Bukkit.reloadData();\n        }\n    }\n\n    public void setTags(Material material, Set<NamespacedKey> tags) {\n        TypedKey<BlockType> blockKey = TypedKey.create(RegistryKey.BLOCK, material.getKey());\n        Set<TagKey<BlockType>> tagKeys = tags.stream().map(tag -> TagKey.create(RegistryKey.BLOCK, tag)).collect(Collectors.toCollection(HashSet::new));\n        Set<TagKey<BlockType>> oldTagKeys = modifiedTags.put(blockKey, tagKeys);\n        if (tagKeys.equals(oldTagKeys)) {\n            return;\n        }\n        VanillaTagHelper.tagsByMaterial.put(material, tags.stream().map(Utilities::namespacedKeyToString).collect(Collectors.toCollection(HashSet::new)));\n        batchReloadNeeded = true;\n    }\n}\n"
  },
  {
    "path": "paper/src/main/java/com/denizenscript/denizen/paper/utilities/PaperAPIToolsImpl.java",
    "content": "package com.denizenscript.denizen.paper.utilities;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.paper.PaperModule;\r\nimport com.denizenscript.denizen.scripts.commands.entity.TeleportCommand;\r\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptContainer;\r\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.destroystokyo.paper.profile.PlayerProfile;\r\nimport com.destroystokyo.paper.profile.ProfileProperty;\r\nimport io.papermc.paper.entity.TeleportFlag;\r\nimport io.papermc.paper.potion.PotionMix;\r\nimport io.papermc.paper.world.WeatheringCopperState;\r\nimport net.kyori.adventure.text.Component;\r\nimport net.kyori.adventure.text.minimessage.MiniMessage;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.Sign;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.event.entity.PlayerDeathEvent;\r\nimport org.bukkit.event.inventory.InventoryType;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\nimport org.bukkit.inventory.*;\r\nimport org.bukkit.inventory.meta.PotionMeta;\r\nimport org.bukkit.potion.PotionBrewer;\r\nimport org.bukkit.scoreboard.Team;\r\nimport org.bukkit.util.Consumer;\r\n\r\nimport java.net.URI;\r\nimport java.util.*;\r\nimport java.util.function.Predicate;\r\nimport java.util.stream.Collectors;\r\n\r\npublic class PaperAPIToolsImpl extends PaperAPITools {\r\n\r\n    @Override\r\n    public Inventory createInventory(InventoryHolder holder, int slots, String title) {\r\n        return Bukkit.getServer().createInventory(holder, slots, PaperModule.parseFormattedText(title, ChatColor.BLACK));\r\n    }\r\n\r\n    @Override\r\n    public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) {\r\n        return Bukkit.getServer().createInventory(holder, type, PaperModule.parseFormattedText(title, ChatColor.BLACK));\r\n    }\r\n\r\n    @Override\r\n    public String parseComponent(Object input) {\r\n        if (input == null) {\r\n            return null;\r\n        }\r\n        if (input instanceof Component) {\r\n            return PaperModule.stringifyComponent((Component) input);\r\n        }\r\n        return super.parseComponent(input);\r\n    }\r\n\r\n    @Override\r\n    public String getTitle(Inventory inventory) {\r\n        // TODO: Paper lacks an inventory.getTitle? 0.o\r\n        return NMSHandler.instance.getTitle(inventory);\r\n    }\r\n\r\n    @Override\r\n    public void setCustomName(Entity entity, String name) {\r\n        entity.customName(PaperModule.parseFormattedText(name, ChatColor.WHITE));\r\n    }\r\n\r\n    @Override\r\n    public String getCustomName(Entity entity) {\r\n        return PaperModule.stringifyComponent(entity.customName());\r\n    }\r\n\r\n    @Override\r\n    public BaseComponent[] getCustomNameComponent(Entity entity) {\r\n        Component customName = entity.customName();\r\n        return customName != null ? FormattedTextHelper.parseJson(PaperModule.componentToJson(customName)) : null;\r\n    }\r\n\r\n    @Override\r\n    public void setPlayerListName(Player player, String name) {\r\n        player.playerListName(PaperModule.parseFormattedText(name, ChatColor.WHITE));\r\n    }\r\n\r\n    @Override\r\n    public String getPlayerListName(Player player) {\r\n        return PaperModule.stringifyComponent(player.playerListName());\r\n    }\r\n\r\n    @Override\r\n    public String[] getSignLines(Sign sign) {\r\n        String[] output = new String[4];\r\n        int i = 0;\r\n        for (Component component : sign.lines()) {\r\n            output[i++] = PaperModule.stringifyComponent(component);\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public void setSignLine(Sign sign, int line, String text) {\r\n        sign.line(line, PaperModule.parseFormattedText(text == null ? \"\" : text, ChatColor.BLACK));\r\n    }\r\n\r\n    @Override\r\n    public void sendResourcePack(Player player, String url, String hash, boolean forced, String prompt) {\r\n        if (prompt == null && !forced) {\r\n            super.sendResourcePack(player, url, hash, false, null);\r\n        }\r\n        else {\r\n            player.setResourcePack(url, CoreUtilities.toLowerCase(hash), forced, PaperModule.parseFormattedText(prompt, ChatColor.WHITE));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendSignUpdate(Player player, Location loc, String[] text) {\r\n        List<Component> components = new ArrayList<>();\r\n        for (String line : text) {\r\n            components.add(PaperModule.parseFormattedText(line, ChatColor.BLACK));\r\n        }\r\n        player.sendSignChange(loc, components);\r\n    }\r\n\r\n    @Override\r\n    public String getCustomName(Nameable object) {\r\n        return PaperModule.stringifyComponent(object.customName());\r\n    }\r\n\r\n    @Override\r\n    public void setCustomName(Nameable object, String name) {\r\n        object.customName(PaperModule.parseFormattedText(name, ChatColor.BLACK));\r\n    }\r\n\r\n    @Override\r\n    public void sendConsoleMessage(CommandSender sender, String text) {\r\n        sender.sendMessage(PaperModule.parseFormattedText(text, ChatColor.WHITE));\r\n    }\r\n\r\n    @Override\r\n    public InventoryView openAnvil(Player player, Location loc) {\r\n        return player.openAnvil(loc, true);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Entity entity, Location loc, PlayerTeleportEvent.TeleportCause cause, List<TeleportCommand.EntityState> entityTeleportFlags, List<TeleportCommand.Relative> relativeTeleportFlags) {\r\n        if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            super.teleport(entity, loc, cause, null, null);\r\n        }\r\n        List<TeleportFlag> teleportFlags = new ArrayList<>();\r\n        if (entityTeleportFlags != null) {\r\n            for (TeleportCommand.EntityState entityTeleportFlag : entityTeleportFlags) {\r\n                teleportFlags.add(TeleportFlag.EntityState.values()[entityTeleportFlag.ordinal()]);\r\n            }\r\n        }\r\n        if (relativeTeleportFlags != null) {\r\n            // TODO: MC 1.21.3: Paper updated this API to work differently due to underlying Minecraft changes.\r\n            for (TeleportCommand.Relative relativeTeleportFlag : relativeTeleportFlags) {\r\n                teleportFlags.add(new ElementTag(relativeTeleportFlag.name()).asEnum(TeleportFlag.Relative.class));\r\n            }\r\n        }\r\n        entity.teleport(loc, cause, teleportFlags.toArray(new TeleportFlag[0]));\r\n    }\r\n\r\n    record BrewingRecipeMatchers(String inputMatcher, String ingredientMatcher) {}\r\n    public static final Map<NamespacedKey, BrewingRecipeMatchers> potionMixes = new HashMap<>();\r\n\r\n    @Override\r\n    public void registerBrewingRecipe(String keyName, ItemStack result, String input, String ingredient, ItemScriptContainer itemScriptContainer) {\r\n        if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            throw new UnsupportedOperationException();\r\n        }\r\n        TagContext context = DenizenCore.implementation.getTagContext(itemScriptContainer);\r\n        RecipeChoice inputChoice = parseBrewingRecipeChoice(itemScriptContainer, input, context);\r\n        if (inputChoice == null) {\r\n            return;\r\n        }\r\n        RecipeChoice ingredientChoice = parseBrewingRecipeChoice(itemScriptContainer, ingredient, context);\r\n        if (ingredientChoice == null) {\r\n            return;\r\n        }\r\n        NamespacedKey key = new NamespacedKey(Denizen.getInstance(), keyName);\r\n        potionMixes.put(key, new BrewingRecipeMatchers(input.startsWith(\"matcher:\") ? input : null, ingredient.startsWith(\"matcher:\") ? ingredient : null));\r\n        Bukkit.getPotionBrewer().addPotionMix(new PotionMix(key, result, inputChoice, ingredientChoice));\r\n    }\r\n\r\n    @Override\r\n    public void clearBrewingRecipes() {\r\n        if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            return;\r\n        }\r\n        PotionBrewer brewer = Bukkit.getPotionBrewer();\r\n        for (NamespacedKey mix : new ArrayList<>(potionMixes.keySet())) {\r\n            brewer.removePotionMix(mix);\r\n            potionMixes.remove(mix);\r\n        }\r\n    }\r\n\r\n    public static RecipeChoice parseBrewingRecipeChoice(ItemScriptContainer container, String choice, TagContext context) {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && choice.startsWith(\"matcher:\")) {\r\n            String matcher = choice.substring(\"matcher:\".length());\r\n            return PotionMix.createPredicateChoice(item -> new ItemTag(item).tryAdvancedMatcher(matcher, context));\r\n        }\r\n        boolean exact = true;\r\n        if (choice.startsWith(\"material:\")) {\r\n            choice = choice.substring(\"material:\".length());\r\n            exact = false;\r\n        }\r\n        ItemStack[] items = ItemScriptHelper.textToItemArray(container, choice, exact);\r\n        if (items == null) {\r\n            return null;\r\n        }\r\n        if (exact) {\r\n            return new RecipeChoice.ExactChoice(items);\r\n        }\r\n        Material[] mats = new Material[items.length];\r\n        for (int i = 0; i < items.length; i++) {\r\n            mats[i] = items[i].getType();\r\n        }\r\n        return new RecipeChoice.MaterialChoice(mats);\r\n    }\r\n\r\n    @Override\r\n    public String getBrewingRecipeInputMatcher(NamespacedKey recipeId) {\r\n        return potionMixes.get(recipeId).inputMatcher();\r\n    }\r\n\r\n    @Override\r\n    public String getBrewingRecipeIngredientMatcher(NamespacedKey recipeId) {\r\n        return potionMixes.get(recipeId).ingredientMatcher();\r\n    }\r\n\r\n    @Override\r\n    public RecipeChoice createPredicateRecipeChoice(Predicate<ItemStack> predicate) {\r\n        return PotionMix.createPredicateChoice(predicate);\r\n    }\r\n\r\n    @Override\r\n    public String getDeathMessage(PlayerDeathEvent event) {\r\n        return PaperModule.stringifyComponent(event.deathMessage());\r\n    }\r\n\r\n    @Override\r\n    public void setDeathMessage(PlayerDeathEvent event, String message) {\r\n        event.deathMessage(PaperModule.parseFormattedText(message, ChatColor.WHITE));\r\n    }\r\n\r\n    public Set<UUID> modifiedTextures = new HashSet<>();\r\n\r\n    @Override\r\n    public void setSkin(Player player, String name) {\r\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) {\r\n            NMSHandler.instance.getProfileEditor().setPlayerSkin(player, name);\r\n            return;\r\n        }\r\n        // Note: this API is present on all supported versions, but currently used for 1.19+ only\r\n        PlayerProfile skinProfile = Bukkit.createProfile(name);\r\n        boolean isOwnName = CoreUtilities.equalsIgnoreCase(player.getName(), name);\r\n        if (isOwnName && modifiedTextures.contains(player.getUniqueId())) {\r\n            skinProfile.removeProperty(\"textures\");\r\n        }\r\n        Bukkit.getScheduler().runTaskAsynchronously(Denizen.instance, () -> {\r\n            if (!skinProfile.complete()) {\r\n                return;\r\n            }\r\n            DenizenCore.runOnMainThread(() -> {\r\n                PlayerProfile playerProfile = player.getPlayerProfile();\r\n                playerProfile.setProperty(getProfileProperty(skinProfile, \"textures\"));\r\n                player.setPlayerProfile(playerProfile);\r\n                if (isOwnName) {\r\n                    modifiedTextures.remove(player.getUniqueId());\r\n                }\r\n                else {\r\n                    modifiedTextures.add(player.getUniqueId());\r\n                }\r\n            });\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void setSkinBlob(Player player, String blob) {\r\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) {\r\n            NMSHandler.instance.getProfileEditor().setPlayerSkinBlob(player, blob);\r\n            return;\r\n        }\r\n        // Note: this API is present on all supported versions, but currently used for 1.19+ only\r\n        List<String> split = CoreUtilities.split(blob, ';');\r\n        PlayerProfile playerProfile = player.getPlayerProfile();\r\n        ProfileProperty currentTextures = getProfileProperty(playerProfile, \"textures\");\r\n        String value = split.get(0);\r\n        String signature = split.size() > 1 ? split.get(1) : null;\r\n        if (!value.equals(currentTextures.getValue()) && (signature == null || !signature.equals(currentTextures.getSignature()))) {\r\n            modifiedTextures.add(player.getUniqueId());\r\n        }\r\n        playerProfile.setProperty(new ProfileProperty(\"textures\", value, signature));\r\n        player.setPlayerProfile(playerProfile);\r\n    }\r\n\r\n    public ProfileProperty getProfileProperty(PlayerProfile profile, String name) {\r\n        for (ProfileProperty property : profile.getProperties()) {\r\n            if (property.getName().equals(name)) {\r\n                return property;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public <T extends Entity> T spawnEntity(Location location, Class<T> type, Consumer<T> configure, CreatureSpawnEvent.SpawnReason reason) {\r\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\r\n            // Takes the deprecated bukkit consumer on older versions\r\n            if (WORLD_SPAWN_BUKKIT_CONSUMER == null) {\r\n                WORLD_SPAWN_BUKKIT_CONSUMER = ReflectionHelper.getMethodHandle(RegionAccessor.class, \"spawn\", Location.class, Class.class, Consumer.class, CreatureSpawnEvent.SpawnReason.class);\r\n            }\r\n            try {\r\n                return (T) WORLD_SPAWN_BUKKIT_CONSUMER.invoke(location.getWorld(), location, type, configure, reason);\r\n            }\r\n            catch (Throwable e) {\r\n                Debug.echoError(e);\r\n                return null;\r\n            }\r\n        }\r\n        return location.getWorld().spawn(location, type, configure, reason);\r\n    }\r\n\r\n    @Override\r\n    public void setTeamPrefix(Team team, String prefix) {\r\n        team.prefix(PaperModule.parseFormattedText(prefix, ChatColor.WHITE));\r\n    }\r\n\r\n    @Override\r\n    public void setTeamSuffix(Team team, String suffix) {\r\n        team.suffix(PaperModule.parseFormattedText(suffix, ChatColor.WHITE));\r\n    }\r\n\r\n    @Override\r\n    public String getTeamPrefix(Team team) {\r\n        return PaperModule.stringifyComponent(team.prefix());\r\n    }\r\n\r\n    @Override\r\n    public String getTeamSuffix(Team team) {\r\n        return PaperModule.stringifyComponent(team.suffix());\r\n    }\r\n\r\n    @Override\r\n    public String convertTextToMiniMessage(String text, boolean splitNewlines) {\r\n        if (splitNewlines) {\r\n            List<String> lines = CoreUtilities.split(text, '\\n');\r\n            return lines.stream().map(l -> convertTextToMiniMessage(l, false)).collect(Collectors.joining(\"\\n\"));\r\n        }\r\n        Component parsed = PaperModule.jsonToComponent(FormattedTextHelper.componentToJson(FormattedTextHelper.parse(text, ChatColor.WHITE, false)));\r\n        return MiniMessage.miniMessage().serialize(parsed);\r\n    }\r\n\r\n    @Override\r\n    public Merchant createMerchant(String title) {\r\n        return Bukkit.createMerchant(PaperModule.parseFormattedText(title, ChatColor.BLACK));\r\n    }\r\n\r\n    @Override\r\n    public String getText(TextDisplay textDisplay) {\r\n        return PaperModule.stringifyComponent(textDisplay.text());\r\n    }\r\n\r\n    @Override\r\n    public void setText(TextDisplay textDisplay, String text) {\r\n        textDisplay.text(PaperModule.parseFormattedText(text, ChatColor.WHITE));\r\n    }\r\n\r\n    @Override\r\n    public void kickPlayer(Player player, String message) {\r\n        player.kick(PaperModule.parseFormattedText(message, ChatColor.WHITE));\r\n    }\r\n\r\n    @Override\r\n    public String getClientBrand(Player player) {\r\n        String clientBrand = player.getClientBrandName();\r\n        return clientBrand != null ? clientBrand : \"unknown\";\r\n    }\r\n\r\n    @Override\r\n    public boolean canUseEquipmentSlot(LivingEntity entity, EquipmentSlot slot) {\r\n        return NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) ? entity.canUseEquipmentSlot(slot) : super.canUseEquipmentSlot(entity, slot);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasCustomName(PotionMeta meta) {\r\n        return meta.hasCustomPotionName();\r\n    }\r\n\r\n    @Override\r\n    public void setMaterialTags(Material type, Set<NamespacedKey> tags) {\r\n        if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            super.setMaterialTags(type, tags);\r\n            return;\r\n        }\r\n        BlockTagsSetter.INSTANCE.setTags(type, tags);\r\n    }\r\n\r\n    @Override\r\n    public void addLink(ServerLinks links, String display, URI uri) {\r\n        links.addLink(PaperModule.parseFormattedText(display, ChatColor.WHITE), uri);\r\n    }\r\n  \r\n    @Override\r\n    public double[] getRecentTps() {\r\n        return Bukkit.getTPS();\r\n    }\r\n\r\n    @Override\r\n    public String getCopperGolemState(CopperGolem copperGolem) {\r\n        return copperGolem.getWeatheringState().name();\r\n    }\r\n\r\n    @Override\r\n    public void setCopperGolemState(ElementTag variant, CopperGolem copperGolem, Mechanism mechanism) {\r\n        if (mechanism.requireEnum(WeatheringCopperState.class)) {\r\n            copperGolem.setWeatheringState(variant.asEnum(WeatheringCopperState.class));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.denizenscript</groupId>\n    <artifactId>denizen</artifactId>\n    <version>1.3.2-SNAPSHOT</version>\n\n    <name>Denizen</name>\n    <description>Scriptable Minecraft and Citizens2</description>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <BUILD_NUMBER>Unknown</BUILD_NUMBER>\n        <BUILD_CLASS>CUSTOM</BUILD_CLASS>\n    </properties>\n\n    <!-- Repositories -->\n    <repositories>\n        <repository>\n            <id>everything</id>\n            <url>https://maven.citizensnpcs.co/repo</url>\n        </repository>\n    </repositories>\n\n    <!-- Dependencies -->\n    <dependencies>\n        <!-- Required APIs -->\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>26.1.2-R0.1-SNAPSHOT</version>\n            <type>jar</type>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizencore</artifactId>\n            <version>1.91.0-SNAPSHOT</version>\n            <scope>compile</scope>\n        </dependency>\n        <!-- Basic dependencies -->\n        <dependency>\n            <groupId>net.citizensnpcs</groupId>\n            <artifactId>citizens-main</artifactId>\n            <version>2.0.42-SNAPSHOT</version>\n            <type>jar</type>\n            <scope>provided</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>*</groupId>\n                    <artifactId>*</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>org.json</groupId>\n            <artifactId>json</artifactId>\n            <version>LATEST</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>net.milkbowl.vault</groupId>\n            <artifactId>Vault</artifactId>\n            <version>1.5.6</version>\n            <scope>system</scope>\n            <systemPath>${project.basedir}/lib/Vault.jar</systemPath>\n        </dependency>\n        <dependency>\n            <groupId>net.kyori</groupId>\n            <artifactId>adventure-nbt</artifactId>\n            <version>4.26.1</version>\n        </dependency>\n        <!-- Contained by Spigot-server -->\n        <dependency>\n            <groupId>it.unimi.dsi</groupId>\n            <artifactId>fastutil-core</artifactId>\n            <version>8.5.8</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <ciManagement>\n        <system>jenkins</system>\n        <url>https://ci.citizensnpcs.co</url>\n    </ciManagement>\n    <scm>\n        <connection>scm:git:git://github.com/DenizenScript/Denizen.git</connection>\n        <developerConnection>scm:git:git:@github.com:DenizenScript/Denizen.git</developerConnection>\n        <url>https://github.com/DenizenScript/Denizen/tree/dev/</url>\n    </scm>\n    <distributionManagement>\n        <repository>\n            <id>citizens-repo</id>\n            <url>https://maven.citizensnpcs.co/repo</url>\n        </repository>\n    </distributionManagement>\n\n    <build>\n        <sourceDirectory>src/main/java</sourceDirectory>\n        <resources>\n            <resource>\n                <filtering>false</filtering>\n                <directory>${basedir}/src/main/resources</directory>\n                <includes>\n                    <include>*.mid</include>\n                </includes>\n            </resource>\n            <resource>\n                <filtering>true</filtering>\n                <directory>${basedir}/src/main/resources</directory>\n                <includes>\n                    <include>*.yml</include>\n                    <include>*.dsc</include>\n                </includes>\n            </resource>\n        </resources>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-deploy-plugin</artifactId>\n                <version>3.0.0</version>\n                <executions>\n                    <execution>\n                        <id>default-deploy</id>\n                        <phase>deploy</phase>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>3.4.1</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                        <configuration>\n                            <minimizeJar>true</minimizeJar>\n                            <filters>\n                                <filter>\n                                    <artifact>com.denizenscript:denizencore</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                                <filter>\n                                    <artifact>org.json:json</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                                <filter>\n                                    <artifact>com.denizenscript:denizen-v**</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                                <filter>\n                                    <artifact>com.denizenscript:denizen-nmshandler</artifact>\n                                    <includes>\n                                        <include>**</include>\n                                    </includes>\n                                </filter>\n                            </filters>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.10.1</version>\n                <configuration>\n                    <source>16</source>\n                    <target>16</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/Denizen.java",
    "content": "package com.denizenscript.denizen;\r\n\r\nimport com.denizenscript.denizen.events.ScriptEventRegistry;\r\nimport com.denizenscript.denizen.events.bukkit.SavesReloadEvent;\r\nimport com.denizenscript.denizen.events.server.ServerPrestartScriptEvent;\r\nimport com.denizenscript.denizen.events.server.ServerStartScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport com.denizenscript.denizen.npc.DenizenNPCHelper;\r\nimport com.denizenscript.denizen.npc.TraitRegistry;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.objects.properties.PropertyRegistry;\r\nimport com.denizenscript.denizen.scripts.commands.BukkitCommandRegistry;\r\nimport com.denizenscript.denizen.scripts.commands.player.ClickableCommand;\r\nimport com.denizenscript.denizen.scripts.containers.ContainerRegistry;\r\nimport com.denizenscript.denizen.scripts.containers.core.*;\r\nimport com.denizenscript.denizen.scripts.triggers.TriggerRegistry;\r\nimport com.denizenscript.denizen.scripts.triggers.core.ChatTrigger;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.tags.core.NPCTagBase;\r\nimport com.denizenscript.denizen.utilities.*;\r\nimport com.denizenscript.denizen.utilities.blocks.FullBlockData;\r\nimport com.denizenscript.denizen.utilities.command.*;\r\nimport com.denizenscript.denizen.utilities.command.manager.CommandManager;\r\nimport com.denizenscript.denizen.utilities.command.manager.Injector;\r\nimport com.denizenscript.denizen.utilities.command.manager.messaging.Messaging;\r\nimport com.denizenscript.denizen.utilities.debugging.BStatsMetricsLite;\r\nimport com.denizenscript.denizen.utilities.debugging.DebugSubmit;\r\nimport com.denizenscript.denizen.utilities.debugging.StatsRecord;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.utilities.entity.DenizenEntityType;\r\nimport com.denizenscript.denizen.utilities.flags.PlayerFlagHandler;\r\nimport com.denizenscript.denizen.utilities.flags.WorldFlagHandler;\r\nimport com.denizenscript.denizen.utilities.implementation.DenizenCoreImplementation;\r\nimport com.denizenscript.denizen.utilities.maps.DenizenMapManager;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizen.utilities.world.VoidGenerator;\r\nimport com.denizenscript.denizen.utilities.world.WorldListChangeTracker;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.objects.ObjectFetcher;\r\nimport com.denizenscript.denizencore.objects.core.SecretTag;\r\nimport com.denizenscript.denizencore.objects.core.TimeTag;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.scripts.ScriptHelper;\r\nimport com.denizenscript.denizencore.scripts.commands.queue.RunLaterCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\r\nimport com.denizenscript.denizencore.utilities.debugging.StrongWarning;\r\nimport com.denizenscript.denizencore.utilities.text.ConfigUpdater;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.OfflinePlayer;\r\nimport org.bukkit.command.Command;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.configuration.file.FileConfiguration;\r\nimport org.bukkit.configuration.file.YamlConfiguration;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.HandlerList;\r\nimport org.bukkit.event.player.PlayerChatEvent;\r\nimport org.bukkit.generator.ChunkGenerator;\r\nimport org.bukkit.plugin.java.JavaPlugin;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.io.*;\r\nimport java.net.URLDecoder;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\nimport java.util.logging.Level;\r\nimport java.util.logging.Logger;\r\nimport java.util.stream.Collectors;\r\n\r\npublic class Denizen extends JavaPlugin {\r\n\r\n    public static Denizen instance;\r\n\r\n    public static Denizen getInstance() {\r\n        return instance;\r\n    }\r\n\r\n    public static boolean hasTickedOnce = false;\r\n\r\n    public static String versionTag = null;\r\n    private boolean startedSuccessful = false;\r\n\r\n    public static boolean supportsPaper = false;\r\n\r\n    public CommandManager commandManager;\r\n\r\n    public TriggerRegistry triggerRegistry;\r\n    public DenizenNPCHelper npcHelper;\r\n\r\n    public BukkitWorldScriptHelper worldScriptHelper;\r\n\r\n    public ItemScriptHelper itemScriptHelper;\r\n\r\n    public ExCommandHandler exCommand;\r\n\r\n    public DenizenCoreImplementation coreImplementation = new DenizenCoreImplementation();\r\n\r\n    /*\r\n     * Sets up Denizen on start of the CraftBukkit server.\r\n     */\r\n    @Override\r\n    public void onEnable() {\r\n        long startTime = System.currentTimeMillis();\r\n        instance = this;\r\n        try {\r\n            versionTag = this.getDescription().getVersion();\r\n\r\n            CoreUtilities.noDebugContext = new BukkitTagContext(null, null, null, false, null);\r\n            CoreUtilities.noDebugContext.showErrors = () -> false;\r\n            CoreUtilities.basicContext = new BukkitTagContext(null, null, null, true, null);\r\n            CoreUtilities.errorButNoDebugContext = new BukkitTagContext(null, null, null, false, null);\r\n            // Load Denizen's core\r\n            DenizenCore.init(coreImplementation);\r\n        }\r\n        catch (Exception e) {\r\n            e.printStackTrace();\r\n            getServer().getPluginManager().disablePlugin(this);\r\n            startedSuccessful = false;\r\n            return;\r\n        }\r\n        PlayerFlagHandler.dataFolder = new File(getDataFolder(), \"player_flags\");\r\n        if (!PlayerFlagHandler.dataFolder.exists()) {\r\n            PlayerFlagHandler.dataFolder.mkdir();\r\n        }\r\n        DebugInternals.alternateTrimLogic = FormattedTextHelper::bukkitSafeDebugTrimming;\r\n        String javaVersion = System.getProperty(\"java.version\");\r\n        Debug.log(\"Running on java version: \" + javaVersion);\r\n        if (javaVersion.startsWith(\"8\") || javaVersion.startsWith(\"1.8\") || javaVersion.startsWith(\"9\") || javaVersion.startsWith(\"1.9\")\r\n                || javaVersion.startsWith(\"10\") || javaVersion.startsWith(\"1.10\") || javaVersion.startsWith(\"11\")\r\n                || javaVersion.startsWith(\"12\") || javaVersion.startsWith(\"13\") || javaVersion.startsWith(\"14\") || javaVersion.startsWith(\"15\")) {\r\n            Debug.log(\"Running on outdated Java version somehow. Denizen requires Java 16+ or newer to function.\");\r\n        }\r\n        else if (javaVersion.startsWith(\"16\")) {\r\n            Debug.log(\"Running on fully supported Java 16.\");\r\n        }\r\n        else if (javaVersion.startsWith(\"17\")) {\r\n            Debug.log(\"Running on fully supported Java 17.\");\r\n        }\r\n        else if (javaVersion.startsWith(\"18\") || javaVersion.startsWith(\"19\")) {\r\n            getLogger().warning(\"Running unreliable Java version. modern Minecraft versions are built for Java 25, 21, or 17. Other Java versions are not guaranteed to function properly.\");\r\n        }\r\n        else if (javaVersion.startsWith(\"21\")) {\r\n            Debug.log(\"Running on fully supported Java 21.\");\r\n        }\r\n        else if (javaVersion.startsWith(\"25\")) {\r\n            Debug.log(\"Running on fully supported Java 25.\");\r\n        }\r\n        else {\r\n            Debug.log(\"Running on unrecognized (future?) Java version. May or may not work.\");\r\n        }\r\n        try {\r\n            if (Class.forName(\"com.destroystokyo.paper.PaperConfig\") != null) {\r\n                supportsPaper = true;\r\n            }\r\n        }\r\n        catch (ClassNotFoundException ex) {\r\n            // Ignore.\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        if (!NMSHandler.initialize(this)) {\r\n            getLogger().warning(\"-------------------------------------\");\r\n            getLogger().warning(\"This build of Denizen is not compatible with this Spigot version! Deactivating Denizen!\");\r\n            getLogger().warning(\"-------------------------------------\");\r\n            getServer().getPluginManager().disablePlugin(this);\r\n            startedSuccessful = false;\r\n            return;\r\n        }\r\n        if (!NMSHandler.instance.isExactServerVersionMatch()) {\r\n            String serverSoftware = supportsPaper ? \"Paper\" : \"Spigot\";\r\n            getLogger().warning(\"\"\"\r\n                    \\n-------------------------------------\r\n                    This build of Denizen was built for a different Minecraft version! This may potentially cause issues.\r\n                    If you are experiencing trouble, update Denizen and <server> both to latest builds!\r\n                    If this message appears with both Denizen and <server> fully up-to-date, contact the Denizen team (via Discord) to request an update be built.\r\n                    -------------------------------------\"\"\".replace(\"<server>\", serverSoftware)\r\n            );\r\n        }\r\n        triggerRegistry = new TriggerRegistry();\r\n        boolean citizensBork = false;\r\n        try {\r\n            // Activate dependencies\r\n            Depends.initialize();\r\n            if (Depends.citizens == null) {\r\n                if (Bukkit.getPluginManager().getPlugin(\"Citizens\") != null) {\r\n                    citizensBork = true;\r\n                    getLogger().warning(\"Citizens is present but doesn't seem to be activated! You may have an error earlier in your logs, or you may have a broken plugin load order.\");\r\n                }\r\n                else {\r\n                    getLogger().warning(\"Citizens does not seem to be available! Denizen will have reduced functionality!\");\r\n                }\r\n            }\r\n            startedSuccessful = true;\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            // Populate config.yml if it doesn't yet exist.\r\n            saveDefaultConfig();\r\n            reloadConfig();\r\n            // Startup procedure\r\n            Debug.log(ChatColor.LIGHT_PURPLE + \"+-------------------------+\");\r\n            Debug.log(ChatColor.YELLOW + \" Denizen \" + ChatColor.GRAY + \" scriptable minecraft\");\r\n            Debug.log(\"\");\r\n            Debug.log(ChatColor.GRAY + \"by:\" + ChatColor.WHITE + \" The DenizenScript team\");\r\n            Debug.log(ChatColor.GRAY + \"Chat with us at:\" + ChatColor.WHITE + \" https://discord.gg/Q6pZGSR\");\r\n            Debug.log(ChatColor.GRAY + \"Or learn more at:\" + ChatColor.WHITE + \" https://denizenscript.com\");\r\n            Debug.log(ChatColor.GRAY + \"version: \" + ChatColor.WHITE + versionTag);\r\n            Debug.log(ChatColor.LIGHT_PURPLE + \"+-------------------------+\");\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        // bstats.org\r\n        try {\r\n            BStatsMetricsLite metrics = new BStatsMetricsLite(this);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            DebugSubmit.init();\r\n            // If Citizens is enabled, Create the NPC Helper\r\n            if (Depends.citizens != null) {\r\n                npcHelper = new DenizenNPCHelper();\r\n            }\r\n            // Create our CommandManager to handle '/denizen' commands\r\n            commandManager = new CommandManager();\r\n            commandManager.setInjector(new Injector(this));\r\n            commandManager.register(DenizenCommandHandler.class);\r\n            // If Citizens is enabled, let it handle '/npc' commands\r\n            if (Depends.citizens != null) {\r\n                Depends.citizens.registerCommandClass(NPCCommandHandler.class);\r\n            }\r\n            DenizenEntityType.registerEntityType(\"ITEM_PROJECTILE\", ItemProjectile.class);\r\n            DenizenEntityType.registerEntityType(\"FAKE_ARROW\", FakeArrow.class);\r\n            DenizenEntityType.registerEntityType(\"FAKE_PLAYER\", FakePlayer.class);\r\n            // Track all player names for quick PlayerTag matching\r\n            for (OfflinePlayer player : Bukkit.getOfflinePlayers()) {\r\n                PlayerTag.notePlayer(player);\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            BukkitCommandRegistry.registerCommands();\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            ContainerRegistry.registerMainContainers();\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            // Ensure the Scripts and Midi folder exist\r\n            new File(getDataFolder() + \"/scripts\").mkdirs();\r\n            new File(getDataFolder() + \"/midi\").mkdirs();\r\n            new File(getDataFolder() + \"/schematics\").mkdirs();\r\n            // Ensure the example Denizen.mid sound file is available\r\n            if (!new File(getDataFolder() + \"/midi/Denizen.mid\").exists()) {\r\n                String sourceFile = URLDecoder.decode(Denizen.class.getProtectionDomain().getCodeSource().getLocation().getFile());\r\n                Debug.log(\"Denizen.mid not found, extracting from \" + sourceFile);\r\n                Utilities.extractFile(new File(sourceFile), \"Denizen.mid\", getDataFolder() + \"/midi/\");\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            // Automatic config file update\r\n            InputStream properConfig = Denizen.class.getResourceAsStream(\"/config.yml\");\r\n            String properConfigString = ScriptHelper.convertStreamToString(properConfig);\r\n            properConfig.close();\r\n            FileInputStream currentConfig = new FileInputStream(getDataFolder() + \"/config.yml\");\r\n            String currentConfigString = ScriptHelper.convertStreamToString(currentConfig);\r\n            currentConfig.close();\r\n            String updated = ConfigUpdater.updateConfig(currentConfigString, properConfigString);\r\n            if (updated != null) {\r\n                Debug.log(\"Your config file is outdated. Automatically updating it...\");\r\n                FileOutputStream configOutput = new FileOutputStream(getDataFolder() + \"/config.yml\");\r\n                OutputStreamWriter writer = new OutputStreamWriter(configOutput);\r\n                writer.write(updated);\r\n                writer.close();\r\n                configOutput.close();\r\n                reloadConfig();\r\n            }\r\n            if (Settings.cache_legacySpigotNamesSupport) {\r\n                Debug.log(\"Legacy Spigot name support enabled. This may be unnecessary; see config.yml for more information.\");\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            worldScriptHelper = new BukkitWorldScriptHelper();\r\n            itemScriptHelper = new ItemScriptHelper();\r\n            new InventoryScriptHelper();\r\n            new EntityScriptHelper();\r\n            new CommandScriptHelper();\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            if (Depends.citizens != null) {\r\n                // Register traits\r\n                TraitRegistry.registerMainTraits();\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        // Register Core Members in the Denizen Registries\r\n        try {\r\n            if (Depends.citizens != null) {\r\n                triggerRegistry.registerCoreMembers();\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            ScriptEventRegistry.registerMainEvents();\r\n            CommonRegistries.registerMainObjects();\r\n            CommonRegistries.registerMainTagHandlers();\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            // Initialize all properties\r\n            PropertyRegistry.registerMainProperties();\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            // TODO: temporary patch, should switch to custom click events\r\n            if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_20)) {\r\n                new CommandEvents();\r\n            }\r\n            if (Settings.cache_packetInterceptAutoInit) {\r\n                NetworkInterceptHelper.enable();\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        try {\r\n            if (supportsPaper) {\r\n                final Class<?> clazz = Class.forName(\"com.denizenscript.denizen.paper.PaperModule\");\r\n                clazz.getMethod(\"init\").invoke(null);\r\n            }\r\n        }\r\n        catch (ClassNotFoundException ex) {\r\n            supportsPaper = false;\r\n        }\r\n        catch (Throwable ex) {\r\n            supportsPaper = false;\r\n            Debug.echoError(ex);\r\n        }\r\n        Debug.log(\"Loaded <A>\" + DenizenCore.commandRegistry.instances.size() + \"<W> core commands and <A>\" + ObjectFetcher.objectsByPrefix.size() + \"<W> core object types, at <A>\" + (System.currentTimeMillis() - startTime) + \"<W>ms from start.\");\r\n        exCommand = new ExCommandHandler();\r\n        exCommand.enableFor(getCommand(\"ex\"));\r\n        ExSustainedCommandHandler exsCommand = new ExSustainedCommandHandler();\r\n        exsCommand.enableFor(getCommand(\"exs\"));\r\n        FullBlockData.init();\r\n        // Load script files without processing.\r\n        DenizenCore.preloadScripts(false, null);\r\n        // Load the saves.yml into memory\r\n        reloadSaves();\r\n        try {\r\n            // Fire the 'on Server PreStart' world event\r\n            ServerPrestartScriptEvent.instance.specialHackRunEvent();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        Debug.log(\"Final full init took <A>\" + (System.currentTimeMillis() - startTime) + \"<W>ms.\");\r\n        final boolean hadCitizensBork = citizensBork;\r\n        // Run everything else on the first server tick\r\n        Bukkit.getScheduler().scheduleSyncDelayedTask(this, () -> {\r\n            hasTickedOnce = true;\r\n            try {\r\n                if (hadCitizensBork) {\r\n                    Depends.setupCitizens();\r\n                    if (Depends.citizens != null) {\r\n                        getLogger().warning(\"Citizens was activated late - this means a plugin load order error occurred. You may have plugins with invalid 'plugin.yml' files (eg that use the 'loadbefore' directive, or that have circular dependencies).\");\r\n                        npcHelper = new DenizenNPCHelper();\r\n                        Depends.citizens.registerCommandClass(NPCCommandHandler.class);\r\n                        TraitRegistry.registerMainTraits();\r\n                        triggerRegistry.registerCoreMembers();\r\n                        BukkitCommandRegistry.registerCitizensCommands();\r\n                        ScriptEventRegistry.registerCitizensEvents();\r\n                        new NPCTagBase();\r\n                        ObjectFetcher.registerWithObjectFetcher(NPCTag.class, NPCTag.tagProcessor);\r\n                    }\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            try {\r\n                // Process script files (events, etc).\r\n                NoteManager.reload();\r\n                DenizenCore.postLoadScripts();\r\n                Debug.log(ChatColor.LIGHT_PURPLE + \"+-------------------------+\");\r\n                // Fire the 'on Server Start' world event\r\n                ServerStartScriptEvent.instance.fire();\r\n                worldScriptHelper.serverStartEvent();\r\n                if (Settings.allowStupidx()) {\r\n                    Debug.echoError(\"Don't screw with bad config values.\");\r\n                    Bukkit.shutdown();\r\n                }\r\n                Bukkit.getScheduler().scheduleSyncRepeatingTask(Denizen.this, () -> {\r\n                    DenizenCore.tick(50); // Sadly, minecraft has no delta timing, so a tick is always 50ms.\r\n                }, 1, 1);\r\n                InventoryTag.setupInventoryTracker();\r\n                if (!CoreConfiguration.skipAllFlagCleanings && !Settings.skipChunkFlagCleaning) {\r\n                    BukkitWorldScriptHelper.cleanAllWorldChunkFlags();\r\n                }\r\n                Bukkit.getPluginManager().registerEvents(new PlayerFlagHandler(), this);\r\n                Debug.log(\"Denizen fully loaded at: \" + TimeTag.now().format());\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }, 1);\r\n        new BukkitRunnable() {\r\n            @Override\r\n            public void run() {\r\n                if (Settings.canRecordStats()) {\r\n                    StatsRecord.trigger();\r\n                }\r\n            }\r\n        }.runTaskTimer(this, 100, 20 * 60 * 60);\r\n        new BukkitRunnable() {\r\n            @Override\r\n            public void run() {\r\n                PlayerFlagHandler.cleanCache();\r\n            }\r\n        }.runTaskTimer(this, 100, 20 * 60);\r\n        new BukkitRunnable() {\r\n            @Override\r\n            public void run() {\r\n                if (!StrongWarning.recentWarnings.isEmpty()) {\r\n                    StringBuilder warnText = new StringBuilder();\r\n                    warnText.append(ChatColor.YELLOW).append(\"[Denizen] \").append(ChatColor.RED).append(\"Recent strong system warnings, scripters need to address ASAP (check earlier console logs for details):\");\r\n                    for (StrongWarning warning : StrongWarning.recentWarnings.keySet()) {\r\n                        warnText.append(\"\\n- \").append(warning.message);\r\n                    }\r\n                    StrongWarning.recentWarnings.clear();\r\n                    Bukkit.getConsoleSender().sendMessage(warnText.toString());\r\n                    for (Player player : Bukkit.getOnlinePlayers()) {\r\n                        if (player.isOp()) {\r\n                            player.sendMessage(warnText.toString());\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }.runTaskTimer(this, 100, 20 * 60 * 5);\r\n        Bukkit.getPluginManager().registerEvents(new WorldListChangeTracker(), this);\r\n    }\r\n\r\n    public boolean hasDisabled = false;\r\n\r\n    /*\r\n     * Unloads Denizen on shutdown of the server.\r\n     */\r\n    @Override\r\n    public void onDisable() {\r\n        if (!startedSuccessful) {\r\n            return;\r\n        }\r\n        if (hasDisabled) {\r\n            return;\r\n        }\r\n        hasDisabled = true;\r\n        DenizenCore.shutdown();\r\n        ScoreboardHelper._saveScoreboards();\r\n        InventoryScriptHelper.savePlayerInventories();\r\n        triggerRegistry.disableCoreMembers();\r\n        getLogger().log(Level.INFO, \" v\" + getDescription().getVersion() + \" disabled.\");\r\n        Bukkit.getServer().getScheduler().cancelTasks(this);\r\n        HandlerList.unregisterAll(this);\r\n        saveSaves(true);\r\n        worldFlags.shutdown();\r\n    }\r\n\r\n    @Override\r\n    public void reloadConfig() {\r\n        super.reloadConfig();\r\n        Settings.refillCache();\r\n        SecretTag.load();\r\n        if (!CoreConfiguration.defaultDebugMode) {\r\n            getLogger().warning(\"Debug is disabled in the Denizen config. This is almost always a mistake, and should not be done in the majority of cases.\");\r\n        }\r\n    }\r\n\r\n    private FileConfiguration scoreboardsConfig = null;\r\n    private File scoreboardsConfigFile = null;\r\n\r\n    public WorldFlagHandler worldFlags;\r\n\r\n    public void reloadSaves() {\r\n        if (scoreboardsConfigFile == null) {\r\n            scoreboardsConfigFile = new File(getDataFolder(), \"scoreboards.yml\");\r\n        }\r\n        scoreboardsConfig = YamlConfiguration.loadConfiguration(scoreboardsConfigFile);\r\n        // Reload scoreboards from scoreboards.yml\r\n        ScoreboardHelper._recallScoreboards();\r\n        // Load maps from maps.yml\r\n        DenizenMapManager.reloadMaps();\r\n        DenizenCore.reloadSaves();\r\n        if (worldFlags == null) {\r\n            worldFlags = new WorldFlagHandler();\r\n        }\r\n        worldFlags.shutdown();\r\n        worldFlags.init();\r\n        RunLaterCommand.init(new File(getDataFolder(), \"run_later.yml\").getPath());\r\n        if (new File(getDataFolder(), \"saves.yml\").exists()) {\r\n            LegacySavesUpdater.updateLegacySaves();\r\n        }\r\n        Bukkit.getServer().getPluginManager().callEvent(new SavesReloadEvent());\r\n    }\r\n\r\n    public FileConfiguration getScoreboards() {\r\n        if (scoreboardsConfig == null) {\r\n            reloadSaves();\r\n        }\r\n        return scoreboardsConfig;\r\n    }\r\n\r\n    /**\r\n     * Immediately saves all non-core save data.\r\n     * @param lockUntilDone 'true' if the system should sleep and lock the thread until saves are complete. 'false' is saves can happen in the future.\r\n     */\r\n    public void saveSaves(boolean lockUntilDone) {\r\n        // Save scoreboards to scoreboards.yml\r\n        ScoreboardHelper._saveScoreboards();\r\n        // Save maps to maps.yml\r\n        DenizenMapManager.saveMaps();\r\n        InventoryScriptHelper.savePlayerInventories();\r\n        // Save server flags\r\n        try {\r\n            scoreboardsConfig.save(scoreboardsConfigFile);\r\n        }\r\n        catch (IOException ex) {\r\n            Logger.getLogger(JavaPlugin.class.getName()).log(Level.SEVERE, \"Could not save to \" + scoreboardsConfigFile, ex);\r\n        }\r\n        PlayerFlagHandler.saveAllNow(lockUntilDone);\r\n        worldFlags.saveAll(lockUntilDone);\r\n        RunLaterCommand.saveToFile(!lockUntilDone);\r\n    }\r\n\r\n    @Override\r\n    public boolean onCommand(CommandSender sender, Command cmd, String alias, String[] args) {\r\n        if (cmd.getName().equals(\"denizenclickable\")) {\r\n            if (!(sender instanceof Player)) {\r\n                return false;\r\n            }\r\n            if (args.length >= 2 && CoreUtilities.equalsIgnoreCase(args[0], \"chat\")) {\r\n                ChatTrigger.instance.chatTriggerInternal(new PlayerChatEvent((Player) sender, Arrays.stream(args).skip(1).collect(Collectors.joining(\" \"))));\r\n                return true;\r\n            }\r\n            if (args.length != 1) {\r\n                return false;\r\n            }\r\n            UUID id;\r\n            try {\r\n                id = UUID.fromString(args[0]);\r\n            }\r\n            catch (IllegalArgumentException ex) {\r\n                return false;\r\n            }\r\n            ClickableCommand.runClickable(id, (Player) sender);\r\n            return true;\r\n        }\r\n        String modifier = args.length > 0 ? args[0] : \"\";\r\n        if (!commandManager.hasCommand(cmd, modifier) && !modifier.isEmpty()) {\r\n            return suggestClosestModifier(sender, cmd.getName(), modifier);\r\n        }\r\n\r\n        Object[] methodArgs = {sender};\r\n        return commandManager.executeSafe(cmd, args, sender, methodArgs);\r\n    }\r\n\r\n    @Override\r\n    public List<String> onTabComplete(CommandSender commandSender, Command command, String alias, String[] strings) {\r\n        if (alias.equals(\"denizen\")) {\r\n            return commandManager.onTabComplete(commandSender, command, alias, strings);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    private boolean suggestClosestModifier(CommandSender sender, String command, String modifier) {\r\n        String closest = commandManager.getClosestCommandModifier(command, modifier);\r\n        if (!closest.isEmpty()) {\r\n            Messaging.send(sender, \"<7>Unknown command. Did you mean:\");\r\n            Messaging.send(sender, \" /\" + command + \" \" + closest);\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {\r\n        return switch (CoreUtilities.toLowerCase(id)) {\r\n            case \"void\" -> new VoidGenerator(true);\r\n            case \"void_biomes\" -> new VoidGenerator(false);\r\n            default -> null;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public File getFile() {\r\n        return super.getFile();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/BukkitScriptEvent.java",
    "content": "package com.denizenscript.denizen.events;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.npc.traits.AssignmentTrait;\nimport com.denizenscript.denizen.objects.*;\nimport com.denizenscript.denizen.scripts.containers.core.EntityScriptHelper;\nimport com.denizenscript.denizen.scripts.containers.core.InventoryScriptHelper;\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper;\nimport com.denizenscript.denizen.tags.BukkitTagContext;\nimport com.denizenscript.denizen.utilities.NotedAreaTracker;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizen.utilities.inventory.SlotHelper;\nimport com.denizenscript.denizencore.events.ScriptEvent;\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\nimport com.denizenscript.denizencore.flags.FlaggableObject;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.JavaReflectedObjectTag;\nimport com.denizenscript.denizencore.objects.notable.Notable;\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.Deprecations;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport org.bukkit.*;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.EntityType;\nimport org.bukkit.entity.Vehicle;\nimport org.bukkit.event.*;\nimport org.bukkit.event.inventory.InventoryType;\nimport org.bukkit.plugin.EventExecutor;\nimport org.bukkit.plugin.IllegalPluginAccessException;\nimport org.bukkit.plugin.Plugin;\nimport org.bukkit.plugin.RegisteredListener;\nimport org.bukkit.scheduler.BukkitRunnable;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.*;\nimport java.util.function.Function;\n\npublic abstract class BukkitScriptEvent extends ScriptEvent {\n\n    // <--[language]\n    // @name Advanced Object Matchables\n    // @group Object System\n    // @description\n    // Script events have a variety of matchable object inputs, and the range of inputs they accept may not always be obvious.\n    // For example, an event might be \"player clicks <block>\"... what can \"<block>\" be filled with?\n    //\n    // \"<block>\" usually indicates that a LocationTag and/or MaterialTag will be matched against.\n    // This means you can specify any valid block material name, like \"stone\" or \"air\", like \"on player clicks stone:\" (will only run the event if the player is clicking stone)\n    // You can also use a catch-all such as \"block\", like \"on player clicks block:\" (will always run the event when the player clicks anything/anywhere)\n    // You can also use some more complicated matchables such as \"vanilla_tagged:\", like \"on player clicks vanilla_tagged:mineable/axe:\" (will run if the block is mineable with axes)\n    // (For more block-related options, refer to the <@link objecttype LocationTag> and <@link objecttype MaterialTag> matchers lists.)\n    //\n    // Many object types can be used for matchables, the valid inputs are unique depending on the object type involved.\n    //\n    // Some inputs don't refer to any object at all - they're just advanced matchers for some generic plaintext,\n    // for example \"<cause>\" implies an enumeration of causes will be matched against.\n    //\n    // Many inputs support advanced matchers. For details on that, see <@link language Advanced Object Matching>.\n    //\n    // A common matchable type found among different objects is a Flag Matchable. This usually looks like \"item_flagged:<flag>\"\n    // This matches if the object has the specified flag, and fails to match if the object doesn't have that flag.\n    // You can specify multiple required flags with '|', like \"item_flagged:a|b|c\", which will match if-and-only-if the item has ALL the flags named.\n    // They can also be used to require the object does NOT have the flag with a \"!\" like \"item_flagged:!<flag>\".\n    // When using multiple flags with \"|\", the \"!\" is per-entry, so \"item_flagged:!a|b\" requires the item DOES have 'b' but does NOT have 'a'.\n    //\n    // Note also that in addition to events, tags often also have matchables as input params,\n    // usually documented like \".types[<matcher>]\", with tag documentation specifying what matcher is used,\n    // or like \"<material_matcher>\" to indicate in this example specifically MaterialTag matchables are allowed.\n    //\n    // Not all object types have defined matchable options, and those that do list them in their ObjectType meta. For an example of this, check <@link objecttype ItemTag>.\n    //\n    // As a special case, \"in:<area>\" style matchable listings in event conform to the following option set:\n    // \"biome:<name>\": matches if the location is in a given biome, using advanced matchers.\n    // \"cuboid\" plaintext: matches if the location is in any noted cuboid.\n    // \"ellipsoid\" plaintext: matches if the location is in any noted ellipsoid.\n    // \"polygon\" plaintext: matches if the location is in any noted polygon.\n    // \"chunk_flagged:<flag>\": a Flag Matchable for ChunkTag flags.\n    // \"area_flagged:<flag>\": a Flag Matchable for AreaObject flags.\n    // Area note name: matches if an AreaObject note that matches the given advanced matcher contains the location.\n    // If none of the above are used, uses WorldTag matchers.\n    //\n    // -->\n\n    // <--[extension]\n    // @name Advanced Object Matching Extension\n    // @target_type language\n    // @target_name Advanced Object Matching\n    // @description\n    // Object types have their own special supported matchable inputs, refer to <@link language Advanced Object Matchables>.\n    // -->\n\n    // <--[language]\n    // @name Script Event Switches\n    // @group Script Events\n    // @description\n    // Modern script events support the concept of 'switches'.\n    // A switch is a specification of additional requirements in an event line other than what's in the event label it.\n    //\n    // A switch consists of a name and a value input, and are can be added anywhere in an event line as \"name:<value>\".\n    // For example, \"on delta time secondly every:5:\" is a valid event, where \"delta time secondly\" is the event itself, and \"every:<#>\" is a switch available to the event.\n    //\n    // A traditional Denizen event might look like \"on <entity> damaged\",\n    // where \"<entity>\" can be filled with \"entity\" or any entity type (like \"player\").\n    // A switch-using event would instead take the format \"on entity damaged\" with switch \"type:<entity type>\"\n    // meaning you can do \"on entity damaged\" for any entity, or \"on entity damaged type:player:\" for players specifically.\n    // This is both more efficient to process and more explicit in what's going on, however it is less clear/readable to the average user, so it is not often used.\n    // Some events may have switches for less-often specified data, and use the event line for other options.\n    //\n    // There are also some standard switches available to every script event, and some available to an entire category of script events.\n    //\n    // One switch available to every event is \"server_flagged:<flag_name>\", which requires that there be a server flag under the given name.\n    // For example, \"on console output server_flagged:recording:\" will only run the handler for console output when the \"recording\" flag is set on the server.\n    // This can also be used to require the server does NOT have a flag with \"server_flagged:!<flag_name>\"\n    //\n    // \"chance:<percent>\" is also a globally available switch.\n    // For example, \"on player breaks diamond_ore chance:25:\" will only fire on average one in every four times that a player breaks a diamond ore block.\n    //\n    // Events that have a player linked have the \"flagged\" and \"permission\" switches available.\n    //\n    // If the switch is specified, and an event doesn't have a linked player, the event will automatically fail to match.\n    // The \"flagged:<flag_name>\" switch will limit the event to only fire when the player has the flag with the specified name.\n    // It can be used like \"on player breaks block flagged:nobreak:\" (that would be used alongside \"- flag player nobreak\").\n    // You can also use \"flagged:!<flag_name>\" to require the player does NOT have the flag, like \"on player breaks block flagged:!griefbypass:\"\n    //\n    // The \"permission:<perm key>\" will limit the event to only fire when the player has the specified permission key.\n    // It can be used like \"on player breaks block permission:denizen.my.perm:\"\n    // For multiple flag or permission requirements, just list them separated by '|' pipes, like \"flagged:a|b|c\". This will require all named flags/permissions to be present, not just one.\n    //\n    // Events that have an NPC linked have the \"assigned\" switch available.\n    // If the switch is specified, and an event doesn't have a linked NPC, the event will automatically fail to match.\n    // The \"assigned:<script name>\" switch will limit the event to only fire when the NPC has an assignment script that matches the given advanced matcher.\n    //\n    // Events that occur at a specific location have the \"in:<area>\" and \"location_flagged\" switches.\n    // This switches will be ignored (not counted one way or the other) for events that don't have a known location.\n    // For \"in:<area>\" switches, 'area' is any area-defining tag type - refer to <@link language Advanced Object Matchables>.\n    // \"location_flagged:<flag name>\" works just like \"server_flagged\" or the player \"flagged\" switches, but for locations.\n    //\n    // All script events have priority switches (see <@link language script event priority>),\n    // All Bukkit events have bukkit priority switches (see <@link language bukkit event priority>),\n    // All cancellable script events have cancellation switches (see <@link language script event cancellation>).\n    //\n    // See also <@link language Advanced Object Matching>.\n    // -->\n\n    public static boolean couldMatchLegacyInArea(String lower) {\n        int index = CoreUtilities.split(lower, ' ').indexOf(\"in\");\n        if (index == -1) {\n            return true;\n        }\n        String in = CoreUtilities.getXthArg(index + 1, lower);\n        if (couldMatchInventory(in)) {\n            return false;\n        }\n        if (in.equals(\"notable\") || in.equals(\"noted\")) {\n            String next = CoreUtilities.getXthArg(index + 2, lower);\n            if (!next.equals(\"cuboid\") && !next.equals(\"ellipsoid\")) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public static HashSet<String> areaCouldMatchableText = new HashSet<>(List.of(\"area\", \"cuboid\", \"polygon\", \"ellipsoid\"));\n    public static HashSet<String> areaCouldMatchPrefixes = new HashSet<>(List.of(\"area_flagged\", \"biome\"));\n\n    public static boolean couldMatchArea(String text) {\n        if (areaCouldMatchableText.contains(text)) {\n            return true;\n        }\n        int colon = text.indexOf(':');\n        if (colon != -1) {\n            if (areaCouldMatchPrefixes.contains(text.substring(0, colon))) {\n                return true;\n            }\n        }\n        if (NoteManager.getSavedObject(text) instanceof AreaContainmentObject) {\n            return true;\n        }\n        if (isAdvancedMatchable(text)) {\n            MatchHelper matcher = createMatcher(text);\n            for (Notable obj : NoteManager.nameToObject.values()) {\n                if (obj instanceof AreaContainmentObject && matcher.doesMatch(((AreaContainmentObject) obj).getNoteName())) {\n                    return true;\n                }\n            }\n            addPossibleCouldMatchFailReason(\"Imperfect area label (allowed due to advanced-matcher usage)\", text);\n            return true;\n        }\n        else {\n            addPossibleCouldMatchFailReason(\"Not a valid area label\", text);\n            return false;\n        }\n    }\n\n    public static boolean exactMatchesEnum(String text, final Enum<?>[] enumVals) {\n        for (Enum<?> val : enumVals) {\n            if (CoreUtilities.equalsIgnoreCase(val.name(), text)) {\n                return true;\n            }\n        }\n        addPossibleCouldMatchFailReason(\"Does not match required enumeration\", text);\n        return false;\n    }\n\n    public static boolean couldMatchEnum(String text, final Enum<?>[] enumVals) {\n        if (exactMatchesEnum(text, enumVals)) {\n            return true;\n        }\n        if (isAdvancedMatchable(text)) {\n            MatchHelper matcher = createMatcher(text);\n            for (Enum<?> val : enumVals) {\n                if (matcher.doesMatch(val.name())) {\n                    return true;\n                }\n            }\n        }\n        addPossibleCouldMatchFailReason(\"Does not match required enumeration\", text);\n        return false;\n    }\n\n    public static boolean couldMatchRegistry(String text, Registry<?> registry) {\n        if (!isAdvancedMatchable(text)) {\n            if (registry.get(Utilities.parseNamespacedKey(text)) != null) {\n                return true;\n            }\n            addPossibleCouldMatchFailReason(\"Does not match required registry\", text);\n            return false;\n        }\n        MatchHelper matcher = createMatcher(text);\n        for (Keyed value : registry) {\n            if (matcher.doesMatch(Utilities.namespacedKeyToString(value.getKey()))) {\n                return true;\n            }\n        }\n        addPossibleCouldMatchFailReason(\"Does not match required registry\", text);\n        return false;\n    }\n\n    public static HashSet<String> inventoryCouldMatchableText = new HashSet<>(List.of(\"inventory\", \"notable\", \"note\", \"gui\"));\n    public static HashSet<String> inventoryCouldMatchPrefixes = new HashSet<>(List.of(\"inventory_flagged\"));\n\n    public static boolean couldMatchInventory(String text) {\n        if (inventoryCouldMatchableText.contains(text)) {\n            return true;\n        }\n        int colon = text.indexOf(':');\n        if (colon != -1) {\n            if (inventoryCouldMatchPrefixes.contains(text.substring(0, colon))) {\n                return true;\n            }\n        }\n        if (InventoryTag.matches(text)) {\n            return true;\n        }\n        if (isAdvancedMatchable(text)) {\n            MatchHelper matcher = createMatcher(text);\n            for (InventoryType type : InventoryType.values()) {\n                if (matcher.doesMatch(type.name())) {\n                    return true;\n                }\n            }\n            for (String type : InventoryTag.idTypes) {\n                if (matcher.doesMatch(type)) {\n                    return true;\n                }\n            }\n            for (String type : InventoryScriptHelper.inventoryScripts.keySet()) {\n                if (matcher.doesMatch(type)) {\n                    return true;\n                }\n            }\n            for (InventoryTag note : InventoryScriptHelper.notedInventories.values()) {\n                if (matcher.doesMatch(note.noteName)) {\n                    return true;\n                }\n            }\n        }\n        addPossibleCouldMatchFailReason(\"Not a valid inventory label\", text);\n        return false;\n    }\n\n    public static boolean couldMatchEntity(String text) {\n        if (exactMatchEntity(text)) {\n            return true;\n        }\n        if (isAdvancedMatchable(text)) {\n            MatchHelper matcher = createMatcher(text);\n            for (EntityType entity : EntityType.values()) {\n                if (matcher.doesMatch(entity.name())) {\n                    return true;\n                }\n            }\n            for (String script : EntityScriptHelper.scripts.keySet()) {\n                if (matcher.doesMatch(script)) {\n                    return true;\n                }\n            }\n        }\n        addPossibleCouldMatchFailReason(\"Not a valid entity label\", text);\n        return false;\n    }\n\n    public static HashSet<String> entityCouldMatchPrefixes = new HashSet<>(List.of(\"entity_flagged\", \"player_flagged\", \"npc_flagged\"));\n\n    public static boolean exactMatchEntity(String text) {\n        if (EntityTag.specialEntityMatchables.contains(text)) {\n            return true;\n        }\n        int colon = text.indexOf(':');\n        if (colon != -1) {\n            if (entityCouldMatchPrefixes.contains(text.substring(0, colon))) {\n                return true;\n            }\n        }\n        if (EntityTag.matches(text)) {\n            return true;\n        }\n        addPossibleCouldMatchFailReason(\"Not a valid entity label\", text);\n        return false;\n    }\n\n    public static HashSet<String> vehicleCouldMatchPrefixes = new HashSet<>(List.of(\"entity_flagged\"));\n\n    public static boolean exactMatchesVehicle(String text) {\n        if (text.equals(\"vehicle\")) {\n            return true;\n        }\n        if (EntityTag.specialEntityMatchables.contains(text)) {\n            return false;\n        }\n        int colon = text.indexOf(':');\n        if (colon != -1) {\n            if (vehicleCouldMatchPrefixes.contains(text.substring(0, colon))) {\n                return true;\n            }\n        }\n        if (text.startsWith(\"player_flagged:\") || text.startsWith(\"npc_flagged:\")) {\n            return false;\n        }\n        if (EntityTag.matches(text)) {\n            EntityTag entity = EntityTag.valueOf(text, CoreUtilities.noDebugContext);\n            if (entity == null) {\n                addPossibleCouldMatchFailReason(\"Broken entity/vehicle reference\", text);\n                return false;\n            }\n            if (!Vehicle.class.isAssignableFrom(entity.getEntityType().getBukkitEntityType().getEntityClass())) {\n                addPossibleCouldMatchFailReason(\"Entity type is not a vehicle\", text);\n                return false;\n            }\n            return true;\n        }\n        addPossibleCouldMatchFailReason(\"Not a valid vehicle label\", text);\n        return false;\n    }\n\n    public static boolean couldMatchVehicle(String text) {\n        if (exactMatchesVehicle(text)) {\n            return true;\n        }\n        if (isAdvancedMatchable(text)) {\n            MatchHelper matcher = createMatcher(text);\n            for (EntityType entity : EntityType.values()) {\n                if (matcher.doesMatch(entity.name())) {\n                    return true;\n                }\n            }\n            for (String script : EntityScriptHelper.scripts.keySet()) {\n                if (matcher.doesMatch(script)) {\n                    return true;\n                }\n            }\n        }\n        addPossibleCouldMatchFailReason(\"Not a valid vehicle label\", text);\n        return false;\n    }\n\n    public static boolean couldMatchBlockOrItem(String text) {\n        if (itemCouldMatchableText.contains(text) || materialCouldMatchableText.contains(text)) {\n            return true;\n        }\n        int colon = text.indexOf(':');\n        if (colon != -1) {\n            if (itemCouldMatchPrefixes.contains(text.substring(0, colon))) {\n                return true;\n            }\n            if (materialCouldMatchPrefixes.contains(text.substring(0, colon))) {\n                return true;\n            }\n        }\n        if (MaterialTag.matches(text)) {\n            MaterialTag mat = MaterialTag.valueOf(text, CoreUtilities.noDebugContext);\n            if (mat == null) {\n                return false;\n            }\n            return true;\n        }\n        if (ItemTag.matches(text)) {\n            return true;\n        }\n        if (isAdvancedMatchable(text)) {\n            MatchHelper matcher = createMatcher(text);\n            for (Material material : Material.values()) {\n                if (matcher.doesMatch(material.name())) {\n                    return true;\n                }\n            }\n            for (String item : ItemScriptHelper.item_scripts.keySet()) {\n                if (matcher.doesMatch(item)) {\n                    return true;\n                }\n            }\n        }\n        addPossibleCouldMatchFailReason(\"Not a valid block or item label\", text);\n        return false;\n    }\n\n    public static boolean couldMatchBlock(String text) {\n        return couldMatchBlock(text, null);\n    }\n\n    public static HashSet<String> materialCouldMatchableText = new HashSet<>(List.of(\"block\", \"material\"));\n    public static HashSet<String> materialCouldMatchPrefixes = new HashSet<>(List.of(\"vanilla_tagged\", \"material_flagged\"));\n\n    public static boolean couldMatchBlock(String text, Function<Material, Boolean> requirement) {\n        if (materialCouldMatchableText.contains(text)) {\n            return true;\n        }\n        if (text.equals(\"item\")) {\n            return false;\n        }\n        int colon = text.indexOf(':');\n        if (colon != -1) {\n            if (materialCouldMatchPrefixes.contains(text.substring(0, colon))) {\n                return true;\n            }\n        }\n        if (MaterialTag.matches(text)) {\n            MaterialTag mat = MaterialTag.valueOf(text, CoreUtilities.noDebugContext);\n            if (mat == null || !mat.getMaterial().isBlock()) {\n                return false;\n            }\n            if (mat.getMaterial().isBlock() && (requirement == null || requirement.apply(mat.getMaterial()))) {\n                return true;\n            }\n            addPossibleCouldMatchFailReason(\"Material is an item not a block\", text);\n            return false;\n        }\n        if (isAdvancedMatchable(text)) {\n            MatchHelper matcher = createMatcher(text);\n            for (Material material : Material.values()) {\n                if (material.isBlock() && matcher.doesMatch(material.name()) && (requirement == null || requirement.apply(material))) {\n                    return true;\n                }\n            }\n        }\n        addPossibleCouldMatchFailReason(\"Not a valid block label\", text);\n        return false;\n    }\n\n    public static HashSet<String> itemCouldMatchableText = new HashSet<>(List.of(\"item\", \"potion\"));\n    public static HashSet<String> itemCouldMatchPrefixes = new HashSet<>(List.of(\"item_flagged\", \"vanilla_tagged\", \"item_enchanted\", \"material_flagged\", \"raw_exact\"));\n\n    public static boolean couldMatchItem(String text) {\n        if (itemCouldMatchableText.contains(text)) {\n            return true;\n        }\n        int colon = text.indexOf(':');\n        if (colon != -1) {\n            if (itemCouldMatchPrefixes.contains(text.substring(0, colon))) {\n                return true;\n            }\n        }\n        int bracketIndex = text.indexOf('[');\n        if (bracketIndex != -1) {\n            return ItemTag.matches(text);\n        }\n        if (MaterialTag.matches(text)) {\n            MaterialTag mat = MaterialTag.valueOf(text, CoreUtilities.noDebugContext);\n            if (mat == null || !mat.getMaterial().isItem()) {\n                addPossibleCouldMatchFailReason(\"Material is not an item\", text);\n                return false;\n            }\n            return true;\n        }\n        if (ItemTag.matches(text)) {\n            return true;\n        }\n        if (isAdvancedMatchable(text)) {\n            MatchHelper matcher = createMatcher(text);\n            for (Material material : Material.values()) {\n                if (material.isItem() && matcher.doesMatch(material.name())) {\n                    return true;\n                }\n            }\n            for (String item : ItemScriptHelper.item_scripts.keySet()) {\n                if (matcher.doesMatch(item)) {\n                    return true;\n                }\n            }\n        }\n        addPossibleCouldMatchFailReason(\"Not a valid item label\", text);\n        return false;\n    }\n\n    public static boolean nonSwitchWithCheck(ScriptPath path, ItemTag held) {\n        int index;\n        for (index = 0; index < path.eventArgsLower.length; index++) {\n            if (path.eventArgsLower[index].equals(\"with\")) {\n                break;\n            }\n        }\n        if (index >= path.eventArgsLower.length) {\n            // No 'with ...' specified\n            return true;\n        }\n\n        String with = path.eventArgLowerAt(index + 1);\n        if (with != null && (held == null || !held.tryAdvancedMatcher(with, path.context))) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public BukkitTagContext getTagContext(ScriptPath path) {\n        return (BukkitTagContext) super.getTagContext(path);\n    }\n\n    public static Class<? extends Event> getRegistrationClass(Class<? extends Event> clazz) {\n        try {\n            clazz.getDeclaredMethod(\"getHandlerList\");\n            return clazz;\n        }\n        catch (NoSuchMethodException var3) {\n            if (clazz.getSuperclass() != null && !clazz.getSuperclass().equals(Event.class) && Event.class.isAssignableFrom(clazz.getSuperclass())) {\n                return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class));\n            }\n            else {\n                throw new IllegalPluginAccessException(\"Unable to find handler list for event \" + clazz.getName() + \". Static getHandlerList method required!\");\n            }\n        }\n    }\n\n    public static HandlerList getEventListeners(Class<? extends Event> type) {\n        try {\n            Method method = getRegistrationClass(type).getDeclaredMethod(\"getHandlerList\");\n            method.setAccessible(true);\n            return (HandlerList) method.invoke(null);\n        }\n        catch (Exception var3) {\n            throw new IllegalPluginAccessException(var3.toString());\n        }\n    }\n\n    private static Field REGISTERED_LISTENER_EXECUTOR_FIELD;\n\n    static {\n        try {\n            REGISTERED_LISTENER_EXECUTOR_FIELD = RegisteredListener.class.getDeclaredField(\"executor\");\n            REGISTERED_LISTENER_EXECUTOR_FIELD.setAccessible(true);\n        }\n        catch (NoSuchFieldException ex) {\n            Debug.echoError(ex);\n        }\n    }\n\n    public static EventExecutor getExecutor(RegisteredListener listener) {\n        try {\n            return (EventExecutor) REGISTERED_LISTENER_EXECUTOR_FIELD.get(listener);\n        }\n        catch (IllegalAccessException ex) {\n            Debug.echoError(ex);\n        }\n        return null;\n    }\n\n    public HashMap<EventPriority, BukkitScriptEvent> priorityHandlers;\n\n    public List<Map.Entry<RegisteredListener, HandlerList>> registeredHandlers;\n\n    // <--[language]\n    // @name Bukkit Event Priority\n    // @group Script Events\n    // @description\n    // Script events that are backed by standard Bukkit events are able to control what underlying Bukkit event priority\n    // they register as.\n    // This can be useful, for example, if a different plugin is cancelling the event at a later priority,\n    // and you're writing a script that needs to un-cancel the event.\n    // This can be done using the \"bukkit_priority\" switch.\n    // Valid priorities, in order of execution, are: LOWEST, LOW, NORMAL, HIGH, HIGHEST, MONITOR.\n    // Monitor is executed last, and is intended to only be used when reading the results of an event but not changing it.\n    // The default priority is \"normal\".\n    // -->\n\n    @Override\n    public ScriptEvent fire() {\n        if (!Bukkit.isPrimaryThread()) {\n            if (CoreConfiguration.debugVerbose) {\n                Debug.log(\"Event is firing async: \" + getName());\n            }\n            BukkitScriptEvent altEvent = (BukkitScriptEvent) clone();\n            new BukkitRunnable() {\n                @Override\n                public void run() {\n                    altEvent.fire();\n                }\n            }.runTask(Denizen.getInstance());\n            return altEvent;\n        }\n        return super.fire();\n    }\n\n    @Override\n    public void cancellationChanged() {\n        if (currentEvent instanceof Cancellable) {\n            ((Cancellable) currentEvent).setCancelled(cancelled);\n        }\n        super.cancellationChanged();\n    }\n\n    public Event currentEvent = null;\n\n    public void fire(Event event) {\n        currentEvent = event;\n        if (event instanceof Cancellable) {\n            cancelled = ((Cancellable) event).isCancelled();\n        }\n        else {\n            cancelled = false;\n        }\n        fire();\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"reflect_event\" -> currentEvent == null ? null : new JavaReflectedObjectTag(currentEvent);\n            default -> super.getContext(name);\n        };\n    }\n\n    @Override\n    public void destroy() {\n        if (priorityHandlers != null) {\n            for (BukkitScriptEvent event : priorityHandlers.values()) {\n                event.destroy();\n            }\n            priorityHandlers = null;\n        }\n        if (registeredHandlers != null) {\n            for (Map.Entry<RegisteredListener, HandlerList> handler : registeredHandlers) {\n                handler.getValue().unregister(handler.getKey());\n            }\n            registeredHandlers = null;\n        }\n    }\n\n    @Override\n    public void init() {\n        if (this instanceof Listener) {\n            initListener((Listener) this);\n        }\n    }\n\n    public void initListener(Listener listener) {\n        if (priorityHandlers == null) {\n            priorityHandlers = new HashMap<>();\n        }\n        for (ScriptPath path : new ArrayList<>(eventPaths)) {\n            String bukkitPriority = path.switches.get(\"bukkit_priority\");\n            if (bukkitPriority != null) {\n                try {\n                    EventPriority priority = EventPriority.valueOf(CoreUtilities.toUpperCase(bukkitPriority));\n                    BukkitScriptEvent handler = priorityHandlers.get(priority);\n                    if (handler == null) {\n                        handler = (BukkitScriptEvent) clone();\n                        handler.eventPaths = new ArrayList<>();\n                        handler.priorityHandlers = null;\n                        handler.registeredHandlers = null;\n                        priorityHandlers.put(priority, handler);\n                        handler.initForPriority(priority, (Listener) handler);\n                    }\n                    handler.eventPaths.add(path);\n                    eventPaths.remove(path);\n                }\n                catch (IllegalArgumentException ex) {\n                    Debug.echoError(\"Invalid 'bukkit_priority' switch for event '\" + path.event + \"' in script '\" + path.container.getName() + \"'.\");\n                    Debug.echoError(ex);\n                }\n            }\n        }\n        if (!eventPaths.isEmpty()) {\n            initForPriority(EventPriority.NORMAL, listener);\n        }\n    }\n\n    public void initForPriority(EventPriority priority, Listener listener) {\n        if (registeredHandlers == null) {\n            registeredHandlers = new ArrayList<>();\n        }\n        Plugin plugin = Denizen.getInstance();\n        for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry :\n                plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) {\n            for (RegisteredListener registeredListener : entry.getValue()) {\n                RegisteredListener newListener = new RegisteredListener(listener, getExecutor(registeredListener), priority, plugin, false);\n                HandlerList handlers = getEventListeners(getRegistrationClass(entry.getKey()));\n                handlers.register(newListener);\n                registeredHandlers.add(new HashMap.SimpleEntry<>(newListener, handlers));\n            }\n        }\n    }\n\n    public boolean runInCheck(ScriptPath path, Location location) {\n        return runInCheck(path, location, \"in\");\n    }\n\n    public static boolean runFlaggedCheck(ScriptPath path, String switchName, AbstractFlagTracker tracker) {\n        String flagged = path.switches.get(switchName);\n        return coreFlaggedCheck(flagged, tracker);\n    }\n\n    public boolean runLocationFlaggedCheck(ScriptPath path, String switchName, Location location) {\n        if (!path.switches.containsKey(switchName)) { // NOTE: opti to avoid 'getFlagTracker' call\n            return true;\n        }\n        return runFlaggedCheck(path, switchName, location == null ? null : new LocationTag(location).getFlagTracker());\n    }\n\n    public static class BoolHolder {\n        public boolean bool;\n    }\n\n    public boolean runInCheck(ScriptPath path, Location location, String innote) {\n        if (!runLocationFlaggedCheck(path, \"location_flagged\", location)) {\n            return false;\n        }\n        String inputText = path.switches.get(innote);\n        if (inputText == null) {\n            int index;\n            for (index = 0; index < path.eventArgsLower.length; index++) {\n                if (path.eventArgsLower[index].equals(innote)) {\n                    break;\n                }\n            }\n            if (index >= path.eventArgsLower.length) {\n                // No 'in ...' specified\n                return true;\n            }\n            if (location == null) {\n                return false;\n            }\n            Deprecations.inAreaSwitchFormat.warn();\n            inputText = path.eventArgLowerAt(index + 1);\n            if (inputText.equals(\"notable\") || inputText.equals(\"noted\")) {\n                String subit = path.eventArgLowerAt(index + 2);\n                if (subit.equals(\"cuboid\")) {\n                    BoolHolder bool = new BoolHolder();\n                    NotedAreaTracker.forEachAreaThatContains(new LocationTag(location), (a) -> { if (a instanceof CuboidTag) { bool.bool = true; } });\n                    return bool.bool;\n                }\n                else if (subit.equals(\"ellipsoid\")) {\n                    BoolHolder bool = new BoolHolder();\n                    NotedAreaTracker.forEachAreaThatContains(new LocationTag(location), (a) -> { if (a instanceof EllipsoidTag) { bool.bool = true; } });\n                    return bool.bool;\n                }\n                else {\n                    Debug.echoError(\"Invalid event 'IN ...' check [\" + getName() + \"] ('in notable ???'): '\" + path.event + \"' for \" + path.container.getName());\n                    return false;\n                }\n            }\n        }\n        if (location == null) {\n            return false;\n        }\n        if (inputText.startsWith(\"!\")) {\n            return !inCheckInternal(path.context, getName(), location, inputText.substring(1), path.event, path.container.getName());\n        }\n        return inCheckInternal(path.context, getName(), location, inputText, path.event, path.container.getName());\n    }\n\n    public static boolean inCheckInternal(TagContext context, String name, Location location, String inputText, String evtLine, String containerName) {\n        String lower = CoreUtilities.toLowerCase(inputText);\n        if (lower.contains(\":\")) {\n            if (lower.startsWith(\"world_flagged:\")) {\n                return coreFlaggedCheck(inputText.substring(\"world_flagged:\".length()), new WorldTag(location.getWorld()).getFlagTracker());\n            }\n            else if (lower.startsWith(\"chunk_flagged:\")) {\n                return coreFlaggedCheck(inputText.substring(\"chunk_flagged:\".length()), new ChunkTag(location).getFlagTracker());\n            }\n            else if (lower.startsWith(\"area_flagged:\")) {\n                String flagName = inputText.substring(\"area_flagged:\".length());\n                BoolHolder bool = new BoolHolder();\n                NotedAreaTracker.forEachAreaThatContains(new LocationTag(location), (a) -> {\n                    if (a instanceof FlaggableObject && coreFlaggedCheck(flagName, ((FlaggableObject) a).getFlagTracker())) {\n                        bool.bool = true;\n                    }\n                });\n                return bool.bool;\n            }\n            else if (lower.startsWith(\"biome:\")) {\n                String biome = inputText.substring(\"biome:\".length());\n                return runGenericCheck(biome, Utilities.namespacedKeyToString(new LocationTag(location).getBiome().getKey()));\n            }\n        }\n        if (lower.equals(\"cuboid\")) {\n            BoolHolder bool = new BoolHolder();\n            NotedAreaTracker.forEachAreaThatContains(new LocationTag(location), (a) -> { if (a instanceof CuboidTag) { bool.bool = true; } });\n            return bool.bool;\n        }\n        else if (lower.equals(\"ellipsoid\")) {\n            BoolHolder bool = new BoolHolder();\n            NotedAreaTracker.forEachAreaThatContains(new LocationTag(location), (a) -> { if (a instanceof EllipsoidTag) { bool.bool = true; } });\n            return bool.bool;\n        }\n        else if (lower.equals(\"polygon\")) {\n            BoolHolder bool = new BoolHolder();\n            NotedAreaTracker.forEachAreaThatContains(new LocationTag(location), (a) -> { if (a instanceof PolygonTag) { bool.bool = true; } });\n            return bool.bool;\n        }\n        else if (WorldTag.matches(inputText)) {\n            return CoreUtilities.equalsIgnoreCase(location.getWorld().getName(), lower);\n        }\n        else if (CuboidTag.matches(inputText)) {\n            CuboidTag cuboid = CuboidTag.valueOf(inputText, context);\n            if (cuboid == null || !cuboid.isUnique()) {\n                if (context.showErrors()) {\n                    Debug.echoError(\"Invalid event 'in:<area>' switch [\" + name + \"] (invalid cuboid): '\" + evtLine + \"' for \" + containerName);\n                }\n                return false;\n            }\n            return cuboid.isInsideCuboid(location);\n        }\n        else if (EllipsoidTag.matches(inputText)) {\n            EllipsoidTag ellipsoid = EllipsoidTag.valueOf(inputText, context);\n            if (ellipsoid == null || !ellipsoid.isUnique()) {\n                if (context.showErrors()) {\n                    Debug.echoError(\"Invalid event 'in:<area>' switch [\" + name + \"] (invalid ellipsoid): '\" + evtLine + \"' for \" + containerName);\n                }\n                return false;\n            }\n            return ellipsoid.contains(location);\n        }\n        else if (PolygonTag.matches(inputText)) {\n            PolygonTag polygon = PolygonTag.valueOf(inputText, context);\n            if (polygon == null || !polygon.isUnique()) {\n                if (context.showErrors()) {\n                    Debug.echoError(\"Invalid event 'in:<area>' switch [\" + name + \"] (invalid polygon): '\" + evtLine + \"' for \" + containerName);\n                }\n                return false;\n            }\n            return polygon.doesContainLocation(location);\n        }\n        else if (isAdvancedMatchable(lower)) {\n            MatchHelper matcher = createMatcher(lower);\n            BoolHolder bool = new BoolHolder();\n            NotedAreaTracker.forEachAreaThatContains(new LocationTag(location), (a) -> { if (matcher.doesMatch(a.getNoteName())) { bool.bool = true; } });\n            if (bool.bool) {\n                return true;\n            }\n            if (matcher.doesMatch(CoreUtilities.toLowerCase(location.getWorld().getName()))) {\n                return true;\n            }\n            return false;\n        }\n        else {\n            if (context.showErrors()) {\n                Debug.echoError(\"Invalid event 'in:<area>' switch [\" + name + \"] ('in:???') (did you make a typo, or forget to 'note' an object with that name?): '\" + evtLine + \"' for \" + containerName);\n            }\n            return false;\n        }\n    }\n\n    public static boolean trySlot(ScriptPath path, String switchName, Entity entity, int slot) {\n        String slotMatch = path.switches.get(switchName);\n        if (slotMatch != null) {\n            return SlotHelper.doesMatch(slotMatch, entity, slot);\n        }\n        return true;\n    }\n\n    public static boolean runWithCheck(ScriptPath path, ItemTag held) {\n        return runWithCheck(path, held, \"with\");\n    }\n\n    public static boolean runWithCheck(ScriptPath path, ItemTag held, String key) {\n        String with = path.switches.get(key);\n        if (with != null) {\n            if (CoreUtilities.equalsIgnoreCase(with, \"item\")) {\n                return true;\n            }\n            if (held == null || !held.tryAdvancedMatcher(with, path.context)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public static boolean runFlaggedCheck(ScriptPath path, PlayerTag player) {\n        return runFlaggedCheck(path, \"flagged\", player);\n    }\n\n    public static boolean runFlaggedCheck(ScriptPath path, String switchName, PlayerTag player) {\n        String flagged = path.switches.get(switchName);\n        if (flagged == null) {\n            return true;\n        }\n        if (player == null) {\n            return false;\n        }\n        return coreFlaggedCheck(flagged, player.getFlagTracker());\n    }\n\n    public static boolean runPermissionCheck(ScriptPath path, PlayerTag player) {\n        return runPermissionCheck(path, \"permission\", player);\n    }\n\n    public static boolean runPermissionCheck(ScriptPath path, String switchName, PlayerTag player) {\n        String perm = path.switches.get(switchName);\n        if (perm == null) {\n            return true;\n        }\n        if (player == null || !player.isOnline()) {\n            return false;\n        }\n        for (String permName : CoreUtilities.split(perm, '|')) {\n            boolean expect = true;\n            if (permName.startsWith(\"!\")) {\n                permName = permName.substring(1);\n                expect = false;\n            }\n            if (player.getPlayerEntity().hasPermission(permName) != expect) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public static boolean runAutomaticPlayerSwitches(ScriptEvent event, ScriptPath path) {\n        if (!path.switches.containsKey(\"flagged\") && !path.switches.containsKey(\"permission\")) {\n            return true;\n        }\n        BukkitScriptEntryData data = (BukkitScriptEntryData) event.getScriptEntryData();\n        if (!data.hasPlayer()) {\n            return false;\n        }\n        if (!runFlaggedCheck(path, data.getPlayer())) {\n            return false;\n        }\n        if (!runPermissionCheck(path, data.getPlayer())) {\n            return false;\n        }\n        return true;\n    }\n\n    public static boolean runAssignedCheck(ScriptPath path, NPCTag npc) {\n        String matcher = path.switches.get(\"assigned\");\n        if (matcher == null) {\n            return true;\n        }\n        if (npc == null) {\n            return false;\n        }\n        AssignmentTrait trait = npc.getCitizen().getTraitNullable(AssignmentTrait.class);\n        if (trait == null) {\n            return false;\n        }\n        for (String script : trait.assignments) {\n            if (runGenericCheck(matcher, script)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public static boolean runAutomaticNPCSwitches(ScriptEvent event, ScriptPath path) {\n        if (!path.switches.containsKey(\"assigned\")) {\n            return true;\n        }\n        BukkitScriptEntryData data = (BukkitScriptEntryData) event.getScriptEntryData();\n        if (!data.hasNPC()) {\n            return false;\n        }\n        if (!runAssignedCheck(path, data.getNPC())) {\n            return false;\n        }\n        return true;\n    }\n\n    // <--[language]\n    // @name Script Event After vs On\n    // @group Script Events\n    // @description\n    // Modern ScriptEvents let you choose between \"on\" and \"after\".\n    // An \"on\" event looks like \"on player breaks block:\" while an \"after\" event looks like \"after player breaks block:\".\n    //\n    // An \"on\" event fires *before* the event actually happens in the world. This means some relevant data won't be updated\n    // (for example, \"<context.location.material>\" would still show the block type that is going to be broken)\n    // and the result of the event can be changed (eg the event can be cancelled to stop it from actually going through).\n    //\n    // An \"after\" event, as the name implies, fires *after* the event actually happens. This means data will be already updated to the new state\n    // (so \"<context.location.material>\" would now show air) but could potentially contain an arbitrary new state from unrelated changes\n    // (for example \"<context.location.material>\" might now show a different block type, or the original one, if the event was changed,\n    // or another thing happened right after the event but before the 'after' event ran).\n    // This also means you cannot affect the outcome of the event at all (you can't cancel it or anything else - the \"determine\" command does nothing).\n    //\n    // See also <@link language Safety In Events>\n    // -->\n\n    // <--[language]\n    // @name Safety In Events\n    // @group Script Events\n    // @description\n    // One of the more common issues in Denizen scripts (particularly ones relating to inventories) is\n    // *event safety*. That is, using commands inside an event that don't get along with the event.\n    //\n    // The most common example of this is editing a player's inventory, within an inventory-related event.\n    // Generally speaking, this problem becomes relevant any time an edit is made to something involved with an event,\n    // within the firing of that event.\n    // Take the following examples:\n    // <code>\n    // on player clicks in inventory:\n    // - take iteminhand\n    // on entity damaged:\n    // - remove <context.entity>\n    // </code>\n    //\n    // In both examples above, something related to the event (the player's inventory, and the entity being damaged)\n    // is being modified within the event itself.\n    // These break due a rather important reason: The event is firing before and/or during the change to the object.\n    // Most events operate this way. A series of changes *to the object* are pending, and will run immediately after\n    // your script does... the problems resultant can range from your changes being lost to situational issues\n    // (eg an inventory suddenly being emptied entirely) to even server crashes!\n    // The second example event also is a good example of another way this can go wrong:\n    // Many scripts and plugins will listen to the entity damage event, in ways that are simply unable to handle\n    // the damaged entity just being gone now (when the event fires, it's *guaranteed* the entity is still present\n    // but that remove command breaks the guarantee!).\n    //\n    // The solution to this problem is simple: Use \"after\" instead of \"on\".\n    // <code>\n    // after player clicks in inventory:\n    // - take iteminhand\n    // after entity damaged:\n    // - if <context.entity.is_spawned||false>:\n    //   - remove <context.entity>\n    // </code>\n    // This will delay the script until *after* the event is complete, and thus outside of the problem area.\n    // And thus should be fine. One limitation you should note is demonstrated in the second example event:\n    // The normal guarantees of the event are no longer present (eg that the entity is still valid) and as such\n    // you should validate these expectations remain true after the event (as seen with the 'if is_spawned' check).\n    // (See also <@link language Script Event After vs On>)\n    //\n    // If you need determine changes to the event, you can instead use 'on' but add a 'wait 1t' after the determine but before other script logic.\n    // This allows the risky parts to be after the event and outside the problem area, but still determine changes to the event.\n    // Be sure to use 'passively' to allow the script to run in full.\n    // <code>\n    // on player clicks in inventory:\n    // - determine passively cancelled\n    // - wait 1t\n    // - take iteminhand\n    // on entity damaged:\n    // - determine passively cancelled\n    // - wait 1t\n    // - if <context.entity.is_spawned||false>:\n    //   - remove <context.entity>\n    // </code>\n    //\n    // -->\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/ScriptEventRegistry.java",
    "content": "package com.denizenscript.denizen.events;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.block.*;\r\nimport com.denizenscript.denizen.events.entity.*;\r\nimport com.denizenscript.denizen.events.item.*;\r\nimport com.denizenscript.denizen.events.npc.NPCNavigationScriptEvent;\r\nimport com.denizenscript.denizen.events.npc.NPCOpensScriptEvent;\r\nimport com.denizenscript.denizen.events.npc.NPCSpawnScriptEvent;\r\nimport com.denizenscript.denizen.events.npc.NPCStuckScriptEvent;\r\nimport com.denizenscript.denizen.events.player.*;\r\nimport com.denizenscript.denizen.events.server.*;\r\nimport com.denizenscript.denizen.events.vehicle.*;\r\nimport com.denizenscript.denizen.events.world.*;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.events.ScriptEventCouldMatcher;\r\n\r\nimport java.util.Arrays;\r\n\r\npublic class ScriptEventRegistry {\r\n\r\n    public static void registerCitizensEvents() {\r\n        ScriptEvent.registerScriptEvent(NPCNavigationScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(NPCOpensScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(NPCSpawnScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(NPCStuckScriptEvent.class);\r\n    }\r\n\r\n\r\n    public static void registerMainEvents() {\r\n        // Special data for matching to register\r\n        ScriptEvent.extraMatchers.add((event, path) -> BukkitScriptEvent.runAutomaticPlayerSwitches(event, path) && BukkitScriptEvent.runAutomaticNPCSwitches(event, path));\r\n\r\n        // <--[data]\r\n        // @name not_switches\r\n        // @values item_flagged, world_flagged, area_flagged, inventory_flagged, player_flagged, npc_flagged, entity_flagged, vanilla_tagged, raw_exact, item_enchanted, material_flagged, location_in, block_flagged\r\n        // -->\r\n        ScriptEvent.ScriptPath.notSwitches.addAll(Arrays.asList(\"item_flagged\", \"world_flagged\", \"area_flagged\", \"inventory_flagged\",\r\n                \"player_flagged\", \"npc_flagged\", \"entity_flagged\", \"vanilla_tagged\", \"raw_exact\", \"item_enchanted\", \"material_flagged\", \"location_in\", \"block_flagged\"));\r\n\r\n        // <--[data]\r\n        // @name global_switches\r\n        // @values bukkit_priority, assigned, flagged, permission, location_flagged\r\n        // -->\r\n        ScriptEvent.globalSwitches.addAll(Arrays.asList(\"bukkit_priority\", \"assigned\", \"flagged\", \"permission\", \"location_flagged\"));\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"entity\", BukkitScriptEvent::couldMatchEntity);\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"hanging\", BukkitScriptEvent::couldMatchEntity);\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"projectile\", BukkitScriptEvent::couldMatchEntity);\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"vehicle\", BukkitScriptEvent::couldMatchVehicle);\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"item\", BukkitScriptEvent::couldMatchItem);\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"inventory\", BukkitScriptEvent::couldMatchInventory);\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"block\", BukkitScriptEvent::couldMatchBlock);\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"material\", BukkitScriptEvent::couldMatchBlockOrItem);\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"area\", BukkitScriptEvent::couldMatchArea);\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"world\", (t) -> true); // TODO: ?\r\n        ScriptEventCouldMatcher.knownValidatorTypes.put(\"biome\", (t) -> true); // TODO: ?\r\n\r\n        ScriptEvent.notNameParts.add(0, \"SpigotImpl\");\r\n\r\n        // Block events\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            ScriptEvent.registerScriptEvent(BellRingScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(BlockBuiltScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockBurnsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockCooksSmeltsItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockDestroyedByExplosionEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockDispensesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockEquipsItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockExplodesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockFadesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockFallsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockFormsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockGrowsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockIgnitesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockPhysicsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockShearEntityScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockSpreadsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BrewingStandFueledScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            ScriptEvent.registerScriptEvent(BrewingStartsScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(BrewsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(CauldronLevelChangeScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            ScriptEvent.registerScriptEvent(CrafterCraftsScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(DragonEggMovesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(FurnaceBurnsItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(FurnaceStartsSmeltingScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(LeafDecaysScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(LiquidLevelChangeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(LiquidSpreadScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            ScriptEvent.registerScriptEvent(LootDispensesFromBlockScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(MoistureChangeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(NoteBlockPlaysNoteScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PistonExtendsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PistonRetractsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(RedstoneScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(SpongeAbsorbsScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            ScriptEvent.registerScriptEvent(TNTPrimesScriptEvent.class);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            ScriptEvent.registerScriptEvent(VaultDisplaysItemScriptEvent.class);\r\n        }\r\n\r\n        // Entity events\r\n        ScriptEvent.registerScriptEvent(AreaEffectCloudApplyScriptEvent.class);\r\n        if (!Denizen.supportsPaper) {\r\n            ScriptEvent.registerScriptEvent(AreaEnterExitScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(CreeperPoweredScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(DragonPhaseChangeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityAirLevelChangeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityBreaksHangingScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityBreedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityChangesBlockScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityChangesPoseScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityCombustsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityCreatePortalScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityDamagedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityDeathScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityDespawnScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityDropsItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityEntersPortalScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(NMSHandler.entityHelper.getEntersVehicleEventImpl());\r\n        ScriptEvent.registerScriptEvent(EntityExitsPortalScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(NMSHandler.entityHelper.getExitsVehicleEventImpl());\r\n        ScriptEvent.registerScriptEvent(EntityExplodesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityExplosionPrimesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityFoodLevelChangeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityFormsBlockScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityGlideScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityGoesIntoBlockScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityHealsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityInteractScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityKilledScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityPicksUpItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityPotionEffectScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityResurrectScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityShootsBowScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntitySpawnerSpawnScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntitySpawnScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntitySpellCastScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntitySwimScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityTamesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityTargetsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityTeleportScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityTransformScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(EntityUnleashedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(FireworkBurstsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(HangingBreaksScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(HorseJumpsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PiglinBarterScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PigZappedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ProjectileHitScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ProjectileLaunchedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(SheepDyedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(SheepRegrowsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(SlimeSplitsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(VillagerAcquiresTradeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(VillagerChangesProfessionScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(VillagerReplenishesTradeScriptEvent.class);\r\n\r\n        // NPC events\r\n        if (Depends.citizens != null) {\r\n            registerCitizensEvents();\r\n        }\r\n\r\n        // Item events\r\n        ScriptEvent.registerScriptEvent(InventoryPicksUpItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ItemDespawnsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ItemEnchantedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ItemMergesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ItemMoveScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ItemRecipeFormedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ItemSpawnsScriptEvent.class);\r\n\r\n        // Player events\r\n        ScriptEvent.registerScriptEvent(BiomeEnterExitScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(BlockDropsItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ChatScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ExperienceBottleBreaksScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(HotbarScrollScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerAnimatesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerArmorStandManipulateScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerBreaksBlockScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerBreaksItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerChangesGamemodeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerChangesMainHandScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerChangesSignScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerChangesWorldScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerChangesXPScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerClicksBlockScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerClicksInInventoryScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerClosesInvScriptEvent.class);\r\n        if (!Denizen.supportsPaper) {\r\n            ScriptEvent.registerScriptEvent(PlayerCompletesAdvancementScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerConsumesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerCraftsItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerDamagesBlockScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerDragsInInvScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerEditsBookScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerEmptiesBucketScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerEntersBedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerFillsBucketScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerFishesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerFlyingScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerHearsSoundScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerIncreasesExhaustionLevelScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            ScriptEvent.registerScriptEvent(PlayerInputScriptEvent.class);\r\n        }\r\n        if (!Denizen.supportsPaper) {\r\n            ScriptEvent.registerScriptEvent(PlayerItemTakesDamageScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerJoinsScriptEvent.class);\r\n        if (!Denizen.supportsPaper) {\r\n            ScriptEvent.registerScriptEvent(PlayerJumpScriptEvent.PlayerJumpsSpigotScriptEventImpl.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerKickedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerLeashesEntityScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerLeavesBedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerLevelsUpScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerLocaleChangeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerLoginScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerMendsItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerOpensInvScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerPickupArrowScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerPlacesBlockScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerPlacesHangingScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerPreLoginScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerPreparesAnvilCraftScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerPreparesEnchantScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerPreparesSmithingTableScriptEvent.class);\r\n        if (!Denizen.supportsPaper) {\r\n            ScriptEvent.registerScriptEvent(PlayerQuitsScriptEvent.class);\r\n        }\r\n        if (!Denizen.supportsPaper || NMSHandler.getVersion().isAtMost(NMSVersion.v1_17)) {\r\n            ScriptEvent.registerScriptEvent(PlayerRaiseLowerItemScriptEvent.PlayerRaiseLowerItemScriptEventSpigotImpl.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerReceivesActionbarScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerReceivesCommandsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerReceivesMessageScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerReceivesPacketScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerReceivesTablistUpdateScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerRecipeDiscoverScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerRespawnsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerRightClicksEntityScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerRiptideScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerSendPacketScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerShearsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerSmithsItemScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerSneakScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerSprintScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerStandsOnScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerStatisticIncrementsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerSteersEntityScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerStepsOnScriptEvent.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            ScriptEvent.registerScriptEvent(PlayerStopsDamagingBlockScriptEvent.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(PlayerSwapsItemsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerTakesFromFurnaceScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerTakesFromLecternScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerThrowsEggScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerTriggersRaidScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerUsesPortalScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerWalkScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PlayerWalksOverScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ResourcePackStatusScriptEvent.class);\r\n\r\n        // Server events\r\n        ScriptEvent.registerScriptEvent(CommandScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(InternalEventScriptEvent.class);\r\n        if (!Denizen.supportsPaper) {\r\n            ScriptEvent.registerScriptEvent(ListPingScriptEvent.ListPingScriptEventSpigotImpl.class);\r\n        }\r\n        ScriptEvent.registerScriptEvent(ServerPrestartScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ServerStartScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(TabCompleteScriptEvent.class);\r\n\r\n        // Vehicle\r\n        ScriptEvent.registerScriptEvent(VehicleCollidesBlockScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(VehicleCollidesEntityScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(VehicleCreatedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(VehicleDamagedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(VehicleDestroyedScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(VehicleMoveScriptEvent.class);\r\n\r\n        // World events\r\n        ScriptEvent.registerScriptEvent(ChunkLoadEntitiesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ChunkLoadScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ChunkUnloadEntitiesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ChunkUnloadScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(GenericGameEventScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(LightningStrikesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(LingeringPotionSplashScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(LootGenerateScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PortalCreateScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(PotionSplashScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(RaidFinishesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(RaidSpawnsWaveScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(RaidStopsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(SpawnChangeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(StructureGrowsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(ThunderChangesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(TimeChangeScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(WeatherChangesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(WorldInitsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(WorldLoadsScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(WorldSavesScriptEvent.class);\r\n        ScriptEvent.registerScriptEvent(WorldUnloadsScriptEvent.class);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BellRingScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.BellRingEvent;\n\npublic class BellRingScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // bell rings\n    //\n    // @Location true\n    //\n    // @Group Block\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a bell block rings. (Requires Paper on versions lower than 1.19)\n    //\n    // @Context\n    // <context.entity> returns the EntityTag that rung the bell, if any.\n    // <context.location> returns the LocationTag of the bell being rung.\n    // <context.direction> returns the ElementTag of the direction the bell was rung in. Can be \"north\", \"west\", \"south\", \"east\". Available only on MC 1.19+.\n    //\n    // @Player when the ringing entity is a player.\n    //\n    // -->\n\n    public BellRingScriptEvent() {\n        registerCouldMatcher(\"bell rings\");\n    }\n\n    public BellRingEvent event;\n    public LocationTag location;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, location)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getEntity());\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"entity\" -> event.getEntity() == null ? null : new EntityTag(event.getEntity()).getDenizenObject();\n            case \"location\" -> location;\n            case \"direction\" -> new ElementTag(event.getDirection());\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void bellRingEvent(BellRingEvent event) {\n        this.event = event;\n        location = new LocationTag(event.getBlock().getLocation());\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockBuiltScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockCanBuildEvent;\r\n\r\npublic class BlockBuiltScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> being built (on <block>)\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an attempt is made to build a block on another block. Not necessarily caused by players. Does not normally fire when players place blocks. Prefer <@link event player places block> for that.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the block the player is trying to build on.\r\n    // <context.old_material> returns the MaterialTag of the block the player is trying to build on.\r\n    // <context.new_material> returns the MaterialTag of the block the player is trying to build.\r\n    // <context.buildable> returns whether the block can physically be placed where it was when the event was fired.\r\n    //\r\n    // @Determine\r\n    // \"BUILDABLE\" to allow the building.\r\n    //\r\n    // @Player when the event is triggered in relation to a player that is causing the block build.\r\n    //\r\n    // -->\r\n\r\n    public BlockBuiltScriptEvent() {\r\n        registerCouldMatcher(\"<block> being built (on <block>)\");\r\n        this.<BlockBuiltScriptEvent>registerTextDetermination(\"buildable\", (evt) -> {\r\n            evt.event.setBuildable(true);\r\n        });\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag oldMaterial;\r\n    public MaterialTag newMaterial;\r\n    public BlockCanBuildEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(4, oldMaterial)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, newMaterial)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        event.setBuildable(!cancelled);\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"new_material\" -> newMaterial;\r\n            case \"old_material\" -> oldMaterial;\r\n            case \"buildable\" -> new ElementTag(event.isBuildable());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockBuilt(BlockCanBuildEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        oldMaterial = new MaterialTag(event.getBlock());\r\n        newMaterial = new MaterialTag(event.getBlockData());\r\n        cancelled = !event.isBuildable();\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockBurnsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockBurnEvent;\r\n\r\npublic class BlockBurnsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> burns\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block is destroyed by fire.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag the block was burned at.\r\n    // <context.material> returns the MaterialTag of the block that was burned.\r\n    //\r\n    // -->\r\n\r\n    public BlockBurnsScriptEvent() {\r\n        registerCouldMatcher(\"<block> burns\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockBurnEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, material)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockBurns(BlockBurnEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockCooksSmeltsItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockCookEvent;\r\n\r\npublic class BlockCooksSmeltsItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> cooks|smelts <item> (into <item>)\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when an item is smelted/cooked by a block.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the block smelting/cooking.\r\n    // <context.source_item> returns the ItemTag that is being smelted/cooked.\r\n    // <context.result_item> returns the ItemTag that is the result of the smelting/cooking.\r\n    //\r\n    // @Determine\r\n    // ItemTag to set the item that is the result of the smelting/cooking.\r\n    //\r\n    // -->\r\n\r\n    public BlockCooksSmeltsItemScriptEvent() {\r\n        registerCouldMatcher(\"<block> cooks|smelts <item> (into <item>)\");\r\n        this.<BlockCooksSmeltsItemScriptEvent, ItemTag>registerDetermination(null, ItemTag.class, (evt, context, result) -> {\r\n            event.setResult(result.getItemStack());\r\n        });\r\n    }\r\n\r\n    public ItemTag source_item;\r\n    public ItemTag result_item;\r\n    public LocationTag location;\r\n    public BlockCookEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(2, source_item)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(3).equals(\"into\")) {\r\n            if (!path.tryArgObject(4, result_item)) {\r\n                return false;\r\n            }\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"source_item\" -> source_item;\r\n            case \"result_item\" -> new ItemTag(event.getResult());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockCooks(BlockCookEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        source_item = new ItemTag(event.getSource());\r\n        result_item = new ItemTag(event.getResult());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockDestroyedByExplosionEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockExplodeEvent;\r\nimport org.bukkit.event.entity.EntityExplodeEvent;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class BlockDestroyedByExplosionEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> destroyed by explosion\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Warning This event fires extremely rapidly. One single TNT detonation can destroy a hundred blocks.\r\n    //\r\n    // @Triggers when a block is destroyed by an explosion (caused by either an entity or a block exploding).\r\n    //\r\n    // @Switch source_entity:<matcher> to only fire the event if the source is an entity that matches the given type. Note that \"Primed_Tnt\" is an entity, not a block.\r\n    // @Switch source_block:<matcher> to only fire the event if the source is a block that matches the given type.\r\n    //\r\n    // @Context\r\n    // <context.block> returns the block that exploded.\r\n    // <context.source_location> returns the location of the source block or entity.\r\n    // <context.source_entity> returns the entity that exploded, if any.\r\n    // <context.strength> returns an ElementTag(Decimal) of the strength of the explosion.\r\n    //\r\n    // -->\r\n\r\n    public BlockDestroyedByExplosionEvent() {\r\n        registerCouldMatcher(\"<block> destroyed by explosion\");\r\n        registerSwitches(\"source_entity\", \"source_block\");\r\n    }\r\n\r\n    public BlockExplodeEvent blockEvent;\r\n    public EntityExplodeEvent entityEvent;\r\n    public LocationTag location;\r\n    public List<Block> rawList;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, location)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (path.switches.containsKey(\"source_entity\") && (entityEvent == null || !new EntityTag(entityEvent.getEntity()).tryAdvancedMatcher(path.switches.get(\"source_entity\"), path.context))) {\r\n            return false;\r\n        }\r\n        if (path.switches.containsKey(\"source_block\") && (blockEvent == null || !new LocationTag(blockEvent.getBlock().getLocation()).tryAdvancedMatcher(path.switches.get(\"source_block\"), path.context))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"block\" -> location;\r\n            case \"source_location\" -> new LocationTag(blockEvent != null ? blockEvent.getBlock().getLocation() : entityEvent.getLocation());\r\n            case \"source_entity\" -> entityEvent == null ? null : new EntityTag(entityEvent.getEntity());\r\n            case \"strength\" -> new ElementTag(blockEvent != null ? blockEvent.getYield() : entityEvent.getYield());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled && rawList.contains(location.getBlock())) {\r\n            rawList.remove(location.getBlock());\r\n        }\r\n        else if (!rawList.contains(location.getBlock())) {\r\n            rawList.add(location.getBlock());\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockExplodes(BlockExplodeEvent event) {\r\n        this.blockEvent = event;\r\n        this.entityEvent = null;\r\n        this.rawList = event.blockList();\r\n        for (Block block : new ArrayList<>(rawList)) {\r\n            this.location = new LocationTag(block.getLocation());\r\n            fire(event);\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityExplodes(EntityExplodeEvent event) {\r\n        this.entityEvent = event;\r\n        this.blockEvent = null;\r\n        this.rawList = event.blockList();\r\n        for (Block block : new ArrayList<>(rawList)) {\r\n            this.location = new LocationTag(block.getLocation());\r\n            fire(event);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockDispensesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockDispenseEvent;\r\n\r\npublic class BlockDispensesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> dispenses <item>\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block dispenses a single item.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the dispenser.\r\n    // <context.item> returns the ItemTag of the item being dispensed.\r\n    // <context.velocity> returns a LocationTag vector of the velocity the item will be shot at.\r\n    //\r\n    // @Determine\r\n    // LocationTag to set the velocity the item will be shot at.\r\n    // \"ITEM:<ItemTag>\" to set the item being shot.\r\n    //\r\n    // -->\r\n\r\n    public BlockDispensesScriptEvent() {\r\n        registerCouldMatcher(\"<block> dispenses <item>\");\r\n        this.<BlockDispensesScriptEvent, ObjectTag>registerOptionalDetermination(null, ObjectTag.class, (evt, context, value) -> {\r\n            if (value.canBeType(LocationTag.class)) {\r\n                LocationTag location = value.asType(LocationTag.class, context);\r\n                if (location != null) {\r\n                    evt.event.setVelocity(location.toVector());\r\n                    return true;\r\n                }\r\n            }\r\n            else if (value.canBeType(ItemTag.class)) {\r\n                BukkitImplDeprecations.blockDispensesItemDetermination.warn();\r\n                ItemTag item = value.asType(ItemTag.class, context);\r\n                if (item != null) {\r\n                    evt.item = item;\r\n                    evt.event.setItem(item.getItemStack());\r\n                    return true;\r\n                }\r\n            }\r\n            return false;\r\n        });\r\n        this.<BlockDispensesScriptEvent, ItemTag>registerDetermination(\"item\", ItemTag.class, (evt, context, value) -> {\r\n            evt.item = value;\r\n            evt.event.setItem(value.getItemStack());\r\n        });\r\n    }\r\n\r\n    public LocationTag location;\r\n    public ItemTag item;\r\n    private MaterialTag material;\r\n    public BlockDispenseEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if  (!path.tryArgObject(2, item)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, material)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"item\" -> new ItemTag(event.getItem());\r\n            case \"velocity\" -> new LocationTag(event.getVelocity());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockDispenses(BlockDispenseEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        item = new ItemTag(event.getItem());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockEquipsItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockDispenseArmorEvent;\r\n\r\npublic class BlockEquipsItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // block equips <item>\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when armor is equipped to an entity by a dispenser.\r\n    //\r\n    // @Switch on:<entity> to only process the event if the entity having the armor equipped matches the entity input.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the ItemTag of the armor being dispensed.\r\n    // <context.entity> returns the EntityTag of the entity having the armor equipped.\r\n    // <context.location> returns the LocationTag of the dispenser.\r\n    //\r\n    // @Player when the equipped entity is a player.\r\n    //\r\n    // @NPC when the equipped entity is an NPC.\r\n    //\r\n    // @Determine\r\n    // \"ITEM:<ItemTag>\" to set the item being dispensed.\r\n    //\r\n    // @Warning Determined armor types must match or armor will be assigned incorrect slots (for example, if the original item was a helmet but the new item is boots, the boots will be assigned to the helmet slot and will not display properly). If you determine a non-armor item, it will be dispensed normally.\r\n    //\r\n    // @Example\r\n    // # Will cause leather armor to be dispensed like a normal item and not be equipped on an armor stand.\r\n    // on block equips leather* on:armor_stand:\r\n    // - determined cancelled\r\n    //\r\n    // @Example\r\n    // # Will equip a golden helmet if a leather helmet is originally being equipped.\r\n    // on block equips leather_helmet:\r\n    // - determine item:golden_helmet\r\n    // -->\r\n\r\n    public BlockEquipsItemScriptEvent() {\r\n        registerCouldMatcher(\"block equips <item>\");\r\n        registerSwitches(\"on\");\r\n        this.<BlockEquipsItemScriptEvent, ItemTag>registerDetermination(\"item\", ItemTag.class, (evt, context, item) -> {\r\n            evt.event.setItem(item.getItemStack());\r\n        });\r\n    }\r\n\r\n    EntityTag entity;\r\n    LocationTag location;\r\n    BlockDispenseArmorEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, new ItemTag(event.getItem()))) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"on\", entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"item\" -> new ItemTag(event.getItem());\r\n            case \"entity\" -> entity;\r\n            case \"location\" -> location;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockEquipsItemOntoEntity(BlockDispenseArmorEvent event) {\r\n        entity = new EntityTag(event.getTargetEntity());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockExplodesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockExplodeEvent;\r\n\r\nimport java.util.List;\r\n\r\npublic class BlockExplodesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> explodes\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block explodes (like a bed in the nether. For TNT, refer to the \"entity explodes\" event instead). For a block being destroyed by an explosion, refer to the \"block destroyed by explosion\" event instead.\r\n    //\r\n    // @Context\r\n    // <context.block> returns the location of the exploding block.\r\n    // <context.blocks> returns a ListTag of blocks that blew up.\r\n    // <context.strength> returns an ElementTag(Decimal) of the strength of the explosion.\r\n    //\r\n    // @Determine\r\n    // ListTag(LocationTag) to set a new lists of blocks that are to be affected by the explosion.\r\n    // \"STRENGTH:<ElementTag(Decimal)>\" to change the strength of the explosion.\r\n    //\r\n    // -->\r\n\r\n    public BlockExplodesScriptEvent() {\r\n        registerCouldMatcher(\"<block> explodes\");\r\n        this.<BlockExplodesScriptEvent, ObjectTag>registerOptionalDetermination(null, ObjectTag.class, (evt, context, value) -> {\r\n            String blocks = value.identify();\r\n            if (blocks.contains(\",\") || blocks.startsWith(\"li@\")) { // Raw ListTag check due to there not being a prefix for block strength previously\r\n                evt.event.blockList().clear();\r\n                for (LocationTag newBlock : value.asType(ListTag.class, context).filter(LocationTag.class, context)) {\r\n                    if (newBlock.getWorld() != null) {\r\n                        evt.event.blockList().add(newBlock.getBlock());\r\n                    }\r\n                    else {\r\n                        Debug.echoError(\"Block input of \" + newBlock + \" does not contain a valid world.\");\r\n                    }\r\n                }\r\n                return true;\r\n            }\r\n            else if (value.asElement().isFloat()) {\r\n                BukkitImplDeprecations.blockExplodesStrengthDetermination.warn();\r\n                evt.event.setYield(value.asElement().asFloat());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n        this.<BlockExplodesScriptEvent, ElementTag>registerOptionalDetermination(\"strength\", ElementTag.class, (evt, context, value) -> {\r\n            if (value.isFloat()) {\r\n                evt.event.setYield(value.asFloat());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public BlockExplodeEvent event;\r\n    public List<Block> blocks;\r\n    public LocationTag location;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, location)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"block\" -> location;\r\n            case \"blocks\" -> new ListTag(this.blocks, block -> new LocationTag(block.getLocation()));\r\n            case \"strength\" -> new ElementTag(event.getYield());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockExplodes(BlockExplodeEvent event) {\r\n        this.blocks = event.blockList();\r\n        this.event = event;\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockFadesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockFadeEvent;\r\n\r\npublic class BlockFadesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> fades\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block fades, melts, or disappears based on world conditions.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag the block faded at.\r\n    // <context.material> returns the MaterialTag of the block that faded.\r\n    //\r\n    // -->\r\n\r\n    public BlockFadesScriptEvent() {\r\n        registerCouldMatcher(\"<block> fades\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockFadeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, material)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockFades(BlockFadeEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockFallsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityChangeBlockEvent;\r\n\r\npublic class BlockFallsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> falls\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block begins to fall. Generic form \"block falls\" (with a material) also fires when the block lands.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the location of the block.\r\n    // <context.entity> returns the entity of the block that fell.\r\n    // <context.old_material> returns the material that was at the location (eg 'sand' when falling, or 'air' when landing).\r\n    // <context.new_material> returns the material that will be at the location (eg 'air' when falling, or 'sand' when landing).\r\n    //\r\n    // -->\r\n\r\n    public BlockFallsScriptEvent() {\r\n        registerCouldMatcher(\"<block> falls\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public EntityChangeBlockEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, material)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"entity\" -> new EntityTag(event.getEntity()).getDenizenObject();\r\n            case \"old_material\" -> material;\r\n            case \"new_material\" -> new MaterialTag(event.getBlockData());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockFalls(EntityChangeBlockEvent event) {\r\n        if (event.getEntityType() != EntityType.FALLING_BLOCK) {\r\n            return;\r\n        }\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockFormsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockFormEvent;\r\n\r\npublic class BlockFormsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> forms\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block is formed based on world conditions, EG, when snow forms in a snow storm or ice forms in a cold biome.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag the block that is forming.\r\n    // <context.material> returns the MaterialTag of the block that is forming.\r\n    //\r\n    // -->\r\n\r\n    public BlockFormsScriptEvent() {\r\n        registerCouldMatcher(\"<block> forms\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockFormEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n\r\n        if (!path.tryArgObject(0, material)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockForms(BlockFormEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getNewState());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockGrowsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.properties.material.MaterialAge;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockGrowEvent;\r\n\r\npublic class BlockGrowsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> grows\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    // @Switch from:<age> to only process the event if the material started at a specific age.\r\n    // @Switch to:<age> to only process the event if the material ended at a specific age.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block grows naturally in the world, EG, when wheat, sugar canes, cacti, watermelons or pumpkins grow.\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the block that grew (still at original material state when event fires).\r\n    // <context.material> returns the MaterialTag of the block's newly grown state.\r\n    //\r\n    // -->\r\n\r\n    public BlockGrowsScriptEvent() {\r\n        registerCouldMatcher(\"<block> grows\");\r\n        registerSwitches(\"from\", \"to\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockGrowEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        MaterialTag mat = MaterialTag.valueOf(path.eventArgLowerAt(0), CoreUtilities.noDebugContext);\r\n        return mat == null || !mat.isStructure();\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, material)) {\r\n            return false;\r\n        }\r\n        if (path.switches.containsKey(\"from\")) {\r\n            if (!MaterialAge.describes(new MaterialTag(location.getBlockState()))) {\r\n                return false;\r\n            }\r\n            int oldState = new MaterialAge(new MaterialTag(location.getBlockState())).getCurrent();\r\n            if (!path.checkSwitch(\"from\", String.valueOf(oldState))) {\r\n                return false;\r\n            }\r\n        }\r\n        if (path.switches.containsKey(\"to\")) {\r\n            if (!MaterialAge.describes(material)) {\r\n                return false;\r\n            }\r\n            int newState = new MaterialAge(material).getCurrent();\r\n            if (!path.checkSwitch(\"to\", String.valueOf(newState))) {\r\n                return false;\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockGrows(BlockGrowEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getNewState());\r\n        if (material.isStructure()) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockIgnitesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockIgniteEvent;\r\n\r\npublic class BlockIgnitesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // block ignites\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    // @Switch cause:<cause> to only process the event when it came from a specified cause.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block is set on fire.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the block that was set on fire.\r\n    // <context.entity> returns the EntityTag of the entity that ignited the block (if any).\r\n    // <context.origin_location> returns the LocationTag of the fire block that ignited this block (if any).\r\n    // <context.cause> returns an ElementTag of the cause of the event: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/block/BlockIgniteEvent.IgniteCause.html>.\r\n    //\r\n    // -->\r\n\r\n    public BlockIgnitesScriptEvent() {\r\n        registerCouldMatcher(\"block ignites\");\r\n        registerSwitches(\"cause\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public ElementTag cause;\r\n    public BlockIgniteEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"cause\", cause.asString())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> new MaterialTag(event.getBlock());\r\n            case \"cause\" -> cause;\r\n            case \"entity\" -> event.getIgnitingEntity() != null ? new EntityTag(event.getIgnitingEntity()).getDenizenObject() : null;\r\n            case \"origin_location\" -> event.getIgnitingBlock() != null ? new LocationTag(event.getIgnitingBlock().getLocation()) : null;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockIgnites(BlockIgniteEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        cause = new ElementTag(event.getCause());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockPhysicsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockPhysicsEvent;\r\n\r\nimport java.lang.reflect.Field;\r\n\r\npublic class BlockPhysicsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> physics\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning This event may fire very rapidly.\r\n    //\r\n    // @Switch adjacent:<block> to only process the event if the block or an immediately adjacent block (up/down/n/e/s/w) matches the LocationTag matcher specified. This can be useful to prevent blocks from breaking.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block's physics update.\r\n    //\r\n    // @Context\r\n    // <context.location> returns a LocationTag of the block the physics is affecting.\r\n    // <context.new_material> returns a MaterialTag of what the block is becoming.\r\n    //\r\n    // -->\r\n\r\n    public BlockPhysicsScriptEvent() {\r\n        registerCouldMatcher(\"<block> physics\");\r\n        registerSwitches(\"adjacent\");\r\n    }\r\n\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockPhysicsEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, material)) {\r\n            return false;\r\n        }\r\n        String adjacent = path.switches.get(\"adjacent\");\r\n        if (adjacent != null) {\r\n            if (!material.tryAdvancedMatcher(adjacent, path.context)) {\r\n                Block block = location.getBlock();\r\n                if (!new LocationTag(block.getRelative(0, 1, 0).getLocation()).tryAdvancedMatcher(adjacent, path.context)\r\n                        && !new LocationTag(block.getRelative(0, -1, 0).getLocation()).tryAdvancedMatcher(adjacent, path.context)\r\n                        && !new LocationTag(block.getRelative(1, 0, 0).getLocation()).tryAdvancedMatcher(adjacent, path.context)\r\n                        && !new LocationTag(block.getRelative(-1, 0, 0).getLocation()).tryAdvancedMatcher(adjacent, path.context)\r\n                        && !new LocationTag(block.getRelative(0, 0, 1).getLocation()).tryAdvancedMatcher(adjacent, path.context)\r\n                        && !new LocationTag(block.getRelative(0, 0, -1).getLocation()).tryAdvancedMatcher(adjacent, path.context)) {\r\n                    return false;\r\n                }\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    public static Field PHYSICS_EVENT_DATA = ReflectionHelper.getFields(BlockPhysicsEvent.class).getFirstOfType(BlockData.class);\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"location\": return location;\r\n            case \"new_material\":\r\n                try {\r\n                    BlockData data = (BlockData) PHYSICS_EVENT_DATA.get(event);\r\n                    return new MaterialTag(data);\r\n                }\r\n                catch (Throwable ex) {\r\n                    Debug.echoError(ex);\r\n                }\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockPhysics(BlockPhysicsEvent event) {\r\n        Material changedType = event.getChangedType();\r\n        if (changedType == Material.REDSTONE_WIRE || changedType == Material.COMPARATOR || changedType == Material.REPEATER) {\r\n            return;\r\n        }\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(location.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockShearEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockShearEntityEvent;\r\n\r\npublic class BlockShearEntityScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <block> shears <entity>\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a dispenser shears a nearby sheep.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the dispenser.\r\n    // <context.tool> returns the ItemTag of the item used to shear the entity.\r\n    // <context.entity> returns the EntityTag of the sheared entity.\r\n    //\r\n    // -->\r\n\r\n    public BlockShearEntityScriptEvent() {\r\n        registerCouldMatcher(\"<block> shears <entity>\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public EntityTag entity;\r\n    public BlockShearEntityEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(2, entity)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"tool\" -> new ItemTag(event.getTool());\r\n            case \"entity\" -> entity;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onShear(BlockShearEntityEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BlockSpreadsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockSpreadEvent;\r\n\r\npublic class BlockSpreadsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // block spreads\r\n    //\r\n    // @Switch type:<block> to only run if the block spreading matches the material input.\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block spreads based on world conditions, EG, when fire spreads, or when mushrooms spread, or when vines grow.\r\n    //\r\n    // @Context\r\n    // <context.source_location> returns the LocationTag of the block that spread.\r\n    // <context.location> returns the LocationTag of the new block.\r\n    // <context.material> returns the MaterialTag of the block that spread.\r\n    //\r\n    // -->\r\n\r\n    public BlockSpreadsScriptEvent() {\r\n        registerCouldMatcher(\"block spreads\");\r\n        registerCouldMatcher(\"<block> spreads\"); // NOTE: exists for historical compat reasons.\r\n        registerSwitches(\"type\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockSpreadEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(0).equals(\"liquid\")) {\r\n            return false;\r\n        }\r\n        if (!path.eventArgLowerAt(0).equals(\"block\")) {\r\n            BukkitImplDeprecations.blockSpreads.warn(getTagContext(path));\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, material)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"type\", material)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            case \"source_location\" -> new LocationTag(event.getSource().getLocation());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockSpreads(BlockSpreadEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getSource());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BrewingStandFueledScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.BrewingStandFuelEvent;\r\n\r\npublic class BrewingStandFueledScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // brewing stand fueled (with <item>)\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a brewing stand receives an item to use as fuel.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the brewing stand.\r\n    // <context.item> returns the ItemTag being inserted as fuel.\r\n    // <context.fuel_power> returns the fuel power level being added. Each unit of fuel can power one brewing operation.\r\n    // <context.consuming> returns a boolean indicating whether the fuel item will be consumed.\r\n    //\r\n    // @Determine\r\n    // \"FUEL_POWER:<ElementTag(Number)>\" to set the fuel power level to be added.\r\n    // \"CONSUMING:<ElementTag(Boolean)>\" to indicate whether the fuel item should be consumed.\r\n    //\r\n    // -->\r\n\r\n    public BrewingStandFueledScriptEvent() {\r\n        registerCouldMatcher(\"brewing stand fueled (with <item>)\");\r\n        this.<BrewingStandFueledScriptEvent, ElementTag>registerOptionalDetermination(\"fuel_power\", ElementTag.class, (evt, context, power) -> {\r\n            if (power.isInt()) {\r\n                evt.event.setFuelPower(power.asInt());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n        this.<BrewingStandFueledScriptEvent, ElementTag>registerOptionalDetermination(\"consuming\", ElementTag.class, (evt, context, value) -> {\r\n            if (value.isBoolean()) {\r\n                evt.event.setConsuming(value.asBoolean());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n        this.<BrewingStandFueledScriptEvent>registerTextDetermination(\"not_consuming\", (evt) -> {\r\n            BukkitImplDeprecations.brewingStandConsumeDetermination.warn();\r\n            evt.event.setConsuming(false);\r\n        });\r\n    }\r\n\r\n    public LocationTag location;\r\n    public ItemTag item;\r\n    public BrewingStandFuelEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(3).equals(\"with\") && !path.tryArgObject(4, item)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"item\" -> item;\r\n            case \"fuel_power\" -> new ElementTag(event.getFuelPower());\r\n            case \"consuming\" -> new ElementTag(event.isConsuming());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBrewingStandFueled(BrewingStandFuelEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        item = new ItemTag(event.getFuel());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BrewingStartsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BrewingStartEvent;\r\n\r\npublic class BrewingStartsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // brewing starts\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a brewing stand starts brewing a potion.\r\n    //\r\n    // @Context\r\n    // <context.item> returns an ItemTag of the used ingredient to brew potions.\r\n    // <context.location> returns a LocationTag of the brewing stand's location.\r\n    // <context.brew_time> returns a DurationTag of the total time it will take to brew the potion.\r\n    //\r\n    // @Determine\r\n    // \"BREW_TIME:DurationTag\" to set the total time for the potion being brewed.\r\n    //\r\n    // -->\r\n\r\n    public BrewingStartsScriptEvent() {\r\n        registerCouldMatcher(\"brewing starts\");\r\n        this.<BrewingStartsScriptEvent, DurationTag>registerDetermination(\"brew_time\", DurationTag.class, (evt, context, time) -> {\r\n            evt.event.setTotalBrewTime(time.getTicksAsInt());\r\n        });\r\n    }\r\n\r\n    public BrewingStartEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getBlock().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"item\" -> new ItemTag(event.getSource());\r\n            case \"location\" -> new LocationTag(event.getBlock().getLocation());\r\n            case \"brew_time\" -> new DurationTag((long) event.getTotalBrewTime());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBrewingStart(BrewingStartEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/BrewsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.BrewEvent;\r\n\r\npublic class BrewsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // brewing stand brews\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a brewing stand brews a potion.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the brewing stand.\r\n    // <context.inventory> returns the InventoryTag of the brewing stand's contents.\r\n    // <context.fuel_level> returns an ElementTag(Number) of the brewing stand's fuel level.\r\n    // <context.result> returns a ListTag(ItemTag) of the items that will be brewed.\r\n    //\r\n    // @Determine\r\n    // \"RESULT:<ListTag(ItemTag)>\" to change the items that are brewed.\r\n    //\r\n    // -->\r\n\r\n    public BrewsScriptEvent() {\r\n        registerCouldMatcher(\"brewing stand brews\");\r\n        this.<BrewsScriptEvent, ListTag>registerDetermination(\"result\", ListTag.class, (evt, context, result) -> {\r\n            evt.event.getResults().clear();\r\n            for (ItemTag item : result.filter(ItemTag.class, context)) {\r\n                evt.event.getResults().add(item.getItemStack());\r\n            }\r\n        });\r\n    }\r\n\r\n    public LocationTag location;\r\n    public BrewEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"inventory\" -> InventoryTag.mirrorBukkitInventory(event.getContents());\r\n            case \"fuel_level\" -> new ElementTag(event.getFuelLevel());\r\n            case \"result\" -> new ListTag(event.getResults(), ItemTag::new);\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBrews(BrewEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/CauldronLevelChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.block.data.Levelled;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.CauldronLevelChangeEvent;\r\n\r\npublic class CauldronLevelChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // cauldron level changes|raises|lowers\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    // @Switch cause:<cause> to only process the event when it came from a specified cause.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a cauldron's level changes.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the cauldron that changed.\r\n    // <context.entity> returns the LocationTag of the entity that caused the cauldron level to change (if any).\r\n    // <context.cause> returns the reason that the cauldron level changed, from <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/block/CauldronLevelChangeEvent.ChangeReason.html>\r\n    // <context.old_level> returns the previous cauldron level.\r\n    // <context.new_level> returns the new cauldron level.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Number) to set the new level.\r\n    //\r\n    // -->\r\n\r\n    public CauldronLevelChangeScriptEvent() {\r\n        registerCouldMatcher(\"cauldron level changes|raises|lowers\");\r\n        registerSwitches(\"cause\");\r\n        this.<CauldronLevelChangeScriptEvent, ElementTag>registerOptionalDetermination(null, ElementTag.class, (evt, context, value) -> {\r\n            if (!value.isInt()) {\r\n                return false;\r\n            }\r\n            int level = value.asInt();\r\n            BlockState cauldronState = evt.event.getNewState();\r\n            if (level <= 0) {\r\n                cauldronState.setType(Material.CAULDRON);\r\n                return true;\r\n            }\r\n            if (level > 3) {\r\n                return false;\r\n            }\r\n            if (cauldronState.getType() != Material.WATER_CAULDRON && cauldronState.getType() != Material.LAVA_CAULDRON) {\r\n                cauldronState.setType(cauldronType);\r\n            }\r\n            if (cauldronState.getBlockData() instanceof Levelled levelled) {\r\n                levelled.setLevel(level);\r\n                cauldronState.setBlockData(levelled);\r\n                evt.newLevel = level;\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public LocationTag location;\r\n    public CauldronLevelChangeEvent event;\r\n    public Material cauldronType;\r\n    public int oldLevel;\r\n    public int newLevel;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"cause\", event.getReason().name())) {\r\n            return false;\r\n        }\r\n        String changeType = path.eventArgLowerAt(2);\r\n        if (changeType.equals(\"raises\")) {\r\n            if (newLevel <= oldLevel) {\r\n                return false;\r\n            }\r\n        }\r\n        else if (changeType.equals(\"lowers\")) {\r\n            if (newLevel >= oldLevel) {\r\n                return false;\r\n            }\r\n        }\r\n        else if (!changeType.equals(\"changes\")) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"cause\" -> new ElementTag(event.getReason());\r\n            case \"old_level\" -> new ElementTag(oldLevel);\r\n            case \"new_level\" -> new ElementTag(newLevel);\r\n            case \"entity\" -> event.getEntity() != null ? new EntityTag(event.getEntity()).getDenizenObject() : null;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onCauldronLevelChange(CauldronLevelChangeEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        cauldronType = event.getBlock().getType();\r\n        oldLevel = event.getBlock().getBlockData() instanceof Levelled levelled ? levelled.getLevel() : 0;\r\n        newLevel = event.getNewState().getBlockData() instanceof Levelled levelled ? levelled.getLevel() : 0;\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/CrafterCraftsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.CrafterCraftEvent;\n\npublic class CrafterCraftsScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // crafter crafts <item>\n    //\n    // @Group Block\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a crafter block crafts an item.\n    //\n    // @Context\n    // <context.location> returns the LocationTag of the crafter block.\n    // <context.item> returns the ItemTag being crafted.\n    // <context.recipe_id> returns the ID of the recipe formed.\n    //\n    // @Determine\n    // \"ITEM:<ItemTag>\" to set the item being crafted. Determinations still consume ingredients.\n    //\n    // -->\n\n    public CrafterCraftsScriptEvent() {\n        registerCouldMatcher(\"crafter crafts <item>\");\n        this.<CrafterCraftsScriptEvent, ItemTag>registerDetermination(\"item\", ItemTag.class, (evt, context, result) -> {\n            event.setResult(result.getItemStack());\n        });\n    }\n\n    public LocationTag location;\n    public ItemTag result;\n    public CrafterCraftEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, location)) {\n            return false;\n        }\n        if (!path.tryArgObject(2, result)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"item\" -> new ItemTag(event.getResult());\n            case \"location\" -> location;\n            case \"recipe_id\" -> new ElementTag(event.getRecipe().getKey().toString(), true);\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onCrafterCrafts(CrafterCraftEvent event) {\n        location = new LocationTag(event.getBlock().getLocation());\n        result = new ItemTag(event.getResult());\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/DragonEggMovesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\n\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.MaterialTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport org.bukkit.Material;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.BlockFromToEvent;\n\npublic class DragonEggMovesScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // dragon egg moves\n    //\n    // @Group Block\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a dragon egg moves.\n    //\n    // @Context\n    // <context.location> returns the LocationTag the egg started at.\n    // <context.destination> returns the LocationTag the egg teleported to.\n    //\n    // -->\n\n    public DragonEggMovesScriptEvent() {\n        registerCouldMatcher(\"dragon egg moves\");\n    }\n\n    public LocationTag location;\n    public LocationTag destination;\n    public BlockFromToEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, location) && !runInCheck(path, destination)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"location\" -> location;\n            case \"destination\" -> destination;\n            case \"material\" -> new MaterialTag(Material.DRAGON_EGG); // for historical compatibility reasons\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onDragonEggMove(BlockFromToEvent event) {\n        if (event.getBlock().getType() != Material.DRAGON_EGG) { // BlockFromToEvent also fires with LiquidSpreadScriptEvent\n            return;\n        }\n        destination = new LocationTag(event.getToBlock().getLocation());\n        location = new LocationTag(event.getBlock().getLocation());\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/FurnaceBurnsItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.FurnaceBurnEvent;\r\n\r\npublic class FurnaceBurnsItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // furnace burns <item>\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a furnace burns an item used as fuel.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the furnace.\r\n    // <context.item> returns the ItemTag burnt.\r\n    //\r\n    // @Determine\r\n    // DurationTag to set the burn time for this fuel.\r\n    //\r\n    // -->\r\n\r\n    public FurnaceBurnsItemScriptEvent() {\r\n        registerCouldMatcher(\"furnace burns <item>\");\r\n        this.<FurnaceBurnsItemScriptEvent, ObjectTag>registerOptionalDetermination(null, ObjectTag.class, (evt, context, time) -> {\r\n            if (time instanceof ElementTag elementTag && elementTag.isInt()) { // Backwards compatibility for non-duration tick input\r\n                evt.event.setBurnTime(elementTag.asInt());\r\n                return true;\r\n            }\r\n            else if (time.canBeType(DurationTag.class)) {\r\n                evt.event.setBurnTime(time.asType(DurationTag.class, context).getTicksAsInt());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public ItemTag item;\r\n    public LocationTag location;\r\n    public FurnaceBurnEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"item\" -> item;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onFurnaceBurns(FurnaceBurnEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        item = new ItemTag(event.getFuel());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/FurnaceStartsSmeltingScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.FurnaceStartSmeltEvent;\r\n\r\npublic class FurnaceStartsSmeltingScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // furnace starts smelting <item>\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a furnace starts smelting an item.\r\n    //\r\n    // @Context\r\n    // <context.location> returns a LocationTag of the furnace's location.\r\n    // <context.item> returns an ItemTag of the item being smelted.\r\n    // <context.recipe_id> returns an ElementTag of the recipe ID being used.\r\n    // <context.total_cook_time> returns a DurationTag of the total time it will take to smelt the item.\r\n    //\r\n    // @Determine\r\n    // DurationTag to set the total cook time for the item being smelted.\r\n    //\r\n    // -->\r\n\r\n    public FurnaceStartsSmeltingScriptEvent() {\r\n        registerCouldMatcher(\"furnace starts smelting <item>\");\r\n        this.<FurnaceStartsSmeltingScriptEvent, DurationTag>registerDetermination(null, DurationTag.class, (evt, context, time) -> {\r\n            evt.event.setTotalCookTime(time.getTicksAsInt());\r\n        });\r\n    }\r\n\r\n    public ItemTag item;\r\n    public LocationTag location;\r\n    public FurnaceStartSmeltEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(3, item)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"item\" -> item;\r\n            case \"recipe_id\" -> new ElementTag(event.getRecipe().getKey().toString(), true);\r\n            case \"total_cook_time\" -> new DurationTag((long) event.getTotalCookTime());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onFurnaceStartsSmelting(FurnaceStartSmeltEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        item = new ItemTag(event.getSource());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/LeafDecaysScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.LeavesDecayEvent;\r\n\r\npublic class LeafDecaysScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // leaves decay\r\n    // <block> decay\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when leaves decay.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the leaves.\r\n    // <context.material> returns the MaterialTag of the leaves.\r\n    //\r\n    // -->\r\n\r\n    public LeafDecaysScriptEvent() {\r\n        registerCouldMatcher(\"leaves decay\");\r\n        registerCouldMatcher(\"<block> decay\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public LeavesDecayEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String mat = path.eventArgLowerAt(0);\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!mat.equals(\"leaves\") && !material.tryAdvancedMatcher(mat, path.context)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onLeafDecays(LeavesDecayEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/LiquidLevelChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.objects.MaterialTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.FluidLevelChangeEvent;\n\npublic class LiquidLevelChangeScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // liquid|lava|water level changes\n    //\n    // @Group Block\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a liquid block's level changes. Note that 'liquid spreads' is fired when a liquid first spreads, and 'level changes' is usually fired when it goes down.\n    //\n    // @Context\n    // <context.location> returns the LocationTag the liquid block that has its level changing.\n    // <context.old_material> returns the original MaterialTag data.\n    // <context.new_material> returns the new MaterialTag data. Sometimes can be a different material (such as air).\n    //\n    // -->\n\n    public LiquidLevelChangeScriptEvent() {\n        registerCouldMatcher(\"liquid|lava|water level changes\");\n    }\n\n    public LocationTag location;\n    public MaterialTag old_material;\n    public FluidLevelChangeEvent event;\n\n    @Override\n    public boolean couldMatch(ScriptPath path) {\n        if (!super.couldMatch(path)) {\n            return false;\n        }\n        if (path.eventLower.startsWith(\"block\")) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        String mat = path.eventArgLowerAt(0);\n        if (!mat.equals(\"liquid\") && !old_material.tryAdvancedMatcher(mat, path.context)) {\n            return false;\n        }\n        if (!runInCheck(path, location)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"location\" -> location;\n            case \"old_material\" -> old_material;\n            case \"new_material\" -> new MaterialTag(event.getNewData());\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onLiquidLevelChange(FluidLevelChangeEvent event) {\n        location = new LocationTag(event.getBlock().getLocation());\n        old_material = new MaterialTag(event.getBlock());\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/LiquidSpreadScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\n\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.objects.MaterialTag;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport org.bukkit.Material;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.BlockFromToEvent;\n\npublic class LiquidSpreadScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // liquid spreads\n    //\n    // @Switch type:<block> to only run if the block spreading matches the material input.\n    //\n    // @Group Block\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a liquid block spreads.\n    //\n    // @Context\n    // <context.destination> returns the LocationTag the block spread to.\n    // <context.location> returns the LocationTag the block spread location.\n    // <context.material> returns the MaterialTag of the block that spread.\n    //\n    // -->\n\n    public LiquidSpreadScriptEvent() {\n        registerCouldMatcher(\"liquid spreads\");\n        registerSwitches(\"type\");\n    }\n\n    public MaterialTag material;\n    public LocationTag location;\n    public LocationTag destination;\n    public BlockFromToEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!path.tryObjectSwitch(\"type\", material)) {\n            return false;\n        }\n        if (!runInCheck(path, location) && !runInCheck(path, destination)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"location\" -> location;\n            case \"destination\" -> destination;\n            case \"material\" -> material;\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onLiquidSpreads(BlockFromToEvent event) {\n        if (event.getBlock().getType() == Material.DRAGON_EGG) { // BlockFromToEvent also fires with DragonEggMovesScriptEvent\n            return;\n        }\n        destination = new LocationTag(event.getToBlock().getLocation());\n        location = new LocationTag(event.getBlock().getLocation());\n        material = new MaterialTag(event.getBlock());\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/LootDispensesFromBlockScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.*;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.BlockDispenseLootEvent;\nimport org.bukkit.inventory.ItemStack;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class LootDispensesFromBlockScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // loot dispenses from <block>\n    //\n    // @Group Block\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Player when the loot dispensing is triggered by a player.\n    //\n    // @Triggers when a block dispenses loot containing multiple items.\n    //\n    // @Context\n    // <context.loot> returns a ListTag(ItemTag) of loot items.\n    // <context.location> returns a LocationTag of the block that is dispensing the items.\n    //\n    // @Determine\n    // \"LOOT:<ListTag(ItemTag)>\" to set the loot items being dispensed.\n    //\n    // -->\n\n    public LootDispensesFromBlockScriptEvent() {\n        registerCouldMatcher(\"loot dispenses from <block>\");\n        this.<LootDispensesFromBlockScriptEvent, ListTag>registerDetermination(\"loot\", ListTag.class, (evt, context, input) -> {\n            List<ItemStack> items = new ArrayList<>(input.size());\n            for (ItemTag item : input.filter(ItemTag.class, context)) {\n                items.add(item.getItemStack());\n            }\n            evt.event.setDispensedLoot(items);\n        });\n    }\n\n    public MaterialTag block;\n    public LocationTag location;\n    public BlockDispenseLootEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!path.tryArgObject(3, block)) {\n            return false;\n        }\n        if (!runInCheck(path, location)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getPlayer());\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"loot\" -> new ListTag(event.getDispensedLoot(), ItemTag::new);\n            case \"location\" -> location;\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onLootDispensesFromBlock(BlockDispenseLootEvent event) {\n        block = new MaterialTag(event.getBlock().getType());\n        location = new LocationTag(event.getBlock().getLocation());\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/MoistureChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.objects.MaterialTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.block.data.type.Farmland;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.MoistureChangeEvent;\n\npublic class MoistureChangeScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // farmland moisture level changes\n    //\n    // @Group Block\n    //\n    // @Location true\n    //\n    // @Triggers when a farmland's moisture level changes.\n    //\n    // @Switch from:<level> to only process the event when the previous moisture level matches the input.\n    // @Switch to:<level> to only process the event when the new moisture level matches the input.\n    //\n    // @Context\n    // <context.location> returns the LocationTag of the farmland block.\n    // <context.material> returns the MaterialTag of the farmland block that changed the moisture level.\n    // <context.old_level> returns the ElementTag(Number) of the previous moisture level.\n    // <context.new_level> returns the ElementTag(Number) of the new moisture level.\n    //\n    // @Cancellable true\n    //\n    // @Example\n    // # Announce when farmland begins to dry out.\n    // on farmland moisture level changes from:7 to:6:\n    // - announce \"Farmland at location <context.location.simple> lost its water source and began to dry!\"\n    //\n    // -->\n\n    public MoistureChangeScriptEvent() {\n        registerCouldMatcher(\"farmland moisture level changes\");\n        registerSwitches(\"from\", \"to\");\n    }\n\n    public MoistureChangeEvent event;\n    public LocationTag location;\n    public Farmland oldFarmland;\n    public Farmland newFarmland;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, location)) {\n            return false;\n        }\n        if (!path.checkSwitch(\"from\", String.valueOf(oldFarmland.getMoisture()))) {\n            return false;\n        }\n        if (!path.checkSwitch(\"to\", String.valueOf(newFarmland.getMoisture()))) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"location\" -> location;\n            case \"material\" -> new MaterialTag(oldFarmland);\n            case \"old_level\" -> new ElementTag(oldFarmland.getMoisture());\n            case \"new_level\" -> new ElementTag(newFarmland.getMoisture());\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onMoistureChange(MoistureChangeEvent event) {\n        location = new LocationTag(event.getBlock().getLocation());\n        oldFarmland = (Farmland) event.getBlock().getBlockData();\n        newFarmland = (Farmland) event.getNewState().getBlockData();\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/NoteBlockPlaysNoteScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.Sound;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.NotePlayEvent;\r\n\r\npublic class NoteBlockPlaysNoteScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // noteblock plays note\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch instrument:<instrument> to only process the event if a specific instrument was played.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a NoteBlock plays a note.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the note block.\r\n    // <context.instrument> returns the name of the instrument played, see list at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Instrument.html>.\r\n    // <context.sound> returns the name of the sound (that fits into <@link command playsound>) represented by the instrument.\r\n    // <context.tone> returns the note tone played (A to G).\r\n    // <context.octave> returns the octave the note is played at (as a number).\r\n    // <context.sharp> returns a boolean indicating whether the note is sharp.\r\n    // <context.pitch> returns the computed pitch value (that fits into <@link command playsound>). Note that volume is always 3.\r\n    //\r\n    // -->\r\n\r\n    public NoteBlockPlaysNoteScriptEvent() {\r\n        registerCouldMatcher(\"noteblock plays note\");\r\n        registerSwitches(\"instrument\");\r\n    }\r\n\r\n    public NotePlayEvent event;\r\n    public LocationTag location;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"instrument\", event.getInstrument().name())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    public Sound getSound() {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            return event.getInstrument().getSound();\r\n        }\r\n        return switch (event.getInstrument()) {\r\n            case PIANO -> Sound.BLOCK_NOTE_BLOCK_HARP;\r\n            case BASS_DRUM -> Sound.BLOCK_NOTE_BLOCK_BASEDRUM;\r\n            case SNARE_DRUM -> Sound.BLOCK_NOTE_BLOCK_SNARE;\r\n            case STICKS -> Sound.BLOCK_NOTE_BLOCK_HAT;\r\n            case BASS_GUITAR -> Sound.BLOCK_NOTE_BLOCK_BASS;\r\n            case FLUTE -> Sound.BLOCK_NOTE_BLOCK_FLUTE;\r\n            case BELL -> Sound.BLOCK_NOTE_BLOCK_BELL;\r\n            case GUITAR -> Sound.BLOCK_NOTE_BLOCK_GUITAR;\r\n            case CHIME -> Sound.BLOCK_NOTE_BLOCK_CHIME;\r\n            case XYLOPHONE -> Sound.BLOCK_NOTE_BLOCK_XYLOPHONE;\r\n            case IRON_XYLOPHONE -> Sound.BLOCK_NOTE_BLOCK_IRON_XYLOPHONE;\r\n            case COW_BELL -> Sound.BLOCK_NOTE_BLOCK_COW_BELL;\r\n            case DIDGERIDOO -> Sound.BLOCK_NOTE_BLOCK_DIDGERIDOO;\r\n            case BIT -> Sound.BLOCK_NOTE_BLOCK_BIT;\r\n            case BANJO -> Sound.BLOCK_NOTE_BLOCK_BANJO;\r\n            case PLING -> Sound.BLOCK_NOTE_BLOCK_PLING;\r\n            default -> null;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"instrument\" -> new ElementTag(event.getInstrument());\r\n            case \"sound\" -> Utilities.enumLikeToLegacyElement(getSound());\r\n            case \"tone\" -> new ElementTag(event.getNote().getTone());\r\n            case \"octave\" -> new ElementTag(event.getNote().getOctave());\r\n            case \"sharp\" -> new ElementTag(event.getNote().isSharped());\r\n            case \"pitch\" -> new ElementTag(Math.pow(2.0, (double) (event.getNote().getId() - 12) / 12.0)); // based on minecraft source\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onNotePlay(NotePlayEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/PistonExtendsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockPistonExtendEvent;\r\n\r\npublic class PistonExtendsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // piston extends\r\n    // <block> extends\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a piston extends.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the piston.\r\n    // <context.material> returns the MaterialTag of the piston.\r\n    // <context.length> returns an ElementTag of the number of blocks that will be moved by the piston.\r\n    // <context.blocks> returns a ListTag of all block locations about to be moved.\r\n    // <context.sticky> returns an ElementTag of whether the piston is sticky.\r\n    // <context.direction> returns a vector location of the direction that blocks will move.\r\n    //\r\n    // -->\r\n\r\n    public PistonExtendsScriptEvent() {\r\n        registerCouldMatcher(\"piston extends\");\r\n        registerCouldMatcher(\"<block> extends\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockPistonExtendEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String mat = path.eventArgLowerAt(0);\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!mat.equals(\"piston\") && !material.tryAdvancedMatcher(mat, path.context)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            case \"sticky\" -> new ElementTag(event.isSticky());\r\n            case \"direction\" -> new LocationTag(event.getDirection().getDirection());\r\n            case \"relative\" -> new LocationTag(event.getBlock().getRelative(event.getDirection()).getLocation()); // Silently deprecated\r\n            case \"blocks\" -> new ListTag(event.getBlocks(), block -> new LocationTag(block.getLocation()));\r\n            case \"length\" -> new ElementTag(event.getBlocks().size());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPistonExtends(BlockPistonExtendEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/PistonRetractsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockPistonRetractEvent;\r\n\r\npublic class PistonRetractsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // piston retracts\r\n    // <block> retracts\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a piston retracts.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the piston.\r\n    // <context.retract_location> returns the new LocationTag of the block that will be moved by the piston if it is sticky.\r\n    // <context.blocks> returns a ListTag of all block locations about to be moved.\r\n    // <context.material> returns the MaterialTag of the piston.\r\n    // <context.sticky> returns an ElementTag of whether the piston is sticky.\r\n    // <context.direction> returns a vector location of the direction that blocks will move.\r\n    //\r\n    // -->\r\n\r\n    public PistonRetractsScriptEvent() {\r\n        registerCouldMatcher(\"piston retracts\");\r\n        registerCouldMatcher(\"<block> retracts\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockPistonRetractEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String mat = path.eventArgLowerAt(0);\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!mat.equals(\"piston\") && !material.tryAdvancedMatcher(mat, path.context)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            case \"sticky\" -> new ElementTag(event.isSticky());\r\n            case \"direction\" -> new LocationTag(event.getDirection().getDirection());\r\n            case \"relative\" -> new LocationTag(event.getBlock().getRelative(event.getDirection().getOppositeFace()).getLocation()); // Silently deprecated\r\n            case \"blocks\" -> new ListTag(event.getBlocks(), block -> new LocationTag(block.getLocation()));\r\n            case \"retract_location\" -> new LocationTag(event.getBlock().getRelative(event.getDirection().getOppositeFace(), 2).getLocation());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPistonRetracts(BlockPistonRetractEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/RedstoneScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockRedstoneEvent;\r\n\r\npublic class RedstoneScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // redstone recalculated\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning This event fires very very rapidly!\r\n    //\r\n    // @Triggers when a redstone wire is recalculated.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the location of the block.\r\n    // <context.old_current> returns what the redstone power level was.\r\n    // <context.new_current> returns what the redstone power level is becoming.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Number) set the current value to a specific value.\r\n    //\r\n    // -->\r\n\r\n    public RedstoneScriptEvent() {\r\n        registerCouldMatcher(\"redstone recalculated\");\r\n        this.<RedstoneScriptEvent, ElementTag>registerOptionalDetermination(null, ElementTag.class, (evt, context, power) -> {\r\n            if (power.isInt()) {\r\n                evt.event.setNewCurrent(power.asInt());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public LocationTag location;\r\n    public BlockRedstoneEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"old_current\" -> new ElementTag(event.getOldCurrent());\r\n            case \"new_current\" -> new ElementTag(event.getNewCurrent());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockRedstone(BlockRedstoneEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/SpongeAbsorbsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.SpongeAbsorbEvent;\n\npublic class SpongeAbsorbsScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // sponge absorbs water\n    //\n    // @Location true\n    //\n    // @Group Block\n    //\n    // @Warning this event may in some cases double-fire, requiring usage of the 'ratelimit' command (like 'ratelimit <context.location> 1t') to prevent doubling actions.\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a sponge block absorbs water.\n    //\n    // @Context\n    // <context.location> returns the location of the sponge.\n    // <context.blocks> returns a ListTag(LocationTag) of blocks (of water) that are being removed.\n    //\n    // -->\n\n    public SpongeAbsorbsScriptEvent() {\n        registerCouldMatcher(\"sponge absorbs water\");\n    }\n\n    public SpongeAbsorbEvent event;\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"location\" -> new LocationTag(event.getBlock().getLocation());\n            case \"blocks\" -> new ListTag(event.getBlocks(), block -> new LocationTag(block.getLocation()));\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onSpongeAbsorbEvent(SpongeAbsorbEvent event) {\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/TNTPrimesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.TNTPrimeEvent;\r\n\r\npublic class TNTPrimesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // tnt primes\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Group Block\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when TNT is activated and will soon explode.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the entity that primed the TNT, if any.\r\n    // <context.block> returns the location of the block that primed the TNT, if any.\r\n    // <context.location> returns the location of the TNT block being primed.\r\n    // <context.cause> returns the cause of the TNT being primed. Refer to <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/block/TNTPrimeEvent.PrimeCause.html>\r\n    // <context.reason> deprecated in favor of <context.cause> for 1.19+.\r\n    //\r\n    // @Player when the priming entity is a player.\r\n    //\r\n    // -->\r\n\r\n    public TNTPrimesScriptEvent() {\r\n        registerCouldMatcher(\"tnt primes\");\r\n    }\r\n\r\n    public TNTPrimeEvent event;\r\n    public LocationTag location;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPrimingEntity());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> event.getPrimingEntity() != null ? new EntityTag(event.getPrimingEntity()).getDenizenObject() : null;\r\n            case \"block\" -> event.getPrimingBlock() != null ? new LocationTag(event.getPrimingBlock().getLocation()) : null;\r\n            case \"location\" -> location;\r\n            case \"cause\" -> new ElementTag(event.getCause());\r\n            case \"reason\" -> new ElementTag(event.getCause() == TNTPrimeEvent.PrimeCause.PLAYER ? \"ITEM\" : event.getCause().name(), true); // For backwards-compatibility\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onTntPrimesEvent(TNTPrimeEvent event) {\r\n        this.event = event;\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/block/VaultDisplaysItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.block;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.VaultDisplayItemEvent;\n\npublic class VaultDisplaysItemScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // vault displays <item>\n    //\n    // @Group Block\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a vault block displays an item.\n    //\n    // @Context\n    // <context.location> returns the LocationTag of the vault block.\n    // <context.item> returns the ItemTag being displayed.\n    //\n    // @Determine\n    // \"ITEM:<ItemTag>\" to set the item being displayed.\n    //\n    // -->\n\n    public VaultDisplaysItemScriptEvent() {\n        registerCouldMatcher(\"vault displays <item>\");\n        this.<VaultDisplaysItemScriptEvent, ItemTag>registerDetermination(\"item\", ItemTag.class, (evt, context, input) -> {\n            evt.event.setDisplayItem(input.getItemStack());\n        });\n    }\n\n    public LocationTag location;\n    public VaultDisplayItemEvent event;\n    public ItemTag item;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, location)) {\n            return false;\n        }\n        if (!path.tryArgObject(2, item)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"item\" -> new ItemTag(event.getDisplayItem());\n            case \"location\" -> location;\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onVaultDisplayItemEvent(VaultDisplayItemEvent event) {\n        location = new LocationTag(event.getBlock().getLocation());\n        item = new ItemTag(event.getDisplayItem());\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/bukkit/ExhaustedNPCEvent.java",
    "content": "package com.denizenscript.denizen.events.bukkit;\r\n\r\nimport net.citizensnpcs.api.event.NPCEvent;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport org.bukkit.event.Cancellable;\r\nimport org.bukkit.event.HandlerList;\r\n\r\n/**\r\n * Bukkit event for when an NPC is exhausted. Exhausted NPCs cannot move\r\n * until their hunger level is below maxhunger, as tracked by the HungerTrait\r\n * provided by Denizen.\r\n */\r\npublic class ExhaustedNPCEvent extends NPCEvent implements Cancellable {\r\n\r\n    private static final HandlerList handlers = new HandlerList();\r\n    private boolean cancelled = false;\r\n\r\n    public ExhaustedNPCEvent(NPC npc) {\r\n        super(npc);\r\n    }\r\n\r\n    public HandlerList getHandlers() {\r\n        return handlers;\r\n    }\r\n\r\n    public static HandlerList getHandlerList() {\r\n        return handlers;\r\n    }\r\n\r\n    @Override\r\n    public boolean isCancelled() {\r\n        return cancelled;\r\n    }\r\n\r\n    @Override\r\n    public void setCancelled(boolean cancelled) {\r\n        this.cancelled = cancelled;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/bukkit/SavesReloadEvent.java",
    "content": "package com.denizenscript.denizen.events.bukkit;\r\n\r\nimport org.bukkit.event.Event;\r\nimport org.bukkit.event.HandlerList;\r\n\r\n/**\r\n * Bukkit event for when Denizen 'saves' are reloaded.\r\n */\r\npublic class SavesReloadEvent extends Event {\r\n\r\n    private static final HandlerList handlers = new HandlerList();\r\n\r\n    public HandlerList getHandlers() {\r\n        return handlers;\r\n    }\r\n\r\n    public static HandlerList getHandlerList() {\r\n        return handlers;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/bukkit/ScriptReloadEvent.java",
    "content": "package com.denizenscript.denizen.events.bukkit;\r\n\r\nimport org.bukkit.event.Event;\r\nimport org.bukkit.event.HandlerList;\r\n\r\n/**\r\n * Bukkit event for when Denizen dScripts are reloaded. This fires after scripts are reloaded.\r\n * Also fires when scripts are loaded for the first time, despite the 're' part of the name.\r\n */\r\npublic class ScriptReloadEvent extends Event {\r\n\r\n    private static final HandlerList handlers = new HandlerList();\r\n\r\n    public HandlerList getHandlers() {\r\n        return handlers;\r\n    }\r\n\r\n    public static HandlerList getHandlerList() {\r\n        return handlers;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/AreaEffectCloudApplyScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.AreaEffectCloudApplyEvent;\r\n\r\npublic class AreaEffectCloudApplyScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // area effect cloud applies\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an area_effect_cloud tries to apply its effect(s) to entities within range.\r\n    //\r\n    // @Warning\r\n    // This runs every 5 ticks if there are any entities in the area effect cloud's bounding box. Prefer <@link event entity potion effects modified> for listening to normal potion effect changes.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the area effect cloud.\r\n    // <context.affected_entities> returns a ListTag of EntityTags affected by the area effect cloud. Note that this can be empty, and only lists which entities are currently being refreshed.\r\n    //\r\n    // @Determine\r\n    // \"AFFECTED_ENTITIES:<ListTag(EntityTag)>\" to determine the entities that will be affected by the area effect cloud. The list should not contain non-living entities.\r\n    //\r\n    // -->\r\n\r\n    public AreaEffectCloudApplyScriptEvent() {\r\n        registerCouldMatcher(\"area effect cloud applies\");\r\n        this.<AreaEffectCloudApplyScriptEvent, ListTag>registerDetermination(\"affected_entities\", ListTag.class, (evt, context, list) -> {\r\n            evt.event.getAffectedEntities().clear();\r\n            for (EntityTag entity : list.filter(EntityTag.class, context)) {\r\n                if (entity.isLivingEntity()) {\r\n                    evt.event.getAffectedEntities().add(entity.getLivingEntity());\r\n                }\r\n                else {\r\n                    Debug.echoError(entity + \" is not a living entity!\");\r\n                }\r\n            }\r\n        });\r\n    }\r\n\r\n    public AreaEffectCloudApplyEvent event;\r\n    public EntityTag entity;\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"affected_entities\" -> new ListTag(event.getAffectedEntities(), EntityTag::new);\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onAreaEffectCloudApply(AreaEffectCloudApplyEvent event) {\r\n        this.event = event;\r\n        entity = new EntityTag(event.getEntity());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/AreaEnterExitScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.AreaContainmentObject;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.NotedAreaTracker;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.notable.Notable;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.Event;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityTeleportEvent;\r\nimport org.bukkit.event.player.*;\r\nimport org.bukkit.event.vehicle.VehicleMoveEvent;\r\n\r\nimport java.util.*;\r\n\r\npublic class AreaEnterExitScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> enters|exits <area>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Triggers when an entity enters or exits a noted area (cuboid, ellipsoid, or polygon). On Spigot servers, only fires for players. Paper is required for other mob types.\r\n    //\r\n    // @Warning cancelling this event will have different results depending on the cause. Teleporting the entity away 1 tick later might be safer.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Context\r\n    // <context.area> returns the area object that was entered or exited.\r\n    // <context.cause> returns the cause of the event. Can be: WALK, WORLD_CHANGE, JOIN, QUIT, TELEPORT, VEHICLE.\r\n    // <context.to> returns the location the entity moved to (might not be available in exit events).\r\n    // <context.from> returns the location the entity moved from (when available, depending on cause).\r\n    // <context.entity> returns the entity that entered/exited an area.\r\n    //\r\n    // @Player When the entity is a player.\r\n    //\r\n    // -->\r\n\r\n    public AreaEnterExitScriptEvent() {\r\n        registerCouldMatcher(\"<entity> enters|exits <area>\");\r\n    }\r\n\r\n    public EntityTag currentEntity;\r\n    public AreaContainmentObject area;\r\n    public boolean isEntering;\r\n    public Location to;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (isEntering && !path.eventArgLowerAt(1).equals(\"enters\")) {\r\n            return false;\r\n        }\r\n        if (!isEntering && !path.eventArgLowerAt(1).equals(\"exits\")) {\r\n            return false;\r\n        }\r\n        String areaName = path.eventArgLowerAt(2);\r\n        if (areaName.equals(\"notable\")) { // TODO: Deprecate?\r\n            areaName = path.eventArgLowerAt(3);\r\n        }\r\n        if (!area.tryAdvancedMatcher(areaName, path.context)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, currentEntity)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(currentEntity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"area\" -> area;\r\n            case \"cause\" -> {\r\n                String cause;\r\n                if (currentEvent instanceof PlayerJoinEvent) {\r\n                    cause = \"JOIN\";\r\n                }\r\n                else if (currentEvent instanceof PlayerQuitEvent) {\r\n                    cause = \"QUIT\";\r\n                }\r\n                else if (currentEvent instanceof PlayerChangedWorldEvent) {\r\n                    cause = \"WORLD_CHANGE\";\r\n                }\r\n                else if (currentEvent instanceof PlayerTeleportEvent) {\r\n                    cause = \"TELEPORT\";\r\n                }\r\n                else if (currentEvent instanceof VehicleMoveEvent) {\r\n                    cause = \"VEHICLE\";\r\n                }\r\n                else if (currentEvent instanceof PlayerMoveEvent) {\r\n                    cause = \"WALK\";\r\n                }\r\n                else {\r\n                    cause = \"UNKNOWN\";\r\n                }\r\n                yield new ElementTag(cause, true);\r\n            }\r\n            case \"to\" -> to != null ? new LocationTag(to) : null;\r\n            case \"from\" -> {\r\n                if (currentEvent instanceof PlayerMoveEvent playerMove) {\r\n                    yield new LocationTag(playerMove.getFrom());\r\n                }\r\n                else if (currentEvent instanceof VehicleMoveEvent vehicleMove) {\r\n                    yield new LocationTag(vehicleMove.getFrom());\r\n                }\r\n                else {\r\n                    yield new LocationTag(currentEntity.getLocation());\r\n                }\r\n            }\r\n            case \"entity\" -> currentEntity.getDenizenObject();\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    public void registerCorrectClass() {\r\n        initListener(new SpigotListeners());\r\n    }\r\n\r\n    @Override\r\n    public void init() {\r\n        doTrackAll = false;\r\n        boolean needsMatchers = false;\r\n        HashSet<String> exacts = new HashSet<>();\r\n        List<MatchHelper> matchList = new ArrayList<>();\r\n        HashSet<String> flags = new HashSet<>();\r\n        onlyTrackPlayers = true;\r\n        for (ScriptPath path : eventPaths) {\r\n            if (!path.eventArgLowerAt(0).equals(\"player\")) {\r\n                onlyTrackPlayers = false;\r\n            }\r\n            String area = path.eventArgLowerAt(2);\r\n            if (area.equals(\"notable\")) {\r\n                area = path.eventArgLowerAt(3);\r\n            }\r\n            if (area.equals(\"cuboid\") || area.equals(\"ellipsoid\") || area.equals(\"polygon\")) {\r\n                doTrackAll = true;\r\n                needsMatchers = true;\r\n            }\r\n            MatchHelper matcher = createMatcher(area);\r\n            if (matcher instanceof AlwaysMatchHelper) {\r\n                doTrackAll = true;\r\n                needsMatchers = true;\r\n            }\r\n            else if (!needsMatchers && (matcher instanceof ExactMatchHelper)) {\r\n                exacts.add(area);\r\n            }\r\n            else {\r\n                needsMatchers = true;\r\n            }\r\n            matchList.add(matcher);\r\n            if (area.startsWith(\"area_flagged:\")) {\r\n                flags.add(CoreUtilities.toLowerCase(area.substring(\"area_flagged:\".length())));\r\n                needsMatchers = true;\r\n            }\r\n        }\r\n        exactTracked = needsMatchers ? null : exacts.toArray(new String[0]);\r\n        matchers = needsMatchers ? matchList.toArray(new MatchHelper[0]) : null;\r\n        flagTracked = !flags.isEmpty() ? flags.toArray(new String[0]) : null;\r\n        registerCorrectClass();\r\n    }\r\n\r\n    public boolean doTrackAll = false;\r\n    public String[] exactTracked = null;\r\n    public String[] flagTracked = null;\r\n    public MatchHelper[] matchers = null;\r\n    public boolean onlyTrackPlayers = true;\r\n    public static HashMap<UUID, HashSet<AreaContainmentObject>> entitiesInArea = new HashMap<>();\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled) {\r\n            HashSet<AreaContainmentObject> inAreas = entitiesInArea.get(currentEntity.getUUID());\r\n            if (isEntering) {\r\n                inAreas.remove(area);\r\n            }\r\n            else {\r\n                inAreas.add(area);\r\n            }\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    public boolean anyMatch(String name, FlaggableObject flaggable) {\r\n        if (doTrackAll) {\r\n            return true;\r\n        }\r\n        if (matchers != null) {\r\n            for (MatchHelper matcher : matchers) {\r\n                if (matcher.doesMatch(name)) {\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        if (flagTracked != null) {\r\n            for (String flag : flagTracked) {\r\n                AbstractFlagTracker tracker = flaggable.getFlagTracker();\r\n                if (tracker != null && tracker.hasFlag(flag)) {\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void processSingle(AreaContainmentObject obj, EntityTag entity, HashSet<AreaContainmentObject> inAreas, Location pos, Event eventCause) {\r\n        boolean containedNow = pos != null && obj.doesContainLocation(pos);\r\n        boolean wasContained = inAreas != null && inAreas.contains(obj);\r\n        if (containedNow == wasContained) {\r\n            return;\r\n        }\r\n        if (inAreas == null) {\r\n            inAreas = new HashSet<>();\r\n            entitiesInArea.put(entity.getUUID(), inAreas);\r\n        }\r\n        if (containedNow) {\r\n            inAreas.add(obj);\r\n        }\r\n        else {\r\n            inAreas.remove(obj);\r\n        }\r\n        currentEntity = entity;\r\n        isEntering = containedNow;\r\n        area = obj;\r\n        to = pos;\r\n        fire(eventCause);\r\n    }\r\n\r\n    private final static List<AreaContainmentObject> reusableClearList = new ArrayList<>();\r\n\r\n    public void processNewPosition(EntityTag entity, Location pos, Event eventCause) {\r\n        if (onlyTrackPlayers && !entity.isPlayer()) {\r\n            return;\r\n        }\r\n        HashSet<AreaContainmentObject> inAreas = entitiesInArea.get(entity.getUUID());\r\n        if (doTrackAll || matchers != null || flagTracked != null) {\r\n            if (pos != null) {\r\n                NotedAreaTracker.forEachAreaThatContains(new LocationTag(pos), (a) -> {\r\n                    if (a instanceof FlaggableObject flaggable && anyMatch(a.getNoteName(), flaggable)) {\r\n                        processSingle(a, entity, inAreas, pos, eventCause);\r\n                    }\r\n                });\r\n            }\r\n            if (inAreas != null) {\r\n                reusableClearList.addAll(inAreas);\r\n                for (AreaContainmentObject area : reusableClearList) {\r\n                    if (area.getNoteName() == null) {\r\n                        inAreas.remove(area);\r\n                    }\r\n                    else {\r\n                        processSingle(area, entity, inAreas, pos, eventCause);\r\n                    }\r\n                }\r\n                reusableClearList.clear();\r\n            }\r\n        }\r\n        else {\r\n            for (String name : exactTracked) {\r\n                Notable obj = NoteManager.getSavedObject(name);\r\n                if (!(obj instanceof AreaContainmentObject areaObject)) {\r\n                    Debug.echoError(\"Invalid area enter/exit event area '\" + name + \"'\");\r\n                    continue;\r\n                }\r\n                processSingle(areaObject, entity, inAreas, pos, eventCause);\r\n            }\r\n        }\r\n        if (inAreas != null && inAreas.isEmpty()) {\r\n            entitiesInArea.remove(entity.getUUID());\r\n        }\r\n    }\r\n\r\n    public class SpigotListeners implements Listener {\r\n\r\n        @EventHandler\r\n        public void onQuit(PlayerQuitEvent event) {\r\n            processNewPosition(new EntityTag(event.getPlayer()), null, event);\r\n            entitiesInArea.remove(event.getPlayer().getUniqueId());\r\n        }\r\n\r\n        @EventHandler\r\n        public void onJoin(PlayerJoinEvent event) {\r\n            processNewPosition(new EntityTag(event.getPlayer()), event.getPlayer().getLocation(), event);\r\n        }\r\n\r\n        @EventHandler\r\n        public void onMove(PlayerMoveEvent event) {\r\n            if (LocationTag.isSameBlock(event.getFrom(), event.getTo())) {\r\n                return;\r\n            }\r\n            processNewPosition(new EntityTag(event.getPlayer()), event.getTo(), event);\r\n        }\r\n\r\n        @EventHandler\r\n        public void onTeleport(PlayerTeleportEvent event) {\r\n            processNewPosition(new EntityTag(event.getPlayer()), event.getTo(), event);\r\n        }\r\n\r\n        @EventHandler\r\n        public void onTeleport(EntityTeleportEvent event) {\r\n            if (!onlyTrackPlayers) {\r\n                processNewPosition(new EntityTag(event.getEntity()), event.getTo(), event);\r\n            }\r\n        }\r\n\r\n        @EventHandler\r\n        public void onWorldChange(PlayerChangedWorldEvent event) {\r\n            processNewPosition(new EntityTag(event.getPlayer()), event.getPlayer().getLocation(), event);\r\n        }\r\n\r\n        @EventHandler\r\n        public void onVehicleMove(VehicleMoveEvent event) {\r\n            if (LocationTag.isSameBlock(event.getFrom(), event.getTo())) {\r\n                return;\r\n            }\r\n            if (!onlyTrackPlayers) {\r\n                processNewPosition(new EntityTag(event.getVehicle()), event.getTo(), event);\r\n            }\r\n            for (Entity entity : event.getVehicle().getPassengers()) {\r\n                if (!onlyTrackPlayers || EntityTag.isPlayer(entity)) {\r\n                    processNewPosition(new EntityTag(entity), event.getTo(), event);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/CreeperPoweredScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.CreeperPowerEvent;\r\n\r\npublic class CreeperPoweredScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // creeper powered (because <'cause'>)\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a creeper is struck by lightning and turned into a powered creeper.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the creeper.\r\n    // <context.lightning> returns the EntityTag of the lightning.\r\n    // <context.cause> returns an ElementTag of the cause for the creeper being powered. Refer to <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/CreeperPowerEvent.PowerCause.html>.\r\n    //\r\n    // -->\r\n\r\n    public CreeperPoweredScriptEvent() {\r\n        registerCouldMatcher(\"creeper powered (because <'cause'>)\");\r\n    }\r\n\r\n    public EntityTag lightning;\r\n    public EntityTag entity;\r\n    public ElementTag cause;\r\n    public CreeperPowerEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.eventArgLowerAt(2).equals(\"because\")\r\n                && !path.eventArgLowerAt(3).equals(CoreUtilities.toLowerCase(cause.toString()))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"lightning\" -> lightning;\r\n            case \"cause\" -> cause;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onCreeperPowered(CreeperPowerEvent event) {\r\n        lightning = new EntityTag(event.getLightning());\r\n        entity = new EntityTag(event.getEntity());\r\n        cause = new ElementTag(event.getCause());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/DragonPhaseChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.entity.EnderDragon;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EnderDragonChangePhaseEvent;\r\n\r\npublic class DragonPhaseChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // ender_dragon changes phase\r\n    // <entity> changes phase\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    // @Switch from:<phase> to only process the event if the dragon was previously in the specified phase.\r\n    // @Switch to:<phase> to only process the event if the dragon is changing to the specified phase.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a dragon's combat phase changes.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the dragon.\r\n    // <context.new_phase> returns an ElementTag of the dragon's new phase. Phases: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/EnderDragon.Phase.html>\r\n    // <context.old_phase> returns an ElementTag of the dragon's old phase. Can be any phase or 'null' in some cases.\r\n    //\r\n    // @Determine\r\n    // ElementTag to change the dragon's new phase.\r\n    //\r\n    // -->\r\n\r\n    public DragonPhaseChangeScriptEvent() {\r\n        registerCouldMatcher(\"<entity> changes phase\");\r\n        registerSwitches(\"from\", \"to\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public EnderDragonChangePhaseEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String target = path.eventArgLowerAt(0);\r\n        if (!entity.tryAdvancedMatcher(target, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"from\", event.getCurrentPhase() == null ? \"null\" : event.getCurrentPhase().name())) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"to\", event.getNewPhase().name())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (exactMatchesEnum(determinationObj.toString(), EnderDragon.Phase.values())) {\r\n            EnderDragon.Phase phase = EnderDragon.Phase.valueOf(determinationObj.toString().toUpperCase());\r\n            event.setNewPhase(phase);\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return entity.getDenizenObject();\r\n            case \"old_phase\":\r\n                return new ElementTag(event.getCurrentPhase() == null ? \"null\" : event.getCurrentPhase().name());\r\n            case \"new_phase\":\r\n                return new ElementTag(event.getNewPhase());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEnderDragonChangePhase(EnderDragonChangePhaseEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityAirLevelChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityAirChangeEvent;\r\n\r\npublic class EntityAirLevelChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> changes air level\r\n    //\r\n    // @Synonyms player loses oxygen,player drowns,player is drowning,oxygen depletion\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity's air level changes.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.air_duration> returns a DurationTag of the entity's new air level.\r\n    //\r\n    // @Determine\r\n    // DurationTag to set the entity's new air level.\r\n    //\r\n    // @Player when the entity that's air level has changed is a player.\r\n    //\r\n    // @NPC when the entity that's air level has changed is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityAirLevelChangeScriptEvent() {\r\n        registerCouldMatcher(\"<entity> changes air level\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public EntityAirChangeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String target = path.eventArgLowerAt(0);\r\n        if (!entity.tryAdvancedMatcher(target, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isInt()) {\r\n            event.setAmount(element.asInt());\r\n            return true;\r\n        }\r\n        else if (DurationTag.matches(determinationObj.toString())) {\r\n            event.setAmount(DurationTag.valueOf(determinationObj.toString(), getTagContext(path)).getTicksAsInt());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return entity.getDenizenObject();\r\n            case \"air\":\r\n                BukkitImplDeprecations.airLevelEventDuration.warn();\r\n                return new ElementTag(event.getAmount());\r\n            case \"air_duration\":\r\n                return new DurationTag((long) event.getAmount());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityAirLevelChanged(EntityAirChangeEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityBreaksHangingScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.hanging.HangingBreakByEntityEvent;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\n\r\npublic class EntityBreaksHangingScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> breaks <hanging> (because <'cause'>)\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a hanging entity (painting, item_frame, or leash_hitch) is broken.\r\n    //\r\n    // @Context\r\n    // <context.cause> returns the cause of the entity breaking. Causes list: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/hanging/HangingBreakEvent.RemoveCause.html>\r\n    // <context.breaker> returns the EntityTag that broke the hanging entity, if any.\r\n    // <context.hanging> returns the EntityTag of the hanging.\r\n    //\r\n    // @Player when the breaker is a player.\r\n    //\r\n    // @NPC when the breaker is an npc.\r\n    //\r\n    // -->\r\n\r\n    public EntityBreaksHangingScriptEvent() {\r\n        registerCouldMatcher(\"<entity> breaks <hanging> (because <'cause'>)\");\r\n    }\r\n\r\n    public ElementTag cause;\r\n    public EntityTag breaker;\r\n    public EntityTag hanging;\r\n    public LocationTag location;\r\n    public HangingBreakByEntityEvent event;\r\n\r\n    public static HashSet<String> notRelevantBreakables = new HashSet<>(Arrays.asList(\"item\", \"held\", \"block\", \"because\"));\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (notRelevantBreakables.contains(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String entName = path.eventArgLowerAt(0);\r\n        String hang = path.eventArgLowerAt(2);\r\n        if (!breaker.tryAdvancedMatcher(entName, path.context)) {\r\n            return false;\r\n        }\r\n        if (!hang.equals(\"hanging\") && !hanging.tryAdvancedMatcher(hang, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(3).equals(\"because\") && !path.eventArgLowerAt(4).equals(cause.asLowerString())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(breaker);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"cause\" -> cause;\r\n            case \"breaker\" -> breaker.getDenizenObject();\r\n            case \"hanging\" -> hanging;\r\n            case \"location\" -> location;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onHangingBreaks(HangingBreakByEntityEvent event) {\r\n        hanging = new EntityTag(event.getEntity());\r\n        cause = new ElementTag(event.getCause());\r\n        location = new LocationTag(hanging.getLocation());\r\n        breaker = new EntityTag(event.getRemover());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityBreedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport org.bukkit.entity.*;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.entity.EntityBreedEvent;\n\npublic class EntityBreedScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // <entity> breeds\n    //\n    // @Group Entity\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers when two entities breed.\n    //\n    // @Context\n    // <context.breeder> returns the EntityTag responsible for breeding, if it exists.\n    // <context.child> returns the child EntityTag.\n    // <context.mother> returns the parent EntityTag creating the child. The child will spawn at the mother's location.\n    // <context.father> returns the other parent EntityTag.\n    // <context.item> returns the ItemTag used to initiate breeding, if it exists.\n    // <context.experience> returns the amount of experience granted by breeding.\n    //\n    // @Determine\n    // ElementTag(Number) to set the amount of experience granted by breeding.\n    //\n    // -->\n\n    public EntityBreedScriptEvent() {\n        registerCouldMatcher(\"<entity> breeds\");\n        this.<EntityBreedScriptEvent, ElementTag>registerOptionalDetermination(null, ElementTag.class, (evt, context, determination) -> {\n            if (determination.isInt()) {\n                evt.event.setExperience(determination.asInt());\n                return true;\n            }\n            return false;\n        });\n    }\n\n    private EntityTag entity;\n    private EntityTag breeder;\n    private EntityTag father;\n    private EntityTag mother;\n    private ItemTag item;\n    private int experience;\n    public EntityBreedEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!path.tryArgObject(0, entity)) {\n            return false;\n        }\n        if (!runInCheck(path, entity.getLocation())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"child\" -> entity.getDenizenObject();\n            case \"breeder\" -> breeder == null ? null : breeder.getDenizenObject();\n            case \"father\" ->  father.getDenizenObject();\n            case \"mother\" -> mother.getDenizenObject();\n            case \"item\" -> item;\n            case \"experience\" -> new ElementTag(experience);\n            default -> super.getContext(name);\n        };\n    }\n\n    @Override\n    public void cancellationChanged() {\n        // Prevent entities from continuing to breed with each other\n        if (cancelled && entity.getBukkitEntity() instanceof Animals) {\n            ((Animals) father.getLivingEntity()).setLoveModeTicks(0);\n            ((Animals) mother.getLivingEntity()).setLoveModeTicks(0);\n        }\n        else if (cancelled && entity.getBukkitEntity() instanceof Villager) {\n            ((Villager) father.getLivingEntity()).getInventory().clear();\n            ((Villager) mother.getLivingEntity()).getInventory().clear();\n        }\n        super.cancellationChanged();\n    }\n\n    @EventHandler\n    public void onEntityBreeds(EntityBreedEvent event) {\n        Entity entity = event.getEntity();\n        this.entity = new EntityTag(entity);\n        breeder = event.getBreeder() == null ? null : new EntityTag(event.getBreeder());\n        father = new EntityTag(event.getFather());\n        mother = new EntityTag(event.getMother());\n        item = event.getBredWith() == null ? null : new ItemTag(event.getBredWith());\n        experience = event.getExperience();\n        this.event = event;\n        EntityTag.rememberEntity(entity);\n        fire(event);\n        EntityTag.forgetEntity(entity);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityChangesBlockScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityChangeBlockEvent;\r\n\r\npublic class EntityChangesBlockScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> changes <block> (into <block>)\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity changes the material of a block.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that changed the block.\r\n    // <context.location> returns the LocationTag of the changed block.\r\n    // <context.old_material> returns the old material of the block.\r\n    // <context.new_material> returns the new material of the block.\r\n    //\r\n    // @Player when the entity that changed the block is a player.\r\n    //\r\n    // -->\r\n\r\n    public EntityChangesBlockScriptEvent() {\r\n        registerCouldMatcher(\"<entity> changes <block> (into <block>)\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    public MaterialTag oldMaterial;\r\n    public MaterialTag newMaterial;\r\n    public EntityChangeBlockEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String entName = path.eventArgLowerAt(0);\r\n        if (!entity.tryAdvancedMatcher(entName, path.context)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(2, oldMaterial)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(3).equals(\"into\")) {\r\n            String mat2 = path.eventArgLowerAt(4);\r\n            if (mat2.isEmpty()) {\r\n                Debug.echoError(\"Invalid event material [\" + getName() + \"]: '\" + path.event + \"' for \" + path.container.getName());\r\n                return false;\r\n            }\r\n            else if (!newMaterial.tryAdvancedMatcher(mat2, path.context)) {\r\n                return false;\r\n            }\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"location\" -> location;\r\n            case \"new_material\" -> newMaterial;\r\n            case \"old_material\" -> oldMaterial;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityChangesBlock(EntityChangeBlockEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        oldMaterial = new MaterialTag(location.getBlock());\r\n        newMaterial = new MaterialTag(event.getTo());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityChangesPoseScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Pose;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityPoseChangeEvent;\r\n\r\npublic class EntityChangesPoseScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> changes pose\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch old:<pose> to only process the event if the old pose matches the input.\r\n    // @Switch new:<pose> to only process the event if the new pose matches the input.\r\n    //\r\n    // @Triggers when an entity changes its visual pose.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that changed its pose.\r\n    // <context.old_pose> returns the name of the old pose. See <@link url https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/Pose.html>\r\n    // <context.new_pose> returns the name of the new pose.\r\n    //\r\n    // @Player when the entity that changed its pose is a player.\r\n    //\r\n    // -->\r\n\r\n    public EntityChangesPoseScriptEvent() {\r\n        registerCouldMatcher(\"<entity> changes pose\");\r\n        registerSwitches(\"old\", \"new\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public Pose oldPose;\r\n    public EntityPoseChangeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"old\", oldPose.name())) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"new\", event.getPose().name())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"old_pose\" -> new ElementTag(oldPose);\r\n            case \"new_pose\" -> new ElementTag(event.getPose());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPoseChange(EntityPoseChangeEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        oldPose = entity.getBukkitEntity().getPose();\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityCombustsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityCombustByBlockEvent;\r\nimport org.bukkit.event.entity.EntityCombustByEntityEvent;\r\nimport org.bukkit.event.entity.EntityCombustEvent;\r\n\r\npublic class EntityCombustsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> combusts\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity catches fire.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the entity that caught fire.\r\n    // <context.duration> returns the length of the burn.\r\n    // <context.source> returns the EntityTag or LocationTag that caused the fire, if any. NOTE: Currently, if the source is a LocationTag, the tag will return a null. It is expected that this will be fixed by Spigot in the future.\r\n    // <context.source_type> returns the type of the source, which can be: ENTITY, LOCATION, NONE.\r\n    //\r\n    // @Determine\r\n    // DurationTag set the burn duration.\r\n    //\r\n    // @Player when the entity that catches fire is a player.\r\n    //\r\n    // @NPC when the entity that catches fire is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityCombustsScriptEvent() {\r\n        registerCouldMatcher(\"<entity> combusts\");\r\n        this.<EntityCombustsScriptEvent, ObjectTag>registerOptionalDetermination(null, ObjectTag.class, (evt, context, determination) -> {\r\n            if (determination instanceof ElementTag element && element.isInt()) {\r\n                evt.event.setDuration(element.asInt());\r\n                return true;\r\n            }\r\n            else if (determination.canBeType(DurationTag.class)) {\r\n                evt.event.setDuration(determination.asType(DurationTag.class, context).getSecondsAsInt());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public EntityCombustEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"duration\" -> new DurationTag(event.getDuration());\r\n            case \"source\" -> {\r\n                if (event instanceof EntityCombustByEntityEvent byEntityEvent) {\r\n                    yield new EntityTag(byEntityEvent.getCombuster()).getDenizenObject();\r\n                }\r\n                else if (event instanceof EntityCombustByBlockEvent byBlockEvent) {\r\n                    Block combuster = byBlockEvent.getCombuster();\r\n                    if (combuster != null) {\r\n                        yield new LocationTag(combuster.getLocation());\r\n                    }\r\n                }\r\n                yield null;\r\n            }\r\n            case \"source_type\" -> new ElementTag(event instanceof EntityCombustByEntityEvent ? \"ENTITY\" : (event instanceof EntityCombustByBlockEvent ? \"LOCATION\" : \"NONE\"));\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityCombusts(EntityCombustEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityCreatePortalScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityCreatePortalEvent;\r\n\r\npublic class EntityCreatePortalScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> creates portal\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity creates a portal. Generally, prefer <@link event portal created> instead of this.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that created the portal.\r\n    // <context.portal_type> returns the type of portal: CUSTOM, ENDER, or NETHER.\r\n    // <context.blocks> returns a list of block locations where the portal is being created.\r\n    //\r\n    // @Player if the entity that created the portal is a player.\r\n    //\r\n    // -->\r\n\r\n    public EntityCreatePortalScriptEvent() {\r\n        registerCouldMatcher(\"<entity> creates portal\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public EntityCreatePortalEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"portal_type\" -> new ElementTag(event.getPortalType());\r\n            case \"blocks\" -> new ListTag(event.getBlocks(), block -> new LocationTag(block.getLocation()));\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityCreatesPortal(EntityCreatePortalEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDamagedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.entity.Projectile;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityDamageByBlockEvent;\r\nimport org.bukkit.event.entity.EntityDamageByEntityEvent;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\nimport org.bukkit.potion.PotionEffectType;\r\n\r\npublic class EntityDamagedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[language]\r\n    // @name Damage Cause\r\n    // @group Useful Lists\r\n    // @description\r\n    // Possible damage causes: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html>\r\n    // These are used in <@link event entity damage>, <@link tag server.damage_causes>, <@link tag EntityTag.last_damage.cause>, ...\r\n    // -->\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> damaged by <entity>\r\n    // <entity> damaged (by <'cause'>)\r\n    // <entity> damages <entity>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch with:<item> to only process the event when the item used to cause damage (in the damager's hand) is a specified item.\r\n    // @Switch type:<entity> to only run if the entity damaged matches the entity input.\r\n    // @Switch block:<block-matcher> to only run if the damage came from a block that matches the given material or location matcher.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity is damaged.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that was damaged.\r\n    // <context.damager> returns the EntityTag damaging the other entity, if any.\r\n    // <context.damager_block> returns the LocationTag of a block that damaged the entity, if any.\r\n    // <context.cause> returns an ElementTag of reason the entity was damaged - see <@link language damage cause> for causes.\r\n    // <context.damage> returns an ElementTag(Decimal) of the amount of damage dealt.\r\n    // <context.final_damage> returns an ElementTag(Decimal) of the amount of damage dealt, after armor is calculated.\r\n    // <context.projectile> returns a EntityTag of the projectile, if one caused the event.\r\n    // <context.damage_type_map> returns a MapTag the damage dealt by a specific damage type with keys: BASE, HARD_HAT, BLOCKING, ARMOR, RESISTANCE, MAGIC, ABSORPTION.\r\n    // <context.was_critical> returns 'true' if the damage was a critical hit. (Warning: this value is calculated and not guaranteed to be correct if the event is altered).\r\n    //\r\n    // @Determine\r\n    // ElementTag(Decimal) to set the amount of damage the entity receives.\r\n    // \"CLEAR_MODIFIERS\" to zero out all damage modifiers other than \"BASE\", effectively making damage == final_damage.\r\n    //\r\n    // @Player when the damager or damaged entity is a player. Cannot be both.\r\n    //\r\n    // @NPC when the damager or damaged entity is an NPC. Cannot be both.\r\n    //\r\n    // @Example\r\n    // on entity damaged:\r\n    // - announce \"A <context.entity.entity_type> took damage!\"\r\n    //\r\n    // @Example\r\n    // on player damages cow:\r\n    // - announce \"<player.name> damaged a cow at <context.entity.location.simple>\"\r\n    //\r\n    // @Example\r\n    // on player damages cow|sheep|chicken with:*_hoe:\r\n    // - narrate \"Whoa there farmer, you almost hurt your farm animals with that farmin' tool!\"\r\n    // - determine cancelled\r\n    //\r\n    // @Example\r\n    // # This example disambiguates this event from the \"vehicle damaged\" event for specific vehicle entity types.\r\n    // on entity damaged type:minecart:\r\n    // - announce \"A minecart took non-vehicular damage!\"\r\n    //\r\n    // -->\r\n\r\n    public EntityDamagedScriptEvent() {\r\n        registerCouldMatcher(\"<entity> damaged (by <'cause'>)\");\r\n        registerCouldMatcher(\"<entity> damaged by <entity>\");\r\n        registerCouldMatcher(\"<entity> damages <entity>\");\r\n        registerSwitches(\"with\", \"type\", \"blocker\");\r\n    }\r\n\r\n\r\n    public EntityTag entity;\r\n    public EntityTag damager;\r\n    public EntityTag projectile;\r\n    public ItemTag held;\r\n    public EntityDamageEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        String cmd = path.eventArgLowerAt(1);\r\n        if (cmd.equals(\"damaged\")) {\r\n            if (path.eventArgLowerAt(0).equals(\"vehicle\")) {\r\n                return false;\r\n            }\r\n        }\r\n        else if (cmd.equals(\"damages\")) {\r\n            if (path.eventArgLowerAt(2).equals(\"vehicle\")) {\r\n                return false;\r\n            }\r\n        }\r\n        else {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        String attacker = cmd.equals(\"damages\") ? path.eventArgLowerAt(0) :\r\n                path.eventArgLowerAt(2).equals(\"by\") ? path.eventArgLowerAt(3) : \"\";\r\n        String target = cmd.equals(\"damages\") ? path.eventArgLowerAt(2) : path.eventArgLowerAt(0);\r\n        if (!attacker.isEmpty()) {\r\n            if (damager != null) {\r\n                if (!runGenericCheck(attacker, event.getCause().name()) && (projectile == null || !projectile.tryAdvancedMatcher(attacker, path.context)) && (damager == null || !damager.tryAdvancedMatcher(attacker, path.context))) {\r\n                    return false;\r\n                }\r\n            }\r\n            else {\r\n                if (!runGenericCheck(attacker, event.getCause().name())) {\r\n                    return false;\r\n                }\r\n            }\r\n        }\r\n        if (!entity.tryAdvancedMatcher(target, path.context) || !path.tryObjectSwitch(\"type\", entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, held)) {\r\n            return false;\r\n        }\r\n        String blockMatcher = path.switches.get(\"block\");\r\n        if (blockMatcher != null) {\r\n            if (!(event instanceof EntityDamageByBlockEvent)) {\r\n                return false;\r\n            }\r\n            Block block = ((EntityDamageByBlockEvent) event).getDamager();\r\n            if (block == null) {\r\n                return false;\r\n            }\r\n            if (!new LocationTag(block.getLocation()).tryAdvancedMatcher(blockMatcher, path.context)) {\r\n                return false;\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            if (CoreUtilities.equalsIgnoreCase(determinationObj.toString(), \"clear_modifiers\")) {\r\n                for (EntityDamageEvent.DamageModifier modifier : EntityDamageEvent.DamageModifier.values()) {\r\n                    if (modifier != EntityDamageEvent.DamageModifier.BASE && event.isApplicable(modifier)) {\r\n                        event.setDamage(modifier, 0);\r\n                    }\r\n                }\r\n                return true;\r\n            }\r\n            else if (((ElementTag) determinationObj).isDouble()) {\r\n                event.setDamage(((ElementTag) determinationObj).asDouble());\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(damager != null && damager.isPlayer() ? damager.getDenizenPlayer() : entity.isPlayer() ? entity.getDenizenPlayer() : null,\r\n                damager != null && damager.isCitizensNPC() ? damager.getDenizenNPC() : entity.isCitizensNPC() ? entity.getDenizenNPC() : null);\r\n    }\r\n\r\n    public boolean calculateWasCritical() {\r\n        if (!(event instanceof EntityDamageByEntityEvent)) {\r\n            return false;\r\n        }\r\n        if (!damager.isPlayer()) {\r\n            return false;\r\n        }\r\n        // This based on the source of NMS EntityHuman#attack(Entity entity)\r\n        // boolean flag = f2 > 0.9F;\r\n        // boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround && !this.isClimbing() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && entity instanceof EntityLiving;\r\n        if (event.getDamage() <= 0.9) {\r\n            return false;\r\n        }\r\n        if (!(event.getEntity() instanceof LivingEntity)) {\r\n            return false;\r\n        }\r\n        Player player = damager.getPlayer();\r\n        if (player.getAttackCooldown() < 0.999) { // attack cooldown is also checked in that method earlier\r\n            return false;\r\n        }\r\n        if (player.getFallDistance() <= 0 || player.isOnGround() || player.isClimbing() || player.isInWater()) {\r\n            return false;\r\n        }\r\n        if (player.hasPotionEffect(PotionEffectType.BLINDNESS)) {\r\n            return false;\r\n        }\r\n        if (player.isInsideVehicle()) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\": return entity.getDenizenObject();\r\n            case \"damage\": return new ElementTag(event.getDamage());\r\n            case \"final_damage\": return new ElementTag(event.getFinalDamage());\r\n            case \"cause\": return new ElementTag(event.getCause());\r\n            case \"damager\":\r\n                if (damager != null) {\r\n                    return damager.getDenizenObject();\r\n                }\r\n                break;\r\n            case \"damager_block\":\r\n                if (event instanceof EntityDamageByBlockEvent) {\r\n                    Block block = ((EntityDamageByBlockEvent) event).getDamager();\r\n                    if (block != null) {\r\n                        return new LocationTag(block.getLocation());\r\n                    }\r\n                }\r\n                break;\r\n            case \"projectile\":\r\n                if (projectile != null) {\r\n                    return projectile.getDenizenObject();\r\n                }\r\n                break;\r\n            case \"damage_type_map\": {\r\n                MapTag map = new MapTag();\r\n                for (EntityDamageEvent.DamageModifier dm : EntityDamageEvent.DamageModifier.values()) {\r\n                    map.putObject(dm.name(), new ElementTag(event.getDamage(dm)));\r\n                }\r\n                return map;\r\n            }\r\n            case \"was_critical\":\r\n                return new ElementTag(calculateWasCritical());\r\n        }\r\n        if (name.startsWith(\"damage_\")) {\r\n            BukkitImplDeprecations.damageEventTypeMap.warn();\r\n            for (EntityDamageEvent.DamageModifier dm : EntityDamageEvent.DamageModifier.values()) {\r\n                if (name.equals(\"damage_\" + CoreUtilities.toLowerCase(dm.name()))) {\r\n                    return new ElementTag(event.getDamage(dm));\r\n                }\r\n            }\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityDamaged(EntityDamageEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        damager = null;\r\n        projectile = null;\r\n        held = null;\r\n        if (event instanceof EntityDamageByEntityEvent) {\r\n            damager = new EntityTag(((EntityDamageByEntityEvent) event).getDamager());\r\n            EntityTag shooter = damager.getShooter();\r\n            if (damager instanceof Projectile) {\r\n                projectile = damager;\r\n            }\r\n            if (shooter != null) {\r\n                projectile = damager;\r\n                damager = shooter;\r\n            }\r\n            if (damager != null) {\r\n                held = damager.getItemInHand();\r\n                if (held != null) {\r\n                    held.setAmount(1);\r\n                }\r\n            }\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDeathScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Projectile;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityDamageByEntityEvent;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\nimport org.bukkit.event.entity.EntityDeathEvent;\r\nimport org.bukkit.event.entity.PlayerDeathEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.List;\r\n\r\npublic class EntityDeathScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> dies|death\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    // @Switch by:<entity> to only process the event if the killer is known and matches the specified entity matcher.\r\n    // @Switch cause:<cause> to only process the event if it was caused by a specific damage cause.\r\n    //\r\n    // @Triggers when an entity dies. Note that this fires *after* the entity dies, and thus some data may be lost from the entity.\r\n    // The death can only be cancelled on Paper.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that died.\r\n    // <context.damager> returns the EntityTag damaging the other entity, if any.\r\n    // <context.projectile> returns the EntityTag of a projectile used to kill the entity, if one was used.\r\n    // <context.message> returns an ElementTag of a player's death message.\r\n    // <context.cause> returns an ElementTag of the cause of the death. See <@link language damage cause> for a list of possible damage causes.\r\n    // <context.drops> returns a ListTag of all pending item drops.\r\n    // <context.xp> returns an ElementTag of the amount of experience to be dropped.\r\n    // <context.keep_inventory> returns true if the player dying is set to keep their inventory, false if not, or null if the dying entity is not a player.\r\n    //\r\n    // @Determine\r\n    // ElementTag to change the death message.\r\n    // \"NO_DROPS\" to specify that any drops should be removed.\r\n    // \"NO_XP\" to specify that any XP orbs should be removed.\r\n    // ListTag(ItemTag) to specify new items to be dropped.\r\n    // ElementTag(Number) to specify the new amount of XP to be dropped.\r\n    // \"KEEP_INV\" to specify (if a player death) that the inventory should be kept.\r\n    // \"KEEP_LEVEL\" to specify (if a player death) that the XP level should be kept.\r\n    // \"NO_MESSAGE\" to hide a player death message.\r\n    //\r\n    // @Player when the entity that died is a player.\r\n    //\r\n    // @NPC when the entity that died is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityDeathScriptEvent() {\r\n        registerCouldMatcher(\"<entity> dies|death\");\r\n        registerSwitches(\"by\", \"cause\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public EntityTag damager;\r\n    public EntityTag projectile;\r\n    public ElementTag cause;\r\n    public EntityDeathEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String target = path.eventArgLowerAt(0);\r\n        if (!entity.tryAdvancedMatcher(target, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"by\", damager)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"cause\", cause == null ? null : cause.asString())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        String lower = CoreUtilities.toLowerCase(determination);\r\n        if (lower.startsWith(\"drops \")) { // legacy drops determination format\r\n            lower = lower.substring(6);\r\n            determination = determination.substring(6);\r\n        }\r\n        if (lower.startsWith(\"no_drops\")) {\r\n            event.getDrops().clear();\r\n            if (lower.endsWith(\"_or_xp\")) {\r\n                event.setDroppedExp(0);\r\n            }\r\n            return true;\r\n        }\r\n        else if (lower.equals(\"no_xp\")) {\r\n            event.setDroppedExp(0);\r\n            return true;\r\n        }\r\n        else if (lower.equals(\"keep_inv\") && event instanceof PlayerDeathEvent playerEvent) {\r\n            playerEvent.setKeepInventory(true);\r\n            return true;\r\n        }\r\n        else if (lower.equals(\"keep_level\") && event instanceof PlayerDeathEvent playerEvent) {\r\n            playerEvent.setKeepLevel(true);\r\n            return true;\r\n        }\r\n        else if (lower.equals(\"no_message\") && event instanceof PlayerDeathEvent playerEvent) {\r\n            playerEvent.setDeathMessage(null);\r\n            return true;\r\n        }\r\n        else if (determinationObj instanceof ElementTag element && element.isInt()) {\r\n            event.setDroppedExp(element.asInt());\r\n            return true;\r\n        }\r\n        else if (Argument.valueOf(lower).matchesArgumentList(ItemTag.class)) {\r\n            List<ItemStack> drops = event.getDrops();\r\n            drops.clear();\r\n            for (ItemTag item : ListTag.getListFor(determinationObj, getTagContext(path)).filter(ItemTag.class, getTagContext(path), true)) {\r\n                if (item != null) {\r\n                    drops.add(item.getItemStack());\r\n                }\r\n            }\r\n            return true;\r\n        }\r\n        else if (event instanceof PlayerDeathEvent playerEvent) {\r\n            PaperAPITools.instance.setDeathMessage(playerEvent, determination);\r\n            return true;\r\n        }\r\n        else {\r\n            return super.applyDetermination(path, determinationObj);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\": return entity.getDenizenObject();\r\n            case \"projectile\": return projectile == null ? null : projectile.getDenizenObject();\r\n            case \"damager\": return damager == null ? null : damager.getDenizenObject();\r\n            case \"message\": return event instanceof PlayerDeathEvent playerEvent ? new ElementTag(PaperAPITools.instance.getDeathMessage(playerEvent)) : null;\r\n            case \"cause\": return cause;\r\n            case \"xp\": return new ElementTag(event.getDroppedExp());\r\n            case \"keep_inventory\": return event instanceof PlayerDeathEvent playerEvent ? new ElementTag(playerEvent.getKeepInventory()) : null;\r\n            case \"drops\":\r\n                ListTag list = new ListTag();\r\n                for (ItemStack stack : event.getDrops()) {\r\n                    list.addObject(new ItemTag(stack));\r\n                }\r\n                return list;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled && event instanceof PlayerDeathEvent playerEvent) {\r\n            playerEvent.setDeathMessage(null); // Historical no_message was by cancelling.\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityDeath(EntityDeathEvent event) {\r\n        LivingEntity livingEntity = event.getEntity();\r\n        EntityTag.rememberEntity(livingEntity);\r\n        entity = new EntityTag(livingEntity);\r\n        cause = null;\r\n        damager = null;\r\n        projectile = null;\r\n        EntityDamageEvent lastDamage = entity.getBukkitEntity().getLastDamageCause();\r\n        if (lastDamage != null) {\r\n            cause = new ElementTag(event.getEntity().getLastDamageCause().getCause().toString());\r\n            if (lastDamage instanceof EntityDamageByEntityEvent byEntEvent) {\r\n                damager = new EntityTag(byEntEvent.getDamager());\r\n                EntityTag shooter = damager.getShooter();\r\n                if (damager instanceof Projectile) {\r\n                    projectile = damager;\r\n                }\r\n                if (shooter != null) {\r\n                    projectile = damager;\r\n                    damager = shooter;\r\n                }\r\n            }\r\n            else if (livingEntity.getKiller() != null) {\r\n                damager = new EntityTag(livingEntity.getKiller());\r\n            }\r\n\r\n        }\r\n        cancelled = false;\r\n        this.event = event;\r\n        fire(event);\r\n        EntityTag.forgetEntity(livingEntity);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDespawnScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\n\r\npublic class EntityDespawnScriptEvent extends BukkitScriptEvent {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> despawns\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Warning this event fires very rapidly.\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch cause:<cause> to only process the event when it came from a specified cause.\r\n    //\r\n    // @Triggers when an entity despawns permanently from the world. May fire repeatedly for one entity.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the entity that despawned.\r\n    // <context.cause> returns the reason the entity despawned. Can be: DEATH, CHUNK_UNLOAD, CITIZENS, or OTHER\r\n    //\r\n    // @NPC when the entity that despawned is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityDespawnScriptEvent() {\r\n        instance = this;\r\n        registerCouldMatcher(\"<entity> despawns\");\r\n        registerSwitches(\"cause\");\r\n    }\r\n\r\n    public static EntityDespawnScriptEvent instance;\r\n    public EntityTag entity;\r\n    public ElementTag cause;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String target = path.eventArgLowerAt(0);\r\n        if (!entity.tryAdvancedMatcher(target, path.context)) {\r\n            return false;\r\n        }\r\n        if (!path.checkSwitch(\"cause\", cause.asLowerString())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"cause\" -> cause;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityDropsItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Item;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityDropItemEvent;\r\nimport org.bukkit.event.player.PlayerDropItemEvent;\r\n\r\npublic class EntityDropsItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> drops <item>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity drops an item.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the ItemTag.\r\n    // <context.entity> returns a EntityTag of the item.\r\n    // <context.dropped_by> returns the EntityTag that dropped the item.\r\n    // <context.location> returns a LocationTag of the item's location.\r\n    //\r\n    // @Player When the entity dropping an item is a player.\r\n    //\r\n    // -->\r\n\r\n    public EntityDropsItemScriptEvent() {\r\n        registerCouldMatcher(\"<entity> drops <item>\");\r\n    }\r\n\r\n    public ItemTag item;\r\n    public LocationTag location;\r\n    public EntityTag itemEntity;\r\n    public EntityTag dropper;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, dropper)) {\r\n            return false;\r\n        }\r\n        String iCheck = path.eventArgLowerAt(2);\r\n        if (!item.tryAdvancedMatcher(iCheck, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(dropper);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"item\" -> item;\r\n            case \"entity\" -> itemEntity;\r\n            case \"dropped_by\" -> dropper.getDenizenObject();\r\n            case \"location\" -> location;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerDropsItem(PlayerDropItemEvent event) {\r\n        dropper = new EntityTag(event.getPlayer());\r\n        location = dropper.getLocation();\r\n        itemEntity = new EntityTag(event.getItemDrop());\r\n        EntityTag.rememberEntity(itemEntity.getBukkitEntity());\r\n        item = new ItemTag(((Item) itemEntity.getBukkitEntity()).getItemStack());\r\n        fire(event);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityDropsItem(EntityDropItemEvent event) {\r\n        dropper = new EntityTag(event.getEntity());\r\n        location = dropper.getLocation();\r\n        itemEntity = new EntityTag(event.getItemDrop());\r\n        EntityTag.rememberEntity(itemEntity.getBukkitEntity());\r\n        item = new ItemTag(((Item) itemEntity.getBukkitEntity()).getItemStack());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityEntersPortalScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityPortalEnterEvent;\r\n\r\npublic class EntityEntersPortalScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> enters portal\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when an entity enters a portal. That is, when the entity touches a portal block.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.location> returns the LocationTag of the portal block touched by the entity.\r\n    //\r\n    // @Player when the entity that entered the portal is a player\r\n    //\r\n    // @NPC when the entity that entered the portal is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityEntersPortalScriptEvent() {\r\n        registerCouldMatcher(\"<entity> enters portal\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    public EntityPortalEnterEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String target = path.eventArgLowerAt(0);\r\n        if (!entity.tryAdvancedMatcher(target, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"location\" -> location;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityEntersPortal(EntityPortalEnterEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        location = new LocationTag(event.getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityEntersVehicleScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityEvent;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\n\r\npublic class EntityEntersVehicleScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // entity enters vehicle\r\n    // <entity> enters <entity>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity mounts another entity.\r\n    //\r\n    // @Context\r\n    // <context.vehicle> returns the EntityTag of the mounted vehicle.\r\n    // <context.entity> returns the EntityTag of the entering entity.\r\n    //\r\n    // @Player when the entity that mounted the vehicle is a player.\r\n    //\r\n    // @NPC when the entity that mounted the vehicle is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityEntersVehicleScriptEvent() {\r\n        registerCouldMatcher(\"<entity> enters <entity>\");\r\n    }\r\n\r\n    public EntityTag vehicle;\r\n    public EntityTag entity;\r\n\r\n    public static HashSet<String> notRelevantEnterables = new HashSet<>(Arrays.asList(\"notable\", \"cuboid\", \"biome\", \"bed\", \"portal\"));\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (notRelevantEnterables.contains(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        String vehicleLabel = path.eventArgLowerAt(2);\r\n        if (!vehicleLabel.equals(\"vehicle\") && !vehicle.tryAdvancedMatcher(vehicleLabel, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, vehicle.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"vehicle\" -> vehicle.getDenizenObject();\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public String getName() { // TODO: once 1.20 is the minimum supported version, remove\r\n        return \"EntityEntersVehicle\";\r\n    }\r\n\r\n    public void fire(EntityEvent event, Entity vehicle) {\r\n        this.entity = new EntityTag(event.getEntity());\r\n        this.vehicle = new EntityTag(vehicle);\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExitsPortalScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityPortalExitEvent;\r\n\r\npublic class EntityExitsPortalScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> exits portal\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when an entity exits a portal. This uses Spigot 'EntityPortalExitEvent' which seems to no longer be called on modern server versions.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.location> returns the LocationTag of the portal block touched by the entity.\r\n    //\r\n    // @Player when the entity that exits the portal is a player.\r\n    //\r\n    // @NPC when the entity that exits the portal is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityExitsPortalScriptEvent() {\r\n        registerCouldMatcher(\"<entity> exits portal\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    public EntityPortalExitEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String target = path.eventArgLowerAt(0);\r\n        if (!entity.tryAdvancedMatcher(target, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"location\" -> location;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityExitsPortal(EntityPortalExitEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        location = new LocationTag(event.getTo());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExitsVehicleScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityEvent;\r\n\r\npublic class EntityExitsVehicleScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // entity exits vehicle\r\n    // <entity> exits <entity>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity dismounts from another entity.\r\n    //\r\n    // @Context\r\n    // <context.vehicle> returns the EntityTag of the mount vehicle.\r\n    // <context.entity> returns the EntityTag of the exiting entity.\r\n    //\r\n    // @Player when the entity that dismounts the vehicle is a player.\r\n    //\r\n    // @NPC when the entity that dismounts the vehicle is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityExitsVehicleScriptEvent() {\r\n        registerCouldMatcher(\"<entity> exits <entity>\");\r\n    }\r\n\r\n    public EntityTag vehicle;\r\n    public EntityTag entity;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        String vehicleLabel = path.eventArgLowerAt(2);\r\n        if (!vehicleLabel.equals(\"vehicle\") && !vehicle.tryAdvancedMatcher(vehicleLabel, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, vehicle.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"vehicle\" -> vehicle.getDenizenObject();\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public String getName() { // TODO: once 1.20 is the minimum supported version, remove\r\n        return \"EntityExitsVehicle\";\r\n    }\r\n\r\n    public void fire(EntityEvent event, Entity vehicle) {\r\n        this.entity = new EntityTag(event.getEntity());\r\n        this.vehicle = new EntityTag(vehicle);\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExplodesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityExplodeEvent;\r\n\r\npublic class EntityExplodesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> explodes\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity explodes (primed_tnt, creeper, etc).\r\n    //\r\n    // @Context\r\n    // <context.blocks> returns a ListTag of blocks that the entity blew up.\r\n    // <context.entity> returns the EntityTag that exploded.\r\n    // <context.location> returns the LocationTag the entity blew up at.\r\n    // <context.strength> returns an ElementTag(Decimal) of the strength of the explosion.\r\n    //\r\n    // @Determine\r\n    // ListTag(LocationTag) to set a new lists of blocks that are to be affected by the explosion.\r\n    // ElementTag(Decimal) to change the strength of the explosion.\r\n    //\r\n    // -->\r\n\r\n    public EntityExplodesScriptEvent() {\r\n        registerCouldMatcher(\"<entity> explodes\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    public EntityExplodeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String target = path.eventArgLowerAt(0);\r\n        if (!entity.tryAdvancedMatcher(target, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (ArgumentHelper.matchesDouble(determination)) {\r\n            event.setYield(Float.parseFloat(determination));\r\n            return true;\r\n        }\r\n        if (determination.contains(\",\") || determination.startsWith(\"li@\")) { // Loose \"contains any location-like value\" check\r\n            event.blockList().clear();\r\n            for (String loc : ListTag.valueOf(determination, getTagContext(path))) {\r\n                LocationTag location = LocationTag.valueOf(loc, getTagContext(path));\r\n                if (location == null) {\r\n                    Debug.echoError(\"Invalid location '\" + loc + \"' check [\" + getName() + \"]: '  for \" + path.container.getName());\r\n                }\r\n                else {\r\n                    event.blockList().add(location.getWorld().getBlockAt(location));\r\n                }\r\n            }\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return entity;\r\n            case \"location\":\r\n                return location;\r\n            case \"blocks\":\r\n                ListTag blocks = new ListTag();\r\n                for (Block block : event.blockList()) {\r\n                    blocks.addObject(new LocationTag(block.getLocation()));\r\n                }\r\n                return blocks;\r\n            case \"strength\":\r\n                return new ElementTag(event.getYield());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityExplodes(EntityExplodeEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        location = new LocationTag(event.getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityExplosionPrimesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.ExplosionPrimeEvent;\r\n\r\npublic class EntityExplosionPrimesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> explosion primes\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity decides to explode.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns an EntityTag of the exploding entity.\r\n    // <context.radius> returns the explosion's radius.\r\n    // <context.fire> returns whether the explosion will create fire.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Decimal) to change the explosion radius.\r\n    // \"FIRE:<ElementTag(Boolean)>\" to set whether the explosion will produce fire.\r\n    // -->\r\n\r\n    public EntityExplosionPrimesScriptEvent() {\r\n        registerCouldMatcher(\"<entity> explosion primes\");\r\n        this.<EntityExplosionPrimesScriptEvent, ElementTag>registerOptionalDetermination(null, ElementTag.class, (evt, context, value) -> {\r\n            if (value.isFloat()) {\r\n                evt.event.setRadius(value.asFloat());\r\n                return true;\r\n            }\r\n            if (value.isBoolean()) {\r\n                BukkitImplDeprecations.explosionPrimeDetermination.warn();\r\n                evt.event.setFire(value.asBoolean());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n        this.<EntityExplosionPrimesScriptEvent, ElementTag>registerOptionalDetermination(\"fire\", ElementTag.class, (evt, context, value) -> {\r\n            if (value.isBoolean()) {\r\n                evt.event.setFire(value.asBoolean());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public ExplosionPrimeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"radius\" -> new ElementTag(event.getRadius());\r\n            case \"fire\" -> new ElementTag(event.getFire());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityExplosionPrimes(ExplosionPrimeEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityFoodLevelChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.FoodLevelChangeEvent;\r\n\r\npublic class EntityFoodLevelChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> changes food level\r\n    //\r\n    // @Synonyms player hunger depletes\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Switch item:<item> to only process the event if it was triggered by an item that matches the specified item.\r\n    //\r\n    // @Triggers when an entity's food level changes.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.food> returns an ElementTag(Number) of the entity's new food level.\r\n    // <context.item> returns an ItemTag of the item that triggered the event, if any.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Number) to set the entity's new food level.\r\n    //\r\n    // @Player when the entity that's food level has changed is a player.\r\n    //\r\n    // @NPC when the entity that's food level has changed is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityFoodLevelChangeScriptEvent() {\r\n        registerCouldMatcher(\"<entity> changes food level\");\r\n        registerSwitches(\"item\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public ItemTag item;\r\n    public FoodLevelChangeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String target = path.eventArgLowerAt(0);\r\n        if (!entity.tryAdvancedMatcher(target, path.context)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"item\", item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isInt()) {\r\n            event.setFoodLevel(element.asInt());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\": return entity.getDenizenObject();\r\n            case \"food\": return new ElementTag(event.getFoodLevel());\r\n            case \"item\": return item;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityFoodLevelChanged(FoodLevelChangeEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        item = event.getItem() != null ? new ItemTag(event.getItem()) : null;\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityFormsBlockScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.EntityBlockFormEvent;\r\n\r\npublic class EntityFormsBlockScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> forms <block>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block is formed by an entity.\r\n    // For example, when a snowman forms snow.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag the block.\r\n    // <context.material> returns the MaterialTag of the block.\r\n    // <context.entity> returns the EntityTag that formed the block.\r\n    //\r\n    // -->\r\n\r\n    public EntityFormsBlockScriptEvent() {\r\n        registerCouldMatcher(\"<entity> forms <block>\");\r\n    }\r\n\r\n    public MaterialTag material;\r\n    public LocationTag location;\r\n    public EntityTag entity;\r\n    public EntityBlockFormEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(2, material)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            case \"entity\" -> entity;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityFormsBlock(EntityBlockFormEvent event) {\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityGlideScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityToggleGlideEvent;\r\n\r\npublic class EntityGlideScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> toggles|starts|stops gliding\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity starts or stops gliding.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of this event.\r\n    // <context.state> returns an ElementTag(Boolean) with a value of \"true\" if the entity is now gliding and \"false\" otherwise.\r\n    //\r\n    // @Player when the entity is a player.\r\n    //\r\n    // @NPC when the entity is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityGlideScriptEvent() {\r\n        registerCouldMatcher(\"<entity> toggles|starts|stops gliding\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public boolean state;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        String cmd = path.eventArgLowerAt(1);\r\n        if (cmd.equals(\"starts\") && !state) {\r\n            return false;\r\n        }\r\n        if (cmd.equals(\"stops\") && state) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"state\" -> new ElementTag(state);\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityToggleGlide(EntityToggleGlideEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        state = event.isGliding();\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityGoesIntoBlockScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityEnterBlockEvent;\r\n\r\npublic class EntityGoesIntoBlockScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> goes into <block>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity enters, and is stored in a block (eg a bee enters a bee nest).\r\n    // Does not fire when a silverfish \"enters\" a stone block. Prefer <@link event entity changes block> for that.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.location> returns the LocationTag of the block entered by the entity.\r\n    // <context.material> returns the MaterialTag of the block entered by the entity.\r\n    //\r\n    // -->\r\n\r\n    public EntityGoesIntoBlockScriptEvent() {\r\n        registerCouldMatcher(\"<entity> goes into <block>\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public MaterialTag material;\r\n    public LocationTag location;\r\n    public EntityEnterBlockEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(3, material)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityEntersBlock(EntityEnterBlockEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityHealsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityRegainHealthEvent;\r\n\r\npublic class EntityHealsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> heals (because <'cause'>)\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity heals.\r\n    //\r\n    // @Context\r\n    // <context.amount> returns the amount the entity healed.\r\n    // <context.entity> returns the EntityTag that healed.\r\n    // <context.reason> returns the cause of the entity healing. Can be: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityRegainHealthEvent.RegainReason.html>\r\n    //\r\n    // @Determine\r\n    // ElementTag(Decimal) to set the amount of health the entity receives.\r\n    //\r\n    // @Player when the entity that was healed is a player.\r\n    //\r\n    // @NPC when the entity that was healed was an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityHealsScriptEvent() {\r\n        registerCouldMatcher(\"<entity> heals (because <'cause'>)\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public ElementTag reason;\r\n    public EntityRegainHealthEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(2).equals(\"because\") && !runGenericCheck(path.eventArgLowerAt(3), reason.toString())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isDouble()) {\r\n            event.setAmount(element.asDouble());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return entity.getDenizenObject();\r\n            case \"reason\":\r\n                return reason;\r\n            case \"amount\":\r\n                return new ElementTag(event.getAmount());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityHeals(EntityRegainHealthEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        reason = new ElementTag(event.getRegainReason().toString());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityInteractScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityInteractEvent;\r\n\r\npublic class EntityInteractScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> interacts with <block>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity interacts with a block (EG an arrow hits a button)\r\n    //\r\n    // @Context\r\n    // <context.location> returns a LocationTag of the block being interacted with.\r\n    // <context.entity> returns a EntityTag of the entity doing the interaction.\r\n    //\r\n    // -->\r\n\r\n    public EntityInteractScriptEvent() {\r\n        registerCouldMatcher(\"<entity> interacts with <block>\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    private MaterialTag material;\r\n    public EntityInteractEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(3, material)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"location\" -> location;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityInteract(EntityInteractEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityKilledScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityDamageByEntityEvent;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\n\r\npublic class EntityKilledScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> killed (by <'cause'>)\r\n    // <entity> killed (by <entity>)\r\n    // <entity> kills <entity>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity is killed.\r\n    //\r\n    // @Warning This event may mis-fire in some cases, particularly with plugins or scripts modify the damage from scripts. If you need reliable death tracking, the entity death event may be better.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that was killed.\r\n    // <context.cause> returns an ElementTag of reason the entity was damaged - see <@link language damage cause> for causes.\r\n    // <context.damage> returns an ElementTag(Decimal) of the amount of damage dealt.\r\n    // <context.final_damage> returns an ElementTag(Decimal) of the amount of damage dealt, after armor is calculated.\r\n    // <context.damager> returns the EntityTag damaging the other entity.\r\n    // <context.projectile> returns a EntityTag of the projectile shot by the damager, if any.\r\n    // <context.damage_type_map> returns a MapTag the damage dealt by a specific damage type with keys: BASE, HARD_HAT, BLOCKING, ARMOR, RESISTANCE, MAGIC, ABSORPTION.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Decimal) to set the amount of damage the entity receives, instead of dying.\r\n    //\r\n    // @Player when the killer or entity that was killed is a player. Cannot be both.\r\n    //\r\n    // @NPC when the killer or entity that was killed is an NPC. Cannot be both.\r\n    //\r\n    // -->\r\n\r\n    public EntityKilledScriptEvent() {\r\n        registerCouldMatcher(\"<entity> killed (by <'cause'>)\");\r\n        registerCouldMatcher(\"<entity> killed (by <entity>)\");\r\n        registerCouldMatcher(\"<entity> kills <entity>\");\r\n    }\r\n\r\n\r\n    public EntityTag entity;\r\n    public ElementTag final_damage;\r\n    public EntityTag damager;\r\n    public EntityTag projectile;\r\n    public EntityDamageEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        String arg0 = path.eventArgLowerAt(0);\r\n        String arg2 = path.eventArgLowerAt(2);\r\n        String arg3 = path.eventArgLowerAt(3);\r\n        String attacker = cmd.equals(\"kills\") ? arg0 : arg2.equals(\"by\") ? arg3 : \"\";\r\n        String target = cmd.equals(\"kills\") ? arg2 : arg0;\r\n        if (!attacker.isEmpty()) {\r\n            if (damager != null) {\r\n                if (!runGenericCheck(attacker, event.getCause().name()) && (projectile == null || !projectile.tryAdvancedMatcher(attacker, path.context)) && (damager == null || !damager.tryAdvancedMatcher(attacker, path.context))) {\r\n                    return false;\r\n                }\r\n            }\r\n            else if (!runGenericCheck(attacker, event.getCause().name())) {\r\n                return false;\r\n            }\r\n        }\r\n        if (!entity.tryAdvancedMatcher(target, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isDouble()) {\r\n            event.setDamage(element.asDouble());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        PlayerTag player = entity.isPlayer() ? EntityTag.getPlayerFrom(event.getEntity()) : null;\r\n        if (damager != null && player == null && damager.isPlayer()) {\r\n            player = EntityTag.getPlayerFrom(damager.getBukkitEntity());\r\n        }\r\n        NPCTag npc = entity.isCitizensNPC() ? entity.getDenizenNPC() : null;\r\n        if (damager != null && npc == null && damager.isCitizensNPC()) {\r\n            npc = damager.getDenizenNPC();\r\n        }\r\n        return new BukkitScriptEntryData(player, npc);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\": return entity.getDenizenObject();\r\n            case \"damage\": return new ElementTag(event.getDamage());\r\n            case \"final_damage\": return final_damage;\r\n            case \"cause\": return new ElementTag(event.getCause());\r\n            case \"damager\": return damager != null ? damager.getDenizenObject() : null;\r\n            case \"projectile\": return  projectile != null ? projectile.getDenizenObject() : null;\r\n            case \"damage_type_map\":\r\n                MapTag map = new MapTag();\r\n                for (EntityDamageEvent.DamageModifier dm : EntityDamageEvent.DamageModifier.values()) {\r\n                    map.putObject(dm.name(), new ElementTag(event.getDamage(dm)));\r\n                }\r\n                return map;\r\n        }\r\n        if (name.startsWith(\"damage_\")) {\r\n            BukkitImplDeprecations.damageEventTypeMap.warn();\r\n            for (EntityDamageEvent.DamageModifier dm : EntityDamageEvent.DamageModifier.values()) {\r\n                if (name.equals(\"damage_\" + CoreUtilities.toLowerCase(dm.name()))) {\r\n                    return new ElementTag(event.getDamage(dm));\r\n                }\r\n            }\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.HIGH)\r\n    public void onEntityKilled(EntityDamageEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        // Check for possibility of death first\r\n        if (entity.isValid() && entity.isLivingEntity()) {\r\n            if (event.getFinalDamage() < entity.getLivingEntity().getHealth()) {\r\n                return;\r\n            }\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n        final_damage = new ElementTag(event.getFinalDamage());\r\n        damager = null;\r\n        projectile = null;\r\n        if (event instanceof EntityDamageByEntityEvent) {\r\n            damager = new EntityTag(((EntityDamageByEntityEvent) event).getDamager());\r\n            if (damager.isProjectile()) {\r\n                projectile = damager;\r\n                if (damager.hasShooter()) {\r\n                    damager = damager.getShooter();\r\n                }\r\n            }\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityPicksUpItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityPickupItemEvent;\r\n\r\nimport java.util.HashSet;\r\nimport java.util.Set;\r\nimport java.util.UUID;\r\n\r\npublic class EntityPicksUpItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> picks up <item>\r\n    // <entity> takes <item>\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity picks up an item.\r\n    //\r\n    // @Context\r\n    // <context.item> returns an ItemTag of the item being picked up.\r\n    // <context.entity> returns an EntityTag of the item entity being picked up.\r\n    // <context.pickup_entity> returns an EntityTag of the entity picking up the item.\r\n    // <context.location> returns a LocationTag of the item's location.\r\n    //\r\n    // @Determine\r\n    // \"ITEM:<ItemTag>\" to change the item being picked up.\r\n    //\r\n    // @Player when the entity picking up the item is a player.\r\n    //\r\n    // @NPC when the entity picking up the item is an npc.\r\n    //\r\n    // -->\r\n\r\n    public EntityPicksUpItemScriptEvent() {\r\n        registerCouldMatcher(\"<entity> picks up <item>\");\r\n        registerCouldMatcher(\"<entity> takes <item>\");\r\n        this.<EntityPicksUpItemScriptEvent, ItemTag>registerDetermination(\"item\", ItemTag.class, (evt, context, item) -> {\r\n            editedItems.add(event.getItem().getUniqueId());\r\n            evt.event.getItem().setItemStack(item.getItemStack());\r\n            evt.event.setCancelled(true);\r\n        });\r\n    }\r\n\r\n    public ItemTag item;\r\n    public EntityTag entity;\r\n    public EntityPickupItemEvent event;\r\n\r\n    private static final Set<UUID> editedItems = new HashSet<>();\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(path.eventArgLowerAt(1).equals(\"picks\") ? 3 : 2, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getItem().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"item\" -> item;\r\n            case \"entity\" -> new EntityTag(event.getItem());\r\n            case \"pickup_entity\" -> entity.getDenizenObject();\r\n            case \"location\" -> new LocationTag(event.getItem().getLocation());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityPicksUpItem(EntityPickupItemEvent event) {\r\n        if (editedItems.remove(event.getItem().getUniqueId())) {\r\n            return;\r\n        }\r\n        entity = new EntityTag(event.getEntity());\r\n        item = new ItemTag(event.getItem().getItemStack());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityPotionEffectScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemPotion;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityPotionEffectEvent;\r\n\r\npublic class EntityPotionEffectScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> potion effects modified\r\n    // <entity> potion effects <'change_action'>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    // @Switch cause:<cause> to only process the event when it came from a specified cause.\r\n    // @Switch effect:<effect type> to only process the event when a specified potion effect is applied.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity's potion effects change.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.cause> returns the cause of the effect change, based on <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityPotionEffectEvent.Cause.html>\r\n    // <context.action> returns the action of the effect changed, which can be 'added', 'changed', 'cleared', or 'removed'\r\n    // <context.override> returns whether the new potion effect will override the old.\r\n    // <context.new_effect_data> returns the new potion effect in <@link language Potion Effect Format>.\r\n    // <context.old_effect_data> returns the old potion effect in <@link language Potion Effect Format>.\r\n    // <context.effect_type> returns the name of the modified potion effect type.\r\n    //\r\n    // @Determine\r\n    // \"OVERRIDE:<ElementTag(Boolean)>\" to set whether the new potion effect should override.\r\n    //\r\n    // @Player when the entity that has changed is a player.\r\n    //\r\n    // @NPC when the entity that has changed is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityPotionEffectScriptEvent() {\r\n        registerCouldMatcher(\"<entity> potion effects modified\");\r\n        registerCouldMatcher(\"<entity> potion effects <'change_action'>\");\r\n        registerSwitches(\"cause\", \"effect\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public EntityPotionEffectEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        String change = path.eventArgAt(3);\r\n        if (!change.equals(\"modified\") && !couldMatchEnum(change, EntityPotionEffectEvent.Action.values())) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String target = path.eventArgLowerAt(0);\r\n        String change = path.eventArgAt(3);\r\n\r\n        if (!change.equals(\"modified\") && !runGenericCheck(change, CoreUtilities.toLowerCase(event.getAction().name()))) {\r\n            return false;\r\n        }\r\n        if (!entity.tryAdvancedMatcher(target, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"cause\", CoreUtilities.toLowerCase(event.getCause().name()))) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"effect\", CoreUtilities.toLowerCase(event.getModifiedType().getName()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String lower = CoreUtilities.toLowerCase(determinationObj.toString());\r\n            if (lower.startsWith(\"override:\")) {\r\n                event.setOverride(lower.substring(\"override\".length()).equals(\"true\"));\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"cause\" -> new ElementTag(event.getCause());\r\n            case \"action\" -> new ElementTag(event.getAction());\r\n            case \"effect_type\" -> new ElementTag(event.getModifiedType().getName());\r\n            case \"override\" -> new ElementTag(event.isOverride());\r\n            case \"new_effect\" -> event.getNewEffect() == null ? null : new ElementTag(ItemPotion.effectToLegacyString(event.getNewEffect(), null));\r\n            case \"old_effect\" -> event.getOldEffect() == null ? null : new ElementTag(ItemPotion.effectToLegacyString(event.getOldEffect(), null));\r\n            case \"new_effect_data\" -> event.getNewEffect() == null ? null : ItemPotion.effectToMap(event.getNewEffect(), true);\r\n            case \"old_effect_data\" -> event.getOldEffect() == null ? null : ItemPotion.effectToMap(event.getOldEffect(), true);\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityPotionEffect(EntityPotionEffectEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityResurrectScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityResurrectEvent;\r\n\r\npublic class EntityResurrectScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> resurrected\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity dies and is resurrected by a totem.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag being resurrected.\r\n    // <context.hand> returns which hand the totem was in during resurrection, if any. Can be either HAND or OFF_HAND. Available only on MC 1.19+.\r\n    //\r\n    // @Player when the entity being resurrected is a player.\r\n    //\r\n    // -->\r\n\r\n    public EntityResurrectScriptEvent() {\r\n        registerCouldMatcher(\"<entity> resurrected\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public EntityResurrectEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"hand\" -> {\r\n                if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n                    yield event.getHand() != null ? new ElementTag(event.getHand()) : null;\r\n                }\r\n                yield null;\r\n            }\r\n            default -> super.getContext(name);\r\n        };\r\n\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityResurrect(EntityResurrectEvent event) {\r\n        EntityTag.rememberEntity(event.getEntity());\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n        EntityTag.forgetEntity(event.getEntity());\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityShootsBowScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.Conversion;\r\nimport com.denizenscript.denizen.utilities.entity.Position;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityShootBowEvent;\r\n\r\nimport java.util.List;\r\n\r\npublic class EntityShootsBowScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // entity shoots bow\r\n    // <entity> shoots <item>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity shoots something out of a bow.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that shot the bow.\r\n    // <context.projectile> returns a EntityTag of the projectile.\r\n    // <context.bow> returns the ItemTag of the bow used to shoot.\r\n    // <context.force> returns the force of the shot.\r\n    // <context.item> returns an ItemTag of the shot projectile, if any.\r\n    // <context.hand> returns \"HAND\" or \"OFF_HAND\" for which hand the bow was in.\r\n    //\r\n    // @Determine\r\n    // ListTag(EntityTag) to change the projectile(s) being shot. (Note that in certain cases, determining an arrow may not be valid).\r\n    // \"KEEP_ITEM\" to keep the projectile item on shooting it.\r\n    //\r\n    // @Player when the entity that shot the bow is a player.\r\n    //\r\n    // @NPC when the entity that shot the bow is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityShootsBowScriptEvent() {\r\n        registerCouldMatcher(\"<entity> shoots <item>\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public ItemTag bow;\r\n    public EntityTag projectile;\r\n    public EntityShootBowEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String attacker = path.eventArgLowerAt(0);\r\n        String item = path.eventArgLowerAt(2);\r\n        if (!entity.tryAdvancedMatcher(attacker, path.context)) {\r\n            return false;\r\n        }\r\n        if (!item.equals(\"bow\") && !bow.tryAdvancedMatcher(item, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (determinationObj instanceof ElementTag) {\r\n            String lower = CoreUtilities.toLowerCase(determination);\r\n            if (lower.equals(\"keep_item\")) {\r\n                event.setConsumeItem(false);\r\n                if (entity.isPlayer()) {\r\n                    final Player p = entity.getPlayer();\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), p::updateInventory, 1);\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        if (Argument.valueOf(determination).matchesArgumentList(EntityTag.class)) {\r\n            cancelled = true;\r\n            cancellationChanged();\r\n            // Get the list of entities\r\n            List<EntityTag> newProjectiles = ListTag.getListFor(determinationObj, getTagContext(path)).filter(EntityTag.class, path.container, true);\r\n            // Go through all the entities, spawning/teleporting them\r\n            for (EntityTag newProjectile : newProjectiles) {\r\n                newProjectile.spawnAt(entity.getEyeLocation().add(entity.getEyeLocation().getDirection()));\r\n                // Set the entity as the shooter of the projectile,\r\n                // where applicable\r\n                if (newProjectile.isProjectile()) {\r\n                    newProjectile.setShooter(entity);\r\n                }\r\n            }\r\n            // Mount the projectiles on top of each other\r\n            Position.mount(Conversion.convertEntities(newProjectiles));\r\n            // Get the last entity on the list, i.e. the one at the bottom\r\n            // if there are many mounted on top of each other\r\n            Entity lastProjectile = newProjectiles.get(newProjectiles.size() - 1).getBukkitEntity();\r\n            // Give it the same velocity as the arrow that would\r\n            // have been shot by the bow\r\n            // Note: No, I can't explain why this has to be multiplied by three, it just does.\r\n            lastProjectile.setVelocity(event.getEntity().getLocation().getDirection().multiply(event.getForce() * 3));\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"force\" -> new ElementTag(event.getForce() * 3);\r\n            case \"bow\" -> bow;\r\n            case \"projectile\" -> projectile;\r\n            case \"item\" -> new ItemTag(event.getConsumable());\r\n            case \"hand\" -> new ElementTag(event.getHand());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled && entity.isPlayer()) {\r\n            final Player p = entity.getPlayer();\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), p::updateInventory, 1);\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityShootsBow(EntityShootBowEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        bow = new ItemTag(event.getBow());\r\n        Entity projectileEntity = event.getProjectile();\r\n        EntityTag.rememberEntity(projectileEntity);\r\n        projectile = new EntityTag(projectileEntity);\r\n        this.event = event;\r\n        fire(event);\r\n        EntityTag.forgetEntity(projectileEntity);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySpawnScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.event.entity.EntitySpawnEvent;\r\nimport org.bukkit.event.entity.SpawnerSpawnEvent;\r\n\r\npublic class EntitySpawnScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> spawns (because <'cause'>)\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Warning This event may fire very rapidly.\r\n    //\r\n    // @Triggers when an entity spawns.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that spawned.\r\n    // <context.location> returns the location the entity will spawn at.\r\n    // <context.reason> returns the reason the entity spawned, can be ENTITY_SPAWN or any of: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/CreatureSpawnEvent.SpawnReason.html>\r\n    // <context.spawner_location> returns the location of the mob spawner, when reason is SPAWNER.\r\n    //\r\n    // -->\r\n\r\n    public EntitySpawnScriptEvent() {\r\n        registerCouldMatcher(\"<entity> spawns (because <'cause'>)\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    public ElementTag reason;\r\n    public EntitySpawnEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (path.eventLower.startsWith(\"item\") || path.eventLower.startsWith(\"spawner\") || path.eventLower.startsWith(\"npc\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(2).equals(\"because\") && !runGenericCheck(path.eventArgLowerAt(3), reason.toString())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"location\" -> location;\r\n            case \"reason\" -> reason;\r\n            case \"spawner_location\" -> event instanceof SpawnerSpawnEvent spawnerEvent ? new LocationTag(spawnerEvent.getSpawner().getLocation()) : null;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntitySpawn(EntitySpawnEvent event) {\r\n        Entity entity = event.getEntity();\r\n        this.entity = new EntityTag(entity);\r\n        location = new LocationTag(event.getLocation());\r\n        if (event instanceof CreatureSpawnEvent creatureSpawnEvent) {\r\n            CreatureSpawnEvent.SpawnReason creatureReason = creatureSpawnEvent.getSpawnReason();\r\n            if (creatureReason == CreatureSpawnEvent.SpawnReason.SPAWNER) {\r\n                return; // Let the SpawnerSpawnEvent happen and handle it instead\r\n            }\r\n            reason = new ElementTag(creatureReason);\r\n        }\r\n        else if (event instanceof SpawnerSpawnEvent) {\r\n            reason = new ElementTag(\"SPAWNER\");\r\n        }\r\n        else {\r\n            reason = new ElementTag(\"ENTITY_SPAWN\");\r\n        }\r\n        this.event = event;\r\n        EntityTag.rememberEntity(entity);\r\n        fire(event);\r\n        EntityTag.forgetEntity(entity);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySpawnerSpawnScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.entity.SpawnerSpawnEvent;\n\npublic class EntitySpawnerSpawnScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // spawner spawns <entity>\n    //\n    // @Group Entity\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers when an entity spawns from a monster spawner.\n    //\n    // @Switch spawner:<location> to only process the event if the spawner's location matches.\n    //\n    // @Context\n    // <context.entity> returns the EntityTag that spawned.\n    // <context.location> returns the LocationTag the entity will spawn at.\n    // <context.spawner_location> returns the LocationTag of the monster spawner.\n    //\n    // -->\n\n    public EntitySpawnerSpawnScriptEvent() {\n        registerCouldMatcher(\"spawner spawns <entity>\");\n        registerSwitches(\"spawner\");\n    }\n\n    private EntityTag entity;\n    private LocationTag location;\n    private LocationTag spawnerLocation;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!path.tryArgObject(2, entity)) {\n            return false;\n        }\n        if (!path.tryObjectSwitch(\"spawner\", spawnerLocation)) {\n            return false;\n        }\n        if (!runInCheck(path, location)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(entity);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"entity\" -> entity;\n            case \"location\" -> location;\n            case \"spawner_location\" -> spawnerLocation;\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onSpawnerSpawn(SpawnerSpawnEvent event) {\n        Entity entity = event.getEntity();\n        this.entity = new EntityTag(entity);\n        location = new LocationTag(event.getLocation());\n        spawnerLocation = new LocationTag(event.getSpawner().getLocation());\n        EntityTag.rememberEntity(entity);\n        fire(event);\n        EntityTag.forgetEntity(entity);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySpellCastScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntitySpellCastEvent;\r\n\r\npublic class EntitySpellCastScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> casts <'spell'>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity, usually an Evoker or Illusioner, casts a spell.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the Spellcaster entity.\r\n    // <context.spell> returns an ElementTag of the spell used. Valid spells can be found at <@link url https://jd.papermc.io/paper/1.20/org/bukkit/entity/Spellcaster.Spell.html>\r\n    //\r\n    // -->\r\n\r\n    public EntitySpellCastScriptEvent() {\r\n        registerCouldMatcher(\"<entity> casts <'spell'>\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public EntitySpellCastEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!path.eventArgLowerAt(2).equals(\"spell\") && !runGenericCheck(path.eventArgLowerAt(2), event.getSpell().toString())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"spell\" -> new ElementTag(event.getSpell());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onSpellCast(EntitySpellCastEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntitySwimScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityToggleSwimEvent;\r\n\r\npublic class EntitySwimScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> toggles|starts|stops swimming\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity starts or stops swimming.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of this event.\r\n    // <context.state> returns an ElementTag(Boolean) with a value of \"true\" if the entity is now swimming and \"false\" otherwise.\r\n    //\r\n    // @Player when the entity is a player.\r\n    //\r\n    // @NPC when the entity is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntitySwimScriptEvent() {\r\n        registerCouldMatcher(\"<entity> toggles|starts|stops swimming\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public boolean state;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        String cmd = path.eventArgLowerAt(1);\r\n        if (cmd.equals(\"starts\") && !state) {\r\n            return false;\r\n        }\r\n        if (cmd.equals(\"stops\") && state) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"state\" -> new ElementTag(state);\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityToggleSwim(EntityToggleSwimEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        state = event.isSwimming();\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityTamesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityTameEvent;\r\n\r\npublic class EntityTamesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // entity tamed\r\n    // <entity> tamed\r\n    // player tames entity\r\n    // player tames <entity>\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity is tamed.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns a EntityTag of the tamed entity.\r\n    // <context.owner> returns a EntityTag of the owner.\r\n    //\r\n    // @Player when a player is what tamed the entity.\r\n    //\r\n    // -->\r\n\r\n    public EntityTamesScriptEvent() {\r\n        registerCouldMatcher(\"<entity> tamed\");\r\n        registerCouldMatcher(\"player tames <entity>\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public EntityTag owner;\r\n    public EntityTameEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        String ownerTest = cmd.equals(\"tames\") ? path.eventArgLowerAt(0) : path.eventArgLowerAt(2);\r\n        String tamed = cmd.equals(\"tamed\") ? path.eventArgLowerAt(0) : path.eventArgLowerAt(2);\r\n        if (!owner.tryAdvancedMatcher(ownerTest, path.context) || !entity.tryAdvancedMatcher(tamed, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(owner);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"owner\" -> owner.getDenizenObject();\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityTames(EntityTameEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        owner = new EntityTag((Entity) event.getOwner());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityTargetsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityTargetEvent;\r\n\r\npublic class EntityTargetsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> targets (<entity>) (because <'cause'>)\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity targets a new entity (usually a hostile mob preparing to attack something), or un-targets one.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the targeting entity.\r\n    // <context.reason> returns the reason the entity changed targets. Refer to <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityTargetEvent.TargetReason.html>.\r\n    // <context.target> returns the targeted entity, if any.\r\n    //\r\n    // @Determine\r\n    // EntityTag to make the entity target a different entity instead.\r\n    //\r\n    // @Player when the entity being targetted is a player.\r\n    //\r\n    // -->\r\n\r\n    public EntityTargetsScriptEvent() {\r\n        registerCouldMatcher(\"<entity> targets (<entity>) (because <'cause'>)\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public ElementTag reason;\r\n    public EntityTag target;\r\n    private LocationTag location;\r\n    public EntityTargetEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        String victim = path.eventArgLowerAt(2);\r\n        if (!victim.equals(\"in\") && !victim.equals(\"because\") && !victim.equals(\"\") && (target == null || !target.tryAdvancedMatcher(victim, path.context))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        int index = path.eventArgLowerAt(3).equals(\"because\") ? 3 : (path.eventArgAt(2).equals(\"because\") ? 2 : -1);\r\n        if (index > 0 && !path.tryArgObject(index + 1, reason)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (EntityTag.matches(determination)) {\r\n            target = EntityTag.valueOf(determination, getTagContext(path));\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(target);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity.getDenizenObject();\r\n            case \"reason\" -> reason;\r\n            case \"target\" -> target == null ? null : target.getDenizenObject();\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityTargets(EntityTargetEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        reason = new ElementTag(event.getReason().toString());\r\n        target = event.getTarget() != null ? new EntityTag(event.getTarget()) : null;\r\n        location = new LocationTag(event.getEntity().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityTeleportScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityPortalEvent;\r\nimport org.bukkit.event.entity.EntityTeleportEvent;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\n\r\npublic class EntityTeleportScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[language]\r\n    // @name Teleport Cause\r\n    // @group Useful Lists\r\n    // @description\r\n    // Possible player teleport causes: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/player/PlayerTeleportEvent.TeleportCause.html>\r\n    // These are used in <@link event entity teleports>, <@link tag server.teleport_causes>, <@link command teleport>, ...\r\n    // Note that these causes will only work for player entities.\r\n    //\r\n    // Additionally, Denizen provides two basic teleport causes for non-player entity teleport events: ENTITY_PORTAL and ENTITY_TELEPORT.\r\n    // These additional causes are only for <@link event entity teleports>, and thus not usable in <@link command teleport>, and will not show in <@link tag server.teleport_causes>.\r\n    // -->\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // entity teleports\r\n    // <entity> teleports\r\n    //\r\n    // @Regex ^on [^\\s]+ teleports$\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    // @Switch cause:<cause> to only process the event when it came from a specified cause.\r\n    //\r\n    // @Triggers when an entity teleports.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.origin> returns the LocationTag the entity teleported from.\r\n    // <context.destination> returns the LocationTag the entity teleported to.\r\n    // <context.cause> returns an ElementTag of the teleport cause - see <@link language teleport cause> for causes.\r\n    //\r\n    // @Determine\r\n    // \"ORIGIN:<LocationTag>\" to change the location the entity teleported from.\r\n    // \"DESTINATION:<LocationTag>\" to change the location the entity teleports to.\r\n    //\r\n    // @Player when the entity being teleported is a player.\r\n    //\r\n    // @NPC when the entity being teleported is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityTeleportScriptEvent() {\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public LocationTag from;\r\n    public LocationTag to;\r\n    public String cause;\r\n    public EntityTeleportEvent event;\r\n    public PlayerTeleportEvent pEvent;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventArgLowerAt(1).equals(\"teleports\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchEntity(path.eventArgLowerAt(0))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"cause\", cause)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, from)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        String dlow = CoreUtilities.toLowerCase(determination);\r\n        if (dlow.startsWith(\"origin:\")) {\r\n            LocationTag new_from = LocationTag.valueOf(determination.substring(\"origin:\".length()), getTagContext(path));\r\n            if (new_from != null) {\r\n                from = new_from;\r\n                if (event != null) {\r\n                    event.setFrom(new_from);\r\n                }\r\n                else {\r\n                    pEvent.setFrom(new_from);\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        else if (dlow.startsWith(\"destination:\")) {\r\n            LocationTag new_to = LocationTag.valueOf(determination.substring(\"destination:\".length()), getTagContext(path));\r\n            if (new_to != null) {\r\n                to = new_to;\r\n                if (event != null) {\r\n                    event.setTo(new_to);\r\n                }\r\n                else {\r\n                    pEvent.setTo(new_to);\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        else if (LocationTag.matches(determination)) {\r\n            LocationTag new_to = LocationTag.valueOf(determination, getTagContext(path));\r\n            if (new_to != null) {\r\n                to = new_to;\r\n                if (event != null) {\r\n                    event.setTo(new_to);\r\n                }\r\n                else {\r\n                    pEvent.setTo(new_to);\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"origin\":\r\n                return from;\r\n            case \"destination\":\r\n                return to;\r\n            case \"entity\":\r\n                return entity.getDenizenObject();\r\n            case \"cause\":\r\n                return new ElementTag(cause);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityTeleports(EntityTeleportEvent event) {\r\n        if (event.getEntity() instanceof Player) {\r\n            return;\r\n        }\r\n        to = new LocationTag(event.getTo());\r\n        from = new LocationTag(event.getFrom());\r\n        entity = new EntityTag(event.getEntity());\r\n        cause = event instanceof EntityPortalEvent ? \"ENTITY_PORTAL\" : \"ENTITY_TELEPORT\";\r\n        this.event = event;\r\n        pEvent = null;\r\n        fire(event);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerTeleports(PlayerTeleportEvent event) {\r\n        from = new LocationTag(event.getFrom());\r\n        to = new LocationTag(event.getTo());\r\n        entity = new EntityTag(event.getPlayer());\r\n        cause = event.getCause().name();\r\n        this.event = null;\r\n        pEvent = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityTransformScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityTransformEvent;\r\n\r\npublic class EntityTransformScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // entity transforms\r\n    // <entity> transforms (into <entity>)\r\n    //\r\n    // @Regex ^on [^\\s]+ transforms( into [^\\s]+)?$\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    // @Switch because:<reason> to only process the event if a specific reason caused the transformation.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity transforms into different entities (including villager infections, slime splitting, etc).\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the old entity that was transformed from.\r\n    // <context.new_entities> returns a list of new entities that were transformed into.\r\n    // <context.cause> returns the reason for transformation, from <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityTransformEvent.TransformReason.html>.\r\n    //\r\n    // -->\r\n\r\n    public EntityTransformScriptEvent() {\r\n    }\r\n\r\n    public EntityTransformEvent event;\r\n    public EntityTag originalEntity;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventArgLowerAt(1).equals(\"transforms\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchEntity(path.eventArgLowerAt(0))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, originalEntity.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"because\", event.getTransformReason().name())) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, originalEntity)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(2).equals(\"into\") && !path.tryArgObject(3, new EntityTag(event.getTransformedEntity()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return originalEntity.getDenizenObject();\r\n            case \"new_entities\":\r\n                ListTag output = new ListTag();\r\n                for (Entity ent : event.getTransformedEntities()) {\r\n                    output.addObject(new EntityTag(ent).getDenizenObject());\r\n                }\r\n                return output;\r\n            case \"cause\":\r\n                return new ElementTag(event.getTransformReason());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityTransform(EntityTransformEvent event) {\r\n        this.event = event;\r\n        originalEntity = new EntityTag(event.getEntity());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/EntityUnleashedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityUnleashEvent;\r\n\r\npublic class EntityUnleashedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <entity> unleashed (because <'reason'>)\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when an entity is unleashed.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.reason> returns an ElementTag of the reason for the unleashing.\r\n    // Reasons include DISTANCE, HOLDER_GONE, PLAYER_UNLEASH, and UNKNOWN\r\n    //\r\n    // @NPC when the entity being unleashed is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public EntityUnleashedScriptEvent() {\r\n        registerCouldMatcher(\"<entity> unleashed (because <'reason'>)\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public ElementTag reason;\r\n    public EntityUnleashEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, entity)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgAt(2).equals(\"because\") && !path.eventArgLowerAt(3).equals(reason.asLowerString())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\")) {\r\n            return entity;\r\n        }\r\n        else if (name.equals(\"reason\")) {\r\n            return reason;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEntityUnleashed(EntityUnleashEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        reason = new ElementTag(event.getReason().toString());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/ExperienceBottleBreaksScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.ExpBottleEvent;\r\n\r\npublic class ExperienceBottleBreaksScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // experience bottle breaks\r\n    //\r\n    // @Regex ^on experience bottle breaks$\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a thrown experience bottle breaks.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the thrown experience bottle.\r\n    // <context.experience> returns the amount of experience to be spawned.\r\n    // <context.show_effect> returns whether the effect should be shown.\r\n    //\r\n    // @Determine\r\n    // \"EXPERIENCE:<ElementTag(Number)>\" to specify the amount of experience to be created.\r\n    // \"EFFECT:<ElementTag(Boolean)>\" to specify if the particle effects will be shown.\r\n    //\r\n    // -->\r\n\r\n    public ExperienceBottleBreaksScriptEvent() {\r\n    }\r\n\r\n    public ExpBottleEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"experience bottle breaks\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getEntity().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String lower = determinationObj.toString().toLowerCase();\r\n        if (lower.startsWith(\"experience:\")) {\r\n            int experience = Argument.valueOf(lower.substring(11)).asElement().asInt();\r\n            event.setExperience(experience);\r\n        }\r\n        else if (lower.startsWith(\"effect:\")) {\r\n            boolean effect = Argument.valueOf(lower.substring(7)).asElement().asBoolean();\r\n            event.setShowEffect(effect);\r\n        }\r\n        else {\r\n            return super.applyDetermination(path, determinationObj);\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return new EntityTag(event.getEntity()).getDenizenObject();\r\n            case \"experience\":\r\n                return new ElementTag(event.getExperience());\r\n            case \"show_effect\":\r\n                return new ElementTag(event.getShowEffect());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onExperienceBottleBreaks(ExpBottleEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/FireworkBurstsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.FireworkExplodeEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic class FireworkBurstsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // firework bursts\r\n    //\r\n    // @Regex ^on firework bursts$\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a firework bursts (explodes).\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the firework that exploded.\r\n    // <context.item>  returns the firework item.\r\n    // <context.location> returns the LocationTag the firework exploded at.\r\n    //\r\n    // -->\r\n\r\n    public FireworkBurstsScriptEvent() {\r\n    }\r\n\r\n    public FireworkExplodeEvent event;\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"firework bursts\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return entity;\r\n            case \"location\":\r\n                return location;\r\n            case \"item\":\r\n                ItemStack itemStack = new ItemStack(Material.FIREWORK_ROCKET);\r\n                itemStack.setItemMeta(event.getEntity().getFireworkMeta());\r\n                return new ItemTag(itemStack);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onFireworkBursts(FireworkExplodeEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        location = new LocationTag(entity.getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/HangingBreaksScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.hanging.HangingBreakByEntityEvent;\r\nimport org.bukkit.event.hanging.HangingBreakEvent;\r\n\r\npublic class HangingBreaksScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <hanging> breaks (because <'cause'>)\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a hanging entity (painting, item_frame, or leash_hitch) is broken.\r\n    //\r\n    // @Context\r\n    // <context.cause> returns the cause of the entity breaking. Causes: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/hanging/HangingBreakEvent.RemoveCause.html>.\r\n    // <context.entity> returns the EntityTag that broke the hanging entity, if any.\r\n    // <context.hanging> returns the EntityTag of the hanging.\r\n    // -->\r\n\r\n    public HangingBreaksScriptEvent() {\r\n        registerCouldMatcher(\"<hanging> breaks (because <'cause'>)\");\r\n    }\r\n\r\n    public ElementTag cause;\r\n    public EntityTag entity;\r\n    public EntityTag hanging;\r\n    public HangingBreakEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String hangCheck = path.eventArgLowerAt(0);\r\n        if (!hanging.tryAdvancedMatcher(hangCheck, path.context)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(2).equals(\"because\") && !path.eventArgLowerAt(3).equals(cause.asLowerString())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, hanging.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"cause\":\r\n                return cause;\r\n            case \"entity\":\r\n                return entity;\r\n            case \"hanging\":\r\n                return hanging;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onHangingBreaks(HangingBreakEvent event) {\r\n        hanging = new EntityTag(event.getEntity());\r\n        cause = new ElementTag(event.getCause());\r\n        if (event instanceof HangingBreakByEntityEvent) {\r\n            entity = new EntityTag(((HangingBreakByEntityEvent) event).getRemover());\r\n        }\r\n        else {\r\n            entity = null;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/HorseJumpsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport org.bukkit.entity.Horse;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.HorseJumpEvent;\r\n\r\npublic class HorseJumpsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // horse jumps\r\n    // <entity> jumps\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a horse jumps.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the horse.\r\n    // <context.color> returns an ElementTag of the horse's color.\r\n    // <context.power> returns an ElementTag(Decimal) of the jump's power.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Decimal) to set the power of the jump.\r\n    //\r\n    // -->\r\n\r\n    public HorseJumpsScriptEvent() {\r\n        registerCouldMatcher(\"<entity> jumps\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public ElementTag color;\r\n    public HorseJumpEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            if (path.eventArgLowerAt(2).equals(\"jumps\") && couldMatchEntity(path.eventArgLowerAt(1))) {\r\n                BukkitImplDeprecations.horseJumpsFormat.warn(path.container);\r\n                return true;\r\n            }\r\n            return false;\r\n        }\r\n        if (path.eventLower.startsWith(\"player\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String arg1 = path.eventArgLowerAt(0);\r\n        String arg2 = path.eventArgLowerAt(1);\r\n        String tamed = arg2.equals(\"jumps\") ? arg1 : arg2;\r\n        if (!entity.tryAdvancedMatcher(tamed, path.context)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(2).equals(\"jumps\") && (color == null || !arg1.equals(CoreUtilities.toLowerCase(color.toString())))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isFloat()) {\r\n            event.setPower(element.asFloat());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return entity;\r\n            case \"color\":\r\n                return color;\r\n            case \"power\":\r\n                return new ElementTag(event.getPower());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onHorseJumps(HorseJumpEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        color = event.getEntity() instanceof Horse ? new ElementTag(((Horse) event.getEntity()).getColor()) : null;\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/PigZappedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.PigZapEvent;\r\n\r\npublic class PigZappedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // pig zapped\r\n    //\r\n    // @Synonyms pig struck by lightning, pig electrocuted, pig lightning strike, pig turns into pig zombie, pig turns into zombie pigman\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a pig is zapped by lightning and turned into a pig zombie.\r\n    //\r\n    // @Context\r\n    // <context.pig> returns the EntityTag of the pig.\r\n    // <context.pig_zombie> returns the EntityTag of the pig zombie.\r\n    // <context.lightning> returns the EntityTag of the lightning.\r\n    //\r\n    // -->\r\n\r\n    public PigZappedScriptEvent() {\r\n        registerCouldMatcher(\"pig zapped\");\r\n    }\r\n\r\n    public EntityTag pig;\r\n    public EntityTag pig_zombie;\r\n    private EntityTag lightning;\r\n    public PigZapEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, pig.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return  switch (name) {\r\n            case \"pig\" -> pig;\r\n            case \"pig_zombie\" -> pig_zombie;\r\n            case \"lightning\" -> lightning;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(pig);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPigZapped(PigZapEvent event) {\r\n        pig = new EntityTag(event.getEntity());\r\n        Entity pigZombie = event.getPigZombie();\r\n        EntityTag.rememberEntity(pigZombie);\r\n        pig_zombie = new EntityTag(pigZombie);\r\n        lightning = new EntityTag(event.getLightning());\r\n        this.event = event;\r\n        fire(event);\r\n        EntityTag.forgetEntity(pigZombie);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/PiglinBarterScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.PiglinBarterEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic class PiglinBarterScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // piglin barter\r\n    //\r\n    // @Switch input:<item> to only process the event if the input item matches the given item matcher.\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a piglin completes a barter.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the piglin.\r\n    // <context.input> returns the ItemTag of the input item.\r\n    // <context.outcome> returns a ListTag(ItemTag) of outcome items.\r\n    //\r\n    // @Determine\r\n    // \"RESULT:<ListTag(ItemTag)>\" to determine the items that are outputted.\r\n    //\r\n    // -->\r\n\r\n    public PiglinBarterScriptEvent() {\r\n        registerCouldMatcher(\"piglin barter\");\r\n        registerSwitches(\"input\");\r\n        this.<PiglinBarterScriptEvent, ListTag>registerDetermination(\"result\", ListTag.class, (evt, context, result) -> {\r\n            evt.event.getOutcome().clear();\r\n            for (ItemTag item : result.filter(ItemTag.class, context)) {\r\n                evt.event.getOutcome().add(item.getItemStack());\r\n            }\r\n        });\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public PiglinBarterEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"input\", new ItemTag(event.getInput()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> entity;\r\n            case \"input\" -> new ItemTag(event.getInput());\r\n            case \"outcome\" -> {\r\n                ListTag result = new ListTag(event.getOutcome().size());\r\n                for (ItemStack item : event.getOutcome()) {\r\n                    result.addObject(new ItemTag(item));\r\n                }\r\n                yield result;\r\n            }\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBarter(PiglinBarterEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/ProjectileHitScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.ProjectileHitEvent;\r\n\r\npublic class ProjectileHitScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <projectile> hits\r\n    //\r\n    // @Synonyms entity shoots\r\n    //\r\n    // @Switch entity:<entity> to only process the event if an entity got hit, and it matches the specified EntityTag matcher.\r\n    // @Switch block:<block> to only process the event if a block got hit, and it matches the specified LocationTag matcher.\r\n    // @Switch shooter:<entity> to only process the event if the projectile was shot by an entity, and it matches the specified EntityTag matcher.\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers When a projectile hits a block or an entity.\r\n    //\r\n    // @Context\r\n    // <context.projectile> returns an EntityTag of the projectile.\r\n    // <context.hit_entity> returns an EntityTag of the entity that was hit, if any.\r\n    // <context.hit_block> returns a LocationTag of the block that was hit, if any.\r\n    // <context.hit_face> returns a LocationTag vector of the hit normal (like '0,1,0' if the projectile hit the top of a block).\r\n    // <context.shooter> returns an EntityTag of the entity that shot the projectile, if any.\r\n    //\r\n    // @Player when the entity that was hit is a player, or when the shooter is a player if no entity was hit.\r\n    //\r\n    // @NPC when the entity that was hit is a npc, or when the shooter is a npc if no entity was hit.\r\n    //\r\n    // -->\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <projectile> hits <'block/entity'>\r\n    // <entity> shoots <material> (with <projectile>)\r\n    // @Group Entity\r\n    // @Triggers N/A - deprecated in favor of <@link event projectile hits>\r\n    // @Deprecated use new 'projectile hits' unified event\r\n    // -->\r\n\r\n    public ProjectileHitScriptEvent() {\r\n        registerCouldMatcher(\"<projectile> hits (<block>)\");\r\n        registerCouldMatcher(\"<projectile> hits <entity>\");\r\n        registerCouldMatcher(\"<entity> shoots <block> (with <projectile>)\");\r\n        registerSwitches(\"entity\", \"block\", \"shooter\", \"with\");\r\n    }\r\n\r\n    public ProjectileHitEvent event;\r\n    public LocationTag hitBlock;\r\n    public EntityTag hitEntity;\r\n    public EntityTag projectile;\r\n    public EntityTag shooter;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(1).equals(\"shoots\")) {\r\n            BukkitImplDeprecations.entityShootsMaterialEvent.warn(path.container);\r\n        }\r\n        else {\r\n            if (path.switches.containsKey(\"with\")) { // 'with' is only valid for the deprecated 'entity shoots material' event\r\n                addPossibleCouldMatchFailReason(\"unrecognized switch name\", \"with\");\r\n                return false;\r\n            }\r\n            if (path.eventArgs.length > 2) {\r\n                BukkitImplDeprecations.projectileHitsEventMatchers.warn(path.container);\r\n            }\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryObjectSwitch(\"entity\", hitEntity)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"block\", hitBlock)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"shooter\", shooter)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(1).equals(\"hits\")) {\r\n            if (!path.tryArgObject(0, projectile)) {\r\n                return false;\r\n            }\r\n            if (path.eventArgs.length > 2 && !path.tryArgObject(2, hitEntity) && !path.tryArgObject(2, hitBlock)) {\r\n                return false;\r\n            }\r\n        }\r\n        else {\r\n            if (event.getHitBlock() == null || !path.tryArgObject(2, new MaterialTag(event.getHitBlock()))) {\r\n                return false;\r\n            }\r\n            if (!path.tryArgObject(0, shooter)) {\r\n                return false;\r\n            }\r\n            if (!path.tryObjectSwitch(\"with\", projectile)) {\r\n                return false;\r\n            }\r\n            if (path.eventArgLowerAt(3).equals(\"with\") && !path.tryArgObject(4, projectile)) {\r\n                return false;\r\n            }\r\n        }\r\n        if (!runInCheck(path, hitEntity != null ? hitEntity.getLocation() : hitBlock)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"projectile\" -> projectile.getDenizenObject();\r\n            case \"hit_entity\" -> hitEntity != null ? hitEntity.getDenizenObject() : null;\r\n            case \"hit_block\" -> hitBlock;\r\n            case \"hit_face\" -> event.getHitBlockFace() != null ? new LocationTag(event.getHitBlockFace().getDirection()) : null;\r\n            case \"shooter\" -> shooter != null ? shooter.getDenizenObject() : null;\r\n            case \"location\" -> {\r\n                BukkitImplDeprecations.projectileHitsBlockLocationContext.warn();\r\n                yield hitBlock;\r\n            }\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(hitEntity != null ? hitEntity : shooter);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onProjectileHit(ProjectileHitEvent event) {\r\n        this.event = event;\r\n        projectile = new EntityTag(event.getEntity());\r\n        // Additional checks for some rare edge-cases\r\n        if (projectile.getLocation() == null) {\r\n            return;\r\n        }\r\n        if (Double.isNaN(projectile.getLocation().getDirection().normalize().getX())) {\r\n            return;\r\n        }\r\n        hitBlock = event.getHitBlock() != null ? new LocationTag(event.getHitBlock().getLocation()) : null;\r\n        hitEntity = event.getHitEntity() != null ? new EntityTag(event.getHitEntity()) : null;\r\n        shooter = projectile.getShooter();\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/ProjectileLaunchedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.ProjectileLaunchEvent;\r\n\r\npublic class ProjectileLaunchedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // projectile launched\r\n    // <entity> launched\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Switch by:<entity> to only process the event if the projectile shooter matches the specified entity matcher.\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a projectile is launched.\r\n    //\r\n    // @Context\r\n    // <context.projectile> returns an EntityTag of the projectile.\r\n    // <context.shooter> returns an EntityTag of the entity that shot the projectile, if any.\r\n    //\r\n    // -->\r\n\r\n    public ProjectileLaunchedScriptEvent() {\r\n        registerCouldMatcher(\"<entity> launched\");\r\n        registerSwitches(\"by\");\r\n    }\r\n\r\n    public EntityTag projectile;\r\n    private LocationTag location;\r\n    public ProjectileLaunchEvent event;\r\n    public EntityTag shooter;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(0, projectile)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"by\", shooter)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"entity\" -> {\r\n                BukkitImplDeprecations.projectileLaunchedEntityContext.warn();\r\n                yield projectile;\r\n            }\r\n            case \"projectile\" -> projectile;\r\n            case \"shooter\" -> shooter.getDenizenObject();\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onProjectileLaunched(ProjectileLaunchEvent event) {\r\n        Entity entity = event.getEntity();\r\n        EntityTag.rememberEntity(entity);\r\n        this.event = event;\r\n        projectile = new EntityTag(entity);\r\n        location = projectile.getLocation();\r\n        shooter = projectile.getShooter();\r\n        fire(event);\r\n        EntityTag.forgetEntity(entity);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/SheepDyedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.DyeColor;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.SheepDyeWoolEvent;\r\n\r\npublic class SheepDyedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // sheep dyed (<'color'>)\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Warning Determine color will not update the clientside, use - wait 1t and adjust <context.entity> color:YOUR_COLOR to force-update.\r\n    //\r\n    // @Triggers when a sheep is dyed.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the sheep.\r\n    // <context.color> returns an ElementTag of the color the sheep is being dyed.\r\n    //\r\n    // @Determine\r\n    // ElementTag that matches DyeColor to dye it a different color.\r\n    //\r\n    // -->\r\n\r\n    public SheepDyedScriptEvent() {\r\n        registerCouldMatcher(\"sheep dyed (<'color'>)\");\r\n        registerCouldMatcher(\"player dyes sheep (<'color'>)\"); // historical\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public DyeColor color;\r\n    public SheepDyeWoolEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        String new_color = cmd.equals(\"dyes\") ? path.eventArgLowerAt(3) : path.eventArgLowerAt(2);\r\n        if (!new_color.isEmpty() && !new_color.equals(CoreUtilities.toLowerCase(color.toString()))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (exactMatchesEnum(determinationObj.toString(), DyeColor.values())) {\r\n            color = DyeColor.valueOf(determinationObj.toString().toUpperCase());\r\n            event.setColor(color);\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"color\")) {\r\n            return new ElementTag(color.toString());\r\n        }\r\n        else if (name.equals(\"entity\")) {\r\n            return entity;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onSheepDyed(SheepDyeWoolEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        color = DyeColor.valueOf(event.getColor().toString());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/SheepRegrowsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.SheepRegrowWoolEvent;\r\n\r\npublic class SheepRegrowsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // sheep regrows wool\r\n    //\r\n    // @Regex ^on sheep regrows wool$\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a sheep regrows wool.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the sheep.\r\n    //\r\n    // -->\r\n\r\n    public SheepRegrowsScriptEvent() {\r\n    }\r\n\r\n    public EntityTag entity;\r\n    private LocationTag location;\r\n    public SheepRegrowWoolEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"sheep regrows wool\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\")) {\r\n            return entity;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onSheepRegrows(SheepRegrowWoolEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        location = new LocationTag(entity.getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/SlimeSplitsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.SlimeSplitEvent;\r\n\r\npublic class SlimeSplitsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // slime splits (into <'#'>)\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a slime splits into smaller slimes.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the slime.\r\n    // <context.count> returns an ElementTag(Number) of the number of smaller slimes it will split into.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Number) to set the number of smaller slimes it will split into.\r\n    //\r\n    // -->\r\n\r\n    public SlimeSplitsScriptEvent() {\r\n        registerCouldMatcher(\"slime splits (into <'#'>)\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public SlimeSplitEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String counts = path.eventArgLowerAt(3);\r\n\r\n        if (path.eventArgLowerAt(2).equals(\"into\") && !counts.isEmpty()) {\r\n            try {\r\n                if (Integer.parseInt(counts) != event.getCount()) {\r\n                    return false;\r\n                }\r\n            }\r\n            catch (NumberFormatException e) {\r\n                return false;\r\n            }\r\n        }\r\n\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isInt()) {\r\n            event.setCount(element.asInt());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\")) {\r\n            return entity;\r\n        }\r\n        else if (name.equals(\"count\")) {\r\n            return new ElementTag(event.getCount());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onSlimeSplits(SlimeSplitEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/VillagerAcquiresTradeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.VillagerAcquireTradeEvent;\r\n\r\npublic class VillagerAcquiresTradeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // villager acquires trade\r\n    //\r\n    // @Regex ^on villager acquires trade$\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a villager acquires a new trade.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the villager.\r\n    // <context.trade> returns the TradeTag for the new trade.\r\n    //\r\n    // @Determine\r\n    // TradeTag to change the new trade.\r\n    // -->\r\n\r\n    public VillagerAcquiresTradeScriptEvent() {\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public VillagerAcquireTradeEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"villager acquires trade\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (TradeTag.matches(determinationObj.toString())) {\r\n            event.setRecipe(determinationObj.asType(TradeTag.class, getTagContext(path)).getRecipe());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\")) {\r\n            return entity;\r\n        }\r\n        else if (name.equals(\"trade\")) {\r\n            return new TradeTag(event.getRecipe()).duplicate();\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onVillagerAcquiresTrade(VillagerAcquireTradeEvent event) {\r\n        this.event = event;\r\n        entity = new EntityTag(event.getEntity());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/VillagerChangesProfessionScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Villager;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.VillagerCareerChangeEvent;\r\n\r\npublic class VillagerChangesProfessionScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // villager changes profession\r\n    //\r\n    // @Regex ^on villager changes profession$\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a villager changes profession.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the villager.\r\n    // <context.profession> returns the name of the new profession. <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Villager.Profession.html>\r\n    // <context.reason> returns the reason for the change. <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/VillagerCareerChangeEvent.ChangeReason.html>\r\n    //\r\n    // @Determine\r\n    // ElementTag to change the profession.\r\n    // -->\r\n\r\n    public VillagerChangesProfessionScriptEvent() {\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public VillagerCareerChangeEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"villager changes profession\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        // TODO This technically has registries on all supported versions\r\n        Villager.Profession newProfession = Utilities.elementToEnumlike(determinationObj.asElement(), Villager.Profession.class);\r\n        if (newProfession != null) {\r\n            event.setProfession(newProfession);\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return entity;\r\n            case \"reason\":\r\n                return new ElementTag(event.getReason());\r\n            case \"profession\":\r\n                return new ElementTag(String.valueOf(event.getProfession()), true);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onVillagerChangesProfession(VillagerCareerChangeEvent event) {\r\n        this.event = event;\r\n        this.entity = new EntityTag(event.getEntity());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/entity/VillagerReplenishesTradeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.entity;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.VillagerReplenishTradeEvent;\r\n\r\npublic class VillagerReplenishesTradeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // villager replenishes trade\r\n    //\r\n    // @Group Entity\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a villager replenishes a trade. A trade being \"replenished\" means its \"uses\" value is reset to \"0\".\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the villager.\r\n    // <context.trade> returns the TradeTag for the trade being replenished.\r\n    // <context.bonus> returns the number of bonus uses added.\r\n    //\r\n    // @Determine\r\n    // TradeTag to change the trade being replenished.\r\n    // ElementTag(Number) to change the number of bonus uses added.\r\n    // -->\r\n\r\n    public VillagerReplenishesTradeScriptEvent() {\r\n        registerCouldMatcher(\"villager replenishes trade\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public VillagerReplenishTradeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (TradeTag.matches(determinationObj.toString())) {\r\n            event.setRecipe(determinationObj.asType(TradeTag.class, getTagContext(path)).getRecipe());\r\n            return true;\r\n        }\r\n        else if (determinationObj instanceof ElementTag element && element.isInt()) {\r\n            event.setBonus(element.asInt());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\": return entity;\r\n            case \"trade\": return new TradeTag(event.getRecipe()).duplicate();\r\n            case \"bonus\": return new ElementTag(event.getBonus());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onVillagerReplenishesTrade(VillagerReplenishTradeEvent event) {\r\n        this.event = event;\r\n        entity = new EntityTag(event.getEntity());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/item/InventoryPicksUpItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.item;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.InventoryPickupItemEvent;\r\n\r\npublic class InventoryPicksUpItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <inventory> picks up <item>\r\n    //\r\n    // @Group Item\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a hopper or hopper minecart picks up an item.\r\n    //\r\n    // @Context\r\n    // <context.inventory> returns the InventoryTag that picked up the item.\r\n    // <context.item> returns the ItemTag.\r\n    // <context.entity> returns a EntityTag of the item entity.\r\n    //\r\n    // -->\r\n\r\n    public InventoryPicksUpItemScriptEvent() {\r\n        registerCouldMatcher(\"<inventory> picks up <item>\");\r\n    }\r\n\r\n    public InventoryTag inventory;\r\n    public ItemTag item;\r\n    public InventoryPickupItemEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (couldMatchEntity(path.eventArgLowerAt(0))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, inventory)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(3, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getItem().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"item\": return item;\r\n            case \"inventory\": return inventory;\r\n            case \"entity\": return new EntityTag(event.getItem());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onInvPicksUpItem(InventoryPickupItemEvent event) {\r\n        this.event = event;\r\n        inventory = InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n        item = new ItemTag(event.getItem().getItemStack());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/item/ItemDespawnsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.item;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.ItemDespawnEvent;\r\n\r\npublic class ItemDespawnsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <item> despawns\r\n    //\r\n    // @Group Item\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an item entity despawns.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the ItemTag of the entity.\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.location> returns the location of the entity to be despawned.\r\n    //\r\n    // -->\r\n\r\n    public ItemDespawnsScriptEvent() {\r\n        registerCouldMatcher(\"<item> despawns\");\r\n    }\r\n\r\n    public ItemTag item;\r\n    public LocationTag location;\r\n    public EntityTag entity;\r\n    public ItemDespawnEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"location\": return location;\r\n            case \"item\": return item;\r\n            case \"entity\": return entity;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onItemDespawns(ItemDespawnEvent event) {\r\n        location = new LocationTag(event.getLocation());\r\n        item = new ItemTag(event.getEntity().getItemStack());\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/item/ItemEnchantedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.item;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.enchantment.EnchantItemEvent;\r\n\r\nimport java.util.Map;\r\n\r\npublic class ItemEnchantedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <item> enchanted\r\n    //\r\n    // @Group Item\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Switch enchant:<name> to only process the event if any of the enchantments being added match the given name.\r\n    //\r\n    // @Triggers when an item is enchanted.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the enchanter (if applicable)\r\n    // <context.location> returns the LocationTag of the enchanting table.\r\n    // <context.inventory> returns the InventoryTag of the enchanting table.\r\n    // <context.item> returns the ItemTag to be enchanted.\r\n    // <context.button> returns which button was pressed to initiate the enchanting.\r\n    // <context.cost> returns the experience level cost of the enchantment.\r\n    // <context.enchants> returns a MapTag of enchantment names to the level that will be applied.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Number) to set the experience level cost of the enchantment.\r\n    // \"RESULT:<ItemTag>\" to change the item result (only affects metadata (like enchantments), not material/quantity/etc!).\r\n    // \"ENCHANTS:<MapTag>\" to change the resultant enchantments.\r\n    //\r\n    // @Player when the enchanter is a player.\r\n    //\r\n    // -->\r\n\r\n    public ItemEnchantedScriptEvent() {\r\n        registerCouldMatcher(\"<item> enchanted\");\r\n        registerSwitches(\"enchant\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    public InventoryTag inventory;\r\n    public ItemTag item;\r\n    public ElementTag button;\r\n    public int cost;\r\n    public EnchantItemEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (path.switches.containsKey(\"enchant\")) {\r\n            boolean any = false;\r\n            for (Enchantment enchant : event.getEnchantsToAdd().keySet()) {\r\n                any = runGenericSwitchCheck(path, \"enchant\", enchant.getKey().getKey());\r\n                if (any) {\r\n                    break;\r\n                }\r\n            }\r\n            if (!any) {\r\n                return false;\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element) {\r\n            if (element.isInt()) {\r\n                cost = element.asInt();\r\n                event.setExpLevelCost(cost);\r\n                return true;\r\n            }\r\n            String determination = determinationObj.toString();\r\n            String lower = CoreUtilities.toLowerCase(determination);\r\n            if (lower.startsWith(\"result:\")) {\r\n                String itemText = determination.substring(\"result:\".length());\r\n                item = ItemTag.valueOf(itemText, path.container);\r\n                event.getItem().setItemMeta(item.getItemMeta());\r\n                return true;\r\n            }\r\n            else if (lower.startsWith(\"enchants:\")) {\r\n                event.getEnchantsToAdd().clear();\r\n                String itemText = determination.substring(\"enchants:\".length());\r\n                if (itemText.startsWith(\"map@\")) {\r\n                    TagContext context = getTagContext(path);\r\n                    MapTag map = MapTag.valueOf(itemText, context);\r\n                    for (Map.Entry<StringHolder, ObjectTag> enchantments : map.entrySet()) {\r\n                        event.getEnchantsToAdd().put(EnchantmentTag.valueOf(enchantments.getKey().low, context).enchantment, enchantments.getValue().asElement().asInt());\r\n                    }\r\n                }\r\n                else {\r\n                    ItemTag enchantsRes = ItemTag.valueOf(itemText, path.container);\r\n                    event.getEnchantsToAdd().putAll(enchantsRes.getItemMeta().getEnchants());\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\": return entity.getDenizenObject();\r\n            case \"location\": return location;\r\n            case \"inventory\": return inventory;\r\n            case \"item\": return item;\r\n            case \"button\": return button;\r\n            case \"cost\": return new ElementTag(cost);\r\n            case \"enchants\": {\r\n                MapTag map = new MapTag();\r\n                for (Map.Entry<Enchantment, Integer> enchant : event.getEnchantsToAdd().entrySet()) {\r\n                    map.putObject(enchant.getKey().getKey().getKey(), new ElementTag(enchant.getValue()));\r\n                }\r\n                return map;\r\n            }\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onItemEnchanted(EnchantItemEvent event) {\r\n        entity = new EntityTag(event.getEnchanter());\r\n        location = new LocationTag(event.getEnchantBlock().getLocation());\r\n        inventory = InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n        item = new ItemTag(event.getItem());\r\n        button = new ElementTag(event.whichButton());\r\n        cost = event.getExpLevelCost();\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/item/ItemMergesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.item;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.entity.Item;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.ItemMergeEvent;\r\n\r\npublic class ItemMergesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <item> merges\r\n    //\r\n    // @Group Item\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an item entity merges into another item entity.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the ItemTag of the entity.\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.target> returns the EntityTag being merged into.\r\n    // <context.location> returns the location of the entity to be spawned.\r\n    //\r\n    // -->\r\n\r\n    public ItemMergesScriptEvent() {\r\n        registerCouldMatcher(\"<item> merges\");\r\n    }\r\n\r\n    public ItemTag item;\r\n    public LocationTag location;\r\n    public EntityTag entity;\r\n    public ItemMergeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"location\": return location;\r\n            case \"item\": return item;\r\n            case \"entity\": return entity;\r\n            case \"target\": return new EntityTag(event.getTarget());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onItemMerges(ItemMergeEvent event) {\r\n        Item entity = event.getEntity();\r\n        Item target = event.getTarget();\r\n        location = new LocationTag(target.getLocation());\r\n        item = new ItemTag(entity.getItemStack());\r\n        this.entity = new EntityTag(entity);\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/item/ItemMoveScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.item;\r\n\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.InventoryMoveItemEvent;\r\n\r\npublic class ItemMoveScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <item> moves from <inventory> (to <inventory>)\r\n    //\r\n    // @Group Item\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an entity or block moves an item from one inventory to another. (Hopper-style movement, not player-induced movement).\r\n    //\r\n    // @Context\r\n    // <context.origin> returns the origin InventoryTag.\r\n    // <context.destination> returns the destination InventoryTag.\r\n    // <context.initiator> returns the InventoryTag that initiated the item's transfer.\r\n    // <context.item> returns the ItemTag that was moved.\r\n    //\r\n    // @Determine\r\n    // ItemTag to set a different item to be moved.\r\n    //\r\n    // -->\r\n\r\n    public ItemMoveScriptEvent() {\r\n        registerCouldMatcher(\"<item> moves from <inventory> (to <inventory>)\");\r\n    }\r\n\r\n\r\n    public InventoryTag origin;\r\n    public InventoryTag destination;\r\n    public ItemTag item;\r\n    public InventoryMoveItemEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, item)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(3, origin)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(4).equals(\"to\") && !path.tryArgObject(5, destination)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, origin.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj.canBeType(ItemTag.class)) {\r\n            item = determinationObj.asType(ItemTag.class, getTagContext(path));\r\n            event.setItem(item.getItemStack());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"origin\": return origin;\r\n            case \"destination\": return destination;\r\n            case \"initiator\": return InventoryTag.mirrorBukkitInventory(event.getInitiator());\r\n            case \"item\": return item;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onInventoryMoveItemEvent(InventoryMoveItemEvent event) {\r\n        this.event = event;\r\n        origin = InventoryTag.mirrorBukkitInventory(event.getSource());\r\n        destination = InventoryTag.mirrorBukkitInventory(event.getDestination());\r\n        item = new ItemTag(event.getItem());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/item/ItemRecipeFormedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.item;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryViewUtil;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Keyed;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.PrepareItemCraftEvent;\r\n\r\nimport java.util.Arrays;\r\n\r\npublic class ItemRecipeFormedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <item> recipe formed\r\n    //\r\n    // @Group Item\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an item's recipe is correctly formed.\r\n    // @Context\r\n    // <context.inventory> returns the InventoryTag of the crafting inventory.\r\n    // <context.item> returns the ItemTag to be formed in the result slot.\r\n    // <context.recipe> returns a ListTag of ItemTags in the recipe.\r\n    // <context.recipe_id> returns the ID of the recipe that was formed.\r\n    // <context.is_repair> returns an ElementTag(Boolean) of whether the event was triggered by a tool repair operation rather than a crafting recipe.\r\n    //\r\n    // @Determine\r\n    // ItemTag to change the item that is formed in the result slot.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public ItemRecipeFormedScriptEvent() {\r\n        registerCouldMatcher(\"<item> recipe formed\");\r\n        this.<ItemRecipeFormedScriptEvent, ObjectTag>registerOptionalDetermination(null, ObjectTag.class, (evt, context, determination) -> {\r\n            if (determination.canBeType(ItemTag.class)) {\r\n                ItemTag result = determination.asType(ItemTag.class, context);\r\n                evt.event.getInventory().setResult(result.getItemStack());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n\r\n    public PrepareItemCraftEvent event;\r\n    public ItemTag result;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, result)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(EntityTag.getPlayerFrom(InventoryViewUtil.getPlayer(event.getView())), null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"item\" -> result;\r\n            case \"inventory\" -> InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n            case \"recipe\" -> new ListTag(Arrays.asList(event.getInventory().getMatrix()), ItemTag::new);\r\n            case \"recipe_id\" -> event.getRecipe() instanceof Keyed keyed ? new ElementTag(keyed.getKey().toString(), true): null;\r\n            case \"is_repair\" -> new ElementTag(event.isRepair());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled) { // Hacked-in cancellation helper\r\n            event.getInventory().setResult(null);\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onRecipeFormed(PrepareItemCraftEvent event) {\r\n        this.event = event;\r\n        if (event.getRecipe() == null) {\r\n            return;\r\n        }\r\n        result = new ItemTag(event.getInventory().getResult());\r\n        if (result.getBukkitMaterial() == Material.AIR) {\r\n            result = new ItemTag(event.getRecipe().getResult());\r\n        }\r\n        cancelled = false;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/item/ItemSpawnsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.item;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.entity.Item;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.ItemSpawnEvent;\r\n\r\npublic class ItemSpawnsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <item> spawns\r\n    //\r\n    // @Group Item\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an item entity spawns.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the ItemTag of the entity.\r\n    // <context.entity> returns the EntityTag.\r\n    // <context.location> returns the location of the entity to be spawned.\r\n    //\r\n    // -->\r\n\r\n    public ItemSpawnsScriptEvent() {\r\n        registerCouldMatcher(\"<item> spawns\");\r\n    }\r\n\r\n    public ItemTag item;\r\n    public LocationTag location;\r\n    public EntityTag entity;\r\n    public ItemSpawnEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"location\": return location;\r\n            case \"item\": return item;\r\n            case \"entity\": return entity;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onItemSpawns(ItemSpawnEvent event) {\r\n        Item entity = event.getEntity();\r\n        location = new LocationTag(event.getLocation());\r\n        item = new ItemTag(entity.getItemStack());\r\n        this.entity = new EntityTag(entity);\r\n        this.event = event;\r\n        EntityTag.rememberEntity(entity);\r\n        fire(event);\r\n        EntityTag.forgetEntity(entity);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/npc/NPCNavigationScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.npc;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport net.citizensnpcs.api.ai.event.NavigationBeginEvent;\r\nimport net.citizensnpcs.api.ai.event.NavigationCancelEvent;\r\nimport net.citizensnpcs.api.ai.event.NavigationCompleteEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class NPCNavigationScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // npc begins|completes|cancels navigation\r\n    //\r\n    // @Group NPC\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning This event may fire very rapidly.\r\n    //\r\n    // @Triggers when an NPC begins, finishes, or cancels navigating.\r\n    //\r\n    // @Switch npc:<npc> to only process the event if the spawned NPC matches.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // @NPC Always.\r\n    //\r\n    // -->\r\n\r\n    public NPCNavigationScriptEvent() {\r\n        registerCouldMatcher(\"npc begins|completes|cancels navigation\");\r\n        registerSwitches(\"npc\");\r\n    }\r\n\r\n    public NPCTag npc;\r\n    public String type;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryObjectSwitch(\"npc\", npc)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, npc.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.eventArgLowerAt(1).equals(type)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(null, npc);\r\n    }\r\n\r\n    @EventHandler\r\n    public void navComplete(NavigationCompleteEvent event) {\r\n        npc = new NPCTag(event.getNPC());\r\n        type = \"completes\";\r\n        fire(event);\r\n    }\r\n\r\n    @EventHandler\r\n    public void navBegin(NavigationBeginEvent event) {\r\n        npc = new NPCTag(event.getNPC());\r\n        type = \"begins\";\r\n        fire(event);\r\n    }\r\n\r\n    @EventHandler\r\n    public void navCancel(NavigationCancelEvent event) {\r\n        npc = new NPCTag(event.getNPC());\r\n        type = \"cancels\";\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/npc/NPCOpensScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.npc;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport net.citizensnpcs.api.event.NPCOpenDoorEvent;\r\nimport net.citizensnpcs.api.event.NPCOpenGateEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class NPCOpensScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // npc opens <block>\r\n    //\r\n    // @Group NPC\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an NPC opens a door or gate.\r\n    //\r\n    // @Switch npc:<npc> to only process the event if the spawned NPC matches.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the location of the door or gate opened.\r\n    //\r\n    // @NPC Always.\r\n    //\r\n    // -->\r\n\r\n    public NPCOpensScriptEvent() {\r\n        registerCouldMatcher(\"npc opens <block>\");\r\n        registerSwitches(\"npc\");\r\n    }\r\n\r\n    public NPCTag npc;\r\n    public LocationTag location;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryObjectSwitch(\"npc\", npc)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(2, new MaterialTag(location.getBlock()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(null, npc);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void NPCOpenDoor(NPCOpenDoorEvent event) {\r\n        npc = new NPCTag(event.getNPC());\r\n        location = new LocationTag(event.getDoor().getLocation());\r\n        fire(event);\r\n    }\r\n\r\n    @EventHandler\r\n    public void NPCOpenGate(NPCOpenGateEvent event) {\r\n        npc = new NPCTag(event.getNPC());\r\n        location = new LocationTag(event.getGate().getLocation());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/npc/NPCSpawnScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.npc;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport net.citizensnpcs.api.event.NPCSpawnEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class NPCSpawnScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // npc spawns\r\n    //\r\n    // @Group NPC\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an NPC spawns.\r\n    //\r\n    // @Switch npc:<npc> to only process the event if the spawned NPC matches.\r\n    // @Switch reason:<reason> to only process the event if the NPC's spawn reason matches. See <@link url https://jd.citizensnpcs.co/net/citizensnpcs/api/event/SpawnReason.html> for a list of reasons.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the location the entity will spawn at.\r\n    // <context.reason> returns the reason of the spawn.\r\n    //\r\n    // @NPC Always.\r\n    //\r\n    // -->\r\n\r\n    public NPCSpawnScriptEvent() {\r\n        registerCouldMatcher(\"npc spawns\");\r\n        registerSwitches(\"npc\", \"reason\");\r\n    }\r\n\r\n    public NPCTag npc;\r\n    public LocationTag location;\r\n    public NPCSpawnEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryObjectSwitch(\"npc\", npc)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"reason\", new ElementTag(event.getReason()))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(null, npc);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"reason\" -> new ElementTag(event.getReason());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onNPCSpawn(NPCSpawnEvent event) {\r\n        this.npc = new NPCTag(event.getNPC());\r\n        location = new LocationTag(event.getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/npc/NPCStuckScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.npc;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport net.citizensnpcs.api.ai.TeleportStuckAction;\r\nimport net.citizensnpcs.api.ai.event.NavigationStuckEvent;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class NPCStuckScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // npc stuck\r\n    //\r\n    // @Group NPC\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when an NPC's navigator is stuck.\r\n    //\r\n    // @Switch npc:<npc> to only process the event if the spawned NPC matches.\r\n    //\r\n    // @Context\r\n    // <context.action> returns 'teleport' or 'none'\r\n    //\r\n    // @Determine\r\n    // \"NONE\" to do nothing.\r\n    // \"TELEPORT\" to teleport.\r\n    //\r\n    // @NPC Always.\r\n    //\r\n    // -->\r\n\r\n    public NPCStuckScriptEvent() {\r\n        registerCouldMatcher(\"npc stuck\");\r\n        registerSwitches(\"npc\");\r\n        this.<NPCStuckScriptEvent, ElementTag>registerDetermination(null, ElementTag.class, (evt, context, action) -> {\r\n            evt.event.setAction(action.asLowerString().equals(\"none\") ? null : TeleportStuckAction.INSTANCE);\r\n        });\r\n    }\r\n\r\n    public NavigationStuckEvent event;\r\n    public NPCTag npc;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryObjectSwitch(\"npc\", npc)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, npc.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(null, npc);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"action\" -> new ElementTag(event.getAction() == TeleportStuckAction.INSTANCE ? \"teleport\" : \"none\");\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void navStuck(NavigationStuckEvent event) {\r\n        this.npc = new NPCTag(event.getNPC());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/BiomeEnterExitScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.BiomeTag;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Registry;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerMoveEvent;\r\n\r\npublic class BiomeEnterExitScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player enters|exits biome\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Switch biome:<name> to only process the event when a specific biome is being entered.\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning Cancelling this event will fire a similar event immediately after.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player enters or exits a biome.\r\n    //\r\n    // @Context\r\n    // <context.from> returns the block location moved from.\r\n    // <context.to> returns the block location moved to.\r\n    // <context.old_biome> returns the biome being left.\r\n    // <context.new_biome> returns the biome being entered.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public BiomeEnterExitScriptEvent() {\r\n        registerCouldMatcher(\"player enters|exits <biome>\");\r\n        registerSwitches(\"biome\");\r\n    }\r\n\r\n\r\n    public LocationTag from;\r\n    public LocationTag to;\r\n    public BiomeTag old_biome;\r\n    public BiomeTag new_biome;\r\n    public PlayerMoveEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (!path.eventArgLowerAt(2).equals(\"biome\") && !couldMatchRegistry(path.eventArgLowerAt(2), Registry.BIOME)) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String biome_test = path.eventArgAt(2);\r\n        String direction = path.eventArgAt(1);\r\n\r\n        if (!runInCheck(path, from) && !runInCheck(path, to)) {\r\n            return false;\r\n        }\r\n        BiomeTag biome = direction.equals(\"enters\") ? new_biome : (direction.equals(\"exits\") ? old_biome : null);\r\n        if (biome == null) {\r\n            return false;\r\n        }\r\n        String biomeKey = Utilities.namespacedKeyToString(biome.getBiome().getKey());\r\n        if (!biome_test.equals(\"biome\") && !biome_test.equals(biomeKey)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"biome\", biomeKey)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(EntityTag.getPlayerFrom(event.getPlayer()), null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"to\":\r\n                return to;\r\n            case \"from\":\r\n                return from;\r\n            case \"old_biome\":\r\n                return old_biome;\r\n            case \"new_biome\":\r\n                return new_biome;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerEntersExitsBiome(PlayerMoveEvent event) {\r\n        if (LocationTag.isSameBlock(event.getFrom(), event.getTo())) {\r\n            return;\r\n        }\r\n        if (!Utilities.isLocationYSafe(event.getFrom()) || !Utilities.isLocationYSafe(event.getTo())) {\r\n            return;\r\n        }\r\n        from = new LocationTag(event.getFrom());\r\n        to = new LocationTag(event.getTo());\r\n        old_biome = new BiomeTag(NMSHandler.instance.getBiomeAt(from.getBlock()));\r\n        new_biome = new BiomeTag(NMSHandler.instance.getBiomeAt(to.getBlock()));\r\n        if (old_biome.identify().equals(new_biome.identify())) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/BlockDropsItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Item;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockDropItemEvent;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class BlockDropsItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // block drops item from breaking\r\n    // <block> drops <item> from breaking\r\n    //\r\n    // @Regex ^on [^\\s]+ drops [^\\s]+ from breaking$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a items drop from a block due to a player breaking the block in survival mode.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag the block was broken at.\r\n    // <context.material> returns the MaterialTag of the block that was broken.\r\n    // <context.drop_entities> returns a ListTag of EntityTags of type DROPPED_ITEM. To get the list of ItemTags, just tack \".parse[item]\" onto this context tag.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public BlockDropsItemScriptEvent() {\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockDropItemEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventArgLowerAt(1).equals(\"drops\") || !path.eventArgsLowEqualStartingAt(3, \"from\", \"breaking\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchBlock(path.eventArgLowerAt(0))) {\r\n            return false;\r\n        }\r\n        if (!couldMatchItem(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, material)) {\r\n            return false;\r\n        }\r\n        String item = path.eventArgLowerAt(2);\r\n        if (!item.equals(\"item\")) {\r\n            boolean anyMatch = false;\r\n            for (Item itemEnt : event.getItems()) {\r\n                if (new ItemTag(itemEnt.getItemStack()).tryAdvancedMatcher(item, path.context)) {\r\n                    anyMatch = true;\r\n                    break;\r\n                }\r\n            }\r\n            if (!anyMatch) {\r\n                return false;\r\n            }\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"location\":\r\n                return location;\r\n            case \"material\":\r\n                return material;\r\n            case \"drop_entities\":\r\n                ListTag toRet = new ListTag();\r\n                for (Item item : event.getItems()) {\r\n                    toRet.addObject(new EntityTag(item));\r\n                }\r\n                return toRet;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onBlockDropsItem(BlockDropItemEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        material = new MaterialTag(event.getBlockState());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        List<Item> items = new ArrayList<>(event.getItems());\r\n        for (Item item : items) {\r\n            EntityTag.rememberEntity(item);\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n        for (Item item : items) {\r\n            EntityTag.forgetEntity(item);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/ChatScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.tags.BukkitTagContext;\nimport com.denizenscript.denizen.utilities.Settings;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\nimport com.denizenscript.denizencore.scripts.containers.core.FormatScriptContainer;\nimport com.denizenscript.denizencore.tags.TagManager;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.EventPriority;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.AsyncPlayerChatEvent;\nimport org.bukkit.event.player.PlayerChatEvent;\n\nimport java.util.List;\nimport java.util.Set;\n\npublic class ChatScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // player chats\n    //\n    // @Regex ^on player chats$\n    //\n    // @Group Player\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Warning Using this will forcibly sync the chat thread.\n    //\n    // @Switch message:<matcher> to only process the event if the chat message matches an advanced matcher.\n    //\n    // @Triggers when a player chats.\n    //\n    // @Context\n    // <context.message> returns the player's message as an Element.\n    // <context.format> returns the chat message's raw format.\n    // <context.full_text> returns the full text of the chat message (ie, the written message with the format applied to it).\n    // <context.recipients> returns a list of all players that will receive the chat.\n    //\n    // @Determine\n    // ElementTag to change the message.\n    // \"FORMAT:<ScriptTag>\" to set the format script the message should use.\n    // \"RAW_FORMAT:<ElementTag>\" to set the format directly (without a format script). (Use with caution, avoid if possible).\n    // \"RECIPIENTS:<ListTag(PlayerTag)>\" to set the list of players that will receive the message.\n    //\n    // @Player Always.\n    //\n    // -->\n\n    public ChatScriptEvent() {\n    }\n\n\n    public PlayerChatEvent pcEvent;\n    public AsyncPlayerChatEvent apcEvent;\n    public PlayerTag player;\n\n    public SyncChatHandler sch = new SyncChatHandler();\n    public AsyncChatHandler asch = new AsyncChatHandler();\n\n    @Override\n    public boolean couldMatch(ScriptPath path) {\n        if (!path.eventLower.startsWith(\"player chats\")) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, player.getLocation())) {\n            return false;\n        }\n        if (!runGenericSwitchCheck(path, \"message\", getMessage())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public void init() {\n        initListener(Settings.worldScriptChatEventAsynchronous() ? asch : sch);\n    }\n\n    @Override\n    public void initForPriority(EventPriority priority, Listener listener) {\n        super.initForPriority(priority, Settings.worldScriptChatEventAsynchronous() ? asch : sch);\n    }\n\n    @Override\n    public ChatScriptEvent clone() {\n        ChatScriptEvent event = (ChatScriptEvent) super.clone();\n        event.sch = event.new SyncChatHandler();\n        event.asch = event.new AsyncChatHandler();\n        return event;\n    }\n\n    public static final String TEXT_CHAR = String.valueOf((char) 0x00), NAME_CHAR = String.valueOf((char) 0x04);\n\n    public String getFormatText(FormatScriptContainer formatScript) {\n        String text = formatScript.getRawFormat().replace(\"<[text]>\", TEXT_CHAR).replace(\"<[name]>\", NAME_CHAR);\n        return TagManager.tag(text, new BukkitTagContext(player, null, new ScriptTag(formatScript)))\n                .replace(\"%\", \"%%\").replace(TEXT_CHAR, \"%2$s\").replace(NAME_CHAR, \"%1$s\");\n    }\n\n    @Override\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\n        if (determinationObj instanceof ElementTag) {\n            String determination = determinationObj.toString();\n            String lower = CoreUtilities.toLowerCase(determination);\n            if (lower.startsWith(\"format:\")) {\n                String name = determination.substring(\"format:\".length());\n                FormatScriptContainer formatscr = ScriptRegistry.getScriptContainer(name);\n                if (formatscr == null) {\n                    Debug.echoError(\"Could not find format script matching '\" + name + '\\'');\n                }\n                else {\n                    String formatstr = getFormatText(formatscr);\n                    if (CoreConfiguration.debugVerbose) {\n                        Debug.log(\"Setting format to \" + formatstr);\n                    }\n                    if (pcEvent != null) {\n                        pcEvent.setFormat(formatstr);\n                    }\n                    else {\n                        apcEvent.setFormat(formatstr);\n                    }\n                }\n                return true;\n            }\n            else if (lower.startsWith(\"raw_format:\")) {\n                String form = determination.substring(\"raw_format:\".length());\n                if (pcEvent != null) {\n                    pcEvent.setFormat(form);\n                }\n                else {\n                    apcEvent.setFormat(form);\n                }\n                return true;\n            }\n            else if (lower.startsWith(\"recipients:\")) {\n                String rec_new = determination.substring(\"recipients:\".length());\n                ListTag recs = ListTag.valueOf(rec_new, getTagContext(path));\n                List<PlayerTag> players = recs.filter(PlayerTag.class, path.container, true);\n                Set<Player> recipients;\n                if (pcEvent != null) {\n                    recipients = pcEvent.getRecipients();\n                }\n                else {\n                    recipients = apcEvent.getRecipients();\n                }\n                recipients.clear();\n                for (PlayerTag player : players) {\n                    recipients.add(player.getPlayerEntity());\n                }\n                return true;\n            }\n            else {\n                if (pcEvent != null) {\n                    pcEvent.setMessage(determination);\n                }\n                else {\n                    apcEvent.setMessage(determination);\n                }\n                return true;\n            }\n        }\n        return super.applyDetermination(path, determinationObj);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(player, null);\n    }\n\n    public String getMessage() {\n        return pcEvent != null ? pcEvent.getMessage() : apcEvent.getMessage();\n    }\n\n    public String getFormat() {\n        return pcEvent != null ? pcEvent.getFormat() : apcEvent.getFormat();\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            case \"message\":\n                return new ElementTag(getMessage(), true);\n            case \"format\":\n                return new ElementTag(getFormat(), true);\n            case \"full_text\":\n                return new ElementTag(String.format(getFormat(), player.getPlayerEntity().getDisplayName(), getMessage()), true);\n        }\n        if (name.equals(\"recipients\")) {\n            ListTag list = new ListTag();\n            for (Player tplayer : pcEvent != null ? pcEvent.getRecipients() : apcEvent.getRecipients()) {\n                list.addObject(PlayerTag.mirrorBukkitPlayer(tplayer));\n            }\n            return list;\n        }\n        return super.getContext(name);\n    }\n\n    class SyncChatHandler implements Listener {\n        @EventHandler\n        public void onSyncChat(PlayerChatEvent event) {\n            pcEvent = event;\n            apcEvent = null;\n            player = new PlayerTag(event.getPlayer());\n            fire(event);\n        }\n    }\n\n    class AsyncChatHandler implements Listener {\n        @EventHandler\n        public void onAsyncChat(AsyncPlayerChatEvent event) {\n            pcEvent = null;\n            apcEvent = event;\n            player = new PlayerTag(event.getPlayer());\n            fire(event);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/HotbarScrollScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerItemHeldEvent;\r\n\r\npublic class HotbarScrollScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player scrolls their hotbar\r\n    // player holds item\r\n    //\r\n    // @Regex ^on player (scrolls their hotbar|holds item)$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    // @Switch item:<item> to only process the event when the player is going to hold a specified item.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player scrolls through their hotbar.\r\n    //\r\n    // @Context\r\n    // <context.new_slot> returns the number of the new inventory slot.\r\n    // <context.previous_slot> returns the number of the old inventory slot.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public HotbarScrollScriptEvent() {\r\n    }\r\n\r\n\r\n    public PlayerItemHeldEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player holds item\")\r\n                && !path.eventLower.startsWith(\"player scrolls their hotbar\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"item\", new ItemTag(event.getPlayer().getInventory().getItem(event.getNewSlot())))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"new_slot\")) {\r\n            return new ElementTag(event.getNewSlot() + 1);\r\n        }\r\n        else if (name.equals(\"previous_slot\")) {\r\n            return new ElementTag(event.getPreviousSlot() + 1);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerScrollsHotbar(PlayerItemHeldEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerAnimatesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerAnimationEvent;\r\nimport org.bukkit.event.player.PlayerAnimationType;\r\n\r\npublic class PlayerAnimatesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player animates (<'animation'>)\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch with:<item> to only run if an item being swung by a swing animation matches the item-matcher.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player performs an animation.\r\n    //\r\n    // @Context\r\n    // <context.animation> returns the name of the animation, from <@link url https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/event/player/PlayerAnimationType.html>.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerAnimatesScriptEvent() {\r\n        registerCouldMatcher(\"player animates (<'animation'>)\");\r\n        registerSwitches(\"with\");\r\n    }\r\n\r\n    public PlayerAnimationEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (!path.eventArgLowerAt(2).isEmpty() && !couldMatchEnum(path.eventArgLowerAt(2), PlayerAnimationType.values())) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        String ani = path.eventArgLowerAt(2);\r\n        if (ani.length() > 0 && !ani.equals(\"in\") && !runGenericCheck(ani, event.getAnimationType().name())) {\r\n            return false;\r\n        }\r\n        String with = path.switches.get(\"with\");\r\n        if (with != null) {\r\n            if (event.getAnimationType() == PlayerAnimationType.ARM_SWING) {\r\n                if (!new ItemTag(event.getPlayer().getEquipment().getItemInMainHand()).tryAdvancedMatcher(with, path.context)) {\r\n                    return false;\r\n                }\r\n            }\r\n            else if (event.getAnimationType() == PlayerAnimationType.OFF_ARM_SWING) {\r\n                if (!new ItemTag(event.getPlayer().getEquipment().getItemInOffHand()).tryAdvancedMatcher(with, path.context)) {\r\n                    return false;\r\n                }\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"animation\": return new ElementTag(event.getAnimationType().name());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerAnimates(PlayerAnimationEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerArmorStandManipulateScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerArmorStandManipulateEvent;\r\n\r\npublic class PlayerArmorStandManipulateScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player changes armor stand item\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player modifies an armor stand entity.\r\n    //\r\n    // @Switch from:<item> to only process the event if the item on the armor stand being interacted with matches the specified item matcher.\r\n    // @Switch hand:<hand> to only process the event if the player is using a specific hand to interact with the armor stand. Available only on MC versions 1.19+.\r\n    // @Switch to:<item> to only process the event if the item held by the player matches the specified item matcher.\r\n    // @Switch slot:<slot> to only process the event if the armor stand's item slot that was interacted with is the specified slot.\r\n    // @Switch armor_stand:<entity> to only process the event if the armor stand being interacted with matches the specified entity matcher.\r\n    //\r\n    // @Context\r\n    // <context.armor_stand_item> returns the ItemTag being interacted with on the armor stand.\r\n    // <context.entity> returns an EntityTag of the armor stand.\r\n    // <context.hand> returns an ElementTag of the hand used by the player to interact with the armor stand, can be either HAND or OFF_HAND. Available only on MC versions 1.19+.\r\n    // <context.player_item> returns the ItemTag held by the player.\r\n    // <context.slot> returns an ElementTag of the armor stand's item slot that was interacted with. Valid equipment slot values can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/EquipmentSlot.html>.\r\n    //\r\n    // @Player Always.\r\n    // -->\r\n\r\n    public PlayerArmorStandManipulateScriptEvent() {\r\n        registerCouldMatcher(\"player changes armor stand item\");\r\n        registerSwitches(\"from\", \"to\", \"hand\", \"slot\", \"armor_stand\");\r\n    }\r\n\r\n    public PlayerArmorStandManipulateEvent event;\r\n    public EntityTag entity;\r\n    public ItemTag armorStandItem;\r\n    public ItemTag playerItem;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getRightClicked().getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"from\", armorStandItem)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"armor_stand\", entity)) {\r\n            return false;\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && !runGenericSwitchCheck(path, \"hand\", event.getHand().name())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"to\", playerItem)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"slot\", event.getSlot().name())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"armor_stand_item\" -> armorStandItem;\r\n            case \"entity\" -> entity;\r\n            case \"hand\" -> NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) ? new ElementTag(event.getHand()) : null;\r\n            case \"player_item\" -> playerItem;\r\n            case \"slot\" -> new ElementTag(event.getSlot());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerManipulatesArmorStand(PlayerArmorStandManipulateEvent event) {\r\n        this.event = event;\r\n        entity = new EntityTag(event.getRightClicked());\r\n        playerItem = new ItemTag(event.getPlayerItem());\r\n        armorStandItem = new ItemTag(event.getArmorStandItem());\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerBreaksBlockScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockBreakEvent;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\n\r\npublic class PlayerBreaksBlockScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player breaks block\r\n    // player breaks <material>\r\n    //\r\n    // @Regex ^on player breaks [^\\s]+$\r\n    //\r\n    // @Synonyms player mines block,player mines ore,player digs block\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    // @Switch with:<item> to only process the event when the player is breaking the block with a specified item.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player breaks a block.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag the block was broken at.\r\n    // <context.material> returns the MaterialTag of the block that was broken.\r\n    // <context.xp> returns how much XP will be dropped.\r\n    // <context.should_drop_items> returns whether the event will drop items.\r\n    //\r\n    // @Determine\r\n    // \"NOTHING\" to make the block drop no items.\r\n    // ListTag(ItemTag) to make the block drop a specified list of items.\r\n    // ElementTag(Number) to set the amount of xp to drop.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerBreaksBlockScriptEvent() {\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockBreakEvent event;\r\n\r\n    public static HashSet<String> notRelevantBreakables = new HashSet<>(Arrays.asList(\"item\", \"held\", \"hanging\", \"painting\", \"item_frame\", \"leash_hitch\"));\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player breaks\")) {\r\n            return false;\r\n        }\r\n        if (notRelevantBreakables.contains(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        if (!couldMatchBlock(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String mat = path.eventArgLowerAt(2);\r\n        if (!material.tryAdvancedMatcher(mat, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, new ItemTag(event.getPlayer().getEquipment().getItemInMainHand()))) {\r\n            return false;\r\n        }\r\n        // Deprecated in favor of with: format\r\n        if (path.eventArgLowerAt(3).equals(\"with\")\r\n                && !path.tryArgObject(4, new ItemTag(event.getPlayer().getEquipment().getItemInMainHand()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        Block block = event.getBlock();\r\n        if (determinationObj instanceof ElementTag) {\r\n            String lower = CoreUtilities.toLowerCase(determination);\r\n            if (lower.equals(\"nothing\")) {\r\n                event.setExpToDrop(0);\r\n                event.setDropItems(false);\r\n                return true;\r\n            }\r\n            else if (((ElementTag) determinationObj).isInt()) {\r\n                event.setExpToDrop(((ElementTag) determinationObj).asInt());\r\n                return true;\r\n            }\r\n        }\r\n        if (Argument.valueOf(determination).matchesArgumentList(ItemTag.class)) {\r\n            event.setDropItems(false);\r\n            for (ItemTag newItem : ListTag.valueOf(determination, getTagContext(path)).filter(ItemTag.class, path.container, true)) {\r\n                block.getWorld().dropItemNaturally(block.getLocation(), newItem.getItemStack()); // Drop each item\r\n            }\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"location\":\r\n                return location;\r\n            case \"material\":\r\n                return material;\r\n            case \"xp\":\r\n                return new ElementTag(event.getExpToDrop());\r\n            case \"should_drop_items\":\r\n                return new ElementTag(event.isDropItems());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerBreaksBlock(BlockBreakEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        material = new MaterialTag(event.getBlock());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerBreaksItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.inventory.SlotHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerItemBreakEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\npublic class PlayerBreaksItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player breaks held item\r\n    // player breaks held <item>\r\n    //\r\n    // @Regex ^on player breaks held [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player breaks the item they are holding.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the item that broke.\r\n    // <context.slot> returns the slot of the item that broke.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerBreaksItemScriptEvent() {\r\n    }\r\n\r\n    public ItemTag item;\r\n    public PlayerItemBreakEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player breaks held\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchItem(path.eventArgLowerAt(3))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(3, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"item\")) {\r\n            return item;\r\n        }\r\n        else if (name.equals(\"slot\")) {\r\n            return new ElementTag(SlotHelper.slotForItem(event.getPlayer().getInventory(), item.getItemStack()) + 1);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled) { // Hacked-in cancellation helper\r\n            final Player player = event.getPlayer();\r\n            final ItemStack itemstack = event.getBrokenItem();\r\n            itemstack.setAmount(itemstack.getAmount() + 1);\r\n            new BukkitRunnable() {\r\n                public void run() {\r\n                    itemstack.setDurability(itemstack.getType().getMaxDurability());\r\n                    player.updateInventory();\r\n                }\r\n            }.runTaskLater(Denizen.getInstance(), 1);\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerItemBreak(PlayerItemBreakEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        item = new ItemTag(event.getBrokenItem());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerChangesGamemodeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerGameModeChangeEvent;\r\n\r\npublic class PlayerChangesGamemodeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player changes gamemode (to <'gamemode'>)\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player's gamemode is changed.\r\n    //\r\n    // @Context\r\n    // <context.gamemode> returns an ElementTag of the gamemode. Game Modes: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/GameMode.html>\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerChangesGamemodeScriptEvent() {\r\n        registerCouldMatcher(\"player changes gamemode (to <'gamemode'>)\");\r\n    }\r\n\r\n    public ElementTag gamemode;\r\n    public PlayerGameModeChangeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String mode = path.eventArgLowerAt(4);\r\n        if (mode.length() > 0) {\r\n            if (!runGenericCheck(mode, gamemode.asString())) {\r\n                return false;\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"gamemode\")) {\r\n            return gamemode;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerChangesGamemode(PlayerGameModeChangeEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        gamemode = new ElementTag(event.getNewGameMode());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerChangesMainHandScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\n\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.PlayerChangedMainHandEvent;\nimport org.bukkit.inventory.MainHand;\n\npublic class PlayerChangesMainHandScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // player changes main hand\n    //\n    // @Group Player\n    //\n    // @Triggers when a player changes their main hand.\n    //\n    // @Context\n    // <context.old_hand> returns the player's old main hand, either LEFT or RIGHT.\n    // <context.new_hand> returns the player's new main hand.\n    //\n    // @Player Always.\n    //\n    // -->\n\n    public PlayerChangesMainHandScriptEvent() {\n        registerCouldMatcher(\"player changes main hand\");\n    }\n\n    public PlayerChangedMainHandEvent event;\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getPlayer());\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            // workaround for spigot bug: getMainHand returns the old value, despite being documented as returning the new value\n            case \"old_hand\": return new ElementTag(event.getMainHand().toString());\n            case \"new_hand\": return new ElementTag(event.getMainHand() == MainHand.LEFT ? \"RIGHT\" : \"LEFT\");\n        }\n        return super.getContext(name);\n    }\n\n    @EventHandler\n    public void onPlayerChangesMainHand(PlayerChangedMainHandEvent event) {\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerChangesSignScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.block.Sign;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.SignChangeEvent;\r\n\r\nimport java.util.Arrays;\r\n\r\npublic class PlayerChangesSignScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player changes sign\r\n    // player changes <material>\r\n    //\r\n    // @Regex ^on player changes [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player changes a sign.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the sign.\r\n    // <context.new> returns the new sign text as a ListTag.\r\n    // <context.old> returns the old sign text as a ListTag.\r\n    // <context.material> returns the MaterialTag of the sign.\r\n    //\r\n    // @Determine\r\n    // ListTag to change the lines.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerChangesSignScriptEvent() {\r\n        instance = this;\r\n    }\r\n\r\n    public static PlayerChangesSignScriptEvent instance;\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public SignChangeEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player changes\")) {\r\n            return false;\r\n        }\r\n        String sign = path.eventArgAt(2);\r\n        if  (!sign.equals(\"sign\") && !couldMatchBlock(sign, (m) -> CoreUtilities.toLowerCase(m.name()).endsWith(\"sign\"))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String mat = path.eventArgLowerAt(2);\r\n        if (!mat.equals(\"sign\") && (!material.tryAdvancedMatcher(mat, path.context))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (determination.length() > 0) {\r\n            ListTag new_text = ListTag.valueOf(determination, getTagContext(path));\r\n            for (int i = 0; i < 4 && i < new_text.size(); i++) {\r\n                event.setLine(i, new_text.get(i));\r\n            }\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"location\":\r\n                return location;\r\n            case \"material\":\r\n                return material;\r\n            case \"new\":\r\n                return new ListTag(Arrays.asList(event.getLines()), true);\r\n            case \"old\":\r\n                if (event.getBlock().getState() instanceof Sign) {\r\n                    return new ListTag(Arrays.asList(((Sign) event.getBlock().getState()).getLines()), true);\r\n                }\r\n                else {\r\n                    return null;\r\n                }\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerChangesSign(SignChangeEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        BlockState state = event.getBlock().getState();\r\n        if (!(state instanceof Sign)) {\r\n            return;\r\n        }\r\n        material = new MaterialTag(event.getBlock());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerChangesWorldScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerChangedWorldEvent;\r\n\r\npublic class PlayerChangesWorldScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player changes world\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch from:<world> to only run if the player came from the specified world.\r\n    // @Switch to:<world> to only run if the player is going to the specified world.\r\n    //\r\n    // @Triggers when a player moves to a different world.\r\n    //\r\n    // @Context\r\n    // <context.origin_world> returns the WorldTag that the player was previously on.\r\n    // <context.destination_world> returns the WorldTag that the player is now in.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerChangesWorldScriptEvent() {\r\n        registerCouldMatcher(\"player changes world (from <world>) (to <world>)\");\r\n        registerSwitches(\"from\", \"to\");\r\n    }\r\n\r\n    public WorldTag origin_world;\r\n    public WorldTag destination_world;\r\n    public PlayerChangedWorldEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String[] data = path.eventArgsLower;\r\n        for (int index = 3; index < data.length; index++) {\r\n            if (data[index].equals(\"from\")) {\r\n                BukkitImplDeprecations.playerChangesWorldSwitches.warn(getTagContext(path));\r\n                if (!origin_world.tryAdvancedMatcher(data[index + 1], path.context)) {\r\n                    return false;\r\n                }\r\n            }\r\n            else if (data[index].equals(\"to\")) {\r\n                BukkitImplDeprecations.playerChangesWorldSwitches.warn(getTagContext(path));\r\n                if (!destination_world.tryAdvancedMatcher(data[index + 1], path.context)) {\r\n                    return false;\r\n                }\r\n            }\r\n        }\r\n        if (!path.tryObjectSwitch(\"from\", origin_world)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"to\", destination_world)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"origin_world\" -> origin_world;\r\n            case \"destination_world\" -> destination_world;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerChangesWorld(PlayerChangedWorldEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        origin_world = new WorldTag(event.getFrom());\r\n        destination_world = new WorldTag(event.getPlayer().getWorld());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerChangesXPScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerExpChangeEvent;\r\n\r\npublic class PlayerChangesXPScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player changes xp\r\n    //\r\n    // @Regex ^on player changes xp$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player's experience amount changes.\r\n    //\r\n    // @Context\r\n    // <context.amount> returns the amount of changed experience.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Number) to set the amount of changed experience.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerChangesXPScriptEvent() {\r\n    }\r\n\r\n    public PlayerExpChangeEvent event;\r\n    public PlayerTag player;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player changes xp\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, player.getLocation())) {\r\n            return false;\r\n        }\r\n\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isInt()) {\r\n            event.setAmount(element.asInt());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"amount\")) {\r\n            return new ElementTag(event.getAmount());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled) {\r\n            event.setAmount(0);\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerChangesXP(PlayerExpChangeEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        player = PlayerTag.mirrorBukkitPlayer(event.getPlayer());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerClicksBlockScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.event.Event;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.Action;\r\nimport org.bukkit.event.player.PlayerInteractEvent;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\n\r\npublic class PlayerClicksBlockScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player (right|left) clicks <block>\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Warning this event may in some cases double-fire, requiring usage of the 'ratelimit' command (like 'ratelimit <player> 1t') to prevent doubling actions.\r\n    // @Warning this sometimes fires at unexpected times, eg when dropping an item.\r\n    //\r\n    // @Switch with:<item> to only process the event if a specified item was held.\r\n    // @Switch using:hand/off_hand/either_hand to only process the event if the specified hand was used to click.\r\n    // @Switch type:<material> to only run if the block clicked matches the material input.\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player clicks on a block or in the air.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the ItemTag the player is clicking with.\r\n    // <context.location> returns the LocationTag the player is clicking on.\r\n    // <context.relative> returns a LocationTag of the air block in front of the clicked block.\r\n    // <context.click_type> returns an ElementTag of the Spigot API click type <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/block/Action.html>.\r\n    // <context.hand> returns an ElementTag of the used hand.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerClicksBlockScriptEvent() {\r\n        registerCouldMatcher(\"player (right|left) clicks <block>\");\r\n        registerSwitches(\"with\", \"using\", \"type\");\r\n    }\r\n\r\n    public PlayerInteractEvent event;\r\n    public ItemTag item;\r\n    public LocationTag location;\r\n    public ElementTag click_type;\r\n    public ElementTag hand;\r\n    public MaterialTag blockMaterial;\r\n\r\n    private boolean runUsingCheck(ScriptPath path) {\r\n        String using = path.switches.get(\"using\");\r\n        if (using == null) {\r\n            int index;\r\n            for (index = 0; index < path.eventArgsLower.length; index++) {\r\n                if (path.eventArgsLower[index].equals(\"using\")) {\r\n                    break;\r\n                }\r\n            }\r\n            if (index >= path.eventArgsLower.length) {\r\n                using = \"hand\";\r\n            }\r\n            else {\r\n                using = path.eventArgLowerAt(index + 1);\r\n            }\r\n        }\r\n        if (!using.equals(\"hand\") && !using.equals(\"off_hand\") && !using.equals(\"either_hand\")) {\r\n            Debug.echoError(\"Invalid USING hand in \" + getName() + \" for '\" + path.event + \"' in \" + path.container.getName());\r\n            return false;\r\n        }\r\n        if (!using.equals(\"either_hand\") && !runGenericCheck(using, hand.identify())) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    private static final HashSet<String> matchHelpList = new HashSet<>(Arrays.asList(\"at\", \"entity\", \"npc\", \"player\", \"vehicle\", \"projectile\", \"hanging\", \"fake\", \"item\"));\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        boolean clickFirst = path.eventArgLowerAt(1).equals(\"clicks\");\r\n        if (!clickFirst && !path.eventArgLowerAt(2).equals(\"clicks\")) {\r\n            return false;\r\n        }\r\n        if (!clickFirst && !path.eventArgLowerAt(1).equals(\"right\") && !path.eventArgLowerAt(1).equals(\"left\")) {\r\n            return false;\r\n        }\r\n        String clickedOn = path.eventArgLowerAt(clickFirst ? 2 : 3);\r\n        if (matchHelpList.contains(clickedOn)) {\r\n            return false;\r\n        }\r\n        if (!clickedOn.isEmpty() && !couldMatchBlock(clickedOn)\r\n                && !clickedOn.equals(\"with\") && !clickedOn.equals(\"in\") && !clickedOn.equals(\"using\")) { // Legacy format support\r\n            return false;\r\n        }\r\n        if (!couldMatchLegacyInArea(path.eventLower)) {\r\n            return false;\r\n        }\r\n        if (clickedOn.isEmpty()) {\r\n            Debug.echoError(\"'on player clicks:' is is not valid, use 'on player clicks block:' (for script '\" + path.container.getName() + \"').\");\r\n        }\r\n        return true;\r\n    }\r\n\r\n    private static final HashSet<String> withHelpList = new HashSet<>(Arrays.asList(\"with\", \"using\", \"in\"));\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        int index = path.eventArgLowerAt(1).equals(\"clicks\") ? 1 : 2;\r\n        if (index == 2 && !click_type.identify().startsWith(path.eventArgLowerAt(1).toUpperCase())) {\r\n            return false;\r\n        }\r\n        String mat = path.eventArgLowerAt(index + 1);\r\n        if (mat.length() > 0 && !withHelpList.contains(mat) && !blockMaterial.tryAdvancedMatcher(mat, path.context)) {\r\n            return false;\r\n        }\r\n        if (!nonSwitchWithCheck(path, new ItemTag(event.getItem()))) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, new ItemTag(event.getItem()))) {\r\n            return false;\r\n        }\r\n        if (!runUsingCheck(path)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location != null ? location : event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"type\", blockMaterial)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    public boolean wasCancellationAltered;\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        wasCancellationAltered = true;\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(EntityTag.getPlayerFrom(event.getPlayer()), null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"item\":\r\n                return item;\r\n            case \"location\":\r\n                return location;\r\n            case \"click_type\":\r\n                return click_type;\r\n            case \"hand\":\r\n                return hand;\r\n            case \"relative\":\r\n                return event.hasBlock() ? new LocationTag(event.getClickedBlock().getRelative(event.getBlockFace()).getLocation()) : null;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        event.setCancelled(cancelled); // Workaround for Spigot=Dumb!\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerClicksBlock(PlayerInteractEvent event) {\r\n        if (event.getAction() == Action.PHYSICAL) {\r\n            return;\r\n        }\r\n        blockMaterial = event.hasBlock() ? new MaterialTag(event.getClickedBlock()) : new MaterialTag(Material.AIR);\r\n        hand = new ElementTag(event.getHand());\r\n        item = new ItemTag(event.getItem());\r\n        location = event.hasBlock() ? new LocationTag(event.getClickedBlock().getLocation()) : null;\r\n        click_type = new ElementTag(event.getAction());\r\n        cancelled = event.isCancelled() && event.useItemInHand() == Event.Result.DENY; // Spigot is dumb!\r\n        this.event = event;\r\n        fire(); // Explicitly don't use `fire(event)` due to spigot bork\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerClicksInInventoryScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.InventoryClickEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\n\r\npublic class PlayerClicksInInventoryScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[language]\r\n    // @Name Inventory Actions\r\n    // @Group Useful Lists\r\n    // @Description\r\n    // Used by some inventory world events to describe the action of the inventory event.\r\n    //\r\n    // Actions, as described by the bukkit javadocs:\r\n    // CLONE_STACK\r\n    // A max-size stack of the clicked item is put on the cursor.\r\n    // COLLECT_TO_CURSOR\r\n    // The inventory is searched for the same material, and they are put on the cursor up to\r\n    //      MaterialTag.max_stack_size.\r\n    // DROP_ALL_CURSOR\r\n    // The entire cursor item is dropped.\r\n    // DROP_ALL_SLOT\r\n    // The entire clicked slot is dropped.\r\n    // DROP_ONE_CURSOR\r\n    // One item is dropped from the cursor.\r\n    // DROP_ONE_SLOT\r\n    // One item is dropped from the clicked slot.\r\n    // HOTBAR_MOVE_AND_READD\r\n    // The clicked item is moved to the hotbar, and the item currently there is re-added to the\r\n    //      player's inventory.\r\n    // HOTBAR_SWAP\r\n    // The clicked slot and the picked hotbar slot are swapped.\r\n    // MOVE_TO_OTHER_INVENTORY\r\n    // The item is moved to the opposite inventory if a space is found.\r\n    // NOTHING\r\n    // Nothing will happen from the click.\r\n    // PICKUP_ALL\r\n    // All of the items on the clicked slot are moved to the cursor.\r\n    // PICKUP_HALF\r\n    // Half of the items on the clicked slot are moved to the cursor.\r\n    // PICKUP_ONE\r\n    // One of the items on the clicked slot are moved to the cursor.\r\n    // PICKUP_SOME\r\n    // Some of the items on the clicked slot are moved to the cursor.\r\n    // PLACE_ALL\r\n    // All of the items on the cursor are moved to the clicked slot.\r\n    // PLACE_ONE\r\n    // A single item from the cursor is moved to the clicked slot.\r\n    // PLACE_SOME\r\n    // Some of the items from the cursor are moved to the clicked slot (usually up to the max stack size).\r\n    // SWAP_WITH_CURSOR\r\n    // The clicked item and the cursor are exchanged.\r\n    // UNKNOWN\r\n    // An unrecognized ClickType.\r\n    //\r\n    // -->\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player (<'click_type'>) clicks (<item>) in <inventory>\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Switch with:<item> to only process the event if a specified cursor item was used.\r\n    // @Switch in_area:<area> replaces the default 'in:<area>' for this event.\r\n    // @Switch action:<action> to only process the event if a specified action occurred.\r\n    // @Switch slot:<slot> to only process the event if a specified slot or slot_type was clicked. For slot input options, see <@link language Slot Inputs>.\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player clicks in an inventory. Note that you likely will also want to listen to <@link event player drags in inventory>.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the ItemTag the player has clicked on.\r\n    // <context.inventory> returns the InventoryTag (the 'top' inventory, regardless of which slot was clicked).\r\n    // <context.clicked_inventory> returns the InventoryTag that was clicked in.\r\n    // <context.cursor_item> returns the item the Player is clicking with.\r\n    // <context.click> returns an ElementTag with the name of the click type. Click type list: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/inventory/ClickType.html>\r\n    // <context.slot_type> returns an ElementTag with the name of the slot type that was clicked. Slot type list: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/inventory/InventoryType.SlotType.html>\r\n    // <context.slot> returns an ElementTag with the number of the slot that was clicked.\r\n    // <context.raw_slot> returns an ElementTag with the raw number of the slot that was clicked.\r\n    // <context.is_shift_click> returns true if 'shift' was used while clicking.\r\n    // <context.action> returns the inventory_action. See <@link language Inventory Actions>.\r\n    // <context.hotbar_button> returns an ElementTag of the button pressed as a number, or 0 if no number button was pressed.\r\n    //\r\n    // @Determine\r\n    // ItemTag to set the current item for the event.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerClicksInInventoryScriptEvent() {\r\n        registerCouldMatcher(\"player (<'click_type'>) clicks (<item>) in <inventory>\");\r\n        registerSwitches(\"with\", \"in_area\", \"action\", \"slot\");\r\n        this.<PlayerClicksInInventoryScriptEvent, ItemTag>registerDetermination(null, ItemTag.class, (evt, context, current) -> {\r\n            event.setCurrentItem(current.getItemStack());\r\n        });\r\n    }\r\n\r\n    public InventoryTag inventory;\r\n    public ItemTag item;\r\n    public ItemTag cursor; // Needed due to internal oddity\r\n    public InventoryClickEvent event;\r\n\r\n    private static final HashSet<String> matchHelpList = new HashSet<>(Arrays.asList(\"at\", \"entity\", \"npc\", \"player\", \"vehicle\", \"projectile\", \"hanging\", \"fake\"));\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        boolean clickFirst = path.eventArgLowerAt(1).equals(\"clicks\");\r\n        if (!clickFirst && !path.eventArgLowerAt(2).equals(\"clicks\")) {\r\n            return false;\r\n        }\r\n        String clickedOn = path.eventArgLowerAt(clickFirst ? 2 : 3);\r\n        if (matchHelpList.contains(clickedOn)) {\r\n            return false;\r\n        }\r\n        int inIndex = -1;\r\n        for (int i = 0; i < path.eventArgsLower.length; i++) {\r\n            if (path.eventArgLowerAt(i).equals(\"in\")) {\r\n                inIndex = i;\r\n            }\r\n        }\r\n        if (inIndex == -1) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        boolean hasClickType = path.eventArgLowerAt(2).equals(\"clicks\");\r\n        if (hasClickType && !runGenericCheck(path.eventArgLowerAt(1), event.getClick().name())) {\r\n            return false;\r\n        }\r\n        String clickedItemText = path.eventArgLowerAt(hasClickType ? 3 : 2);\r\n        if (!clickedItemText.equals(\"in\") && !item.tryAdvancedMatcher(clickedItemText, path.context)) {\r\n            return false;\r\n        }\r\n        int inIndex = -1;\r\n        for (int i = 0; i < path.eventArgsLower.length; i++) {\r\n            if (path.eventArgLowerAt(i).equals(\"in\")) {\r\n                inIndex = i;\r\n            }\r\n        }\r\n        if (!inventory.tryAdvancedMatcher(path.eventArgLowerAt(inIndex + 1), path.context)) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, cursor)) {\r\n            return false;\r\n        }\r\n        if (!nonSwitchWithCheck(path, cursor)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getWhoClicked().getLocation(), \"in_area\")) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"action\", event.getAction().name())) {\r\n            return false;\r\n        }\r\n        if (!trySlot(path, \"slot\", event.getWhoClicked(), event.getSlot()) && !runGenericSwitchCheck(path, \"slot\", event.getSlotType().name())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getWhoClicked());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"inventory\" -> inventory;\r\n            case \"item\" -> item;\r\n            case \"cursor_item\" -> cursor;\r\n            case \"click\" -> new ElementTag(event.getClick());\r\n            case \"action\" -> new ElementTag(event.getAction());\r\n            case \"slot_type\" -> new ElementTag(event.getSlotType());\r\n            case \"is_shift_click\" -> new ElementTag(event.isShiftClick());\r\n            case \"slot\" -> new ElementTag(event.getSlot() + 1);\r\n            case \"raw_slot\" -> new ElementTag(event.getRawSlot() + 1);\r\n            case \"hotbar_button\" -> new ElementTag(event.getHotbarButton() + 1);\r\n            case \"clicked_inventory\" -> event.getClickedInventory() != null ? InventoryTag.mirrorBukkitInventory(event.getClickedInventory()) : null;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void inventoryClickEvent(InventoryClickEvent event) {\r\n        inventory = InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n        item = event.getCurrentItem() == null ? new ItemTag(Material.AIR) : new ItemTag(event.getCurrentItem().clone());\r\n        cursor = new ItemTag(event.getCursor() == null ? new ItemStack(Material.AIR) : event.getCursor().clone());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerClosesInvScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.InventoryCloseEvent;\r\n\r\npublic class PlayerClosesInvScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player closes inventory\r\n    // player closes <inventory>\r\n    //\r\n    // @Regex ^on player closes [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player closes an inventory.\r\n    //\r\n    // @Context\r\n    // <context.inventory> returns the InventoryTag.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerClosesInvScriptEvent() {\r\n    }\r\n\r\n\r\n    public InventoryTag inventory;\r\n    private PlayerTag player;\r\n    public InventoryCloseEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player closes \")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchInventory(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, inventory)) {\r\n            return false;\r\n        }\r\n        LocationTag loc = inventory.getLocation();\r\n        if (loc == null) {\r\n            loc = player.getLocation();\r\n        }\r\n        if (!runInCheck(path, loc)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"inventory\")) {\r\n            return inventory;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerClosesInv(InventoryCloseEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        inventory = InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n        player = new PlayerTag((Player) event.getPlayer());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerCompletesAdvancementScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerAdvancementDoneEvent;\r\n\r\npublic class PlayerCompletesAdvancementScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player completes advancement\r\n    //\r\n    // @Regex ^on player completes advancement$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Switch name:<name> to only fire if the advancement has the specified name.\r\n    //\r\n    // @Triggers when a player has completed all criteria in an advancement.\r\n    //\r\n    // @Context\r\n    // <context.criteria> returns all the criteria present in this advancement.\r\n    // <context.advancement> returns the completed advancement's minecraft ID key.\r\n    // <context.message> returns an ElementTag of the advancement message (only on Paper).\r\n    //\r\n    // @Determine\r\n    // ElementTag to change the advancement message (only on Paper).\r\n    // \"NO_MESSAGE\" to hide the advancement message (only on Paper).\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerCompletesAdvancementScriptEvent() {\r\n    }\r\n\r\n    public PlayerAdvancementDoneEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player completes advancement\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runGenericSwitchCheck(path, \"name\", event.getAdvancement().getKey().getKey())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"criteria\")) {\r\n            ListTag criteria = new ListTag();\r\n            criteria.addAll(event.getAdvancement().getCriteria());\r\n            return criteria;\r\n        }\r\n        else if (name.equals(\"advancement\")) {\r\n            return new ElementTag(event.getAdvancement().getKey().getKey());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerCompletesAdvancement(PlayerAdvancementDoneEvent event) {\r\n        // TODO: Should this not fire if it's a 'fake' advancement created by Denizen?\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerConsumesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerItemConsumeEvent;\r\n\r\npublic class PlayerConsumesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player consumes item\r\n    // player consumes <item>\r\n    //\r\n    // @Regex ^on player consumes [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player consumes (eats/drinks) an item (like food or potions).\r\n    //\r\n    // @Context\r\n    // <context.item> returns the ItemTag.\r\n    // <context.hand> returns an ElementTag of the hand being used to consume the item. Can be either HAND or OFF_HAND. Requires a 1.19+ server.\r\n    //\r\n    // @Determine\r\n    // ItemTag to change the item being consumed. Use with caution, if the player is eating a stack of items, this will replace the entire stack.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerConsumesScriptEvent() {\r\n    }\r\n\r\n\r\n    public ItemTag item;\r\n    public PlayerItemConsumeEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player consumes\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchItem(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (ItemTag.matches(determination)) {\r\n            BukkitTagContext context = new BukkitTagContext(EntityTag.getPlayerFrom(event.getPlayer()), null, new ScriptTag(path.container));\r\n            ItemTag newitem = ItemTag.valueOf(determination, context);\r\n            if (newitem != null) {\r\n                event.setItem(newitem.getItemStack());\r\n                return true;\r\n            }\r\n            else {\r\n                Debug.echoError(\"Invalid event 'item' check [\" + getName() + \"] ('determine item ????'): '\" + determination + \"' for \" + path.container.getName());\r\n            }\r\n\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        // TODO: Store the player / npc?\r\n        return new BukkitScriptEntryData(event != null ? EntityTag.getPlayerFrom(event.getPlayer()) : null, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"item\" -> item;\r\n            case \"hand\" -> NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) ? new ElementTag(event.getHand()) : null;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerConsumes(PlayerItemConsumeEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        item = new ItemTag(event.getItem());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerCraftsItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.inventory.RecipeHelper;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Keyed;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.ClickType;\r\nimport org.bukkit.event.inventory.CraftItemEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic class PlayerCraftsItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player crafts item\r\n    // player crafts <item>\r\n    //\r\n    // @Regex ^on player crafts [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player fully crafts an item.\r\n    // @Context\r\n    // <context.inventory> returns the InventoryTag of the crafting inventory.\r\n    // <context.item> returns the ItemTag to be crafted.\r\n    // <context.amount> returns the amount of the item that will be crafted (usually 1, except when shift clicked. Can be above 64).\r\n    // <context.recipe> returns a ListTag of ItemTags in the recipe.\r\n    // <context.recipe_id> returns the ID of the recipe that is being crafted.\r\n    // <context.click_type> returns an ElementTag with the name of the click type. Click type list: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/inventory/ClickType.html>\r\n    //\r\n    // @Determine\r\n    // ItemTag to change the item that is crafted.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerCraftsItemScriptEvent() {\r\n    }\r\n\r\n    public CraftItemEvent event;\r\n    public ItemTag result;\r\n    public PlayerTag player;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventArgsLowEqualStartingAt(0, \"player\", \"crafts\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchItem(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, result)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (ItemTag.matches(determination)) {\r\n            event.setCurrentItem(ItemTag.valueOf(determination, path.container).getItemStack());\r\n            return true;\r\n        }\r\n\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"item\")) {\r\n            return result;\r\n        }\r\n        else if (name.equals(\"inventory\")) {\r\n            return InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n        }\r\n        else if (name.equals(\"amount\")) {\r\n            int amount = event.getRecipe().getResult().getAmount();\r\n            if (event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT) {\r\n                amount *= RecipeHelper.getMaximumOutputQuantity(event.getRecipe(), event.getInventory());\r\n            }\r\n            return new ElementTag(amount);\r\n        }\r\n        else if (name.equals(\"click_type\")) {\r\n            return new ElementTag(event.getClick());\r\n        }\r\n        else if (name.equals(\"recipe\")) {\r\n            ListTag recipe = new ListTag();\r\n            for (ItemStack itemStack : event.getInventory().getMatrix()) {\r\n                if (itemStack != null) {\r\n                    recipe.addObject(new ItemTag(itemStack));\r\n                }\r\n                else {\r\n                    recipe.addObject(new ItemTag(Material.AIR));\r\n                }\r\n            }\r\n            return recipe;\r\n        }\r\n        else if (name.equals(\"recipe_id\") && event.getRecipe() instanceof Keyed) {\r\n            return new ElementTag(((Keyed) event.getRecipe()).getKey().toString());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled) { // This event has a weird cancellation handler\r\n            event.setCancelled(true);\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onCraftItem(CraftItemEvent event) {\r\n        // This event fires even when nothing crafts due to a cursor item, so disregard those cases.\r\n        if (event.getClick() != ClickType.SHIFT_LEFT && event.getCursor() != null && event.getCursor().getType() != Material.AIR) {\r\n            if (!event.getCursor().isSimilar(event.getCurrentItem())) {\r\n                return;\r\n            }\r\n            if (event.getCursor().getAmount() + event.getRecipe().getResult().getAmount() > event.getCursor().getType().getMaxStackSize()) {\r\n                return;\r\n            }\r\n        }\r\n        HumanEntity humanEntity = event.getWhoClicked();\r\n        if (EntityTag.isNPC(humanEntity)) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        result = new ItemTag(event.getInventory().getResult());\r\n        if (result.getBukkitMaterial() == Material.AIR) {\r\n            result = new ItemTag(event.getRecipe().getResult());\r\n        }\r\n        this.player = EntityTag.getPlayerFrom(humanEntity);\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerDamagesBlockScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockDamageEvent;\r\n\r\npublic class PlayerDamagesBlockScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player damages <block>\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch with:<item> to only process the event when the player is hitting the block with a specified item.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a block is damaged by a player.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag the block that was damaged.\r\n    // <context.material> returns the MaterialTag of the block that was damaged.\r\n    //\r\n    // @Determine\r\n    // \"INSTABREAK\" to make the block get broken instantly.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerDamagesBlockScriptEvent() {\r\n        registerCouldMatcher(\"player damages <block>\");\r\n        registerSwitches(\"with\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockDamageEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, material)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, new ItemTag(event.getPlayer().getEquipment().getItemInMainHand()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            if (CoreUtilities.equalsIgnoreCase(determinationObj.toString(), \"instabreak\")) {\r\n                event.setInstaBreak(true);\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerDamagesBlock(BlockDamageEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        material = new MaterialTag(event.getBlock());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerDragsInInvScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryViewUtil;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.InventoryDragEvent;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryHolder;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\npublic class PlayerDragsInInvScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player drags in inventory\r\n    // player drags (<item>) (in <inventory>)\r\n    //\r\n    // @Regex ^on player drags( [^\\s]+)?( in [^\\s]+)?$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Switch in_area:<area> replaces the default 'in:<area>' for this event.\r\n    // @Switch drag_type:<type> to only run the event if the given drag type (SINGLE or EVEN) was used.\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player drags in an inventory (that is, clicks and then holds the mouse button down while moving the mouse across multiple slots).\r\n    //\r\n    // @Context\r\n    // <context.item> returns the ItemTag the player has dragged.\r\n    // <context.inventory> returns the InventoryTag (the 'top' inventory, regardless of which slot was clicked).\r\n    // <context.clicked_inventory> returns the InventoryTag that was clicked in.\r\n    // <context.slots> returns a ListTag of the slot numbers dragged through.\r\n    // <context.raw_slots> returns a ListTag of the raw slot numbers dragged through.\r\n    // <context.drag_type> returns either SINGLE or EVEN depending on whether the player used their left or right mouse button.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerDragsInInvScriptEvent() {\r\n    }\r\n\r\n\r\n    public Inventory inventory;\r\n    public ItemTag item;\r\n    private PlayerTag entity;\r\n    private InventoryTag dInv;\r\n    public InventoryDragEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player drags\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String arg2 = path.eventArgLowerAt(2);\r\n        String arg3 = path.eventArgLowerAt(3);\r\n        String arg4 = path.eventArgLowerAt(4);\r\n        String inv = arg2.equals(\"in\") ? arg3 : arg3.equals(\"in\") ? arg4 : \"\";\r\n        if (!inv.equals(\"\") && !dInv.tryAdvancedMatcher(inv, path.context)) {\r\n            return false;\r\n        }\r\n        if (!arg2.equals(\"in\") && !item.tryAdvancedMatcher(arg2, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation(), \"in_area\")) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"drag_type\", event.getType().name())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"inventory\":\r\n                return dInv;\r\n            case \"slots\":\r\n                ListTag slots = new ListTag();\r\n                for (Integer slot : event.getInventorySlots()) {\r\n                    slots.add(String.valueOf(slot + 1));\r\n                }\r\n                return slots;\r\n            case \"raw_slots\":\r\n                ListTag raw_slots = new ListTag();\r\n                for (Integer raw_slot : event.getRawSlots()) {\r\n                    raw_slots.add(String.valueOf(raw_slot + 1));\r\n                }\r\n                return raw_slots;\r\n            case \"item\":\r\n                return item;\r\n            case \"clicked_inventory\":\r\n                return InventoryTag.mirrorBukkitInventory(InventoryViewUtil.getInventory(event.getView(), event.getRawSlots().stream().findFirst().orElse(0)));\r\n            case \"drag_type\":\r\n                return new ElementTag(event.getType());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled) {\r\n            final InventoryHolder holder = inventory.getHolder();\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    entity.getPlayerEntity().updateInventory();\r\n                    if (holder instanceof Player) {\r\n                        ((Player) holder).updateInventory();\r\n                    }\r\n                }\r\n            }.runTaskLater(Denizen.getInstance(), 1);\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerDragsInInv(InventoryDragEvent event) {\r\n        if (EntityTag.isCitizensNPC(event.getWhoClicked())) {\r\n            return;\r\n        }\r\n        entity = EntityTag.getPlayerFrom(event.getWhoClicked());\r\n        inventory = event.getInventory();\r\n        dInv = InventoryTag.mirrorBukkitInventory(inventory);\r\n        item = new ItemTag(event.getOldCursor());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerEditsBookScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.BookScriptContainer;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerEditBookEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.meta.BookMeta;\r\n\r\npublic class PlayerEditsBookScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player edits book\r\n    // player signs book\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player edits or signs a book.\r\n    //\r\n    // @Context\r\n    // <context.title> returns the name of the book, if any.\r\n    // <context.pages> returns the number of pages in the book.\r\n    // <context.book> returns the book item being edited, containing the new page contents.\r\n    // <context.old_book> returns the book item being edited, containing the old page contents.\r\n    // <context.signing> returns whether the book is about to be signed.\r\n    //\r\n    // @Determine\r\n    // \"NOT_SIGNING\" to prevent the book from being signed.\r\n    // ScriptTag to set the book information to set it to instead.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerEditsBookScriptEvent() {\r\n        registerCouldMatcher(\"player edits book\");\r\n        registerCouldMatcher(\"player signs book\");\r\n        this.<PlayerEditsBookScriptEvent>registerTextDetermination(\"not_signing\", (evt) -> {\r\n            evt.event.setSigning(false);\r\n        });\r\n        this.<PlayerEditsBookScriptEvent, ScriptTag>registerOptionalDetermination(null, ScriptTag.class, (evt, context, value) -> {\r\n            if (value.getContainer() instanceof BookScriptContainer script) {\r\n                ItemTag dBook = script.getBookFrom(context);\r\n                BookMeta bookMeta = (BookMeta) dBook.getItemMeta();\r\n                if (dBook.getBukkitMaterial() == Material.WRITABLE_BOOK) {\r\n                    evt.event.setSigning(false);\r\n                }\r\n                evt.event.setNewBookMeta(bookMeta);\r\n                return true;\r\n            }\r\n            else {\r\n                Debug.echoError(\"Script '\" + value + \"' is valid, but not of type 'book'!\");\r\n                return false;\r\n            }\r\n        });\r\n    }\r\n\r\n    public PlayerEditBookEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String action = path.eventArgLowerAt(1);\r\n        if (!(action.equals(\"edits\") && !event.isSigning()) && !(action.equals(\"signs\") && event.isSigning())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"signing\" -> new ElementTag(event.isSigning());\r\n            case \"title\" -> event.isSigning() ? new ElementTag(event.getNewBookMeta().getTitle(), true) : null;\r\n            case \"pages\" -> new ElementTag(event.getNewBookMeta().getPageCount());\r\n            case \"book\" ->  {\r\n                ItemStack book = new ItemStack(event.isSigning() ? Material.WRITTEN_BOOK : Material.WRITABLE_BOOK);\r\n                book.setItemMeta(event.getNewBookMeta());\r\n                yield new ItemTag(book);\r\n            }\r\n            case \"old_book\" -> {\r\n                ItemStack book = new ItemStack(Material.WRITABLE_BOOK);\r\n                book.setItemMeta(event.getPreviousBookMeta());\r\n                yield new ItemTag(book);\r\n            }\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerEditsBook(PlayerEditBookEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerEmptiesBucketScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.objects.MaterialTag;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.PlayerBucketEmptyEvent;\n\npublic class PlayerEmptiesBucketScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // player empties bucket\n    // player empties <item>\n    //\n    // @Group Player\n    //\n    // @Location true\n    //\n    // @Triggers when a player empties a bucket.\n    //\n    // @Cancellable true\n    //\n    // @Context\n    // <context.item> returns the ItemTag of the bucket being emptied (just material, other properties are lost - use 'player.item_in_hand' if you need full data).\n    // <context.location> returns the LocationTag of the block clicked with the bucket.\n    // <context.relative> returns the LocationTag of the block in front of the clicked block.\n    //\n    // @Player Always.\n    //\n    // -->\n\n    public PlayerEmptiesBucketScriptEvent() {\n        registerCouldMatcher(\"player empties bucket\");\n        registerCouldMatcher(\"player empties <item>\");\n    }\n\n\n    public ItemTag item;\n    public MaterialTag material;\n    public LocationTag location;\n    public PlayerBucketEmptyEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        String iTest = path.eventArgLowerAt(2);\n        if ((!iTest.equals(\"bucket\") && !item.tryAdvancedMatcher(iTest, path.context)) || !runInCheck(path, location)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event != null ? EntityTag.getPlayerFrom(event.getPlayer()) : null, null);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            case \"location\":\n                return location;\n            case \"relative\":\n                return new LocationTag(event.getBlockClicked().getRelative(event.getBlockFace()).getLocation());\n            case \"item\":\n                return item;\n        }\n        return super.getContext(name);\n    }\n\n    @EventHandler\n    public void onBucketEmpty(PlayerBucketEmptyEvent event) {\n        location = new LocationTag(event.getBlockClicked().getLocation());\n        item = new ItemTag(event.getBucket());\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerEntersBedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerBedEnterEvent;\r\n\r\npublic class PlayerEntersBedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player enters bed\r\n    //\r\n    // @Regex ^on player enters bed$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player enters a bed.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the bed.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerEntersBedScriptEvent() {\r\n    }\r\n\r\n    public LocationTag location;\r\n    public PlayerBedEnterEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player enters bed\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"location\")) {\r\n            return location;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerEntersBed(PlayerBedEnterEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        location = new LocationTag(event.getBed().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerFillsBucketScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.objects.MaterialTag;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.PlayerBucketFillEvent;\n\npublic class PlayerFillsBucketScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // player fills bucket\n    // player fills <item>\n    //\n    // @Group Player\n    //\n    // @Location true\n    //\n    // @Triggers when a player fills a bucket.\n    //\n    // @Cancellable true\n    //\n    // @Context\n    // <context.item> returns the ItemTag of the filled bucket.\n    // <context.location> returns the LocationTag of the block clicked with the bucket.\n    // <context.material> returns the MaterialTag of the LocationTag.\n    //\n    // @Player Always.\n    //\n    // -->\n\n    public PlayerFillsBucketScriptEvent() {\n        registerCouldMatcher(\"player fills bucket\");\n        registerCouldMatcher(\"player fills <item>\");\n    }\n\n\n    public EntityTag entity;\n    public ItemTag item;\n    public MaterialTag material;\n    public LocationTag location;\n    public PlayerBucketFillEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        String iTest = path.eventArgLowerAt(2);\n        if ((!iTest.equals(\"bucket\") && !item.tryAdvancedMatcher(iTest, path.context)) || !runInCheck(path, location)) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        // TODO: Store the player / npc?\n        return new BukkitScriptEntryData(event != null ? EntityTag.getPlayerFrom(event.getPlayer()) : null,\n                entity.isNPC() ? entity.getDenizenNPC() : null);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            case \"location\":\n                return location;\n            case \"item\":\n                return item;\n            case \"material\":\n                return material;\n        }\n        return super.getContext(name);\n    }\n\n    @EventHandler\n    public void onBucketFill(PlayerBucketFillEvent event) {\n        entity = new EntityTag(event.getPlayer());\n        location = new LocationTag(event.getBlockClicked().getLocation());\n        item = new ItemTag(event.getItemStack());\n        material = new MaterialTag(event.getBlockClicked());\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerFishesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Item;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerFishEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic class PlayerFishesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player fishes (<entity>) (while <'state'>)\r\n    // player fishes (<item>) (while <'state'>)\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Switch with:<item> to only process the event if the fishing rod is a specified item.\r\n    //\r\n    // @Triggers when a player uses a fishing rod.\r\n    //\r\n    // @Context\r\n    // <context.hook> returns an EntityTag of the hook.\r\n    // <context.state> returns an ElementTag of the fishing state. Valid states: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/player/PlayerFishEvent.State.html>\r\n    // <context.entity> returns an EntityTag of the entity that got caught.\r\n    // <context.item> returns an ItemTag of the item gotten, if any.\r\n    // <context.xp> returns the amount of experience that will drop.\r\n    //\r\n    // @Determine\r\n    // \"CAUGHT:<ItemTag>\" to change the item that was caught (only if an item was already being caught).\r\n    // \"XP:<ElementTag(Number)>\" to change how much experience will drop.\r\n    //\r\n    // @Player If the fisher or the caught entity is a player (in most cases, the fisher can be assumed to be a real player).\r\n    // @NPC If the fisher or the caught entity is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public PlayerFishesScriptEvent() {\r\n        registerCouldMatcher(\"player fishes (<entity>) (while <'state'>)\");\r\n        registerCouldMatcher(\"player fishes (<item>) (while <'state'>)\");\r\n        registerSwitches(\"with\");\r\n    }\r\n\r\n    public EntityTag hook;\r\n    public ElementTag state;\r\n    public EntityTag entity;\r\n    public ItemTag item;\r\n    public PlayerFishEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String fish = path.eventArgLowerAt(2);\r\n        if (!fish.isEmpty() && !fish.equals(\"in\") && !fish.equals(\"while\")) {\r\n            if (entity == null) {\r\n                return false;\r\n            }\r\n            if (!entity.tryAdvancedMatcher(fish, path.context)) {\r\n                if (item == null) {\r\n                    return false;\r\n                }\r\n                if (!item.tryAdvancedMatcher(fish, path.context)) {\r\n                    return false;\r\n                }\r\n            }\r\n        }\r\n        String[] data = path.eventArgsLower;\r\n        for (int index = 2; index < data.length; index++) {\r\n            if (data[index].equals(\"while\") && !data[index + 1].equalsIgnoreCase(state.asString())) {\r\n                return false;\r\n            }\r\n        }\r\n        if (!runInCheck(path, hook.getLocation())) {\r\n            return false;\r\n        }\r\n        if (path.switches.containsKey(\"with\")) {\r\n            if (!EntityTag.isPlayer(event.getPlayer())) {\r\n                return false;\r\n            }\r\n            ItemStack held = event.getPlayer().getEquipment().getItemInMainHand();\r\n            if (held.getType() != Material.FISHING_ROD) {\r\n                held = event.getPlayer().getEquipment().getItemInOffHand();\r\n            }\r\n            if (!runWithCheck(path, new ItemTag(held))) {\r\n                return false;\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determination = determinationObj.toString();\r\n            String determinationLower = CoreUtilities.toLowerCase(determination);\r\n            if (determinationLower.startsWith(\"caught:\")) {\r\n                item = ItemTag.valueOf(determination.substring(\"caught:\".length()), getTagContext(path));\r\n                if (entity != null && entity.getBukkitEntity() instanceof Item itemEnt) {\r\n                    itemEnt.setItemStack(item.getItemStack());\r\n                    return true;\r\n                }\r\n            }\r\n            else if (determinationLower.startsWith(\"xp:\")) {\r\n                int newXP = new ElementTag(determination.substring(\"xp:\".length())).asInt();\r\n                event.setExpToDrop(newXP);\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(EntityTag.isPlayer(event.getPlayer()) ? EntityTag.getPlayerFrom(event.getPlayer()) :\r\n                EntityTag.isPlayer(event.getCaught()) ? EntityTag.getPlayerFrom(event.getCaught()) : null,\r\n                EntityTag.isCitizensNPC(event.getPlayer()) ? EntityTag.getNPCFrom(event.getPlayer()) :\r\n                        EntityTag.isCitizensNPC(event.getCaught()) ? EntityTag.getNPCFrom(event.getCaught()) : null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"hook\")) {\r\n            return hook;\r\n        }\r\n        else if (name.equals(\"entity\") && entity != null) {\r\n            return entity.getDenizenObject();\r\n        }\r\n        else if (name.equals(\"item\") && item != null) {\r\n            return item;\r\n        }\r\n        else if (name.equals(\"state\")) {\r\n            return state;\r\n        }\r\n        else if (name.equals(\"xp\")) {\r\n            return new ElementTag(event.getExpToDrop());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerFishes(PlayerFishEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        Entity hookEntity = event.getHook();\r\n        EntityTag.rememberEntity(hookEntity);\r\n        hook = new EntityTag(hookEntity);\r\n        state = new ElementTag(event.getState().toString());\r\n        item = null;\r\n        entity = null;\r\n        Entity caughtEntity = event.getCaught();\r\n        if (caughtEntity != null) {\r\n            EntityTag.rememberEntity(caughtEntity);\r\n            entity = new EntityTag(caughtEntity);\r\n            if (caughtEntity instanceof Item) {\r\n                item = new ItemTag(((Item) caughtEntity).getItemStack());\r\n            }\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n        EntityTag.forgetEntity(hookEntity);\r\n        EntityTag.forgetEntity(caughtEntity);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerFlyingScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerToggleFlightEvent;\r\n\r\npublic class PlayerFlyingScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player toggles flying\r\n    // player starts flying\r\n    // player stops flying\r\n    //\r\n    // @Regex ^on player (toggles|starts|stops) flying$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player starts or stops flying.\r\n    //\r\n    // @Context\r\n    // <context.state> returns an ElementTag(Boolean) with a value of \"true\" if the player is now flying and \"false\" otherwise.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerFlyingScriptEvent() {\r\n    }\r\n\r\n    public boolean state;\r\n    public PlayerToggleFlightEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventArgLowerAt(2).equals(\"flying\") && !path.eventArgLowerAt(2).equals(\"flight\")) {\r\n            return false;\r\n        }\r\n        if (!path.eventArgLowerAt(0).equals(\"player\")) {\r\n            return false;\r\n        }\r\n        if (!path.eventArgLowerAt(1).equals(\"starts\") && !path.eventArgLowerAt(1).equals(\"stops\") && !path.eventArgLowerAt(1).equals(\"toggles\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        if (cmd.equals(\"starts\") && !state) {\r\n            return false;\r\n        }\r\n        if (cmd.equals(\"stops\") && state) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"state\")) {\r\n            return new ElementTag(state);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerFlying(PlayerToggleFlightEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        state = event.isFlying();\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerHearsSoundScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Registry;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class PlayerHearsSoundScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player hears sound\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player receives a sound packet from the server.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch key:<sound_key> to only process the event if the sound matches the modern Minecraft sound key.\r\n    //\r\n    // @Context\r\n    // <context.sound_key> returns an ElementTag of the modern Minecraft sound key.\r\n    // <context.sound_name> returns an ElementTag of the sound's Bukkit name.\r\n    // <context.category> returns the name of the category the sound is from.\r\n    // <context.is_custom> returns 'true' if the sound is custom, otherwise false.\r\n    // <context.source_entity> returns the entity this sound came from (if any).\r\n    // <context.location> returns the location the sound will play at.\r\n    // <context.volume> returns the volume level.\r\n    // <context.pitch> returns the pitch.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerHearsSoundScriptEvent() {\r\n        instance = this;\r\n        registerCouldMatcher(\"player hears sound\");\r\n        registerSwitches(\"key\");\r\n    }\r\n\r\n    public static PlayerHearsSoundScriptEvent instance;\r\n\r\n    public Player player;\r\n    public String soundName;\r\n    public String category;\r\n    public boolean isCustom;\r\n    public Entity entity;\r\n    public Location location;\r\n    public float volume, pitch;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"key\", soundName)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player);\r\n    }\r\n\r\n    @Override\r\n    public void init() {\r\n        NetworkInterceptHelper.enable();\r\n        super.init();\r\n    }\r\n\r\n    @Override\r\n    public void destroy() {\r\n        entity = null;\r\n        player = null;\r\n        location = null;\r\n        super.destroy();\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"sound_key\": return new ElementTag(soundName);\r\n            case \"sound_name\": return isCustom ? null : Utilities.enumLikeToLegacyElement(Registry.SOUNDS.get(Utilities.parseNamespacedKey(soundName)));\r\n            case \"category\": return new ElementTag(category);\r\n            case \"is_custom\": return new ElementTag(isCustom);\r\n            case \"source_entity\": return entity == null ? null : new EntityTag(entity);\r\n            case \"location\": return new LocationTag(location);\r\n            case \"volume\": return new ElementTag(volume);\r\n            case \"pitch\": return new ElementTag(pitch);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    public boolean run(Player player, String soundName, String category, boolean isCustom, Entity entity, Location location, float volume, float pitch) {\r\n        this.player = player;\r\n        this.soundName = soundName;\r\n        this.category = category;\r\n        this.isCustom = isCustom;\r\n        this.entity = entity;\r\n        this.location = location == null ? entity.getLocation() : location;\r\n        this.volume = volume;\r\n        this.pitch = pitch;\r\n        ScriptEvent fired = fire();\r\n        return fired.cancelled;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerIncreasesExhaustionLevelScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityExhaustionEvent;\r\n\r\npublic class PlayerIncreasesExhaustionLevelScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player exhaustion level increases\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player does an activity that increases their exhaustion level, which increases the rate of hunger.\r\n    //\r\n    // @Switch reason:<reason> to only process the event if the reason matches a specific reason.\r\n    //\r\n    // @Context\r\n    // <context.exhaustion> returns the amount of exhaustion added to the player.\r\n    // <context.reason> returns the reason of exhaustion. See <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityExhaustionEvent.ExhaustionReason.html> for a list of valid reasons.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Decimal) to change the amount of exhaustion that will be added to the player.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Warning This event may fire very rapidly.\r\n    //\r\n    // -->\r\n\r\n    public PlayerIncreasesExhaustionLevelScriptEvent() {\r\n        registerCouldMatcher(\"player exhaustion level increases\");\r\n        registerSwitches(\"reason\");\r\n    }\r\n\r\n    public EntityExhaustionEvent event;\r\n\r\n    public ElementTag reason;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runGenericSwitchCheck(path, \"reason\", reason.asString())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getEntity().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"exhaustion\": return new ElementTag(event.getExhaustion());\r\n            case \"reason\": return reason;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            ElementTag value = determinationObj.asElement();\r\n            if (value.isFloat()) {\r\n                event.setExhaustion(value.asFloat());\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getEntity());\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerIncreasesExhaustionLevel(EntityExhaustionEvent event) {\r\n        if (EntityTag.isNPC(event.getEntity())) {\r\n            return;\r\n        }\r\n        reason = new ElementTag(event.getExhaustionReason());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerInputScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Input;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerInputEvent;\r\n\r\npublic class PlayerInputScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player input\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player sends updated keyboard/gamepad movement control input to the server.\r\n    //\r\n    // @Context\r\n    // <context.backward> returns whether the player is providing backwards movement input (normally this means they are pressing S).\r\n    // <context.forward> returns whether the player is providing forwards movement input (normally this means they are pressing W).\r\n    // <context.left> returns whether the player is providing left movement input (normally this means they are pressing A).\r\n    // <context.right> returns whether the player is providing right movement input (normally this means they are pressing D).\r\n    // <context.jump> returns whether the player is providing jump input (normally this means they are pressing SPACEBAR).\r\n    // <context.sneak> returns whether the player is providing sneak input (normally this means they are pressing SHIFT).\r\n    // <context.sprint> returns whether the player is providing sprint input (normally this means they are pressing CONTROL).\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerInputScriptEvent() {\r\n        registerCouldMatcher(\"player input\");\r\n    }\r\n\r\n    public PlayerInputEvent event;\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    public ObjectTag getContext(String name) {\r\n        Input i = event.getInput();\r\n        return switch (name) {\r\n            case \"backward\" -> new ElementTag(i.isBackward());\r\n            case \"forward\" -> new ElementTag(i.isForward());\r\n            case \"left\" -> new ElementTag(i.isLeft());\r\n            case \"right\" -> new ElementTag(i.isRight());\r\n            case \"jump\" -> new ElementTag(i.isJump());\r\n            case \"sneak\" -> new ElementTag(i.isSneak());\r\n            case \"sprint\" -> new ElementTag(i.isSprint());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerInputEvent(PlayerInputEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerItemTakesDamageScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.inventory.SlotHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerItemDamageEvent;\r\n\r\npublic class PlayerItemTakesDamageScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player item takes damage\r\n    // player <item> takes damage\r\n    //\r\n    // @Synonyms item durability changes\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when the player damages an item.\r\n    //\r\n    // @Context\r\n    // <context.damage> returns the amount of damage the item has taken.\r\n    // <context.original_damage> returns the original amount of damage the item would have taken, before any modifications such as the unbreaking enchantment (only on Paper).\r\n    // <context.item> returns the item that has taken damage.\r\n    // <context.slot> returns the slot of the item that has taken damage. This value is a bit of a hack and is not reliable.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Number) to set the amount of damage the item will take.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerItemTakesDamageScriptEvent() {\r\n        registerCouldMatcher(\"player <item> takes damage\");\r\n    }\r\n\r\n    public PlayerItemDamageEvent event;\r\n    ItemTag item;\r\n    LocationTag location;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(1, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"item\": return item;\r\n            case \"damage\": return new ElementTag(event.getDamage());\r\n            case \"slot\": return new ElementTag(SlotHelper.slotForItem(event.getPlayer().getInventory(), item.getItemStack()) + 1);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isInt()) {\r\n            event.setDamage(element.asInt());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public BukkitScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled) {\r\n            final Player p = event.getPlayer();\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), p::updateInventory, 1);\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerItemTakesDamage(PlayerItemDamageEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        item = new ItemTag(event.getItem());\r\n        location = new LocationTag(event.getPlayer().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerJoinsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerJoinEvent;\r\n\r\npublic class PlayerJoinsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player joins\r\n    // player join\r\n    //\r\n    // @Regex ^on player (joins|join)$\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player joins the server.\r\n    //\r\n    // @Context\r\n    // <context.message> returns an ElementTag of the join message.\r\n    //\r\n    // @Determine\r\n    // ElementTag to change the join message.\r\n    // \"NONE\" to cancel the join message.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerJoinsScriptEvent() {\r\n    }\r\n\r\n    public PlayerJoinEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player join\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determination = determinationObj.toString();\r\n            if (CoreUtilities.equalsIgnoreCase(determination, \"none\")) {\r\n                event.setJoinMessage(null);\r\n                return true;\r\n            }\r\n            event.setJoinMessage(determination);\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"message\")) {\r\n            return new ElementTag(event.getJoinMessage());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerJoins(PlayerJoinEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerJumpScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerMoveEvent;\r\n\r\npublic class PlayerJumpScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player jumps\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player jumps.\r\n    //\r\n    // @Warning On Spigot servers, this event and its data are inaccurate and unreliable. This event works more stably on Paper servers.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the location the player jumped from.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerJumpScriptEvent() {\r\n        registerCouldMatcher(\"player jumps\");\r\n    }\r\n\r\n\r\n    public LocationTag location;\r\n    public PlayerTag player;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"location\")) {\r\n            return location;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    public static class PlayerJumpsSpigotScriptEventImpl extends PlayerJumpScriptEvent {\r\n\r\n        @EventHandler\r\n        public void onPlayerJumps(PlayerMoveEvent event) {\r\n            if (EntityTag.isNPC(event.getPlayer())) {\r\n                return;\r\n            }\r\n            // Check that the block level changed (Upward)\r\n            if (event.getTo().getBlockY() > event.getFrom().getBlockY()\r\n                    // and also that the player has a high velocity (jump instead of walking up stairs)\r\n                    && Math.abs(event.getPlayer().getVelocity().getY()) > 0.1\r\n                    // and that the player isn't in any form of fast moving vehicle\r\n                    && event.getPlayer().getVehicle() == null) {\r\n                // Not perfect checking, but close enough until Bukkit adds a proper event\r\n                location = new LocationTag(event.getFrom());\r\n                player = new PlayerTag(event.getPlayer());\r\n                fire(event);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerKickedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerKickEvent;\r\n\r\npublic class PlayerKickedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player kicked (for flying)\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player is kicked from the server.\r\n    //\r\n    // @Context\r\n    // <context.message> returns an ElementTag of the kick message sent to all players.\r\n    // <context.reason> returns an ElementTag of the kick reason.\r\n    // <context.flying> returns whether the player is being automatically kicked for flying.\r\n    //\r\n    // @Determine\r\n    // \"MESSAGE:<ElementTag>\" to change the kick message.\r\n    // \"REASON:<ElementTag>\" to change the kick reason.\r\n    // \"FLY_COOLDOWN:<DurationTag>\" to cancel the automatic fly kick and set its next cooldown.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerKickedScriptEvent() {\r\n        registerCouldMatcher(\"player kicked (for flying)\");\r\n    }\r\n\r\n    public PlayerTag player;\r\n    public PlayerKickEvent event;\r\n\r\n    public boolean isFlying() {\r\n        return NMSHandler.playerHelper.getFlyKickCooldown(player.getPlayerEntity()) == 0;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.eventArgLowerAt(3).equals(\"flying\")) {\r\n            return isFlying();\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determination = determinationObj.toString();\r\n            String lower = CoreUtilities.toLowerCase(determination);\r\n            if (lower.startsWith(\"message:\")) {\r\n                event.setLeaveMessage(determination.substring(\"message:\".length()));\r\n                return true;\r\n            }\r\n            else if (lower.startsWith(\"reason:\")) {\r\n                event.setReason(determination.substring(\"reason:\".length()));\r\n                return true;\r\n            }\r\n            else if (lower.startsWith(\"fly_cooldown:\")) {\r\n                DurationTag duration = DurationTag.valueOf(determination.substring(\"fly_cooldown:\".length()), getTagContext(path));\r\n                if (duration != null) {\r\n                    NMSHandler.playerHelper.setFlyKickCooldown(player.getPlayerEntity(), (int) duration.getTicks());\r\n                    cancelled = true;\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"message\":\r\n                return new ElementTag(event.getLeaveMessage());\r\n            case \"reason\":\r\n                return new ElementTag(event.getReason());\r\n            case \"flying\":\r\n                return new ElementTag(isFlying());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerKicked(PlayerKickEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        player = PlayerTag.mirrorBukkitPlayer(event.getPlayer());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerLeashesEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.PlayerLeashEntityEvent;\r\n\r\npublic class PlayerLeashesEntityScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player leashes entity\r\n    // player leashes <entity>\r\n    //\r\n    // @Regex ^on player leashes [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player leashes an entity.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the leashed entity.\r\n    // <context.holder> returns the EntityTag that is holding the leash.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerLeashesEntityScriptEvent() {\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public PlayerTag holder;\r\n    public PlayerLeashEntityEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player leashes\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchEntity(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(holder, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"holder\")) {\r\n            return holder;\r\n        }\r\n        else if (name.equals(\"entity\")) {\r\n            return entity;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerLeashes(PlayerLeashEntityEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        holder = PlayerTag.mirrorBukkitPlayer(event.getPlayer());\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerLeavesBedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerBedLeaveEvent;\r\n\r\npublic class PlayerLeavesBedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player leaves bed\r\n    //\r\n    // @Regex ^on player leaves bed$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player leaves a bed.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the bed.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerLeavesBedScriptEvent() {\r\n    }\r\n\r\n    public LocationTag location;\r\n    public PlayerBedLeaveEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player leaves bed\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"location\")) {\r\n            return location;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerLeavesBed(PlayerBedLeaveEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        location = new LocationTag(event.getBed().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerLevelsUpScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerLevelChangeEvent;\r\n\r\npublic class PlayerLevelsUpScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player levels up (from <'level'>) (to <'level'>)\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player levels up.\r\n    //\r\n    // @Context\r\n    // <context.new_level> returns an ElementTag of the player's new level.\r\n    // <context.old_level> returns an ElementTag of the player's old level.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerLevelsUpScriptEvent() {\r\n        registerCouldMatcher(\"player levels up (from <'level'>) (to <'level'>)\");\r\n    }\r\n\r\n    public int new_level;\r\n    public int old_level;\r\n    public PlayerTag player;\r\n    public PlayerLevelChangeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String[] data = path.eventArgsLower;\r\n        for (int index = 3; index < data.length; index++) {\r\n            if (data[index].equals(\"from\")) {\r\n                if (Integer.parseInt(data[index + 1]) != old_level) {\r\n                    return false;\r\n                }\r\n            }\r\n            if (data[index].equals(\"to\")) {\r\n                if (Integer.parseInt(data[index + 1]) != new_level) {\r\n                    return false;\r\n                }\r\n            }\r\n        }\r\n\r\n        if (!runInCheck(path, player.getLocation())) {\r\n            return false;\r\n        }\r\n\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"level\") || name.equals(\"new_level\")) {\r\n            return new ElementTag(new_level);\r\n        }\r\n        else if (name.equals(\"old_level\")) {\r\n            return new ElementTag(old_level);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerLevels(PlayerLevelChangeEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        player = PlayerTag.mirrorBukkitPlayer(event.getPlayer());\r\n        old_level = event.getOldLevel();\r\n        new_level = event.getNewLevel();\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerLocaleChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerLocaleChangeEvent;\r\n\r\npublic class PlayerLocaleChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player locale change\r\n    //\r\n    // @Regex ^on player locale change$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player changes their locale in their client settings.\r\n    //\r\n    // @Context\r\n    // <context.new_locale> returns an ElementTag of the player's new locale.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerLocaleChangeScriptEvent() {\r\n    }\r\n\r\n    public PlayerLocaleChangeEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player locale change\");\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"new_locale\")) {\r\n            return new ElementTag(event.getLocale());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerLocaleChange(PlayerLocaleChangeEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerLoginScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerLoginEvent;\r\n\r\npublic class PlayerLoginScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player logs in (for the first time)\r\n    // player (first) login\r\n    //\r\n    // @Regex ^on player( logs in( for the first time)?|( first)? login)$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player logs in to the server. This is during the authentication process, and should NOT be confused with <@link event player joins>.\r\n    //\r\n    // @Warning Generally avoid this event. This is not a way to get a 'first join' event. This is an internal technical event, with specific uses (eg custom whitelisting).\r\n    //\r\n    // @Context\r\n    // <context.hostname> returns an ElementTag of the player's IP address.\r\n    // <context.server_hostname> returns an ElementTag of the server address that the player used to connect to the server.\r\n    //\r\n    // @Determine\r\n    // \"KICKED\" to kick the player from the server.\r\n    // \"KICKED:<ElementTag>\" to kick the player and specify a message to show.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerLoginScriptEvent() {\r\n    }\r\n\r\n    public PlayerLoginEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player login\") || path.eventLower.startsWith(\"player first login\")\r\n                || path.eventLower.startsWith(\"player logs in\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.eventLower.contains(\"first\") && PlayerTag.isNoted(event.getPlayer())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determination = determinationObj.toString();\r\n            if (CoreUtilities.toLowerCase(determination).startsWith(\"kicked\")) {\r\n                String message = determination.length() > \"KICKED:\".length() ? determination.substring(\"KICKED:\".length()) : determination;\r\n                event.disallow(PlayerLoginEvent.Result.KICK_OTHER, message);\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"hostname\" -> new ElementTag(event.getAddress().toString());\r\n            case \"server_hostname\" -> new ElementTag(event.getHostname());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerLogin(PlayerLoginEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerMendsItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.inventory.SlotHelper;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerItemMendEvent;\r\n\r\npublic class PlayerMendsItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player mends item\r\n    // player mends <item>\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when an XP orb is used to repair an item with the Mending enchantment in the player's inventory.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the item that is repaired.\r\n    // <context.repair_amount> returns how much durability the item recovers.\r\n    // <context.xp_orb> returns the XP orb that triggered the event.\r\n    // <context.slot> returns the slot of the item that has been repaired. This value is a bit of a hack and is not reliable.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Number) to set the amount of durability the item recovers.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerMendsItemScriptEvent() {\r\n        registerCouldMatcher(\"player mends <item>\");\r\n    }\r\n\r\n    public PlayerItemMendEvent event;\r\n    public ItemTag item;\r\n    public LocationTag location;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"item\": return item;\r\n            case \"repair_amount\": return new ElementTag(event.getRepairAmount());\r\n            case \"xp_orb\": return new EntityTag(event.getExperienceOrb());\r\n            case \"slot\": return new ElementTag(SlotHelper.slotForItem(event.getPlayer().getInventory(), item.getItemStack()) + 1);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isInt()) {\r\n            event.setRepairAmount(element.asInt());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerItemMend(PlayerItemMendEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        item = new ItemTag(event.getItem());\r\n        location = new LocationTag(event.getPlayer().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerOpensInvScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.InventoryOpenEvent;\r\n\r\npublic class PlayerOpensInvScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player opens inventory\r\n    // player opens <inventory>\r\n    //\r\n    // @Regex ^on player opens [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player opens an inventory. (EG, chests, not the player's main inventory.)\r\n    //\r\n    // @Context\r\n    // <context.inventory> returns the InventoryTag.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerOpensInvScriptEvent() {\r\n    }\r\n\r\n\r\n    public InventoryTag inventory;\r\n    public InventoryOpenEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player opens\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchInventory(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, inventory)) {\r\n            return false;\r\n        }\r\n        Location loc = inventory.getLocation();\r\n        if (loc == null) {\r\n            loc = event.getPlayer().getLocation();\r\n        }\r\n        if (!runInCheck(path, loc)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        // TODO: Store the player / npc?\r\n        return new BukkitScriptEntryData(event != null ? EntityTag.getPlayerFrom(event.getPlayer()) : null, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"inventory\")) {\r\n            return inventory;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerOpensInv(InventoryOpenEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        inventory = InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPickupArrowScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerPickupArrowEvent;\r\n\r\npublic class PlayerPickupArrowScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player picks up launched arrow\r\n    //\r\n    // @Regex ^on player picks up launched arrow$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player picks up a launched arrow projectile entity that is embedded into the ground. Will not necessarily fire for creative players.\r\n    //\r\n    // @Context\r\n    // <context.arrow> returns the arrow entity.\r\n    // <context.item> returns the item of the arrow.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerPickupArrowScriptEvent() {\r\n    }\r\n\r\n\r\n    public PlayerPickupArrowEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player picks up launched arrow\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"arrow\")) {\r\n            return new EntityTag(event.getArrow());\r\n        }\r\n        else if (name.equals(\"item\")) {\r\n            return new ItemTag(event.getItem().getItemStack());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerConsumes(PlayerPickupArrowEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPlacesBlockScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockPlaceEvent;\r\n\r\npublic class PlayerPlacesBlockScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player places block\r\n    // player places <item>\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch using:<hand_type> to only process the event if the player is using the specified hand type (HAND or OFF_HAND).\r\n    // @Switch against:<location> to only process the event if block that this new block is being placed against matches the specified LocationTag matcher.\r\n    // @Switch type:<material> to only process the event if the block placed matches the MaterialTag matcher input.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player places a block.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the block that was placed.\r\n    // <context.material> returns the MaterialTag of the block that was placed.\r\n    // <context.old_material> returns the MaterialTag of the block that was replaced.\r\n    // <context.item_in_hand> returns the ItemTag of the item in hand.\r\n    // <context.hand> returns the name of the hand that the block was in (HAND or OFF_HAND).\r\n    // <context.against> returns the LocationTag of the block this block was placed against.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Example\r\n    // on player places block:\r\n    //\r\n    // @Example\r\n    // after player places torch using:off_hand:\r\n    //\r\n    // @Example\r\n    // on player places cactus against:sand:\r\n    //\r\n    // @Example\r\n    // # This example process the event only if the player places any block that isn't tnt.\r\n    // on player places block type:!tnt:\r\n    // - announce \"<player.name> has placed a block that isn't TNT. Lucky!\"\r\n    //\r\n    // -->\r\n\r\n    public PlayerPlacesBlockScriptEvent() {\r\n        registerCouldMatcher(\"player places <material>\");\r\n        registerSwitches(\"using\", \"against\", \"type\");\r\n    }\r\n\r\n    public BlockPlaceEvent event;\r\n    public LocationTag location, against;\r\n    public MaterialTag material;\r\n    public ElementTag hand;\r\n    public ItemTag item_in_hand;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String mat = path.eventArgLowerAt(2);\r\n        if (!item_in_hand.tryAdvancedMatcher(mat, path.context) && !material.tryAdvancedMatcher(mat, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"using\", hand.asString())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"against\", against)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"type\", material)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"location\": return location;\r\n            case \"material\": return material;\r\n            case \"old_material\": return new MaterialTag(event.getBlockReplacedState());\r\n            case \"item_in_hand\": return item_in_hand;\r\n            case \"hand\": return hand;\r\n            case \"against\": return against;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerPlacesBlock(BlockPlaceEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        hand = new ElementTag(event.getHand());\r\n        material = new MaterialTag(event.getBlock());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        against = new LocationTag(event.getBlockAgainst().getLocation());\r\n        item_in_hand = new ItemTag(event.getItemInHand());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPlacesHangingScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.hanging.HangingPlaceEvent;\r\n\r\npublic class PlayerPlacesHangingScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player places <hanging>\r\n    //\r\n    // @Switch item:<item> to only process the event when the hangable item matches the given ItemTag matcher.\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a hanging entity (painting or itemframe) is placed.\r\n    //\r\n    // @Context\r\n    // <context.hanging> returns the EntityTag of the hanging.\r\n    // <context.location> returns the LocationTag of the block the hanging was placed on.\r\n    // <context.item> returns the ItemTag that was placed.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerPlacesHangingScriptEvent() {\r\n        registerCouldMatcher(\"player places <hanging>\");\r\n        registerSwitches(\"item\");\r\n    }\r\n\r\n    public EntityTag hanging;\r\n    public ItemTag item;\r\n    public LocationTag location;\r\n    public HangingPlaceEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String hangCheck = path.eventArgLowerAt(2);\r\n        if (!hanging.tryAdvancedMatcher(hangCheck, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"item\", item)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"hanging\": return hanging;\r\n            case \"location\": return location;\r\n            case \"item\": return item;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerPlacesHanging(HangingPlaceEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        Entity hangingEntity = event.getEntity();\r\n        EntityTag.rememberEntity(hangingEntity);\r\n        hanging = new EntityTag(hangingEntity);\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        item = new ItemTag(event.getItemStack());\r\n        this.event = event;\r\n        fire(event);\r\n        EntityTag.forgetEntity(hangingEntity);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreLoginScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.QueueTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.OfflinePlayer;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.AsyncPlayerPreLoginEvent;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.concurrent.Future;\r\nimport java.util.concurrent.TimeUnit;\r\n\r\npublic class PlayerPreLoginScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player prelogin\r\n    //\r\n    // @Regex ^on player prelogin$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player starts to log in to the server.\r\n    // This is during the EARLY authentication process, and should NOT be confused with <@link event player joins>.\r\n    //\r\n    // @Warning This is a very special-case handler, that delays logins until the events are handled on the main thread.\r\n    // Generally, prefer <@link event on player logs in>.\r\n    //\r\n    // @Context\r\n    // <context.hostname> returns an ElementTag of the player's hostname.\r\n    // <context.name> returns an ElementTag of the player's name.\r\n    // <context.uuid> returns an ElementTag of the player's UUID.\r\n    //\r\n    // @Determine\r\n    // QueueTag to cause the event to wait until the queue is complete.\r\n    // \"KICKED\" to kick the player from the server.\r\n    // \"KICKED <ElementTag>\" to kick the player and specify a message to show.\r\n    //\r\n    // @Player When the player has previously joined (and thus the UUID is valid).\r\n    //\r\n    // -->\r\n\r\n    public PlayerPreLoginScriptEvent() {\r\n    }\r\n\r\n    public AsyncPlayerPreLoginEvent event;\r\n    public PlayerTag player;\r\n    public List<QueueTag> waitForQueues = new ArrayList<>();\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player prelogin\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (determinationObj instanceof ElementTag) {\r\n            if (CoreUtilities.toLowerCase(determination).startsWith(\"kicked\")) {\r\n                String message = determination.length() > 7 ? determination.substring(7) : determination;\r\n                event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, message);\r\n                return true;\r\n            }\r\n        }\r\n        if (QueueTag.matches(determination)) {\r\n            QueueTag newQueue = QueueTag.valueOf(determination, getTagContext(path));\r\n            if (newQueue != null && newQueue.getQueue() != null) {\r\n                waitForQueues.add(newQueue);\r\n            }\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"hostname\":\r\n                return new ElementTag(event.getAddress().toString());\r\n            case \"name\":\r\n                return new ElementTag(event.getName());\r\n            case \"uuid\":\r\n                return new ElementTag(event.getUniqueId().toString());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    public boolean needsToWait() {\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.log(\"Prelogin: queues that might need waiting: \" + waitForQueues.size());\r\n        }\r\n        for (QueueTag queue : waitForQueues) {\r\n            if (!queue.getQueue().isStopped) {\r\n                if (CoreConfiguration.debugVerbose) {\r\n                    Debug.log(\"Prelogin: need to wait for \" + queue.getQueue().id);\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.log(\"Prelogin: no need to wait\");\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerLogin(AsyncPlayerPreLoginEvent event) {\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            PlayerPreLoginScriptEvent altEvent = (PlayerPreLoginScriptEvent) clone();\r\n            Future future = Bukkit.getScheduler().callSyncMethod(Denizen.getInstance(), () -> {\r\n                altEvent.onPlayerLogin(event);\r\n                return null;\r\n            });\r\n            try {\r\n                future.get(30, TimeUnit.SECONDS);\r\n                while (altEvent.needsToWait()) {\r\n                    Thread.sleep(50);\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            return;\r\n        }\r\n        waitForQueues = new ArrayList<>();\r\n        OfflinePlayer bukkitPlayer = Bukkit.getOfflinePlayer(event.getUniqueId());\r\n        if (bukkitPlayer != null && bukkitPlayer.getName() != null) {\r\n            player = new PlayerTag(bukkitPlayer);\r\n        }\r\n        else {\r\n            player = null;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreparesAnvilCraftScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.PrepareAnvilEvent;\r\n\r\npublic class PlayerPreparesAnvilCraftScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player prepares anvil craft item\r\n    // player prepares anvil craft <item>\r\n    //\r\n    // @Regex ^on player prepares anvil craft [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player prepares an anvil to craft an item.\r\n    //\r\n    // @Warning The player doing the crafting is estimated and may be inaccurate.\r\n    //\r\n    // @Context\r\n    // <context.inventory> returns the InventoryTag of the anvil inventory.\r\n    // <context.item> returns the ItemTag to be crafted.\r\n    // <context.repair_cost> returns an ElementTag(Number) of the repair cost.\r\n    // <context.new_name> returns an ElementTag of the new name.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Number) to set the repair cost.\r\n    // ItemTag to change the item that is crafted.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerPreparesAnvilCraftScriptEvent() {\r\n    }\r\n\r\n    public PrepareAnvilEvent event;\r\n    public ItemTag result;\r\n    public PlayerTag player;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player prepares anvil craft\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchItem(path.eventArgLowerAt(4))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(4, result)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isInt()) {\r\n            event.getInventory().setRepairCost(element.asInt());\r\n            return true;\r\n        }\r\n        String determination = determinationObj.toString();\r\n        if (ItemTag.matches(determination)) {\r\n            result = ItemTag.valueOf(determination, path.container);\r\n            event.setResult(result.getItemStack());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"item\":\r\n                return result;\r\n            case \"repair_cost\":\r\n                return new ElementTag(event.getInventory().getRepairCost());\r\n            case \"new_name\":\r\n                return new ElementTag(event.getInventory().getRenameText());\r\n            case \"inventory\":\r\n                return InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onCraftItem(PrepareAnvilEvent event) {\r\n        if (event.getInventory().getViewers().isEmpty()) {\r\n            return;\r\n        }\r\n        HumanEntity humanEntity = event.getInventory().getViewers().get(0);\r\n        if (EntityTag.isNPC(humanEntity)) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        result = new ItemTag(event.getResult());\r\n        this.player = EntityTag.getPlayerFrom(humanEntity);\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreparesEnchantScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.enchantments.EnchantmentOffer;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.enchantment.PrepareItemEnchantEvent;\r\n\r\npublic class PlayerPreparesEnchantScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player prepares item enchant\r\n    // player prepares <item> enchant\r\n    //\r\n    // @Regex ^on player prepares [^\\s]+ enchant$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player prepares to enchant an item.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the enchanting block.\r\n    // <context.item> returns the ItemTag to be enchanted.\r\n    // <context.bonus> returns an ElementTag(Number) of the enchanting bonus available (number of bookshelves).\r\n    // <context.offers> returns a ListTag of the available enchanting offers, each as a MapTag with keys 'cost', 'enchantment_type', and 'level'.\r\n    //\r\n    // @Determine\r\n    // \"OFFERS:<ListTag>\" of MapTags to set the offers available. Cannot be a different size list than the size of context.offers.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerPreparesEnchantScriptEvent() {\r\n    }\r\n\r\n    public PrepareItemEnchantEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player prepares\") || !path.eventArgLowerAt(3).equals(\"enchant\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchItem(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, new ItemTag(event.getItem()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determineLow = CoreUtilities.toLowerCase(determinationObj.toString());\r\n            if (determineLow.startsWith(\"offers:\")) {\r\n                ListTag offers = ListTag.valueOf(determineLow.substring(\"offers:\".length()), getTagContext(path));\r\n                if (offers.size() != event.getOffers().length) {\r\n                    Debug.echoError(\"Offer list size incorrect.\");\r\n                    return false;\r\n                }\r\n                for (int i = 0; i < offers.size(); i++) {\r\n                    MapTag map = MapTag.getMapFor(offers.getObject(i), getTagContext(path));\r\n                    if (map.isEmpty()) {\r\n                        event.getOffers()[i] = null;\r\n                        continue;\r\n                    }\r\n                    event.getOffers()[i].setCost(map.getElement(\"cost\").asInt());\r\n                    EnchantmentTag enchantment = map.getObjectAs(\"enchantment_type\", EnchantmentTag.class, getTagContext(path));\r\n                    if (enchantment == null) {\r\n                        enchantment = map.getObjectAs(\"enchantment\", EnchantmentTag.class, getTagContext(path));\r\n                    }\r\n                    event.getOffers()[i].setEnchantment(enchantment.enchantment);\r\n                    event.getOffers()[i].setEnchantmentLevel(map.getElement(\"level\").asInt());\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getEnchanter());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"item\":\r\n                return new ItemTag(event.getItem());\r\n            case \"location\":\r\n                return new LocationTag(event.getEnchantBlock().getLocation());\r\n            case \"bonus\":\r\n                return new ElementTag(event.getEnchantmentBonus());\r\n            case \"offers\":\r\n                ListTag output = new ListTag();\r\n                for (EnchantmentOffer offer : event.getOffers()) {\r\n                    if (offer == null) {\r\n                        output.addObject(new MapTag());\r\n                        continue;\r\n                    }\r\n                    MapTag map = new MapTag();\r\n                    map.putObject(\"cost\", new ElementTag(offer.getCost()));\r\n                    map.putObject(\"enchantment\", new ElementTag(offer.getEnchantment().getKey().getKey()));\r\n                    map.putObject(\"enchantment_type\", new EnchantmentTag(offer.getEnchantment()));\r\n                    map.putObject(\"level\", new ElementTag(offer.getEnchantmentLevel()));\r\n                    output.addObject(map);\r\n                }\r\n                return output;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onEnchantItem(PrepareItemEnchantEvent event) {\r\n        if (event.getInventory().getViewers().isEmpty()) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerPreparesSmithingTableScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.PrepareSmithingEvent;\r\n\r\npublic class PlayerPreparesSmithingTableScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player prepares smithing item\r\n    // player prepares smithing <item>\r\n    //\r\n    // @Regex ^on player prepares smithing [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player prepares to upgrade an item on a smithing table.\r\n    //\r\n    // @Warning The player doing the smithing is estimated and may be inaccurate.\r\n    //\r\n    // @Context\r\n    // <context.inventory> returns the InventoryTag of the smithing table inventory.\r\n    // <context.item> returns the ItemTag after upgrading.\r\n    //\r\n    // @Determine\r\n    // ItemTag to change the item that results from the upgrade.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerPreparesSmithingTableScriptEvent() {\r\n    }\r\n\r\n    public PrepareSmithingEvent event;\r\n    public ItemTag result;\r\n    public PlayerTag player;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player prepares smithing\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchItem(path.eventArgLowerAt(3))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(3, result)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (ItemTag.matches(determination)) {\r\n            result = determinationObj.asType(ItemTag.class, getTagContext(path));\r\n            event.setResult(result.getItemStack());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"item\")) {\r\n            return result;\r\n        }\r\n        else if (name.equals(\"inventory\")) {\r\n            return InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onCraftItem(PrepareSmithingEvent event) {\r\n        if (event.getInventory().getViewers().isEmpty()) {\r\n            return;\r\n        }\r\n        HumanEntity humanEntity = event.getInventory().getViewers().get(0);\r\n        if (EntityTag.isNPC(humanEntity)) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        result = new ItemTag(event.getResult());\r\n        this.player = EntityTag.getPlayerFrom(humanEntity);\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerQuitsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerQuitEvent;\r\n\r\npublic class PlayerQuitsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player quits\r\n    // player quit\r\n    //\r\n    // @Synonyms Player Disconnects,Player Logs Off,Player Leaves\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Switch cause:<cause> to only process the event when it matches the specific cause (only on Paper).\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player quit the server.\r\n    //\r\n    // @Context\r\n    // <context.message> returns an ElementTag of the quit message.\r\n    // <context.cause> returns an ElementTag of the cause of the quit (only on Paper): <@link url https://jd.papermc.io/paper/1.21.1/org/bukkit/event/player/PlayerQuitEvent.QuitReason.html>.\r\n    //\r\n    // @Determine\r\n    // ElementTag to change the quit message.\r\n    // \"NONE\" to cancel the quit message.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerQuitsScriptEvent() {\r\n        registerCouldMatcher(\"player quits|quit\");\r\n        this.<PlayerQuitsScriptEvent>registerTextDetermination(\"none\", (evt) -> {\r\n            event.setQuitMessage(null);\r\n        });\r\n        this.<PlayerQuitsScriptEvent, ElementTag>registerDetermination(null, ElementTag.class, (evt, context, determination) -> {\r\n            event.setQuitMessage(determination.asString());\r\n        });\r\n    }\r\n\r\n    public PlayerQuitEvent event;\r\n    public LocationTag location;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"message\" -> new ElementTag(event.getQuitMessage());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerQuits(PlayerQuitEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        if (!event.getPlayer().isOnline()) { // Workaround: Paper event misfire - refer to comments in NetworkInterceptHelper\r\n            return;\r\n        }\r\n        location = new LocationTag(event.getPlayer().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerRaiseLowerItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.PlayerDeathEvent;\r\nimport org.bukkit.event.player.PlayerDropItemEvent;\r\nimport org.bukkit.event.player.PlayerItemHeldEvent;\r\nimport org.bukkit.event.player.PlayerQuitEvent;\r\nimport org.bukkit.event.player.PlayerSwapHandItemsEvent;\r\n\r\nimport java.util.EnumSet;\r\nimport java.util.HashSet;\r\nimport java.util.Set;\r\nimport java.util.UUID;\r\n\r\npublic class PlayerRaiseLowerItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player raises|lowers|toggles <item>\r\n    //\r\n    // @Synonyms player raises shield, player raises spyglass\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    // @Switch reason:<reason> to only process the event if the reason matches the input.\r\n    //\r\n    // @Triggers when a player starts or stops holding up an item, such as a shield, spyglass, or crossbow.\r\n    //\r\n    // @Warning For 'lowers', the item may be tracked incorrectly. Prefer 'player lowers item' (the generic item form) for a 'lowers' event (similar for 'toggles').\r\n    // Also be aware this event may misfire in some cases.\r\n    // This event and its data are more accurate on Paper servers.\r\n    //\r\n    // @Context\r\n    // <context.state> returns an ElementTag(Boolean) of whether the player raised or lowered the item.\r\n    // <context.held_for> returns a DurationTag of how long the player held the item up for (only on Paper).\r\n    // <context.hand> returns an ElementTag of the hand that the player is raising or lowering (only on Paper).\r\n    // <context.item> returns an ItemTag of the item that the player is raising or lowering (only on Paper).\r\n    // <context.reason> returns the reason for a state change. Can be: raise, lower, swap, hold, drop, quit, death.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public static final EnumSet<Material> raisableItems = EnumSet.of(Material.SHIELD, Material.CROSSBOW, Material.BOW, Material.TRIDENT, Material.SPYGLASS);\r\n\r\n    public PlayerRaiseLowerItemScriptEvent() {\r\n        registerCouldMatcher(\"player raises|lowers|toggles <item>\");\r\n        registerSwitches(\"reason\");\r\n        instance = this;\r\n    }\r\n\r\n    public static PlayerRaiseLowerItemScriptEvent instance;\r\n    public PlayerTag player;\r\n    public boolean state;\r\n    public ItemTag item;\r\n    public ElementTag reason;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String action = path.eventArgLowerAt(1);\r\n        if (action.equals(\"raises\") && !state) {\r\n            return false;\r\n        }\r\n        if (action.equals(\"lowers\") && state) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, player.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"reason\", reason)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(2, item)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"state\" -> new ElementTag(state);\r\n            case \"reason\" -> reason;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    public static Set<UUID> raisedItems = new HashSet<>();\r\n\r\n    @Override\r\n    public void init() {\r\n        NetworkInterceptHelper.enable();\r\n        super.init();\r\n    }\r\n\r\n    public void run(Player pl, String reason) {\r\n        player = new PlayerTag(pl);\r\n        this.reason = new ElementTag(reason);\r\n        if (raisableItems.contains(player.getHeldItem().getBukkitMaterial()) || !raisableItems.contains(player.getOffhandItem().getBukkitMaterial())) {\r\n            item = player.getHeldItem();\r\n        }\r\n        else {\r\n            item = player.getOffhandItem();\r\n        }\r\n        fire();\r\n    }\r\n\r\n    public static void signalDidRaise(Player player) {\r\n        if (!raisedItems.add(player.getUniqueId())) {\r\n            return;\r\n        }\r\n        instance.state = true;\r\n        instance.run(player, \"raise\");\r\n    }\r\n\r\n    public static void signalDidLower(Player player, String reason) {\r\n        if (!raisedItems.remove(player.getUniqueId())) {\r\n            return;\r\n        }\r\n        instance.state = false;\r\n        instance.run(player, reason);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerQuit(PlayerQuitEvent event) {\r\n        signalDidLower(event.getPlayer(), \"quit\");\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerDeath(PlayerDeathEvent event) {\r\n        signalDidLower(event.getEntity(), \"death\");\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerSwapHandItems(PlayerSwapHandItemsEvent event) {\r\n        signalDidLower(event.getPlayer(), \"swap\");\r\n    }\r\n\r\n    public static class PlayerRaiseLowerItemScriptEventSpigotImpl extends PlayerRaiseLowerItemScriptEvent {\r\n\r\n        @EventHandler\r\n        public void onPlayerDropItem(PlayerDropItemEvent event) {\r\n            signalDidLower(event.getPlayer(), \"drop\");\r\n        }\r\n\r\n        @EventHandler\r\n        public void onPlayerChangeHeldItem(PlayerItemHeldEvent event) {\r\n            signalDidLower(event.getPlayer(), \"hold\");\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerReceivesActionbarScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\npublic class PlayerReceivesActionbarScriptEvent extends PlayerReceivesMessageScriptEvent {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player receives actionbar\r\n    //\r\n    // @Regex ^on player receives actionbar$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Warning Triggering new actionbar messages in this event will cause it to re-fire.\r\n    //\r\n    // @Triggers when a player receives any actionbar from the server.\r\n    //\r\n    // @Context\r\n    // <context.message> returns an ElementTag of the actionbar.\r\n    // <context.raw_json> returns an ElementTag of the raw JSON used for the actionbar.\r\n    //\r\n    // @Determine\r\n    // \"MESSAGE:<ElementTag>\" to change the actionbar.\r\n    // \"RAW_JSON:<ElementTag>\" to change the JSON used for the actionbar.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerReceivesActionbarScriptEvent() {\r\n        instance = this;\r\n    }\r\n\r\n    public static PlayerReceivesActionbarScriptEvent instance;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player receives actionbar\");\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerReceivesCommandsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.PlayerCommandSendEvent;\n\npublic class PlayerReceivesCommandsScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // player receives commands\n    //\n    // @Regex ^on player receives commands$\n    //\n    // @Group Player\n    //\n    // @Triggers when the list of available server commands is sent to the player for tab completion.\n    //\n    // @Context\n    // <context.commands> returns a ListTag of received commands.\n    //\n    // @Determine\n    // ListTag to set the player's available commands. NOTE: It is not possible to add entries to the command list, only remove them.\n    //\n    // @Player Always.\n    //\n    // -->\n\n    public PlayerReceivesCommandsScriptEvent() {\n    }\n\n    public PlayerCommandSendEvent event;\n\n    @Override\n    public boolean couldMatch(ScriptPath path) {\n        return path.eventLower.startsWith(\"player receives commands\");\n    }\n\n    @Override\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\n        String determination = determinationObj.toString();\n        if (determination.length() > 0) {\n            event.getCommands().clear();\n            event.getCommands().addAll(ListTag.getListFor(determinationObj, getTagContext(path)));\n            return true;\n        }\n        return super.applyDetermination(path, determinationObj);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getPlayer());\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        if (name.equals(\"commands\")) {\n            ListTag list = new ListTag();\n            list.addAll(event.getCommands());\n            return list;\n        }\n        return super.getContext(name);\n    }\n\n    @EventHandler\n    public void onPlayerCommandSend(PlayerCommandSendEvent event) {\n        if (EntityTag.isNPC(event.getPlayer())) {\n            return;\n        }\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerReceivesMessageScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\n\r\npublic class PlayerReceivesMessageScriptEvent extends BukkitScriptEvent {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player receives message\r\n    //\r\n    // @Regex ^on player receives message$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Warning Using this will forcibly sync the chat thread.\r\n    //\r\n    // @Triggers when a player receives any chat message from the server. This does not normally include *player* chat, instead prefer <@link event player chats> for that.\r\n    //\r\n    // @Context\r\n    // <context.message> returns an ElementTag of the message.\r\n    // <context.raw_json> returns an ElementTag of the raw JSON used for the message.\r\n    // <context.system_message> returns true if the message is a system message (not player chat).\r\n    //\r\n    // @Determine\r\n    // \"MESSAGE:<ElementTag>\" to change the message.\r\n    // \"RAW_JSON:<ElementTag>\" to change the JSON used for the message.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerReceivesMessageScriptEvent() {\r\n        instance = this;\r\n    }\r\n\r\n    public static PlayerReceivesMessageScriptEvent instance;\r\n    public ElementTag message;\r\n    public ElementTag rawJson;\r\n    public boolean didModify;\r\n    public BaseComponent[] altMessageDetermination;\r\n    public ElementTag system;\r\n    public boolean modified;\r\n    public PlayerTag player;\r\n    public boolean loaded;\r\n\r\n    public void reset() {\r\n        player = null;\r\n        message = null;\r\n        rawJson = null;\r\n        system = null;\r\n        cancelled = false;\r\n        modified = false;\r\n        altMessageDetermination = null;\r\n        didModify = false;\r\n    }\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player receives message\");\r\n    }\r\n\r\n    @Override\r\n    public void init() {\r\n        NetworkInterceptHelper.enable();\r\n        loaded = true;\r\n    }\r\n\r\n    @Override\r\n    public void destroy() {\r\n        loaded = false;\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determination = determinationObj.toString();\r\n            String lower = CoreUtilities.toLowerCase(determination);\r\n            if (lower.startsWith(\"message:\")) {\r\n                message = new ElementTag(determination.substring(\"message:\".length()), true);\r\n                altMessageDetermination = FormattedTextHelper.parse(message.asString(), ChatColor.WHITE);\r\n                modified = true;\r\n                return true;\r\n            }\r\n            if (lower.startsWith(\"raw_json:\")) {\r\n                rawJson = new ElementTag(determination.substring(\"raw_json:\".length()));\r\n                altMessageDetermination = null;\r\n                message = new ElementTag(FormattedTextHelper.stringify(FormattedTextHelper.parseJson(rawJson.asString())), true);\r\n                modified = true;\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"message\": return message;\r\n            case \"system_message\": return system;\r\n            case \"raw_json\":\r\n                if (altMessageDetermination != null) {\r\n                    return new ElementTag(FormattedTextHelper.componentToJson(altMessageDetermination), true);\r\n                }\r\n                return rawJson;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    public PlayerReceivesMessageScriptEvent triggerNow() {\r\n        PlayerReceivesMessageScriptEvent event = (PlayerReceivesMessageScriptEvent) fire();\r\n        if (event.modified && event.altMessageDetermination == null) {\r\n            event.altMessageDetermination = FormattedTextHelper.parseJson(event.rawJson.asString());\r\n        }\r\n        return event;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerReceivesPacketScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.JavaReflectedObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\r\nimport org.bukkit.entity.Player;\r\n\r\npublic class PlayerReceivesPacketScriptEvent extends BukkitScriptEvent {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player receives packet\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player receives a packet from the server.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Switch class:<classname-matcher> to only process the event when the packet class name matches a given classname matcher.\r\n    //\r\n    // @Warning This event will fire extremely rapidly. Use with caution.\r\n    //\r\n    // @Context\r\n    // <context.class> returns an ElementTag of the packet's class name. Note that these are spigot-mapped names, not Mojang-mapped.\r\n    // <context.reflect_packet> returns a JavaReflectedObjectTag of the packet object.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerReceivesPacketScriptEvent() {\r\n        instance = this;\r\n        registerCouldMatcher(\"player receives packet\");\r\n        registerSwitches(\"class\");\r\n    }\r\n\r\n    public static PlayerReceivesPacketScriptEvent instance;\r\n\r\n    public ElementTag className;\r\n    public PlayerTag player;\r\n    public Object packet;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runGenericSwitchCheck(path, \"class\", className.asString())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public void init() {\r\n        NetworkInterceptHelper.enable();\r\n        super.init();\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"class\": return className;\r\n            case \"reflect_packet\": return new JavaReflectedObjectTag(packet);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    public static boolean fireFor(Player player, Object packet) {\r\n        instance.player = new PlayerTag(player);\r\n        instance.className = new ElementTag(DebugInternals.getClassNameOpti(packet.getClass()));\r\n        instance.packet = packet;\r\n        return instance.fire().cancelled;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerReceivesTablistUpdateScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.UUID;\r\n\r\npublic class PlayerReceivesTablistUpdateScriptEvent extends BukkitScriptEvent {\r\n\r\n    // TODO: 1.21.3: players now have a list order value that's controlled here\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player receives tablist update\r\n    //\r\n    // @Switch mode:add/update/remove to only trigger if the tablist update is the specified mode.\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player receives a tablist update.\r\n    //\r\n    // @Location true\r\n    // @Cancellable true\r\n    //\r\n    // @Context\r\n    // <context.mode> returns the update mode: 'add', 'remove', 'initialize_chat', 'update_gamemode', 'update_latency', 'update_listed', or 'update_display'. As of 1.19.3, you can also receive update combos like \"update_gamemode|update_latency\".\r\n    // <context.uuid> returns the packet's associated UUID.\r\n    // <context.name> returns the packet's associated name (if any).\r\n    // <context.display> returns the packet's associated display name (if any).\r\n    // <context.latency> returns the packet's associated latency (if any).\r\n    // <context.gamemode> returns the packet's associated gamemode (if any).\r\n    // <context.skin_blob> returns the packet's associated skin blob (if any).\r\n    // <context.listed> returns true if the entry should be listed in the tab list, or false if not.\r\n    //\r\n    // @Determine\r\n    // \"LATENCY:<ElementTag(Number)>\" to change the latency.\r\n    // \"NAME:<ElementTag>\" to change the name.\r\n    // \"DISPLAY:<ElementTag>\" to change the display name. 'name', 'display' and 'cancelled' determinations require 'Allow restricted actions' in Denizen/config.yml\r\n    // \"GAMEMODE:<ElementTag>\" to change the gamemode.\r\n    // \"SKIN_BLOB:<ElementTag>\" to change the skin blob.\r\n    // \"LISTED:<ElementTag(Boolean)>\" to change whether the entry is listed.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerReceivesTablistUpdateScriptEvent() {\r\n        instance = this;\r\n        registerCouldMatcher(\"player receives tablist update\");\r\n        registerSwitches(\"mode\");\r\n    }\r\n\r\n    public static PlayerReceivesTablistUpdateScriptEvent instance;\r\n\r\n    public static class TabPacketData {\r\n\r\n        public TabPacketData(String mode, UUID id, boolean isListed, String name, String display, String gamemode, String texture, String signature, int latency) {\r\n            this.mode = mode;\r\n            this.id = id;\r\n            this.name = name;\r\n            this.display = display;\r\n            this.gamemode = gamemode;\r\n            this.texture = texture;\r\n            this.signature = signature;\r\n            this.latency = latency;\r\n            this.isListed = isListed;\r\n        }\r\n\r\n        public UUID id;\r\n\r\n        public String mode, name, display, gamemode, texture, signature;\r\n\r\n        public int latency;\r\n\r\n        public boolean isListed;\r\n\r\n        public boolean cancelled = false;\r\n\r\n        public boolean modified = false;\r\n    }\r\n\r\n    public Player player;\r\n\r\n    public TabPacketData data;\r\n\r\n    @Override\r\n    public void init() {\r\n        NetworkInterceptHelper.enable();\r\n        super.init();\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled && !CoreConfiguration.allowRestrictedActions) {\r\n            Debug.echoError(\"Cannot use 'receives tablist update' event to cancel a tablist packet: 'Allow restricted actions' is disabled in Denizen config.yml.\");\r\n            return;\r\n        }\r\n        data.cancelled = cancelled;\r\n        data.modified = true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, player.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runGenericSwitchCheck(path, \"mode\", data.mode)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        String determinationLow = CoreUtilities.toLowerCase(determination);\r\n        if (determinationLow.contains(\":\")) {\r\n            if (determinationLow.startsWith(\"latency:\")) {\r\n                data.modified = true;\r\n                data.latency = Integer.parseInt(determination.substring(\"latency:\".length()));\r\n                return true;\r\n            }\r\n            else if (determinationLow.startsWith(\"name:\")) {\r\n                if (!CoreConfiguration.allowRestrictedActions) {\r\n                    Debug.echoError(\"Cannot use 'receives tablist update' event to edit a display name: 'Allow restricted actions' is disabled in Denizen config.yml.\");\r\n                    return true;\r\n                }\r\n                data.modified = true;\r\n                data.name = determination.substring(\"name:\".length());\r\n                return true;\r\n            }\r\n            else if (determinationLow.startsWith(\"display:\")) {\r\n                if (!CoreConfiguration.allowRestrictedActions) {\r\n                    Debug.echoError(\"Cannot use 'receives tablist update' event to edit a display name: 'Allow restricted actions' is disabled in Denizen config.yml.\");\r\n                    return true;\r\n                }\r\n                data.modified = true;\r\n                data.name = determination.substring(\"display:\".length());\r\n                return true;\r\n            }\r\n            else if (determinationLow.startsWith(\"gamemode:\")) {\r\n                data.modified = true;\r\n                data.gamemode = determination.substring(\"gamemode:\".length());\r\n                return true;\r\n            }\r\n            else if (determinationLow.startsWith(\"skin_blob:\")) {\r\n                String blob = determination.substring(\"skin_blob:\".length());\r\n                int semicolon = blob.indexOf(';');\r\n                if (semicolon == -1) {\r\n                    Debug.echoError(\"Invalid skin blob!\");\r\n                    return true;\r\n                }\r\n                data.modified = true;\r\n                data.texture = blob.substring(0, semicolon);\r\n                data.signature = blob.substring(semicolon + 1);\r\n                return true;\r\n            }\r\n            else if (determinationLow.startsWith(\"listed:\")) {\r\n                data.modified = true;\r\n                data.isListed = new ElementTag(determination.substring(\"listed:\".length())).asBoolean();\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"name\": return new ElementTag(data.name);\r\n            case \"uuid\": return new ElementTag(data.id.toString());\r\n            case \"mode\": return new ElementTag(data.mode);\r\n            case \"display\": return new ElementTag(data.display);\r\n            case \"gamemode\": return new ElementTag(data.gamemode);\r\n            case \"skin_blob\": return new ElementTag(data.texture + \";\" + data.signature);\r\n            case \"latency\": return new ElementTag(data.latency);\r\n            case \"listed\": return new ElementTag(data.isListed);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    public static void fire(Player player, TabPacketData data) {\r\n        instance.player = player;\r\n        instance.data = data;\r\n        instance.fire();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerRecipeDiscoverScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.PlayerRecipeDiscoverEvent;\n\npublic class PlayerRecipeDiscoverScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // player discovers recipe\n    //\n    // @Group Player\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a player discovers a new item in the recipe book.\n    //\n    // @Context\n    // <context.recipe_id> returns the ID of the recipe discovered.\n    //\n    // @Player Always.\n    //\n    // -->\n\n    public PlayerRecipeDiscoverScriptEvent() {\n        registerCouldMatcher(\"player discovers recipe\");\n    }\n\n    public PlayerRecipeDiscoverEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getPlayer());\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        return switch (name) {\n            case \"recipe_id\" -> new ElementTag(event.getRecipe().toString(), true);\n            default -> super.getContext(name);\n        };\n    }\n\n    @EventHandler\n    public void onPlayerDiscoversRecipe(PlayerRecipeDiscoverEvent event) {\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerRespawnsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerRespawnEvent;\r\n\r\npublic class PlayerRespawnsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player respawns (at bed)\r\n    // player respawns elsewhere\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player respawns.\r\n    //\r\n    // @Context\r\n    // <context.location> returns a LocationTag of the respawn location.\r\n    // <context.is_bed_spawn> returns a boolean indicating whether the player is about to respawn at their bed.\r\n    //\r\n    // @Determine\r\n    // LocationTag to change the respawn location.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerRespawnsScriptEvent() {\r\n        registerCouldMatcher(\"player respawns (at bed)\");\r\n        registerCouldMatcher(\"player respawns elsewhere\");\r\n    }\r\n\r\n    public PlayerRespawnEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String loc = path.eventArgLowerAt(2);\r\n        if (loc.equals(\"at\") && !event.isBedSpawn()) {\r\n            return false;\r\n        }\r\n        if (loc.equals(\"elsewhere\") && event.isBedSpawn()) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (!CoreUtilities.equalsIgnoreCase(determination, \"none\")) {\r\n            LocationTag loc = LocationTag.valueOf(determination, getTagContext(path));\r\n            if (loc != null) {\r\n                event.setRespawnLocation(loc);\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"location\")) {\r\n            return new LocationTag(event.getRespawnLocation());\r\n        }\r\n        else if (name.equals(\"is_bed_spawn\")) {\r\n            return new ElementTag(event.isBedSpawn());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerRespawns(PlayerRespawnEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerRightClicksEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerInteractAtEntityEvent;\r\nimport org.bukkit.event.player.PlayerInteractEntityEvent;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\n\r\npublic class PlayerRightClicksEntityScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player right clicks <entity>\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    // @Switch with:<item> to only process the event when the player is holding a specified item.\r\n    // @Switch type:<entity> to only run if the entity clicked matches the entity input.\r\n    //\r\n    // @Warning this event may in some cases double-fire, requiring usage of the 'ratelimit' command (like 'ratelimit <player> 1t') to prevent doubling actions.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player right clicks on an entity.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag the player is clicking on.\r\n    // <context.item> returns the ItemTag the player is clicking with.\r\n    // <context.hand> returns \"offhand\" or \"mainhand\" to indicate which hand was used to fire the event. Some events fire twice - once for each hand.\r\n    // <context.click_position> returns a LocationTag of the click position (as a world-less vector, relative to the entity's center). This is only available when clicking armor stands.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    PlayerInteractEntityEvent event;\r\n    EntityTag entity;\r\n    ItemTag item;\r\n\r\n    public PlayerRightClicksEntityScriptEvent() {\r\n        registerCouldMatcher(\"player right clicks <entity>\");\r\n        registerSwitches(\"with\", \"type\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        boolean isAt = path.eventArgLowerAt(3).equals(\"at\");\r\n        if (!entity.tryAdvancedMatcher(path.eventArgLowerAt(isAt ? 4 : 3), path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, item)) {\r\n            return false;\r\n        }\r\n        // Deprecated in favor of with: format\r\n        if (path.eventArgLowerAt(isAt ? 5 : 4).equals(\"with\") && !item.tryAdvancedMatcher(path.eventArgLowerAt(isAt ? 6 : 5), path.context)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"type\", entity)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(PlayerTag.mirrorBukkitPlayer(event.getPlayer()), entity.isNPC() ? entity.getDenizenNPC() : null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\": return entity.getDenizenObject();\r\n            case \"item\": return item;\r\n            case \"hand\": return new ElementTag(event.getHand() == EquipmentSlot.OFF_HAND ? \"offhand\" : \"mainhand\");\r\n            case \"location\":\r\n                BukkitImplDeprecations.playerRightClicksEntityContext.warn();\r\n                return entity.getLocation();\r\n            case \"click_position\":\r\n                if (event instanceof PlayerInteractAtEntityEvent) {\r\n                    return new LocationTag(((PlayerInteractAtEntityEvent) event).getClickedPosition());\r\n                }\r\n                break;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerRightClicksAtEntity(PlayerInteractAtEntityEvent event) {\r\n        playerRightClicksEntityHandler(event);\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerRightClicksEntity(PlayerInteractEntityEvent event) {\r\n        if (event instanceof PlayerInteractAtEntityEvent) {\r\n            return;\r\n        }\r\n        playerRightClicksEntityHandler(event);\r\n    }\r\n\r\n    public void playerRightClicksEntityHandler(PlayerInteractEntityEvent event) {\r\n        entity = new EntityTag(event.getRightClicked());\r\n        item = new ItemTag(event.getPlayer().getEquipment().getItem(event.getHand()));\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerRiptideScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerRiptideEvent;\r\n\r\npublic class PlayerRiptideScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player activates riptide\r\n    //\r\n    // @Regex ^on player activates riptide$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player activates the riptide effect.\r\n    //\r\n    // @Context\r\n    // <context.item> returns the ItemTag of the trident.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerRiptideScriptEvent() {\r\n    }\r\n\r\n    public PlayerRiptideEvent event;\r\n    private ItemTag item;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player activates riptide\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"item\")) {\r\n            return item;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerRiptide(PlayerRiptideEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        this.item = new ItemTag(event.getItem());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerSendPacketScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.JavaReflectedObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\r\nimport org.bukkit.entity.Player;\r\n\r\npublic class PlayerSendPacketScriptEvent extends BukkitScriptEvent {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player sends packet\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player sends a packet to the server.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Switch class:<classname-matcher> to only process the event when the packet class name matches a given classname matcher.\r\n    //\r\n    // @Warning This event will fire extremely rapidly. Use with caution.\r\n    //\r\n    // @Context\r\n    // <context.class> returns an ElementTag of the packet's class name. Note that these are spigot-mapped names, not Mojang-mapped.\r\n    // <context.reflect_packet> returns a JavaReflectedObjectTag of the packet object.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerSendPacketScriptEvent() {\r\n        instance = this;\r\n        registerCouldMatcher(\"player sends packet\");\r\n        registerSwitches(\"class\");\r\n    }\r\n\r\n    public static PlayerSendPacketScriptEvent instance;\r\n\r\n    public ElementTag className;\r\n    public PlayerTag player;\r\n    public Object packet;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runGenericSwitchCheck(path, \"class\", className.asString())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public void init() {\r\n        NetworkInterceptHelper.enable();\r\n        super.init();\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"class\": return className;\r\n            case \"reflect_packet\": return new JavaReflectedObjectTag(packet);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    public static boolean fireFor(Player player, Object packet) {\r\n        instance.player = new PlayerTag(player);\r\n        instance.className = new ElementTag(DebugInternals.getClassNameOpti(packet.getClass()));\r\n        instance.packet = packet;\r\n        return instance.fire().cancelled;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerShearsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.DyeColor;\r\nimport org.bukkit.entity.Sheep;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerShearEntityEvent;\r\n\r\npublic class PlayerShearsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player shears <entity>\r\n    // player shears <'color'> sheep\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player shears an entity.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag of the sheep.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerShearsScriptEvent() {\r\n        registerCouldMatcher(\"player shears <entity>\");\r\n        registerCouldMatcher(\"player shears <'color'> sheep\");\r\n    }\r\n\r\n    public EntityTag entity;\r\n    public PlayerShearEntityEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (!couldMatchEntity(path.eventArgLowerAt(2)) && !couldMatchEnum(path.eventArgLowerAt(2), DyeColor.values())) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String ent = path.eventArgLowerAt(3).equals(\"sheep\") ? \"sheep\" : path.eventArgLowerAt(2);\r\n\r\n        if (!ent.equals(\"sheep\") && !entity.tryAdvancedMatcher(ent, path.context)) {\r\n            return false;\r\n        }\r\n\r\n        String color = path.eventArgLowerAt(3).equals(\"sheep\") ? path.eventArgLowerAt(2) : \"\";\r\n        if (color.length() > 0 && !color.equals(CoreUtilities.toLowerCase(((Sheep) entity.getBukkitEntity()).getColor().name()))) {\r\n            return false;\r\n        }\r\n\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"entity\")) {\r\n            return entity.getDenizenObject();\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerShears(PlayerShearEntityEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        entity = new EntityTag(event.getEntity());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerSmithsItemScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryViewUtil;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.SmithItemEvent;\r\n\r\npublic class PlayerSmithsItemScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player smiths item\r\n    // player smiths <item>\r\n    //\r\n    // @Regex ^on player smiths [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player upgrades an item on a smithing table.\r\n    //\r\n    // @Context\r\n    // <context.inventory> returns the InventoryTag of the smithing table inventory.\r\n    // <context.item> returns the ItemTag after upgrading.\r\n    //\r\n    // @Determine\r\n    // ItemTag to change the item that results from the upgrade.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerSmithsItemScriptEvent() {\r\n    }\r\n\r\n    public SmithItemEvent event;\r\n    public ItemTag result;\r\n    public PlayerTag player;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player smiths\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchItem(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, result)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (ItemTag.matches(determination)) {\r\n            result = determinationObj.asType(ItemTag.class, getTagContext(path));\r\n            event.getInventory().setResult(result.getItemStack());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"item\")) {\r\n            return result;\r\n        }\r\n        else if (name.equals(\"inventory\")) {\r\n            return InventoryTag.mirrorBukkitInventory(event.getInventory());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onCraftItem(SmithItemEvent event) {\r\n        HumanEntity humanEntity = InventoryViewUtil.getPlayer(event.getView());\r\n        if (EntityTag.isNPC(humanEntity)) {\r\n            return;\r\n        }\r\n        this.event = event;\r\n        result = new ItemTag(event.getInventory().getResult());\r\n        this.player = EntityTag.getPlayerFrom(humanEntity);\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerSneakScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerToggleSneakEvent;\r\n\r\npublic class PlayerSneakScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player toggles|starts|stops sneaking\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player starts or stops sneaking.\r\n    //\r\n    // @Context\r\n    // <context.state> returns an ElementTag(Boolean) with a value of \"true\" if the player is now sneaking and \"false\" otherwise.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerSneakScriptEvent() {\r\n        registerCouldMatcher(\"player toggles|starts|stops sneaking\");\r\n    }\r\n\r\n    public boolean state;\r\n    public PlayerToggleSneakEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        if (cmd.equals(\"starts\") && !state) {\r\n            return false;\r\n        }\r\n        if (cmd.equals(\"stops\") && state) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"state\")) {\r\n            return new ElementTag(state);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerSneak(PlayerToggleSneakEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        state = event.isSneaking();\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerSprintScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerToggleSprintEvent;\r\n\r\npublic class PlayerSprintScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player toggles|starts|stops sprinting\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player starts or stops sprinting.\r\n    //\r\n    // @Context\r\n    // <context.state> returns an ElementTag(Boolean) with a value of \"true\" if the player is now sprinting and \"false\" otherwise.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerSprintScriptEvent() {\r\n        registerCouldMatcher(\"player toggles|starts|stops sprinting\");\r\n    }\r\n\r\n    public boolean state;\r\n    public PlayerToggleSprintEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        if (cmd.equals(\"starts\") && !state) {\r\n            return false;\r\n        }\r\n        if (cmd.equals(\"stops\") && state) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"state\")) {\r\n            return new ElementTag(state);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerSprint(PlayerToggleSprintEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        state = event.isSprinting();\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerStandsOnScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.Action;\r\nimport org.bukkit.event.player.PlayerInteractEvent;\r\n\r\npublic class PlayerStandsOnScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player stands on material\r\n    // player stands on (<material>)\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player stands on a physical-interactable block (such as a pressure plate, tripwire, or redstone ore).\r\n    // @Context\r\n    // <context.location> returns the LocationTag the player is interacting with.\r\n    // <context.material> returns the MaterialTag the player is interacting with.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerStandsOnScriptEvent() {\r\n    }\r\n\r\n    PlayerInteractEvent event;\r\n    LocationTag location;\r\n    MaterialTag material;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player stands on\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchBlock(path.eventArgLowerAt(3))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String mat = path.eventArgLowerAt(3);\r\n        if (mat.length() > 0 && !mat.equals(\"in\") && !material.tryAdvancedMatcher(mat, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(EntityTag.getPlayerFrom(event.getPlayer()), null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"location\")) {\r\n            return location;\r\n        }\r\n        else if (name.equals(\"material\")) {\r\n            return material;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerStandsOn(PlayerInteractEvent event) {\r\n        if (event.getAction() != Action.PHYSICAL) {\r\n            return;\r\n        }\r\n        material = new MaterialTag(event.getClickedBlock());\r\n        location = new LocationTag(event.getClickedBlock().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerStatisticIncrementsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Statistic;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerStatisticIncrementEvent;\r\n\r\npublic class PlayerStatisticIncrementsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player statistic increments\r\n    // player statistic <'statistic'> increments\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player's statistics increment.\r\n    //\r\n    // @Context\r\n    // <context.statistic> returns the statistic that incremented. Statistic names: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Statistic.html>\r\n    // <context.previous_value> returns the old value of the statistic.\r\n    // <context.new_value> returns the new value of the statistic.\r\n    // <context.qualifier> returns the qualifier (EntityTag/MaterialTag) if any.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerStatisticIncrementsScriptEvent() {\r\n        registerCouldMatcher(\"player statistic increments\");\r\n        registerCouldMatcher(\"player statistic <'statistic'> increments\");\r\n    }\r\n\r\n    public Statistic statistic;\r\n    public PlayerStatisticIncrementEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (!path.eventArgLowerAt(2).equals(\"increments\")) {\r\n            if (!path.eventArgLowerAt(3).equals(\"increments\") || !couldMatchEnum(path.eventArgLowerAt(2), Statistic.values())) {\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String stat = path.eventArgLowerAt(2);\r\n\r\n        if (!stat.equals(\"increments\") && !stat.equals(CoreUtilities.toLowerCase(statistic.toString()))) {\r\n            return false;\r\n        }\r\n\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"statistic\":\r\n                return new ElementTag(statistic.toString());\r\n            case \"previous_value\":\r\n                return new ElementTag(event.getPreviousValue());\r\n            case \"new_value\":\r\n                return new ElementTag(event.getNewValue());\r\n            case \"qualifier\":\r\n                if (statistic.getType() == Statistic.Type.BLOCK || statistic.getType() == Statistic.Type.ITEM) {\r\n                    return new MaterialTag(event.getMaterial());\r\n                }\r\n                else if (statistic.getType() == Statistic.Type.ENTITY) {\r\n                    return new EntityTag(event.getEntityType());\r\n                }\r\n                break;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerStatisticIncrements(PlayerStatisticIncrementEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        statistic = event.getStatistic();\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerSteersEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\n\r\nimport java.util.function.Consumer;\r\n\r\npublic class PlayerSteersEntityScriptEvent extends BukkitScriptEvent {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player steers entity\r\n    // player steers <entity>\r\n    //\r\n    // @Regex ^on player steers [^\\s]+$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers every tick that a player is controlling a vehicle. Use <@link event player input> on MC 1.21+.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag being steered by the player.\r\n    // <context.sideways> returns an ElementTag(Decimal) where a positive number signifies leftward movement.\r\n    // <context.forward> returns an ElementTag(Decimal) where a positive number signifies forward movement.\r\n    // <context.jump> returns an ElementTag(Boolean) that signifies whether the player is attempting to jump with the entity.\r\n    // <context.dismount> returns an ElementTag(Boolean) that signifies whether the player is attempting to dismount.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @deprecated Use the 'player input' event on MC 1.21+.\r\n    //\r\n    // -->\r\n\r\n    public PlayerSteersEntityScriptEvent() {\r\n        instance = this;\r\n    }\r\n\r\n    public static PlayerSteersEntityScriptEvent instance;\r\n    public EntityTag entity;\r\n    public PlayerTag player;\r\n    public ElementTag sideways;\r\n    public ElementTag forward;\r\n    public ElementTag jump;\r\n    public ElementTag dismount;\r\n\r\n    public Consumer<Boolean> modifyCancellation;\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        modifyCancellation.accept(cancelled);\r\n    }\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player steers\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchEntity(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            BukkitImplDeprecations.playerSteerEntityEvent.warn(path.container);\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String entityName = path.eventArgLowerAt(2);\r\n        if (!entity.tryAdvancedMatcher(entityName, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, entity.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public void init() {\r\n        NetworkInterceptHelper.enable();\r\n        super.init();\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, entity.isCitizensNPC() ? entity.getDenizenNPC() : null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\":\r\n                return entity;\r\n            case \"sideways\":\r\n                return sideways;\r\n            case \"forward\":\r\n                return forward;\r\n            case \"jump\":\r\n                return jump;\r\n            case \"dismount\":\r\n                return dismount;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerStepsOnScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerMoveEvent;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\n\r\npublic class PlayerStepsOnScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player steps on block\r\n    // player steps on <material>\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning This event may fire very rapidly.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player steps onto a specific block material.\r\n    //\r\n    // @Context\r\n    // <context.location> returns a LocationTag of the block the player is stepping on.\r\n    // <context.previous_location> returns a LocationTag of where the player was before stepping onto the block.\r\n    // <context.new_location> returns a LocationTag of where the player is now.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerStepsOnScriptEvent() {\r\n        registerCouldMatcher(\"player steps on <block>\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public PlayerMoveEvent event;\r\n    public MaterialTag material;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(3, material)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"previous_location\" -> new LocationTag(event.getFrom());\r\n            case \"new_location\" -> new LocationTag(event.getTo());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerStepsOn(PlayerMoveEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        if (event.getTo() == null) {\r\n            return;\r\n        }\r\n        Location from = event.getFrom().clone().subtract(0, 0.05, 0), to = event.getTo().clone().subtract(0, 0.05, 0);\r\n        if (LocationTag.isSameBlock(from, to)) {\r\n            return;\r\n        }\r\n        location = new LocationTag(to);\r\n        if (!Utilities.isLocationYSafe(location)) {\r\n            return;\r\n        }\r\n        material = new MaterialTag(location.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerTeleport(PlayerTeleportEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        onPlayerStepsOn(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerStopsDamagingBlockScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockDamageAbortEvent;\r\n\r\npublic class PlayerStopsDamagingBlockScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player stops damaging <block>\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a block stops being damaged by a player.\r\n    //\r\n    // @Switch with:<item> to only process the event when the player stops hitting the block with a specified item.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag the block no longer being damaged.\r\n    // <context.material> returns the MaterialTag of the block no longer being damaged.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // @Example\r\n    // on player stops damaging block:\r\n    // - narrate \"You were so close to breaking that block! You got this!\"\r\n    //\r\n    // @Example\r\n    // on player stops damaging infested*:\r\n    // - narrate \"It's Silverfish time!\"\r\n    // - spawn silverfish|silverfish|silverfish|silverfish|silverfish <context.location> persistent\r\n    // -->\r\n\r\n    public LocationTag location;\r\n    public MaterialTag material;\r\n    public BlockDamageAbortEvent event;\r\n\r\n    public PlayerStopsDamagingBlockScriptEvent() {\r\n        registerCouldMatcher(\"player stops damaging <block>\");\r\n        registerSwitches(\"with\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(3, material)) {\r\n            return false;\r\n        }\r\n        if (!runWithCheck(path, new ItemTag(event.getItemInHand()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"material\" -> material;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerStopsDamagingBlockEvent(BlockDamageAbortEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerSwapsItemsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerSwapHandItemsEvent;\r\n\r\npublic class PlayerSwapsItemsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player swaps items\r\n    //\r\n    // @Regex ^on player swaps items$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Switch main:<item> to only process the event if the item being put into the main hand matches the input item.\r\n    // @Switch offhand:<item> to only process the event if the item being put into the off-hand matches the input item.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player swaps the items in their main and off hands.\r\n    //\r\n    // @Context\r\n    // <context.main> returns the ItemTag switched to the main hand.\r\n    // <context.offhand> returns the ItemTag switched to the off hand.\r\n    //\r\n    // @Determine\r\n    // \"MAIN:<ItemTag>\" to set the item in the main hand.\r\n    // \"OFFHAND:<ItemTag>\" to set the item in the off hand.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerSwapsItemsScriptEvent() {\r\n    }\r\n\r\n    public PlayerTag player;\r\n    public PlayerSwapHandItemsEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player swaps items\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryObjectSwitch(\"main\", new ItemTag(event.getMainHandItem()))) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"offhand\", new ItemTag(event.getOffHandItem()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determination = determinationObj.toString();\r\n            String lower = CoreUtilities.toLowerCase(determination);\r\n            if (lower.startsWith(\"main:\")) {\r\n                event.setMainHandItem(ItemTag.valueOf(determination.substring(\"main:\".length()), path.container).getItemStack());\r\n                return true;\r\n            }\r\n            else if (lower.startsWith(\"offhand:\")) {\r\n                event.setOffHandItem(ItemTag.valueOf(determination.substring(\"offhand:\".length()), path.container).getItemStack());\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"main\")) {\r\n            return new ItemTag(event.getMainHandItem());\r\n        }\r\n        else if (name.equals(\"offhand\")) {\r\n            return new ItemTag(event.getOffHandItem());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerSwapsItems(PlayerSwapHandItemsEvent event) {\r\n        player = PlayerTag.mirrorBukkitPlayer(event.getPlayer());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerTakesFromFurnaceScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.FurnaceExtractEvent;\r\n\r\npublic class PlayerTakesFromFurnaceScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player takes item from furnace\r\n    // player takes <item> from furnace\r\n    //\r\n    // @Regex ^on player takes [^\\s]+ from furnace$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player takes an item from a furnace.\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the furnace.\r\n    // <context.item> returns the ItemTag taken out of the furnace.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Number) to set the amount of experience the player will get.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerTakesFromFurnaceScriptEvent() {\r\n    }\r\n\r\n    public LocationTag location;\r\n    public ItemTag item;\r\n    public FurnaceExtractEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player takes\") || !path.eventArgsLowEqualStartingAt(3, \"from\", \"furnace\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchItem(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(2, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isInt()) {\r\n            int xp = element.asInt();\r\n            event.setExpToDrop(xp);\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"location\")) {\r\n            return location;\r\n        }\r\n        else if (name.equals(\"item\")) {\r\n            return item;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerTakesFromFurnace(FurnaceExtractEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        item = new ItemTag(event.getItemType(), event.getItemAmount());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerTakesFromLecternScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerTakeLecternBookEvent;\r\n\r\npublic class PlayerTakesFromLecternScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player takes item from lectern\r\n    // player takes <item> from lectern\r\n    //\r\n    // @Regex ^on player takes [^\\s]+ from lectern$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player takes a book from a lectern.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the LocationTag of the lectern.\r\n    // <context.item> returns the book ItemTag taken out of the lectern.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerTakesFromLecternScriptEvent() {\r\n    }\r\n\r\n    public LocationTag location;\r\n    public ItemTag item;\r\n    public PlayerTakeLecternBookEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.startsWith(\"player takes\") || !path.eventArgsLowEqualStartingAt(3, \"from\", \"lectern\")) {\r\n            return false;\r\n        }\r\n        if (!couldMatchItem(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptEvent.ScriptPath path) {\r\n        if (!path.tryArgObject(2, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"location\")) {\r\n            return location;\r\n        }\r\n        else if (name.equals(\"item\")) {\r\n            return item;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerTakesFromLectern(PlayerTakeLecternBookEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        item = new ItemTag(event.getBook());\r\n        location = new LocationTag(event.getLectern().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerThrowsEggScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerEggThrowEvent;\r\n\r\npublic class PlayerThrowsEggScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player throws (hatching|non-hatching) egg\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player throws an egg - this event specifically fires when the egg hits, for the initial throw event use <@link event projectile launched>.\r\n    //\r\n    // @Context\r\n    // <context.egg> returns the EntityTag of the egg.\r\n    // <context.is_hatching> returns an ElementTag with a value of \"true\" if the egg will hatch and \"false\" otherwise.\r\n    //\r\n    // @Determine\r\n    // EntityTag to set the type of the hatching entity.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerThrowsEggScriptEvent() {\r\n        registerCouldMatcher(\"player throws (hatching|non-hatching) egg\");\r\n    }\r\n\r\n    public EntityTag egg;\r\n    public PlayerEggThrowEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.eventArgLowerAt(2).equals(\"hatching\") && !event.isHatching()) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(2).equals(\"non-hatching\") && event.isHatching()) {\r\n            return false;\r\n        }\r\n\r\n        if (!runInCheck(path, egg.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        String determination = determinationObj.toString();\r\n        if (EntityTag.matches(determination)) {\r\n            event.setHatching(true);\r\n            EntityType type = EntityTag.valueOf(determination, getTagContext(path)).getBukkitEntityType();\r\n            event.setHatchingType(type);\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"is_hatching\")) {\r\n            return new ElementTag(event.isHatching());\r\n        }\r\n        else if (name.equals(\"egg\")) {\r\n            return egg;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        event.setHatching(!cancelled);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerThrowsEgg(PlayerEggThrowEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        Entity eggEntity = event.getEgg();\r\n        EntityTag.rememberEntity(eggEntity);\r\n        egg = new EntityTag(event.getEgg());\r\n        this.event = event;\r\n        fire(event);\r\n        EntityTag.forgetEntity(eggEntity);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerTriggersRaidScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\n\nimport com.denizenscript.denizen.events.world.RaidScriptEvent;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.raid.RaidTriggerEvent;\n\npublic class PlayerTriggersRaidScriptEvent extends RaidScriptEvent<RaidTriggerEvent> implements Listener {\n\n    // <--[event]\n    // @Events\n    // player triggers raid\n    //\n    // @Group Player\n    //\n    // @Location true\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a player triggers a village raid.\n    //\n    // @Context\n    // <context.raid> returns the raid data. See <@link language Raid Event Data>.\n    //\n    // @Player Always.\n    //\n    // -->\n\n    public PlayerTriggersRaidScriptEvent() {\n        super(false);\n        registerCouldMatcher(\"player triggers raid\");\n    }\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runInCheck(path, event.getPlayer().getLocation())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getPlayer());\n    }\n\n    @EventHandler\n    public void onPlayerTriggersRaid(RaidTriggerEvent event) {\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerUsesPortalScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerPortalEvent;\r\n\r\npublic class PlayerUsesPortalScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player uses portal\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch from:<block> to only process the event if the block the player teleported from matches the LocationTag matcher provided.\r\n    // @Switch to:<block> to only process the event if the block the player teleported to matches the LocationTag matcher provided.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player enters a portal.\r\n    //\r\n    // @Context\r\n    // <context.from> returns the location teleported from.\r\n    // <context.to> returns the location teleported to (can sometimes be null).\r\n    // <context.can_create> returns whether the server will attempt to create a destination portal.\r\n    // <context.creation_radius> returns the radius that will be checked for a free space to create the portal in.\r\n    // <context.search_radius> returns the radius that will be checked for an existing portal to teleport to.\r\n    //\r\n    // @Determine\r\n    // LocationTag to change the destination.\r\n    // \"CAN_CREATE:<ElementTag(Boolean)>\" to set whether the server will attempt to create a destination portal.\r\n    // \"CREATION_RADIUS:<ElementTag(Number)>\" to set the radius that will be checked for a free space to create the portal in.\r\n    // \"SEARCH_RADIUS:<ElementTag(Number)>\" to set the radius that will be checked for an existing portal to teleport to.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerUsesPortalScriptEvent() {\r\n        registerCouldMatcher(\"player uses portal\");\r\n        registerSwitches(\"from\", \"to\");\r\n    }\r\n\r\n    public LocationTag to;\r\n    public LocationTag from;\r\n    public PlayerPortalEvent event;\r\n\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, to) && !runInCheck(path, from)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"from\", from)) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"to\", to)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determination = CoreUtilities.toLowerCase(determinationObj.toString());\r\n            if (determination.startsWith(\"can_create:\")) {\r\n                event.setCanCreatePortal(new ElementTag(determination.substring(\"can_create:\".length())).asBoolean());\r\n                return true;\r\n            }\r\n            else if (determination.startsWith(\"creation_radius:\")) {\r\n                event.setCreationRadius(new ElementTag(determination.substring(\"creation_radius:\".length())).asInt());\r\n                return true;\r\n            }\r\n            else if (determination.startsWith(\"search_radius:\")) {\r\n                event.setSearchRadius(new ElementTag(determination.substring(\"search_radius:\".length())).asInt());\r\n                return true;\r\n            }\r\n        }\r\n        if (determinationObj.canBeType(LocationTag.class)) {\r\n            to = determinationObj.asType(LocationTag.class, getTagContext(path));\r\n            event.setTo(to);\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"to\": return to;\r\n            case \"from\": return from;\r\n            case \"can_create\": return new ElementTag(event.getCanCreatePortal());\r\n            case \"creation_radius\": return new ElementTag(event.getCreationRadius());\r\n            case \"search_radius\": return new ElementTag(event.getSearchRadius());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerEntersPortal(PlayerPortalEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        to = event.getTo() == null ? null : new LocationTag(event.getTo());\r\n        from = new LocationTag(event.getFrom());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerWalkScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerMoveEvent;\r\n\r\npublic class PlayerWalkScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player walks\r\n    //\r\n    // @Regex ^on player walks$\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning This event fires very very rapidly!\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player moves in the slightest.\r\n    //\r\n    // @Context\r\n    // <context.old_location> returns the location of where the player was.\r\n    // <context.new_location> returns the location of where the player is.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerWalkScriptEvent() {\r\n    }\r\n\r\n\r\n    public LocationTag old_location;\r\n    public LocationTag new_location;\r\n    public PlayerMoveEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        return path.eventLower.startsWith(\"player walks\") && !path.eventArgLowerAt(2).equals(\"over\");\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, old_location)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, new_location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"old_location\")) {\r\n            return old_location;\r\n        }\r\n        else if (name.equals(\"new_location\")) {\r\n            return new_location;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerMoves(PlayerMoveEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        old_location = new LocationTag(event.getFrom());\r\n        new_location = new LocationTag(event.getTo());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/PlayerWalksOverScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerMoveEvent;\r\n\r\npublic class PlayerWalksOverScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // player walks over notable\r\n    // player walks over <'location'>\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a player walks over a noted location. In most cases, it is preferable to use <@link event player enters area> with a small cuboid.\r\n    //\r\n    // @Context\r\n    // <context.notable> returns an ElementTag of the notable location's name.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public PlayerWalksOverScriptEvent() {\r\n        registerCouldMatcher(\"player walks over notable\");\r\n        registerCouldMatcher(\"player walks over <'location'>\");\r\n    }\r\n\r\n    public String notable;\r\n    public PlayerMoveEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String loc = path.eventArgLowerAt(3);\r\n        MatchHelper matcher = createMatcher(loc);\r\n        if (!loc.equals(\"notable\") && !matcher.doesMatch(notable)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"notable\")) {\r\n            return new ElementTag(notable);\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerWalksOver(PlayerMoveEvent event) {\r\n        if (LocationTag.isSameBlock(event.getFrom(), event.getTo())) {\r\n            return;\r\n        }\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        notable = NoteManager.getSavedId(new LocationTag(event.getTo()).getBlockLocation());\r\n        if (notable == null) {\r\n            return;\r\n        }\r\n        notable = CoreUtilities.toLowerCase(notable);\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/player/ResourcePackStatusScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.player;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\n\r\npublic class ResourcePackStatusScriptEvent extends BukkitScriptEvent {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // resource pack status\r\n    //\r\n    // @Group Player\r\n    //\r\n    // @Triggers when a player accepts, denies, successfully loads, or fails to download a resource pack.\r\n    //\r\n    // @Switch status:<status> to only process the event when a specific status is returned. Same status names as returned by 'context.status'.\r\n    //\r\n    // @Context\r\n    // <context.status> returns an ElementTag of the status. Can be: SUCCESSFULLY_LOADED, DECLINED, FAILED_DOWNLOAD, ACCEPTED.\r\n    //\r\n    // @Player Always.\r\n    //\r\n    // -->\r\n\r\n    public ResourcePackStatusScriptEvent() {\r\n        instance = this;\r\n        registerCouldMatcher(\"resource pack status\");\r\n        registerSwitches(\"status\");\r\n    }\r\n\r\n    public static ResourcePackStatusScriptEvent instance;\r\n\r\n    public ElementTag status;\r\n    public PlayerTag player;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runGenericSwitchCheck(path, \"status\", status.asString())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public void init() {\r\n        NetworkInterceptHelper.enable();\r\n        super.init();\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"status\")) {\r\n            return status;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(player, null);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/server/CommandScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.server;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.tags.core.EscapeTagUtil;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.command.BlockCommandSender;\r\nimport org.bukkit.entity.minecart.CommandMinecart;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerCommandPreprocessEvent;\r\nimport org.bukkit.event.server.ServerCommandEvent;\r\n\r\nimport java.util.Arrays;\r\n\r\npublic class CommandScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // command\r\n    // <'command_name'> command\r\n    //\r\n    // @Group Server\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a player, console, or command block/minecart runs a Bukkit command. This happens before\r\n    // any code of established commands, allowing scripts to 'override' existing commands.\r\n    // Note that for the sake of the event line, escaping is used, so 'bukkit:plugins' becomes 'bukkit&coplugins'\r\n    //\r\n    // @Warning This event is to override existing commands, and should not be used to create new commands - use a command script instead.\r\n    //\r\n    // @Context\r\n    // <context.command> returns the command name as an ElementTag.\r\n    // <context.raw_args> returns any args used, unmodified as plaintext.\r\n    // <context.args> returns a ListTag of the arguments.\r\n    // <context.source_type> returns the source of the command. Can be: PLAYER, SERVER, COMMAND_BLOCK, or COMMAND_MINECART.\r\n    // <context.command_block_location> returns the command block's location (if the command was run from one).\r\n    // <context.command_minecart> returns the EntityTag of the command minecart (if the command was run from one).\r\n    //\r\n    // @Determine\r\n    // \"FULFILLED\" to tell Bukkit the command was handled.\r\n    //\r\n    // @Player when source_type is player.\r\n    //\r\n    // -->\r\n\r\n    public CommandScriptEvent() {\r\n        registerCouldMatcher(\"command\");\r\n        registerCouldMatcher(\"<'command_name'> command\");\r\n    }\r\n\r\n    public String commandName;\r\n    public String fullMessage;\r\n    public String sourceType;\r\n    public Location location;\r\n    public PlayerCommandPreprocessEvent playerEvent;\r\n    public ServerCommandEvent serverEvent;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, playerEvent == null ? null : playerEvent.getPlayer().getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.eventArgLowerAt(0).equals(\"command\") && !runGenericCheck(path.eventArgLowerAt(0), EscapeTagUtil.escape(commandName))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(playerEvent == null ? null : new PlayerTag(playerEvent.getPlayer()), null);\r\n    }\r\n\r\n    public String cleanMessageArgs() {\r\n        return fullMessage.split(\" \").length > 1 ? fullMessage.split(\" \", 2)[1] : \"\";\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determination = determinationObj.toString();\r\n            String lower = CoreUtilities.toLowerCase(determination);\r\n            if (lower.equals(\"fulfilled\")) {\r\n                cancelled = true;\r\n                cancellationChanged();\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"command\")) {\r\n            return new ElementTag(commandName, true);\r\n        }\r\n        else if (name.equals(\"raw_args\")) {\r\n            return new ElementTag(cleanMessageArgs(), true);\r\n        }\r\n        else if (name.equals(\"args\")) {\r\n            return new ListTag(Arrays.asList(ArgumentHelper.buildArgs(cleanMessageArgs(), false)), true);\r\n        }\r\n        else if (name.equals(\"server\")) {\r\n            return new ElementTag(sourceType.equals(\"server\"));\r\n        }\r\n        else if (name.equals(\"source_type\")) {\r\n            return new ElementTag(sourceType);\r\n        }\r\n        else if (name.equals(\"command_block_location\") && serverEvent != null && serverEvent.getSender() instanceof BlockCommandSender) {\r\n            return new LocationTag(((BlockCommandSender) serverEvent.getSender()).getBlock().getLocation());\r\n        }\r\n        else if (name.equals(\"command_minecart\") && serverEvent != null && serverEvent.getSender() instanceof CommandMinecart) {\r\n            return new EntityTag((CommandMinecart) serverEvent.getSender());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerEvent(PlayerCommandPreprocessEvent event) {\r\n        this.playerEvent = event;\r\n        this.serverEvent = null;\r\n        this.fullMessage = event.getMessage();\r\n        this.commandName = fullMessage.split(\" \")[0].substring(1);\r\n        this.location = event.getPlayer().getLocation();\r\n        this.sourceType = \"player\";\r\n        fire(event);\r\n    }\r\n\r\n    @Override\r\n    public void cancellationChanged() {\r\n        if (cancelled && serverEvent != null) {\r\n            serverEvent.setCommand(\"denizen do_nothing\");\r\n        }\r\n        super.cancellationChanged();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onServerEvent(ServerCommandEvent event) {\r\n        this.playerEvent = null;\r\n        this.serverEvent = event;\r\n        this.fullMessage = event.getCommand();\r\n        this.commandName = fullMessage.split(\" \")[0];\r\n        if (event.getSender() instanceof BlockCommandSender) {\r\n            this.location = ((BlockCommandSender) event.getSender()).getBlock().getLocation();\r\n            this.sourceType = \"command_block\";\r\n        }\r\n        else if (event.getSender() instanceof CommandMinecart) {\r\n            this.location = ((CommandMinecart) event.getSender()).getLocation();\r\n            this.sourceType = \"command_minecart\";\r\n        }\r\n        else {\r\n            this.location = null;\r\n            this.sourceType = \"server\";\r\n        }\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/server/InternalEventScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.server;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport org.bukkit.event.*;\nimport org.bukkit.plugin.Plugin;\nimport org.bukkit.plugin.RegisteredListener;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Modifier;\nimport java.util.*;\n\npublic class InternalEventScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // internal bukkit event\n    //\n    // @Switch event:<path> (required) to specify the Bukkit event path to use (like \"event:org.bukkit.event.block.BlockBreakEvent\")\n    //\n    // @Warning This exists primarily for testing/debugging, and is almost never a good idea to include in a real script.\n    //\n    // @Group Server\n    //\n    // @Cancellable true\n    //\n    // @Triggers when the specified internal Bukkit event fires. Useful for testing/debugging, or for interoperation with external plugins that have their own Bukkit events. Get the raw event via 'context.reflect_event'.\n    //\n    // -->\n\n    public InternalEventScriptEvent() {\n        registerCouldMatcher(\"internal bukkit event\");\n        registerSwitches(\"event\");\n    }\n\n\n    @Override\n    public boolean couldMatch(ScriptPath path) {\n        if (!super.couldMatch(path)) {\n            return false;\n        }\n        if (!path.switches.containsKey(\"event\")) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!currentEvent.getClass().getCanonicalName().equals(path.switches.get(\"event\"))) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            case \"fields\":\n                if (!CoreConfiguration.allowReflectionFieldReads) {\n                    return null;\n                }\n                BukkitImplDeprecations.internalEventReflectionContext.warn();\n                ListTag result = new ListTag();\n                Class c = currentEvent.getClass();\n                while (c != null && c != Object.class) {\n                    for (Field field : ReflectionHelper.getFields(c).getAllFields()) {\n                        if (!Modifier.isStatic(field.getModifiers())) {\n                            result.addObject(new ElementTag(field.getName(), true));\n                        }\n                    }\n                    c = c.getSuperclass();\n                }\n                return result;\n        }\n        if (name.startsWith(\"field_\")) {\n            if (!CoreConfiguration.allowReflectionFieldReads) {\n                return null;\n            }\n            BukkitImplDeprecations.internalEventReflectionContext.warn();\n            String fName = CoreUtilities.toLowerCase(name.substring(\"field_\".length()));\n            Class c = currentEvent.getClass();\n            while (c != null && c != Object.class) {\n                ReflectionHelper.FieldCache fields = ReflectionHelper.getFields(c);\n                for (Field field : fields.getAllFields()) {\n                    if (!Modifier.isStatic(field.getModifiers()) && CoreUtilities.toLowerCase(field.getName()).equals(fName)) {\n                        Object val = null;\n                        try {\n                            val = field.get(currentEvent);\n                        }\n                        catch (Throwable ex) {\n                            Debug.echoError(ex);\n                        }\n                        if (val != null) {\n                            return CoreUtilities.objectToTagForm(val, CoreUtilities.errorButNoDebugContext, false, false, false);\n                        }\n                    }\n                }\n                c = c.getSuperclass();\n            }\n        }\n        return super.getContext(name);\n    }\n\n\n    @Override\n    public void destroy() {\n        if (registeredHandlers != null) {\n            for (Map.Entry<RegisteredListener, HandlerList> handler : registeredHandlers) {\n                handler.getValue().unregister(handler.getKey());\n            }\n            registeredHandlers = null;\n        }\n    }\n\n    @Override\n    public void init() {\n        registeredHandlers = new ArrayList<>();\n        HashSet<String> eventsGrabbed = new HashSet<>();\n        for (ScriptPath path : new ArrayList<>(eventPaths)) {\n            String eventName = path.switches.get(\"event\");\n            if (!eventsGrabbed.add(eventName)) {\n                continue;\n            }\n            try {\n                Class<?> clazz = Class.forName(eventName);\n                if (!Event.class.isAssignableFrom(clazz)) {\n                    Debug.echoError(\"Cannot initialize Internal Bukkit Event for event '\" + eventName + \"': that class is not an event class.\");\n                    return;\n                }\n                EventPriority priority = EventPriority.NORMAL;\n                String bukkitPriority = path.switches.get(\"bukkit_priority\");\n                if (bukkitPriority != null) {\n                    try {\n                        priority = EventPriority.valueOf(bukkitPriority.toUpperCase());\n                    }\n                    catch (IllegalArgumentException ex) {\n                        Debug.echoError(\"Invalid 'bukkit_priority' switch for event '\" + path.event + \"' in script '\" + path.container.getName() + \"'.\");\n                        Debug.echoError(ex);\n                    }\n                }\n                InternalEventScriptEvent handler = (InternalEventScriptEvent) clone();\n                handler.eventPaths = new ArrayList<>();\n                handler.eventPaths.add(path);\n                handler.registeredHandlers = null;\n                handler.initForPriority(priority, this, (Class<? extends Event>) clazz);\n                eventPaths.remove(path);\n            }\n            catch (ClassNotFoundException ex) {\n                Debug.echoError(\"Cannot initialize Internal Bukkit Event for event '\" + eventName + \"': that event class does not exist.\");\n            }\n            catch (Throwable ex) {\n                Debug.echoError(ex);\n            }\n        }\n    }\n\n    public void initForPriority(EventPriority priority, InternalEventScriptEvent baseEvent, Class<? extends Event> clazz) {\n        Plugin plugin = Denizen.getInstance();\n        for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : plugin.getPluginLoader().createRegisteredListeners(this, plugin).entrySet()) {\n            for (RegisteredListener registeredListener : entry.getValue()) {\n                RegisteredListener newListener = new RegisteredListener(this, getExecutor(registeredListener), priority, plugin, false);\n                HandlerList handlers = getEventListeners(clazz);\n                handlers.register(newListener);\n                baseEvent.registeredHandlers.add(new HashMap.SimpleEntry<>(newListener, handlers));\n            }\n        }\n    }\n\n    @EventHandler\n    public void onEventHappens(Event event) {\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/server/ListPingScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.server;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.server.ServerListPingEvent;\r\nimport org.bukkit.util.CachedServerIcon;\r\n\r\nimport java.io.File;\r\nimport java.util.HashMap;\r\nimport java.util.concurrent.Future;\r\nimport java.util.concurrent.TimeUnit;\r\n\r\npublic class ListPingScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // server list ping\r\n    //\r\n    // @Group Server\r\n    //\r\n    // @Triggers when the server is pinged for a client's server list.\r\n    //\r\n    // @Context\r\n    // <context.motd> returns the MOTD that will show.\r\n    // <context.max_players> returns the number of max players that will show.\r\n    // <context.num_players> returns the number of online players that will show.\r\n    // <context.address> returns the IP address requesting the list.\r\n    // <context.hostname> returns an ElementTag of the server address that is being pinged. Available only on MC 1.19+.\r\n    // <context.protocol_version> returns the protocol ID of the server's version (only on Paper).\r\n    // <context.version_name> returns the name of the server's version (only on Paper).\r\n    // <context.client_protocol_version> returns the client's protocol version ID (only on Paper).\r\n    //\r\n    // @Determine\r\n    // \"MAX_PLAYERS:<ElementTag(Number)>\" to change the max player amount that will show.\r\n    // \"ICON:<ElementTag>\" of a file path to an icon image, to change the icon that will display.\r\n    // \"PROTOCOL_VERSION:<ElementTag(Number)>\" to change the protocol ID number of the server's version (only on Paper).\r\n    // \"VERSION_NAME:<ElementTag>\" to change the server's version name (only on Paper).\r\n    // \"EXCLUDE_PLAYERS:<ListTag(PlayerTag)>\" to exclude a set of players from showing in the player count or preview of online players (only on Paper).\r\n    // \"ALTERNATE_PLAYER_TEXT:<ListTag>\" to set custom text for the player list section of the server status (only on Paper). (Requires \"Allow restricted actions\" in Denizen/config.yml). Usage of this to present lines that look like player names (but aren't) is forbidden.\r\n    // \"MOTD:<ElementTag>\" to change the MOTD that will show.\r\n    //\r\n    // -->\r\n\r\n    public ListPingScriptEvent() {\r\n        registerCouldMatcher(\"server list ping\");\r\n        this.<ListPingScriptEvent, ListTag>registerDetermination(null, ListTag.class, (evt, context, list) -> {\r\n            if (ArgumentHelper.matchesInteger(list.get(0))) {\r\n                evt.event.setMaxPlayers(Integer.parseInt(list.get(0)));\r\n                if (list.size() == 2) {\r\n                    evt.setMotd(list.get(1));\r\n                }\r\n            }\r\n            else {\r\n                evt.setMotd(list.get(0));\r\n            }\r\n        });\r\n        this.<ListPingScriptEvent, ElementTag>registerOptionalDetermination(\"max_players\", ElementTag.class, (evt, context, max) -> {\r\n            if (max.isInt()) {\r\n                evt.event.setMaxPlayers(max.asInt());\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n        this.<ListPingScriptEvent, ElementTag>registerDetermination(\"motd\", ElementTag.class, (evt, context, motd) -> {\r\n            evt.setMotd(motd.asString());\r\n        });\r\n        this.<ListPingScriptEvent, ElementTag>registerOptionalDetermination(\"icon\", ElementTag.class, (evt, context, iconPath) -> {\r\n            String iconFile = iconPath.toString();\r\n            CachedServerIcon icon = iconCache.get(iconFile);\r\n            if (icon != null) {\r\n                evt.event.setServerIcon(icon);\r\n                return true;\r\n            }\r\n            File file = new File(iconFile);\r\n            if (!Utilities.canReadFile(file)) {\r\n                Debug.echoError(\"Cannot read icon file '\" + iconFile + \"' due to security settings in Denizen/config.yml\");\r\n                return false;\r\n            }\r\n            try {\r\n                icon = Bukkit.loadServerIcon(file);\r\n            }\r\n            catch (Exception ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            if (icon != null) {\r\n                iconCache.put(iconFile, icon);\r\n                evt.event.setServerIcon(icon);\r\n                return true;\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n\r\n\r\n    public ServerListPingEvent event;\r\n\r\n    // Despite the 'cached' class name, there's no actual internal cache.\r\n    public static HashMap<String, CachedServerIcon> iconCache = new HashMap<>();\r\n\r\n    public void setMotd(String text) {\r\n        event.setMotd(text);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"motd\" -> new ElementTag(event.getMotd());\r\n            case \"max_players\" -> new ElementTag(event.getMaxPlayers());\r\n            case \"num_players\" -> new ElementTag(event.getNumPlayers());\r\n            case \"address\" -> new ElementTag(event.getAddress().toString());\r\n            case \"hostname\" -> NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) ? new ElementTag(event.getHostname(), true) : null;\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    public void syncFire(ServerListPingEvent event) {\r\n        this.event = event;\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            BukkitScriptEvent altEvent = (BukkitScriptEvent) clone();\r\n            Future future = Bukkit.getScheduler().callSyncMethod(Denizen.getInstance(), () -> {\r\n                altEvent.fire(event);\r\n                return null;\r\n            });\r\n            try {\r\n                future.get(5, TimeUnit.SECONDS);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            return;\r\n        }\r\n        fire(event);\r\n    }\r\n\r\n    public static class ListPingScriptEventSpigotImpl extends ListPingScriptEvent {\r\n\r\n        @EventHandler\r\n        public void onListPing(ServerListPingEvent event) {\r\n            syncFire(event);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/server/ServerPrestartScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.server;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.scripts.containers.core.WorldScriptContainer;\r\n\r\npublic class ServerPrestartScriptEvent extends BukkitScriptEvent {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // server prestart\r\n    //\r\n    // @Group Server\r\n    //\r\n    // @Triggers before the server finishes starting... fired after some saves are loaded, but before other data is loaded. Use with extreme caution.\r\n    //\r\n    // @Warning This event uses special pre-loading tricks to fire before everything else. Use extreme caution.\r\n    //\r\n    // @Example\r\n    // on server prestart:\r\n    // - createworld my_extra_world\r\n    //\r\n    // -->\r\n\r\n    public ServerPrestartScriptEvent() {\r\n        instance = this;\r\n        registerCouldMatcher(\"server prestart\");\r\n    }\r\n\r\n    public static ServerPrestartScriptEvent instance;\r\n\r\n    public void specialHackRunEvent() {\r\n        for (WorldScriptContainer container : ScriptEvent.worldContainers) {\r\n            if (container.containsScriptSection(\"events.on server prestart\") && container.shouldEnable()) {\r\n                ScriptPath path = new ScriptPath(container, \"server prestart\", \"on server prestart\");\r\n                clone().run(path);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/server/ServerStartScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.server;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\n\r\npublic class ServerStartScriptEvent extends BukkitScriptEvent {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // server start\r\n    //\r\n    // @Group Server\r\n    //\r\n    // @Triggers when the server starts.\r\n    //\r\n    // -->\r\n\r\n    public ServerStartScriptEvent() {\r\n        instance = this;\r\n        registerCouldMatcher(\"server start\");\r\n    }\r\n\r\n    public static ServerStartScriptEvent instance;\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/server/TabCompleteScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.server;\n\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\nimport org.bukkit.command.ConsoleCommandSender;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.server.TabCompleteEvent;\n\npublic class TabCompleteScriptEvent extends BukkitScriptEvent implements Listener {\n\n    // <--[event]\n    // @Events\n    // tab complete\n    //\n    // @Group Server\n    //\n    // @Switch command:<command_name> to only process the event if the command matches the input name.\n    //\n    // @Cancellable true\n    //\n    // @Triggers when a player or the console is sent a list of available tab completions.\n    //\n    // @Context\n    // <context.buffer> returns the full raw command buffer.\n    // <context.command> returns the command name.\n    // <context.current_arg> returns the current argument for completion.\n    // <context.completions> returns a list of available tab completions.\n    // <context.server> returns true if the tab completion was triggered from the console.\n    //\n    // @Determine\n    // ListTag to set the list of available tab completions.\n    //\n    // @Player when the tab completion is done by a player.\n    //\n    // -->\n\n    public TabCompleteScriptEvent() {\n        registerCouldMatcher(\"tab complete\");\n        registerSwitches(\"command\");\n    }\n\n    public TabCompleteEvent event;\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runGenericSwitchCheck(path, \"command\", getCommand())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    public String getCommand() {\n        String[] args = event.getBuffer().trim().split(\" \");\n        String cmd = args.length > 0 ? args[0] : \"\";\n        if (event.getSender() instanceof Player) {\n            cmd = cmd.replaceFirst(\"/\", \"\");\n        }\n        return cmd;\n    }\n\n    public String getCurrentArg() {\n        int i = event.getBuffer().lastIndexOf(' ');\n        return i > 0 ? event.getBuffer().substring(i + 1) : getCommand();\n    }\n\n    @Override\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\n        String determination = determinationObj.toString();\n        if (determination.length() > 0) {\n            event.setCompletions(ListTag.valueOf(determination, getTagContext(path)));\n            return true;\n        }\n        return super.applyDetermination(path, determinationObj);\n    }\n\n    @Override\n    public ScriptEntryData getScriptEntryData() {\n        return new BukkitScriptEntryData(event.getSender() instanceof Player ? new PlayerTag((Player) event.getSender()) : null, null);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            case \"buffer\":\n                return new ElementTag(event.getBuffer());\n            case \"command\":\n                return new ElementTag(getCommand());\n            case \"current_arg\":\n                return new ElementTag(getCurrentArg());\n            case \"completions\":\n                return new ListTag(event.getCompletions());\n            case \"server\":\n                return new ElementTag(event.getSender() instanceof ConsoleCommandSender);\n        }\n        return super.getContext(name);\n    }\n\n    @EventHandler\n    public void onTabComplete(TabCompleteEvent event) {\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/vehicle/VehicleCollidesBlockScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.vehicle;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.vehicle.VehicleBlockCollisionEvent;\r\n\r\npublic class VehicleCollidesBlockScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // vehicle collides with block\r\n    // vehicle collides with <material>\r\n    // <vehicle> collides with block\r\n    // <vehicle> collides with <material>\r\n    //\r\n    // @Group Vehicle\r\n    //\r\n    // @Regex ^on [^\\s]+ collides with [^\\s]+$\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Triggers when a vehicle collides with a block.\r\n    //\r\n    // @Context\r\n    // <context.vehicle> returns the EntityTag of the vehicle.\r\n    // <context.location> returns the LocationTag of the block.\r\n    //\r\n    // -->\r\n\r\n    public VehicleCollidesBlockScriptEvent() {\r\n    }\r\n\r\n\r\n    public EntityTag vehicle;\r\n    public LocationTag location;\r\n    private MaterialTag material;\r\n    public VehicleBlockCollisionEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventLower.contains(\"collides with\")) {\r\n            return false;\r\n        }\r\n        if (!exactMatchesVehicle(path.eventArgLowerAt(0))) {\r\n            return false;\r\n        }\r\n        if (!couldMatchBlock(path.eventArgLowerAt(3))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, vehicle)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(3, material)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"vehicle\")) {\r\n            return vehicle;\r\n        }\r\n        else if (name.equals(\"location\")) {\r\n            return location;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onVehicleCollidesBlock(VehicleBlockCollisionEvent event) {\r\n        vehicle = new EntityTag(event.getVehicle());\r\n        location = new LocationTag(event.getBlock().getLocation());\r\n        material = new MaterialTag(event.getBlock());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/vehicle/VehicleCollidesEntityScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.vehicle;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.vehicle.VehicleEntityCollisionEvent;\r\n\r\npublic class VehicleCollidesEntityScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // vehicle collides with entity\r\n    // vehicle collides with <entity>\r\n    // <vehicle> collides with entity\r\n    // <vehicle> collides with <entity>\r\n    //\r\n    // @Group Vehicle\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch type:<entity> to only process the event if the colliding entity matches the EntityTag matcher input.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a vehicle collides with an entity.\r\n    //\r\n    // @Context\r\n    // <context.vehicle> returns the EntityTag of the vehicle.\r\n    // <context.entity> returns the EntityTag of the entity the vehicle has collided with.\r\n    // <context.pickup> returns whether the vehicle can pick up the entity.\r\n    //\r\n    // @Determine\r\n    // \"PICKUP:<ElementTag(Boolean)>\" to set whether the vehicle is allowed to pick up the entity or not.\r\n    //\r\n    // @Player when a vehicle collides with a player.\r\n    //\r\n    // @NPC when a vehicle collides with an NPC.\r\n    //\r\n    // @Example\r\n    // on vehicle collides with entity:\r\n    //\r\n    // @Example\r\n    // on minecart collides with sheep:\r\n    //\r\n    // @Example\r\n    // # This example disambiguates this event from the \"projectile collides with entity\" event for specific entity types.\r\n    // on vehicle collides with entity type:creeper:\r\n    // - announce \"A <context.vehicle.entity_type> collided with a creeper!\"\r\n    //\r\n    // -->\r\n\r\n    public VehicleCollidesEntityScriptEvent() {\r\n        registerCouldMatcher(\"<vehicle> collides with <entity>\");\r\n        registerSwitches(\"type\");\r\n    }\r\n\r\n    public VehicleEntityCollisionEvent event;\r\n    public EntityTag vehicle;\r\n    public EntityTag entity;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, vehicle)) {\r\n            return false;\r\n        }\r\n        if (!path.tryArgObject(3, entity)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, vehicle.getLocation())) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"type\", entity)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String lower = CoreUtilities.toLowerCase(determinationObj.toString());\r\n            if (lower.startsWith(\"pickup:\")) {\r\n                event.setPickupCancelled(!(new ElementTag(lower.substring(\"pickup:\".length())).asBoolean()));\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"vehicle\": return vehicle;\r\n            case \"entity\": return entity.getDenizenObject();\r\n            case \"pickup\": return new ElementTag(!event.isPickupCancelled());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onVehicleCollidesEntity(VehicleEntityCollisionEvent event) {\r\n        entity = new EntityTag(event.getEntity());\r\n        vehicle = new EntityTag(event.getVehicle());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/vehicle/VehicleCreatedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.vehicle;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.vehicle.VehicleCreateEvent;\r\n\r\npublic class VehicleCreatedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // vehicle created\r\n    // <vehicle> created\r\n    //\r\n    // @Group Vehicle\r\n    //\r\n    // @Regex ^on [^\\s]+ created$\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a vehicle is created.\r\n    //\r\n    // @Context\r\n    // <context.vehicle> returns the EntityTag of the vehicle.\r\n    //\r\n    // -->\r\n\r\n    public VehicleCreatedScriptEvent() {\r\n    }\r\n\r\n    public EntityTag vehicle;\r\n    public VehicleCreateEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventArgLowerAt(1).equals(\"created\")) {\r\n            return false;\r\n        }\r\n        if (!exactMatchesVehicle(path.eventArgLowerAt(0))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, vehicle)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, vehicle.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    // TODO: Can the vehicle be an NPC?\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"vehicle\")) {\r\n            return vehicle;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onVehicleCreated(VehicleCreateEvent event) {\r\n        Entity entity = event.getVehicle();\r\n        EntityTag.rememberEntity(entity);\r\n        vehicle = new EntityTag(entity);\r\n        this.event = event;\r\n        fire(event);\r\n        EntityTag.forgetEntity(entity);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/vehicle/VehicleDamagedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.vehicle;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.vehicle.VehicleDamageEvent;\r\n\r\npublic class VehicleDamagedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <vehicle> damaged\r\n    // <entity> damages <vehicle>\r\n    //\r\n    // @Group Vehicle\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch type:<vehicle> to only run if the vehicle damaged matches the EntityTag matcher input.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a vehicle is damaged.\r\n    //\r\n    // @Context\r\n    // <context.vehicle> returns the EntityTag of the vehicle.\r\n    // <context.entity> returns the EntityTag of the attacking entity.\r\n    // <context.damage> returns the amount of damage to be received.\r\n    //\r\n    // @Determine\r\n    // ElementTag(Decimal) to set the value of the damage received by the vehicle.\r\n    //\r\n    // @NPC when the entity that damaged the vehicle is a player.\r\n    //\r\n    // @NPC when the entity that damaged the vehicle is an NPC.\r\n    //\r\n    // @Example\r\n    // on vehicle damaged:\r\n    // - announce \"A <context.vehicle.entity_type> took a hit!\"\r\n    //\r\n    // @Example\r\n    // # This example disambiguates this event from the \"entity damaged\" event for specific vehicle entity types.\r\n    // on vehicle damaged type:minecart:\r\n    // - announce \"<context.vehicle.entity_type> took vehicular damage!\"\r\n    //\r\n    // @Example\r\n    // on player damages minecart:\r\n    // - announce \"<player.name> caused damage to a minecart!\"\r\n    // -->\r\n\r\n    public VehicleDamagedScriptEvent() {\r\n        registerCouldMatcher(\"<vehicle> damaged\");\r\n        registerCouldMatcher(\"<entity> damages <vehicle>\");\r\n        registerSwitches(\"type\");\r\n    }\r\n\r\n    public EntityTag vehicle;\r\n    public EntityTag entity;\r\n    public VehicleDamageEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (!exactMatchesVehicle(path.eventArgLowerAt(0)) && !exactMatchesVehicle(path.eventArgLowerAt(2))) {\r\n            return false;\r\n        }\r\n        if (!couldMatchEntity(path.eventArgLowerAt(0))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        String veh = cmd.equals(\"damaged\") ? path.eventArgLowerAt(0) : path.eventArgLowerAt(2);\r\n        String ent = cmd.equals(\"damages\") ? path.eventArgLowerAt(0) : \"\";\r\n        if (!vehicle.tryAdvancedMatcher(veh, path.context)) {\r\n            return false;\r\n        }\r\n        if (!ent.isEmpty() && (entity == null || !entity.tryAdvancedMatcher(ent, path.context))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, vehicle.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag element && element.isDouble()) {\r\n            event.setDamage(element.asDouble());\r\n            return true;\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"vehicle\")) {\r\n            return vehicle.getDenizenObject();\r\n        }\r\n        else if (name.equals(\"entity\") && entity != null) {\r\n            return entity.getDenizenObject();\r\n        }\r\n        else if (name.equals(\"damage\")) {\r\n            return new ElementTag(event.getDamage());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onVehicleDestroyed(VehicleDamageEvent event) {\r\n        vehicle = new EntityTag(event.getVehicle());\r\n        entity = event.getAttacker() != null ? new EntityTag(event.getAttacker()) : null;\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/vehicle/VehicleDestroyedScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.vehicle;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.vehicle.VehicleDestroyEvent;\r\n\r\npublic class VehicleDestroyedScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // vehicle destroyed\r\n    // <vehicle> destroyed\r\n    // entity destroys vehicle\r\n    // <entity> destroys vehicle\r\n    // entity destroys <vehicle>\r\n    // <entity> destroys <vehicle>\r\n    //\r\n    // @Group Vehicle\r\n    //\r\n    // @Regex ^on [^\\s]+ (destroyed|destroys [^\\s]+)$\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a vehicle is destroyed.\r\n    //\r\n    // @Context\r\n    // <context.vehicle> returns the EntityTag of the vehicle.\r\n    // <context.entity> returns the EntityTag of the attacking entity.\r\n    //\r\n    // @NPC when the entity that destroyed the vehicle is a player..\r\n    //\r\n    // @NPC when the entity that destroyed the vehicle is an NPC.\r\n    //\r\n    // -->\r\n\r\n    public VehicleDestroyedScriptEvent() {\r\n    }\r\n\r\n    public EntityTag vehicle;\r\n    public EntityTag entity;\r\n    public VehicleDestroyEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        if (cmd.equals(\"destroys\")) {\r\n            if (!couldMatchEntity(path.eventArgLowerAt(0))) {\r\n                return false;\r\n            }\r\n            if (!exactMatchesVehicle(path.eventArgLowerAt(2))) {\r\n                return false;\r\n            }\r\n        }\r\n        else if (cmd.equals(\"destroyed\")) {\r\n            if (!exactMatchesVehicle(path.eventArgLowerAt(0))) {\r\n                return false;\r\n            }\r\n            if (path.eventArgLowerAt(2).equals(\"by\")) {\r\n                return false;\r\n            }\r\n        }\r\n        else {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        String veh = cmd.equals(\"destroyed\") ? path.eventArgLowerAt(0) : path.eventArgLowerAt(2);\r\n        String ent = cmd.equals(\"destroys\") ? path.eventArgLowerAt(0) : \"\";\r\n        if (!vehicle.tryAdvancedMatcher(veh, path.context)) {\r\n            return false;\r\n        }\r\n        if (ent.length() > 0 && (entity == null || !entity.tryAdvancedMatcher(ent, path.context))) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, vehicle.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(entity);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"vehicle\")) {\r\n            return vehicle;\r\n        }\r\n        else if (name.equals(\"entity\") && entity != null) {\r\n            return entity.getDenizenObject();\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onVehicleDestroyed(VehicleDestroyEvent event) {\r\n        vehicle = new EntityTag(event.getVehicle());\r\n        entity = event.getAttacker() != null ? new EntityTag(event.getAttacker()) : null;\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/vehicle/VehicleMoveScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.vehicle;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.vehicle.VehicleMoveEvent;\r\n\r\npublic class VehicleMoveScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // vehicle moves\r\n    // <vehicle> moves\r\n    //\r\n    // @Group Vehicle\r\n    //\r\n    // @Regex ^on [^\\s]+ moves$\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning This event fires very very rapidly!\r\n    //\r\n    // @Triggers when a vehicle moves in the slightest.\r\n    //\r\n    // @Context\r\n    // <context.vehicle> returns the EntityTag of the vehicle.\r\n    // <context.from> returns the location of where the vehicle was.\r\n    // <context.to> returns the location of where the vehicle is.\r\n    //\r\n    // -->\r\n\r\n    public VehicleMoveScriptEvent() {\r\n    }\r\n\r\n    public EntityTag vehicle;\r\n    public LocationTag from;\r\n    public LocationTag to;\r\n    public VehicleMoveEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!path.eventArgLowerAt(1).equals(\"moves\")) {\r\n            return false;\r\n        }\r\n        if (!exactMatchesVehicle(path.eventArgLowerAt(0))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, vehicle)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, vehicle.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    // TODO: Can the vehicle be an NPC?\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"from\":\r\n                return from;\r\n            case \"to\":\r\n                return to;\r\n            case \"vehicle\":\r\n                return vehicle;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onVehicleMove(VehicleMoveEvent event) {\r\n        to = new LocationTag(event.getTo());\r\n        from = new LocationTag(event.getFrom());\r\n        vehicle = new EntityTag(event.getVehicle());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/ChunkLoadEntitiesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.ChunkTag;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.EntitiesLoadEvent;\r\n\r\npublic class ChunkLoadEntitiesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // chunk loads entities\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning This event will fire very rapidly.\r\n    //\r\n    // @Switch entity_type:<type-matcher> to only fire in the chunk contains an entity that matches the given entity matcher.\r\n    // @Switch include_empty:<true/false> defaults to false, set to 'true' to include chunks loading an empty set of entities.\r\n    //\r\n    // @Triggers when a chunk loads in its entities.\r\n    //\r\n    // @Context\r\n    // <context.chunk> returns the loading chunk.\r\n    // <context.entities> returns a ListTag of all entities to be loaded.\r\n    //\r\n    // -->\r\n\r\n    public ChunkLoadEntitiesScriptEvent() {\r\n        registerCouldMatcher(\"chunk loads entities\");\r\n        registerSwitches(\"entity_type\", \"include_empty\");\r\n    }\r\n\r\n\r\n    public ChunkTag chunk;\r\n    public EntitiesLoadEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.checkSwitch(\"include_empty\", \"false\") && event.getEntities().isEmpty()) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, chunk.getCenter())) {\r\n            return false;\r\n        }\r\n        String typeMatch = path.switches.get(\"entity_type\");\r\n        if (typeMatch != null) {\r\n            boolean any = false;\r\n            for (Entity e : event.getEntities()) {\r\n                any = new EntityTag(e).tryAdvancedMatcher(typeMatch, path.context);\r\n                if (any) {\r\n                    break;\r\n                }\r\n            }\r\n            if (!any) {\r\n                return false;\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"chunk\": return chunk;\r\n            case \"entities\":\r\n                ListTag entList = new ListTag();\r\n                for (Entity e : event.getEntities()) {\r\n                    entList.addObject(new EntityTag(e));\r\n                }\r\n                return entList;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onChunkLoad(EntitiesLoadEvent event) {\r\n        chunk = new ChunkTag(event.getChunk());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/ChunkLoadScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.ChunkTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.ChunkLoadEvent;\r\n\r\npublic class ChunkLoadScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // chunk loads (for the first time)\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning This event will fire *extremely* rapidly and often when using 'for the first time'.\r\n    // When not using that, it will fire so rapidly that lag is almost guaranteed. Use with maximum caution.\r\n    //\r\n    // @Triggers when a new chunk is loaded\r\n    //\r\n    // @Context\r\n    // <context.chunk> returns the loading chunk.\r\n    //\r\n    // -->\r\n\r\n    public ChunkLoadScriptEvent() {\r\n        registerCouldMatcher(\"chunk loads (for the first time)\");\r\n    }\r\n\r\n\r\n    public ChunkTag chunk;\r\n    public ChunkLoadEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.eventArgLowerAt(2).equals(\"for\") && !event.isNewChunk()) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, chunk.getCenter())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"chunk\")) {\r\n            return chunk;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onChunkLoad(ChunkLoadEvent event) {\r\n        chunk = new ChunkTag(event.getChunk());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/ChunkUnloadEntitiesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.ChunkTag;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.EntitiesUnloadEvent;\r\n\r\npublic class ChunkUnloadEntitiesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // chunk unloads entities\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning This event will fire very rapidly.\r\n    //\r\n    // @Switch entity_type:<type-matcher> to only fire in the chunk contains an entity that matches the given entity matcher.\r\n    // @Switch include_empty:<true/false> defaults to false, set to 'true' to include chunks loading an empty set of entities.\r\n    //\r\n    // @Triggers when a chunk unloads in its entities. Note that this is basically a notification - it's already too late to change entity data.\r\n    //\r\n    // @Context\r\n    // <context.chunk> returns the unloading chunk.\r\n    // <context.entities> returns a ListTag of all entities being unloaded.\r\n    //\r\n    // -->\r\n\r\n    public ChunkUnloadEntitiesScriptEvent() {\r\n        registerCouldMatcher(\"chunk unloads entities\");\r\n        registerSwitches(\"entity_type\", \"include_empty\");\r\n    }\r\n\r\n\r\n    public ChunkTag chunk;\r\n    public EntitiesUnloadEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.checkSwitch(\"include_empty\", \"false\") && event.getEntities().isEmpty()) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, chunk.getCenter())) {\r\n            return false;\r\n        }\r\n        String typeMatch = path.switches.get(\"entity_type\");\r\n        if (typeMatch != null) {\r\n            boolean any = false;\r\n            for (Entity e : event.getEntities()) {\r\n                any = new EntityTag(e).tryAdvancedMatcher(typeMatch, path.context);\r\n                if (any) {\r\n                    break;\r\n                }\r\n            }\r\n            if (!any) {\r\n                return false;\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"chunk\": return chunk;\r\n            case \"entities\":\r\n                ListTag entList = new ListTag();\r\n                for (Entity e : event.getEntities()) {\r\n                    entList.addObject(new EntityTag(e));\r\n                }\r\n                return entList;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onChunkLoad(EntitiesUnloadEvent event) {\r\n        chunk = new ChunkTag(event.getChunk());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/ChunkUnloadScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.ChunkTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.ChunkUnloadEvent;\r\n\r\npublic class ChunkUnloadScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // chunk unloads\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Warning This event will fire *extremely* rapidly and often!\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a chunk is unloaded\r\n    //\r\n    // @Context\r\n    // <context.chunk> returns the unloading chunk.\r\n    //\r\n    // -->\r\n\r\n    public ChunkUnloadScriptEvent() {\r\n        registerCouldMatcher(\"chunk unloads\");\r\n    }\r\n\r\n\r\n    public ChunkTag chunk;\r\n    public ChunkUnloadEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, chunk.getCenter())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"chunk\")) {\r\n            return chunk;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onChunkUnload(ChunkUnloadEvent event) {\r\n        chunk = new ChunkTag(event.getChunk());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/GenericGameEventScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.GenericGameEvent;\r\n\r\npublic class GenericGameEventScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // generic game event\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch type:<game_event_name> to only process the event when a specific game event is fired.\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when the minecraft world experiences a generic minecraft game event. This is normally used for sculk sensors.\r\n    //\r\n    // @Context\r\n    // <context.location> returns the location of the event.\r\n    // <context.entity> returns the entity that triggered the event, if any.\r\n    // <context.game_event> returns the name of the Minecraft game event, for example \"minecraft:block_change\". See <@link url https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/GameEvent.html>.\r\n    // <context.radius> returns the radius, in blocks, that the event is broadcast to.\r\n    //\r\n    // -->\r\n\r\n    public GenericGameEventScriptEvent() {\r\n        registerCouldMatcher(\"generic game event\");\r\n        registerSwitches(\"type\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public GenericGameEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        String typeSwitch = path.switches.get(\"type\");\r\n        if (typeSwitch != null) {\r\n            if (!runGenericCheck(typeSwitch, event.getEvent().getKey().toString()) && !runGenericCheck(typeSwitch, event.getEvent().getKey().getKey())) {\r\n                return false;\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getEntity());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"location\": return location;\r\n            case \"entity\":\r\n                if (event.getEntity() != null) {\r\n                    return new EntityTag(event.getEntity()).getDenizenObject();\r\n                }\r\n                break;\r\n            case \"game_event\": return new ElementTag(event.getEvent().getKey().toString());\r\n            case \"radius\": return new ElementTag(event.getRadius());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onGenericGameEvent(GenericGameEvent event) {\r\n        location = new LocationTag(event.getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/LightningStrikesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.weather.LightningStrikeEvent;\r\n\r\npublic class LightningStrikesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // lightning strikes\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when lightning strikes in a world.\r\n    //\r\n    // @Context\r\n    // <context.lightning> returns the EntityTag of the lightning.\r\n    // <context.location> returns the LocationTag where the lightning struck.\r\n    //\r\n    // -->\r\n\r\n    public LightningStrikesScriptEvent() {\r\n        registerCouldMatcher(\"lightning strikes\");\r\n    }\r\n\r\n    public LocationTag location;\r\n    public LightningStrikeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"lightning\":\r\n                return new EntityTag(event.getLightning());\r\n            case \"location\":\r\n                return location;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onLightningStrikes(LightningStrikeEvent event) {\r\n        location = new LocationTag(event.getLightning().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/LingeringPotionSplashScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.LingeringPotionSplashEvent;\r\n\r\npublic class LingeringPotionSplashScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // lingering potion splash|splashes\r\n    // lingering <item> splash|splashes\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a lingering splash potion breaks open\r\n    //\r\n    // @Context\r\n    // <context.potion> returns an ItemTag of the potion that broke open.\r\n    // <context.location> returns the LocationTag the splash potion broke open at.\r\n    // <context.entity> returns an EntityTag of the splash potion.\r\n    // <context.cloud> returns the EntityTag of the area of effect cloud.\r\n    // <context.radius> returns the radius of the effect cloud.\r\n    // <context.duration> returns the lingering duration of the effect cloud.\r\n    //\r\n    // -->\r\n\r\n    public LingeringPotionSplashScriptEvent() {\r\n        registerCouldMatcher(\"lingering <item> splash|splashes\");\r\n    }\r\n\r\n    public LingeringPotionSplashEvent event;\r\n    public LocationTag location;\r\n    public ItemTag item;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(1, item)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"location\" -> location;\r\n            case \"radius\" -> new ElementTag(event.getAreaEffectCloud().getRadius());\r\n            case \"duration\" -> new DurationTag((long) event.getAreaEffectCloud().getDuration());\r\n            case \"potion\" -> item;\r\n            case \"entity\" -> new EntityTag(event.getEntity());\r\n            case \"cloud\" -> new EntityTag(event.getAreaEffectCloud());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onLingeringPotionSplash(LingeringPotionSplashEvent event) {\r\n        item = new ItemTag(event.getEntity().getItem());\r\n        location = new LocationTag(event.getAreaEffectCloud().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/LootGenerateScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.LootGenerateEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.ArrayList;\r\n\r\npublic class LootGenerateScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // loot generates\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Switch for:<type> to only process the event if a certain inventory type is receiving loot (like 'for:chest').\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when loot is generated somewhere in the world (like a vanilla chest being opened for the first time).\r\n    //\r\n    // @Context\r\n    // <context.entity> returns an entity that caused loot generation, if any.\r\n    // <context.inventory> returns the InventoryTag that loot is generating into.\r\n    // <context.items> returns a ListTag of the items being generated.\r\n    // <context.loot_table_id> returns an element indicating the minecraft key for the loot-table that was generated.\r\n    //\r\n    // @Determine\r\n    // \"LOOT:<ListTag(ItemTag)>\" to change the list of items that will generate as loot.\r\n    //\r\n    // @Player when the linked entity is a player.\r\n    //\r\n    // -->\r\n\r\n    public LootGenerateScriptEvent() {\r\n        registerCouldMatcher(\"loot generates\");\r\n        registerSwitches(\"for\");\r\n    }\r\n\r\n    public LootGenerateEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!runInCheck(path, event.getLootContext().getLocation())) {\r\n            return false;\r\n        }\r\n        if (path.switches.containsKey(\"for\")) {\r\n            if (event.getInventoryHolder() == null || !runGenericSwitchCheck(path, \"for\", event.getInventoryHolder().getInventory().getType().name())) {\r\n                return false;\r\n            }\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getEntity() != null ? new EntityTag(event.getEntity()) : null);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String determination = determinationObj.toString();\r\n            String determinationLower = CoreUtilities.toLowerCase(determination);\r\n            if (determinationLower.startsWith(\"loot:\")) {\r\n                ListTag list = ListTag.valueOf(determination.substring(\"loot:\".length()), getTagContext(path));\r\n                ArrayList<ItemStack> newLoot = new ArrayList<>(list.size());\r\n                for (ItemTag item : list.filter(ItemTag.class, getTagContext(path))) {\r\n                    newLoot.add(item.getItemStack());\r\n                }\r\n                event.setLoot(newLoot);\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"inventory\") && event.getInventoryHolder() != null) {\r\n            return InventoryTag.mirrorBukkitInventory(event.getInventoryHolder().getInventory());\r\n        }\r\n        else if (name.equals(\"entity\") && event.getEntity() != null) {\r\n            return new EntityTag(event.getEntity()).getDenizenObject();\r\n        }\r\n        else if (name.equals(\"items\")) {\r\n            ListTag result = new ListTag();\r\n            for (ItemStack item : event.getLoot()) {\r\n                result.addObject(new ItemTag(item));\r\n            }\r\n            return result;\r\n        }\r\n        else if (name.equals(\"loot_table_id\")) {\r\n            return new ElementTag(event.getLootTable().getKey().toString());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onLootGenerate(LootGenerateEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/PortalCreateScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.PortalCreateEvent;\r\n\r\npublic class PortalCreateScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // portal created (because <'reason'>)\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a portal is created.\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the EntityTag that created the portal.\r\n    // <context.world> returns the WorldTag the portal was created in.\r\n    // <context.reason> returns an ElementTag of the reason the portal was created. (FIRE, NETHER_PAIR, END_PLATFORM)\r\n    // <context.blocks> returns a ListTag of all the blocks that will become portal blocks.\r\n    //\r\n    // -->\r\n\r\n    public PortalCreateScriptEvent() {\r\n        registerCouldMatcher(\"portal created (because <'reason'>)\");\r\n    }\r\n\r\n    public ElementTag reason;\r\n    public PortalCreateEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.eventArgLowerAt(2).equals(\"because\") && !runGenericCheck(path.eventArgLowerAt(3), reason.asString())) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getBlocks().get(0).getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\": return event.getEntity() != null ? new EntityTag(event.getEntity()).getDenizenObject() : null;\r\n            case \"world\": return new WorldTag(event.getWorld());\r\n            case \"reason\": return reason;\r\n            case \"blocks\":\r\n                ListTag blocks = new ListTag();\r\n                for (BlockState blockState : event.getBlocks()) {\r\n                    blocks.addObject(new LocationTag(blockState.getLocation()));\r\n                }\r\n                return blocks;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPortalCreate(PortalCreateEvent event) {\r\n        reason = new ElementTag(event.getReason());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/PotionSplashScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.PotionSplashEvent;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class PotionSplashScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // potion splash|splashes\r\n    // <item> splash|splashes\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a splash potion breaks open.\r\n    //\r\n    // @Context\r\n    // <context.potion> returns an ItemTag of the potion that broke open.\r\n    // <context.entities> returns a ListTag of affected entities.\r\n    // <context.location> returns the LocationTag the splash potion broke open at.\r\n    // <context.entity> returns an EntityTag of the splash potion.\r\n    // <context.intensity> returns an ListTag(MapTag) of the intensity for all affected entities.\r\n    //\r\n    // @Determine\r\n    // INTENSITY:<ListTag(MapTag)>\" to set the intensity of specified entities.\r\n    //\r\n    // @Example\r\n    // # This example sets the intensity of the first affected entity to 0.6.\r\n    // on potion splashes:\r\n    // - if <context.entities.any>:\r\n    //      - determine INTENSITY:[entity=<context.entities.first>;intensity=0.6]\r\n    //\r\n    // -->\r\n\r\n    public PotionSplashScriptEvent() {\r\n        registerCouldMatcher(\"<item> splash|splashes\");\r\n    }\r\n\r\n    public ItemTag potion;\r\n    public LocationTag location;\r\n    public PotionSplashEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String item = path.eventArgLowerAt(0);\r\n        if (!potion.tryAdvancedMatcher(item, path.context)) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, location)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"entity\": return new EntityTag(event.getEntity());\r\n            case \"location\": return location;\r\n            case \"potion\": return potion;\r\n            case \"entities\":\r\n                ListTag entities = new ListTag();\r\n                for (Entity e : event.getAffectedEntities()) {\r\n                    entities.addObject(new EntityTag(e).getDenizenObject());\r\n                }\r\n                return entities;\r\n            case \"intensity\":\r\n                ListTag intensity = new ListTag();\r\n                for (Entity e : event.getAffectedEntities()) {\r\n                    if (e instanceof LivingEntity) {\r\n                        intensity.addObject(intensityToMap((LivingEntity) e));\r\n                    }\r\n                }\r\n                return intensity;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @Override\r\n    public boolean applyDetermination(ScriptPath path, ObjectTag determinationObj) {\r\n        if (determinationObj instanceof ElementTag) {\r\n            String lower = CoreUtilities.toLowerCase(determinationObj.toString());\r\n            if (lower.startsWith(\"intensity:\")) {\r\n                TagContext context = getTagContext(path);\r\n                ObjectTag obj = ListTag.valueOf(lower.substring(\"intensity:\".length()), context);\r\n                List<ObjectTag> data = new ArrayList<>(CoreUtilities.objectToList(obj, context));\r\n                for (ObjectTag result : data) {\r\n                    if (result.canBeType(MapTag.class)) {\r\n                        EntityTag entity = result.asType(MapTag.class, context).getObjectAs(\"entity\", EntityTag.class, context);\r\n                        ElementTag intensity = result.asType(MapTag.class, context).getObjectAs(\"intensity\", ElementTag.class, context);\r\n                        if (entity == null || intensity == null) {\r\n                            Debug.echoError(\"Cannot return values from map. Are you sure your MapTag input matches [entity=<EntityTag>;intensity=<ElementTag(Number)>]?\");\r\n                            continue;\r\n                        }\r\n                        if (intensity.isDouble()) {\r\n                            event.setIntensity(entity.getLivingEntity(), intensity.asDouble());\r\n                        }\r\n                    }\r\n                    else {\r\n                        Debug.echoError(\"MapTag input invalid. Are you sure you provided a valid MapTag?\");\r\n                    }\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        return super.applyDetermination(path, determinationObj);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPotionSplash(PotionSplashEvent event) {\r\n        potion = new ItemTag(event.getPotion().getItem());\r\n        location = new LocationTag(event.getEntity().getLocation());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n\r\n    public MapTag intensityToMap(LivingEntity entity) {\r\n        MapTag result = new MapTag();\r\n        result.putObject(\"entity\", new EntityTag(entity));\r\n        result.putObject(\"intensity\", new ElementTag(event.getIntensity(entity)));\r\n        return result;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/RaidFinishesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\n\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.raid.RaidFinishEvent;\n\npublic class RaidFinishesScriptEvent extends RaidScriptEvent<RaidFinishEvent> implements Listener {\n\n    // <--[event]\n    // @Events\n    // raid finishes\n    //\n    // @Group World\n    //\n    // @Location true\n    //\n    // @Triggers when a village raid finishes normally.\n    //\n    // @Context\n    // <context.raid> returns the raid data. See <@link language Raid Event Data>.\n    // <context.winners> returns the ListTag of players who completed the raid. This is separate from the raid's heroes in that the winners are guaranteed to be online.\n    //\n    // -->\n\n    public RaidFinishesScriptEvent() {\n        super(true);\n        registerCouldMatcher(\"raid finishes\");\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            case \"winners\":\n                ListTag list = new ListTag();\n                for (Player player : event.getWinners()) {\n                    list.addObject(new PlayerTag(player));\n                }\n                return list;\n        }\n        return super.getContext(name);\n    }\n\n    @EventHandler\n    public void onRaidFinishes(RaidFinishEvent event) {\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/RaidScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport org.bukkit.Raid;\nimport org.bukkit.entity.Raider;\nimport org.bukkit.event.raid.RaidEvent;\n\nimport java.util.UUID;\n\npublic class RaidScriptEvent<T extends RaidEvent> extends BukkitScriptEvent {\n\n    // <--[language]\n    // @name Raid Event Data\n    // @group Useful Lists\n    // @description\n    // Every event related to village raids has a <context.raid> property, a MapTag wrapper around raid data (see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Raid.html>).\n    // These events are <@link event player triggers raid>, <@link event raid finishes>, <@link event raid spawns wave>, and <@link event raid stops>.\n    //\n    // The data format is as follows:\n    // location: a LocationTag of the raid's center\n    // heroes: a list of PlayerTags that have participated in the raid\n    // raiders: a list of raider EntityTags that remain in the current wave\n    // status: the current status of the raid. See <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Raid.RaidStatus.html>\n    // age: the raid's age (active time) as a DurationTag\n    // level: the Bad Omen level that the raid was started with\n    // spawned_groups: the number of raider groups spawned\n    // total_groups: the number of groups planned to spawn or already spawned\n    // health: the combined health of all current raiders\n    // waves: the number of waves in the raid\n    // -->\n\n    public boolean checkRaidLocation;\n\n    public RaidScriptEvent(boolean checkRaidLocation) {\n        this.checkRaidLocation = checkRaidLocation;\n    }\n\n    public T event;\n\n    public static MapTag getRaidMap(Raid raid) {\n        MapTag data = new MapTag();\n        data.putObject(\"location\", new LocationTag(raid.getLocation()));\n        ListTag heroes = new ListTag();\n        for (UUID uuid : raid.getHeroes()) {\n            heroes.addObject(new PlayerTag(uuid));\n        }\n        data.putObject(\"heroes\", heroes);\n        ListTag raiders = new ListTag();\n        for (Raider raider : raid.getRaiders()) {\n            raiders.addObject(new EntityTag(raider));\n        }\n        data.putObject(\"raiders\", raiders);\n        data.putObject(\"status\", new ElementTag(raid.getStatus()));\n        data.putObject(\"age\", new DurationTag(raid.getActiveTicks()));\n        data.putObject(\"level\", new ElementTag(raid.getBadOmenLevel()));\n        data.putObject(\"total_groups\", new ElementTag(raid.getTotalGroups()));\n        data.putObject(\"spawned_groups\", new ElementTag(raid.getSpawnedGroups()));\n        data.putObject(\"health\", new ElementTag(raid.getTotalHealth()));\n        data.putObject(\"waves\", new ElementTag(raid.getTotalWaves()));\n        return data;\n    }\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (checkRaidLocation && !runInCheck(path, event.getRaid().getLocation())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            case \"raid\":\n                return getRaidMap(event.getRaid());\n        }\n        return super.getContext(name);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/RaidSpawnsWaveScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport org.bukkit.entity.Raider;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.raid.RaidSpawnWaveEvent;\n\npublic class RaidSpawnsWaveScriptEvent extends RaidScriptEvent<RaidSpawnWaveEvent> implements Listener {\n\n    // <--[event]\n    // @Events\n    // raid spawns wave\n    //\n    // @Group World\n    //\n    // @Location true\n    //\n    // @Triggers when a village raid spawns a new wave of raiders.\n    //\n    // @Context\n    // <context.raid> returns the raid data. See <@link language Raid Event Data>.\n    // <context.leader> returns the EntityTag of the patrol leader of the wave.\n    // <context.new_raiders> returns the ListTag of all new raiders.\n    //\n    // -->\n\n    public RaidSpawnsWaveScriptEvent() {\n        super(true);\n        registerCouldMatcher(\"raid spawns wave\");\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            case \"leader\":\n                return new EntityTag(event.getPatrolLeader());\n            case \"new_raiders\":\n                ListTag raiders = new ListTag();\n                for (Raider raider : event.getRaiders()) {\n                    raiders.addObject(new EntityTag(raider));\n                }\n                return raiders;\n        }\n        return super.getContext(name);\n    }\n\n    @EventHandler\n    public void onRaidSpawnsWave(RaidSpawnWaveEvent event) {\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/RaidStopsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\n\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.raid.RaidStopEvent;\n\npublic class RaidStopsScriptEvent extends RaidScriptEvent<RaidStopEvent> implements Listener {\n\n    // <--[event]\n    // @Events\n    // raid stops\n    //\n    // @Group World\n    //\n    // @Location true\n    //\n    // @Switch reason:<reason> to only process the event if the raid stopped for a certain reason.\n    //\n    // @Triggers when a village raid stops for any reason.\n    //\n    // @Context\n    // <context.raid> returns the raid data. See <@link language Raid Event Data>.\n    // <context.reason> returns the reason for stopping. See <@link url https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/event/raid/RaidStopEvent.Reason.html>.\n    //\n    // -->\n\n    public RaidStopsScriptEvent() {\n        super(true);\n        registerCouldMatcher(\"raid stops\");\n        registerSwitches(\"reason\");\n    }\n\n    @Override\n    public boolean matches(ScriptPath path) {\n        if (!runGenericSwitchCheck(path, \"reason\", event.getReason().name())) {\n            return false;\n        }\n        return super.matches(path);\n    }\n\n    @Override\n    public ObjectTag getContext(String name) {\n        switch (name) {\n            case \"reason\":\n                return new ElementTag(event.getReason());\n        }\n        return super.getContext(name);\n    }\n\n    @EventHandler\n    public void onRaidStops(RaidStopEvent event) {\n        this.event = event;\n        fire(event);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/SpawnChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.SpawnChangeEvent;\r\n\r\npublic class SpawnChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // spawn changes\r\n    //\r\n    // @Switch world:<world> to only process the event when a specified world's spawn changes.\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Triggers when the world's spawn point changes.\r\n    //\r\n    // @Context\r\n    // <context.world> returns the WorldTag that the spawn point changed in.\r\n    // <context.old_location> returns the LocationTag of the old spawn point.\r\n    // <context.new_location> returns the LocationTag of the new spawn point.\r\n    //\r\n    // -->\r\n\r\n    public SpawnChangeScriptEvent() {\r\n        registerCouldMatcher(\"spawn changes\");\r\n        registerSwitches(\"world\");\r\n    }\r\n\r\n    public SpawnChangeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryObjectSwitch(\"world\", new WorldTag(event.getWorld()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        return switch (name) {\r\n            case \"world\" -> new WorldTag(event.getWorld());\r\n            case \"old_location\" -> new LocationTag(event.getPreviousLocation());\r\n            case \"new_location\" -> new LocationTag(event.getWorld().getSpawnLocation());\r\n            default -> super.getContext(name);\r\n        };\r\n    }\r\n\r\n    @EventHandler\r\n    public void onSpawnChange(SpawnChangeEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/StructureGrowsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport org.bukkit.TreeType;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.StructureGrowEvent;\r\n\r\npublic class StructureGrowsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <'structure/plant'> grows (naturally)\r\n    // <'structure/plant'> grows from bonemeal\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Location true\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Player when a player caused the structure growth to occur (eg with bonemeal).\r\n    //\r\n    // @Triggers when a structure (a tree or a mushroom) grows in a world.\r\n    //\r\n    // @Context\r\n    // <context.world> returns the WorldTag the structure grew in.\r\n    // <context.location> returns the LocationTag the structure grew at.\r\n    // <context.structure> returns an ElementTag of the structure's type. Refer to <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/TreeType.html>.\r\n    // <context.blocks> returns a ListTag of all block locations to be modified.\r\n    // <context.new_materials> returns a ListTag of the new block materials, to go with <context.blocks>.\r\n    //\r\n    // -->\r\n\r\n    public StructureGrowsScriptEvent() {\r\n        registerCouldMatcher(\"<'structure/plant'> grows (naturally)\");\r\n        registerCouldMatcher(\"<'structure/plant'> grows from bonemeal\");\r\n    }\r\n\r\n    public StructureGrowEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        String type = path.eventArgLowerAt(0);\r\n        if (!type.equals(\"structure\") && !type.equals(\"plant\") && !couldMatchEnum(type, TreeType.values())) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String struct = path.eventArgLowerAt(0);\r\n        if (!struct.equals(\"structure\") && !struct.equals(\"plant\") && !runGenericCheck(struct, event.getSpecies().name())) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(2).equals(\"from\") && !event.isFromBonemeal()) {\r\n            return false;\r\n        }\r\n        else if (path.eventArgLowerAt(2).equals(\"naturally\") && event.isFromBonemeal()) {\r\n            return false;\r\n        }\r\n        if (!runInCheck(path, event.getLocation())) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        return new BukkitScriptEntryData(event.getPlayer());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch (name) {\r\n            case \"world\":\r\n                return new WorldTag(event.getWorld());\r\n            case \"location\":\r\n                return new LocationTag(event.getLocation());\r\n            case \"structure\":\r\n                return new ElementTag(event.getSpecies());\r\n            case \"blocks\":\r\n                ListTag blocks = new ListTag();\r\n                for (BlockState block : event.getBlocks()) {\r\n                    blocks.addObject(new LocationTag(block.getLocation()));\r\n                }\r\n                return blocks;\r\n            case \"new_materials\":\r\n                ListTag new_materials = new ListTag();\r\n                for (BlockState block : event.getBlocks()) {\r\n                    new_materials.addObject(new MaterialTag(block));\r\n                }\r\n                return new_materials;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onStructureGrow(StructureGrowEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/ThunderChangesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.weather.ThunderChangeEvent;\r\n\r\npublic class ThunderChangesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // thunder changes|begins|clears\r\n    //\r\n    // @Switch in:<world> to only run the event if it applies to a specific world.\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when thunder starts or stops in a world.\r\n    //\r\n    // @Context\r\n    // <context.world> returns the WorldTag the thunder changed in.\r\n    // <context.thunder> returns true if thunder is starting, or false if thunder is stopping.\r\n    //\r\n    // -->\r\n\r\n    public ThunderChangesScriptEvent() {\r\n        registerCouldMatcher(\"thunder changes|begins|clears\");\r\n        registerSwitches(\"in\");\r\n    }\r\n\r\n    public ThunderChangeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String changeType = path.eventArgLowerAt(1);\r\n        if (changeType.equals(\"clears\")) {\r\n            if (event.toThunderState()) {\r\n                return false;\r\n            }\r\n        }\r\n        else if (changeType.equals(\"begins\")) {\r\n            if (!event.toThunderState()) {\r\n                return false;\r\n            }\r\n        }\r\n        else if (!changeType.equals(\"changes\")) {\r\n            return false;\r\n        }\r\n        if (!path.tryObjectSwitch(\"in\", new WorldTag(event.getWorld()))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        switch(name) {\r\n            case \"world\":\r\n                return new WorldTag(event.getWorld());\r\n            case \"thunder\":\r\n                return new ElementTag(event.toThunderState());\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onThunderChanges(ThunderChangeEvent event) {\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/TimeChangeScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class TimeChangeScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // time changes (in <world>)\r\n    // time <'0-23'> (in <world>)\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Triggers when the current time changes in a world (once per mine-hour).\r\n    //\r\n    // @Context\r\n    // <context.time> returns the current time (the hour, as a number from 0 to 23).\r\n    // <context.world> returns the world.\r\n    //\r\n    // -->\r\n\r\n    public TimeChangeScriptEvent() {\r\n        instance = this;\r\n        registerCouldMatcher(\"time changes (in <world>)\");\r\n        registerCouldMatcher(\"time <'0-23'> (in <world>)\");\r\n    }\r\n\r\n    public static TimeChangeScriptEvent instance;\r\n\r\n    public int hour;\r\n\r\n    public WorldTag world;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (path.eventArgLowerAt(2).equals(\"in\") && !path.tryArgObject(3, world)) {\r\n            return false;\r\n        }\r\n        String arg1 = path.eventArgLowerAt(1);\r\n        if (!arg1.equals(\"changes\") && !arg1.equals(String.valueOf(hour))) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"time\")) {\r\n            return new ElementTag(hour);\r\n        }\r\n        else if (name.equals(\"world\")) {\r\n            return world;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/WeatherChangesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.weather.WeatherChangeEvent;\r\n\r\npublic class WeatherChangesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // weather changes|rains|clears (in <world>)\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when weather changes in a world.\r\n    //\r\n    // @Context\r\n    // <context.world> returns the WorldTag the weather changed in.\r\n    // <context.weather> returns an ElementTag with the name of the new weather (rains or clears).\r\n    //\r\n    // -->\r\n\r\n    public WeatherChangesScriptEvent() {\r\n        registerCouldMatcher(\"weather changes|rains|clears (in <world>)\");\r\n    }\r\n\r\n    public WorldTag world;\r\n    public ElementTag weather;\r\n    public WeatherChangeEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        String cmd = path.eventArgLowerAt(1);\r\n        if (!cmd.equals(\"changes\") && !cmd.equals(weather.asString())) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(2).equals(\"in\") && !path.tryArgObject(3, world)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"world\")) {\r\n            return world;\r\n        }\r\n        else if (name.equals(\"weather\")) {\r\n            return weather;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onWeatherChanges(WeatherChangeEvent event) {\r\n        world = new WorldTag(event.getWorld());\r\n        weather = new ElementTag(event.toWeatherState() ? \"rains\" : \"clears\");\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/WorldInitsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.WorldInitEvent;\r\n\r\npublic class WorldInitsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <world> initializes\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Triggers when a world is initialized.\r\n    //\r\n    // @Context\r\n    // <context.world> returns the WorldTag that was initialized.\r\n    //\r\n    // -->\r\n\r\n    public WorldInitsScriptEvent() {\r\n        registerCouldMatcher(\"<world> initializes\");\r\n    }\r\n\r\n    public WorldTag world;\r\n    public WorldInitEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, world)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"world\")) {\r\n            return world;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onWorldInits(WorldInitEvent event) {\r\n        world = new WorldTag(event.getWorld());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/WorldLoadsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.WorldLoadEvent;\r\n\r\npublic class WorldLoadsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <world> loads\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Triggers when a world is loaded.\r\n    //\r\n    // @Context\r\n    // <context.world> returns the WorldTag that was loaded.\r\n    //\r\n    // -->\r\n\r\n    public WorldLoadsScriptEvent() {\r\n        registerCouldMatcher(\"<world> loads\");\r\n    }\r\n\r\n    public WorldTag world;\r\n    public WorldLoadEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(0).equals(\"chunk\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, world)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"world\")) {\r\n            return world;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onWorldLoads(WorldLoadEvent event) {\r\n        world = new WorldTag(event.getWorld());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/WorldSavesScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.WorldSaveEvent;\r\n\r\npublic class WorldSavesScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <world> saves\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Triggers when a world is saved.\r\n    //\r\n    // @Context\r\n    // <context.world> returns the WorldTag that was saved.\r\n    //\r\n    // -->\r\n\r\n    public WorldSavesScriptEvent() {\r\n        registerCouldMatcher(\"<world> saves\");\r\n    }\r\n\r\n    public WorldTag world;\r\n    public WorldSaveEvent event;\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, world)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"world\")) {\r\n            return world;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onWorldSaves(WorldSaveEvent event) {\r\n        world = new WorldTag(event.getWorld());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/events/world/WorldUnloadsScriptEvent.java",
    "content": "package com.denizenscript.denizen.events.world;\r\n\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.WorldUnloadEvent;\r\n\r\npublic class WorldUnloadsScriptEvent extends BukkitScriptEvent implements Listener {\r\n\r\n    // <--[event]\r\n    // @Events\r\n    // <world> unloads\r\n    //\r\n    // @Group World\r\n    //\r\n    // @Cancellable true\r\n    //\r\n    // @Triggers when a world is unloaded.\r\n    //\r\n    // @Context\r\n    // <context.world> returns the WorldTag that was unloaded.\r\n    //\r\n    // -->\r\n\r\n    public WorldUnloadsScriptEvent() {\r\n        registerCouldMatcher(\"<world> unloads\");\r\n    }\r\n\r\n    public WorldTag world;\r\n    public WorldUnloadEvent event;\r\n\r\n    @Override\r\n    public boolean couldMatch(ScriptPath path) {\r\n        if (!super.couldMatch(path)) {\r\n            return false;\r\n        }\r\n        if (path.eventArgLowerAt(0).equals(\"chunk\")) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean matches(ScriptPath path) {\r\n        if (!path.tryArgObject(0, world)) {\r\n            return false;\r\n        }\r\n        return super.matches(path);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getContext(String name) {\r\n        if (name.equals(\"world\")) {\r\n            return world;\r\n        }\r\n        return super.getContext(name);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onWorldUnloads(WorldUnloadEvent event) {\r\n        world = new WorldTag(event.getWorld());\r\n        this.event = event;\r\n        fire(event);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/NMSHandler.java",
    "content": "package com.denizenscript.denizen.nms;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.*;\r\nimport com.denizenscript.denizen.nms.interfaces.*;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.World;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryView;\r\nimport org.bukkit.persistence.PersistentDataContainer;\r\nimport org.bukkit.plugin.java.JavaPlugin;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic abstract class NMSHandler {\r\n\r\n    public static NMSHandler instance;\r\n    private static NMSVersion version;\r\n    private static JavaPlugin javaPlugin;\r\n\r\n    public static boolean debugPackets = false;\r\n    public static String debugPacketFilter = \"\";\r\n\r\n    static {\r\n        String bukkitVersion = Bukkit.getBukkitVersion();\r\n        for (NMSVersion potentialVersion : NMSVersion.values()) {\r\n            if (bukkitVersion.startsWith(potentialVersion.minecraftVersion)) {\r\n                version = potentialVersion;\r\n                break;\r\n            }\r\n        }\r\n        if (version == null) {\r\n            version = NMSVersion.NOT_SUPPORTED;\r\n        }\r\n    }\r\n\r\n    public static boolean initialize(JavaPlugin plugin) {\r\n        javaPlugin = plugin;\r\n        if (getVersion() == NMSVersion.NOT_SUPPORTED) {\r\n            instance = null;\r\n            return false;\r\n        }\r\n        try {\r\n            // Get the class of our handler for this version\r\n            final Class<?> clazz = Class.forName(\"com.denizenscript.denizen.nms.\" + version.name() + \".Handler\");\r\n            if (NMSHandler.class.isAssignableFrom(clazz)) {\r\n                // Found and loaded - good to go!\r\n                instance = (NMSHandler) clazz.getDeclaredConstructor().newInstance();\r\n                return true;\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            e.printStackTrace();\r\n        }\r\n        // Someone made an oopsie and didn't implement this version properly :(\r\n        version = NMSVersion.NOT_SUPPORTED;\r\n        instance = null;\r\n        return false;\r\n    }\r\n\r\n    public static NMSHandler getInstance() {\r\n        return instance;\r\n    }\r\n\r\n    public static NMSVersion getVersion() {\r\n        return version;\r\n    }\r\n\r\n    public static JavaPlugin getJavaPlugin() {\r\n        return javaPlugin;\r\n    }\r\n\r\n    public static AdvancementHelper advancementHelper;\r\n    public static AnimationHelper animationHelper;\r\n    public static BlockHelper blockHelper;\r\n    public static ChunkHelper chunkHelper;\r\n    public static CustomEntityHelper customEntityHelper;\r\n    public static EntityHelper entityHelper;\r\n    public static FishingHelper fishingHelper;\r\n    public static ItemHelper itemHelper;\r\n    public static PacketHelper packetHelper;\r\n    public static PlayerHelper playerHelper;\r\n    public static WorldHelper worldHelper;\r\n    public static EnchantmentHelper enchantmentHelper;\r\n\r\n    public boolean isExactServerVersionMatch() {\r\n        return true;\r\n    }\r\n\r\n    public abstract void disableAsyncCatcher();\r\n\r\n    public abstract void undisableAsyncCatcher();\r\n\r\n    public abstract Sidebar createSidebar(Player player);\r\n\r\n    public abstract BlockLight createBlockLight(Location location, int lightLevel, long ticks);\r\n\r\n    public abstract PlayerProfile fillPlayerProfile(PlayerProfile playerProfile);\r\n\r\n    public abstract PlayerProfile getPlayerProfile(Player player);\r\n\r\n    public abstract ProfileEditor getProfileEditor();\r\n\r\n    public List<BiomeNMS> getBiomes(World world) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract BiomeNMS getBiomeNMS(World world, NamespacedKey key);\r\n\r\n    public BiomeNMS getBiomeAt(Block block) {\r\n        return NMSHandler.instance.getBiomeNMS(block.getWorld(), block.getBiome().getKey());\r\n    }\r\n\r\n    public abstract double[] getRecentTps();\r\n\r\n    public abstract String getTitle(Inventory inventory);\r\n\r\n    public void setInventoryTitle(InventoryView view, String title) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract ArrayList<String> containerListFlags(PersistentDataContainer container, String prefix);\r\n\r\n    public abstract boolean containerHas(PersistentDataContainer container, String key);\r\n\r\n    public abstract String containerGetString(PersistentDataContainer container, String key);\r\n\r\n    public UUID getBossbarUUID(BossBar bar) {\r\n        return null;\r\n    }\r\n\r\n    public void setBossbarUUID(BossBar bar, UUID id) {\r\n    }\r\n\r\n    public String updateLegacyName(Class<?> type, String legacyName) {\r\n        return legacyName;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/NMSVersion.java",
    "content": "package com.denizenscript.denizen.nms;\r\n\r\npublic enum NMSVersion {\r\n\r\n    NOT_SUPPORTED(\"not_supported\"),\r\n    v1_17(\"1.17\"),\r\n    v1_18(\"1.18\"),\r\n    v1_19(\"1.19\"),\r\n    v1_20(\"1.20\"),\r\n    v1_21(\"1.21\"),\r\n    v26_1(\"26.1\");\r\n\r\n    final String minecraftVersion;\r\n\r\n    NMSVersion(String minecraftVersion) {\r\n        this.minecraftVersion = minecraftVersion;\r\n    }\r\n\r\n    public boolean isAtLeast(NMSVersion version) {\r\n        return ordinal() >= version.ordinal();\r\n    }\r\n\r\n    public boolean isAtMost(NMSVersion version) {\r\n        return ordinal() <= version.ordinal();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/abstracts/AnimationHelper.java",
    "content": "package com.denizenscript.denizen.nms.abstracts;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.EntityAnimation;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Mob;\r\nimport org.bukkit.entity.Villager;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic abstract class AnimationHelper {\r\n\r\n    public static final Map<String, EntityAnimation> entityAnimations = new HashMap<>();\r\n\r\n    static {\r\n        entityAnimations.put(\"villager_shake_head\", entity -> {\r\n            if (entity instanceof Villager) {\r\n                ((Villager) entity).shakeHead();\r\n            }\r\n        });\r\n        entityAnimations.put(\"skeleton_start_arm_swing\", entity -> {\r\n            if (entity.getType() == EntityType.SKELETON) {\r\n                BukkitImplDeprecations.skeletonSwingArm.warn();\r\n                NMSHandler.entityHelper.setAggressive((Mob) entity, true);\r\n            }\r\n        });\r\n        entityAnimations.put(\"skeleton_stop_arm_swing\", entity -> {\r\n            if (entity.getType() == EntityType.SKELETON) {\r\n                BukkitImplDeprecations.skeletonSwingArm.warn();\r\n                NMSHandler.entityHelper.setAggressive((Mob) entity, false);\r\n            }\r\n        });\r\n        entityAnimations.put(\"swing_main_hand\", entity -> {\r\n            ((LivingEntity) entity).swingMainHand();\r\n        });\r\n        entityAnimations.put(\"swing_off_hand\", entity -> {\r\n            ((LivingEntity) entity).swingOffHand();\r\n        });\r\n    }\r\n\r\n    protected void register(String name, EntityAnimation animation) {\r\n        entityAnimations.put(CoreUtilities.toLowerCase(name), animation);\r\n    }\r\n\r\n    public boolean hasEntityAnimation(String name) {\r\n        return entityAnimations.containsKey(CoreUtilities.toLowerCase(name));\r\n    }\r\n\r\n    public EntityAnimation getEntityAnimation(String name) {\r\n        return entityAnimations.get(CoreUtilities.toLowerCase(name));\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/abstracts/BiomeNMS.java",
    "content": "package com.denizenscript.denizen.nms.abstracts;\r\n\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.World;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic abstract class BiomeNMS {\r\n\r\n    public NamespacedKey key;\r\n\r\n    public World world;\r\n\r\n    public BiomeNMS(World world, NamespacedKey key) {\r\n        this.world = world;\r\n        this.key = key;\r\n    }\r\n\r\n    public NamespacedKey getKey() {\r\n        return key;\r\n    }\r\n\r\n    public DownfallType getDownfallType() {\r\n        if (!hasDownfall()) {\r\n            return DownfallType.NONE;\r\n        }\r\n        return getBaseTemperature() > 0.15f ? DownfallType.RAIN : DownfallType.SNOW;\r\n    }\r\n\r\n    public DownfallType getDownfallTypeAt(Location location) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract float getHumidity();\r\n\r\n    public abstract float getBaseTemperature();\r\n\r\n    public float getTemperatureAt(Location location) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public boolean hasDownfall() {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public List<EntityType> getAllEntities() {\r\n        List<EntityType> entityTypes = new ArrayList<>();\r\n        entityTypes.addAll(getAmbientEntities());\r\n        entityTypes.addAll(getCreatureEntities());\r\n        entityTypes.addAll(getMonsterEntities());\r\n        entityTypes.addAll(getWaterEntities());\r\n        return entityTypes;\r\n    }\r\n\r\n    public abstract List<EntityType> getAmbientEntities();\r\n\r\n    public abstract List<EntityType> getCreatureEntities();\r\n\r\n    public abstract List<EntityType> getMonsterEntities();\r\n\r\n    public abstract List<EntityType> getWaterEntities();\r\n\r\n    public abstract int getFoliageColor();\r\n\r\n    public abstract void setHumidity(float humidity);\r\n\r\n    public abstract void setBaseTemperature(float temperature);\r\n\r\n    public void setPrecipitation(DownfallType type) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void setHasDownfall(boolean hasDownfall) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public enum DownfallType {\r\n        RAIN, SNOW, NONE\r\n    }\r\n\r\n    public abstract void setFoliageColor(int color);\r\n\r\n    public int getFogColor() {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void setFogColor(int color) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public int getWaterFogColor() {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void setWaterFogColor(int color) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract void setTo(Block block);\r\n\r\n    public ColorTag getColor(int x, int y) {\r\n        ColorTag topLeft = new ColorTag(26, 191, 0);\r\n        ColorTag topRight = new ColorTag(28, 164, 73);\r\n        ColorTag bottomLeft = new ColorTag(174, 164, 42);\r\n        ColorTag bottomRight = new ColorTag(96, 161, 123);\r\n        float normalizedX = x / 255.0f;\r\n        float normalizedY = y / 255.0f;\r\n        ColorTag lu = scaleColor(topLeft, (1 - normalizedX) * (1 - normalizedY));\r\n        ColorTag ru = scaleColor(topRight, normalizedX * (1 - normalizedY));\r\n        ColorTag ld = scaleColor(bottomLeft, (1 - normalizedX) * normalizedY);\r\n        ColorTag rd = scaleColor(bottomRight, normalizedX * normalizedY);\r\n        int r = lu.red + ld.red + rd.red + ru.red;\r\n        int g = lu.green + ld.green + rd.green + ru.green;\r\n        int b = lu.blue + ld.blue + rd.blue + ru.blue;\r\n        return new ColorTag(r, g, b);\r\n    }\r\n\r\n    public float clampColor(float n) {\r\n        if (n < 0.0f) {\r\n            return 0.0f;\r\n        }\r\n        else if (n > 1.0f) {\r\n            return 1.0f;\r\n        }\r\n        return n;\r\n    }\r\n\r\n    private ColorTag scaleColor(ColorTag color, float scale) {\r\n        float r = color.red * scale;\r\n        float g = color.green * scale;\r\n        float b = color.blue * scale;\r\n        return new ColorTag((int) r, (int) g, (int) b);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/abstracts/BlockLight.java",
    "content": "package com.denizenscript.denizen.nms.abstracts;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic abstract class BlockLight {\r\n\r\n    public static final Map<Location, BlockLight> lightsByLocation = new HashMap<>();\r\n    public static final Map<ChunkCoordinate, List<BlockLight>> lightsByChunk = new HashMap<>();\r\n\r\n    public final Block block;\r\n    public final ChunkCoordinate chunkCoord;\r\n    public Chunk chunk;\r\n    public final int originalLight;\r\n    public int currentLight;\r\n    public int cachedLight;\r\n    public int intendedLevel;\r\n    public BukkitTask removeTask;\r\n    public BukkitTask updateTask;\r\n\r\n    public Chunk getChunk() {\r\n        chunk = Bukkit.getWorld(chunkCoord.worldName).getChunkAt(chunkCoord.x, chunkCoord.z);\r\n        return chunk;\r\n    }\r\n\r\n    protected BlockLight(Location location, long ticks) {\r\n        NetworkInterceptHelper.enable();\r\n        this.block = location.getBlock();\r\n        this.chunk = location.getChunk();\r\n        this.chunkCoord = new ChunkCoordinate(chunk);\r\n        this.originalLight = block.getLightFromBlocks();\r\n        this.currentLight = originalLight;\r\n        this.cachedLight = originalLight;\r\n        this.intendedLevel = originalLight;\r\n        this.removeLater(ticks);\r\n    }\r\n\r\n    public void removeLater(long ticks) {\r\n        if (ticks > 0) {\r\n            this.removeTask = new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    removeTask = null;\r\n                    removeLight(block.getLocation());\r\n                }\r\n            }.runTaskLater(NMSHandler.getJavaPlugin(), ticks);\r\n        }\r\n    }\r\n\r\n    public static void removeLight(Location location) {\r\n        location = location.getBlock().getLocation();\r\n        BlockLight blockLight = lightsByLocation.get(location);\r\n        if (blockLight != null) {\r\n            if (blockLight.updateTask != null) {\r\n                blockLight.updateTask.cancel();\r\n                blockLight.updateTask = null;\r\n            }\r\n            blockLight.reset(true);\r\n            if (blockLight.removeTask != null) {\r\n                blockLight.removeTask.cancel();\r\n                blockLight.removeTask = null;\r\n            }\r\n            lightsByLocation.remove(location);\r\n            List<BlockLight> lights = lightsByChunk.get(blockLight.chunkCoord);\r\n            lights.remove(blockLight);\r\n            if (lights.isEmpty()) {\r\n                lightsByChunk.remove(blockLight.chunkCoord);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void reset(boolean updateChunk) {\r\n        this.update(originalLight, updateChunk);\r\n    }\r\n\r\n    public abstract void update(int lightLevel, boolean updateChunk);\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/abstracts/ImprovedOfflinePlayer.java",
    "content": "package com.denizenscript.denizen.nms.abstracts;\r\n\r\n/*\r\n * ImprovedOfflinePlayer, a library for Bukkit.\r\n * Copyright (C) 2013 one4me@github.com\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining\r\n * a copy of this software and associated documentation files (the\r\n * \"Software\"), to deal in the Software without restriction, including\r\n * without limitation the rights to use, copy, modify, merge, publish,\r\n * distribute, sublicense, and/or sell copies of the Software, and to\r\n * permit persons to whom the Software is furnished to do so, subject to\r\n * the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n *\r\n * @name ImprovedOfflinePlayer\r\n * @version 1.6.0\r\n * @author one4me\r\n *\r\n *\r\n *\r\n * Modified by mcmonkey and the DenizenScript team, to update to more recent Minecraft versions and improve performance\r\n *\r\n */\r\n\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.scheduling.OneTimeSchedulable;\r\nimport net.kyori.adventure.nbt.*;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.GameMode;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.PlayerInventory;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.io.File;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\nimport java.util.function.Consumer;\r\nimport java.util.function.UnaryOperator;\r\n\r\npublic abstract class ImprovedOfflinePlayer {\r\n\r\n    public static void invalidateNow(UUID id) {\r\n        ImprovedOfflinePlayer player = offlinePlayers.remove(id);\r\n        if (player != null) {\r\n            if (player.inventory != null) {\r\n                player.setInventory(player.inventory);\r\n            }\r\n            if (player.enderchest != null) {\r\n                player.setEnderChest(player.enderchest);\r\n            }\r\n            if (player.modified) {\r\n                player.saveToFile();\r\n            }\r\n        }\r\n    }\r\n\r\n    public static Map<UUID, ImprovedOfflinePlayer> offlinePlayers = new HashMap<>();\r\n\r\n    public UUID player;\r\n    public File file;\r\n    public CompoundBinaryTag compound;\r\n    public boolean exists;\r\n    public PlayerInventory inventory;\r\n    public Inventory enderchest;\r\n    public boolean modified = false;\r\n    public long timeLastLoaded;\r\n\r\n    public void markModified() {\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.verboseLog(\"[Verbose] player data modified for \" + player + \", wasModified=\" + modified + \", delay=\" + Settings.worldPlayerDataSaveDelay);\r\n        }\r\n        if (Settings.worldPlayerDataSaveDelay <= 0) {\r\n            saveToFile();\r\n            return;\r\n        }\r\n        if (!modified && Settings.worldPlayerDataSaveDelay < 60 * 60 * 24) {\r\n            DenizenCore.schedule(new OneTimeSchedulable(() -> {\r\n                if (modified && offlinePlayers.get(player) == this) {\r\n                    modified = false;\r\n                    CompoundBinaryTag tag = compound;\r\n                    if (CoreConfiguration.debugVerbose) {\r\n                        Debug.verboseLog(\"[Verbose] async-saving player data for \" + player);\r\n                    }\r\n                    DenizenCore.runAsync(() -> saveInternal(tag));\r\n                }\r\n            }, Settings.worldPlayerDataSaveDelay));\r\n        }\r\n        modified = true;\r\n    }\r\n\r\n    public ImprovedOfflinePlayer(UUID playeruuid) {\r\n        timeLastLoaded = DenizenCore.currentTimeMonotonicMillis;\r\n        this.exists = loadPlayerData(playeruuid);\r\n    }\r\n\r\n    private void modifyAbilities(UnaryOperator<CompoundBinaryTag> editor) {\r\n        CompoundBinaryTag abilitiesCompoundTag = this.compound.getCompound(\"abilities\");\r\n        this.compound = this.compound.put(\"abilities\", editor.apply(abilitiesCompoundTag));\r\n        markModified();\r\n    }\r\n\r\n    public CompoundBinaryTag getBukkitData() {\r\n        return this.compound.getCompound(\"bukkit\", null);\r\n    }\r\n\r\n    public String getName() {\r\n        CompoundBinaryTag bukkitData = getBukkitData();\r\n        return bukkitData != null ? bukkitData.getString(\"lastKnownName\", null) : null;\r\n    }\r\n\r\n    public abstract PlayerInventory getInventory();\r\n\r\n    public abstract void setInventory(PlayerInventory inventory);\r\n\r\n    public abstract Inventory getEnderChest();\r\n\r\n    public abstract void setEnderChest(Inventory inventory);\r\n\r\n    public Location getLocation() {\r\n        ListBinaryTag position = this.compound.getList(\"Pos\");\r\n        ListBinaryTag rotation = this.compound.getList(\"Rotation\");\r\n        return new Location(\r\n                Bukkit.getWorld(new UUID(this.compound.getLong(\"WorldUUIDMost\"),\r\n                        this.compound.getLong(\"WorldUUIDLeast\"))),\r\n                position.getDouble(0),\r\n                position.getDouble(1),\r\n                position.getDouble(2),\r\n                rotation.getFloat(0),\r\n                rotation.getFloat(1)\r\n        );\r\n    }\r\n\r\n    public void setLocation(Location location) {\r\n        World w = location.getWorld();\r\n        UUID uuid = w.getUID();\r\n        ListBinaryTag.Builder<DoubleBinaryTag> position = ListBinaryTag.builder(BinaryTagTypes.DOUBLE);\r\n        position.add(DoubleBinaryTag.doubleBinaryTag(location.getX()));\r\n        position.add(DoubleBinaryTag.doubleBinaryTag(location.getY()));\r\n        position.add(DoubleBinaryTag.doubleBinaryTag(location.getZ()));\r\n        ListBinaryTag.Builder<FloatBinaryTag> rotation = ListBinaryTag.builder(BinaryTagTypes.FLOAT);\r\n        rotation.add(FloatBinaryTag.floatBinaryTag(location.getYaw()));\r\n        rotation.add(FloatBinaryTag.floatBinaryTag(location.getPitch()));\r\n        this.compound = CompoundBinaryTag.builder().put(this.compound)\r\n                .putLong(\"WorldUUIDMost\", uuid.getMostSignificantBits())\r\n                .putLong(\"WorldUUIDLeast\", uuid.getLeastSignificantBits())\r\n                .putInt(\"Dimension\", w.getEnvironment().ordinal())\r\n                .put(\"Pos\", position.build())\r\n                .put(\"Rotation\", rotation.build()).build();\r\n        markModified();\r\n    }\r\n\r\n    public float getHealthFloat() {\r\n        return this.compound.getFloat(\"Health\");\r\n    }\r\n\r\n    public void setHealthFloat(float input) {\r\n        this.compound = compound.putFloat(\"Health\", input);\r\n        markModified();\r\n    }\r\n\r\n    public abstract double getMaxHealth();\r\n\r\n    public abstract void setMaxHealth(double input);\r\n\r\n    protected abstract boolean loadPlayerData(UUID uuid);\r\n\r\n    public void saveToFile() {\r\n        if (exists && modified) {\r\n            modified = false;\r\n            saveInternal(compound);\r\n        }\r\n    }\r\n\r\n    public abstract void saveInternal(CompoundBinaryTag compound);\r\n\r\n    public boolean exists() {\r\n        return this.exists;\r\n    }\r\n\r\n    public float getAbsorptionAmount() {\r\n        return this.compound.getFloat(\"AbsorptionAmount\");\r\n    }\r\n\r\n    public void setAbsorptionAmount(float input) {\r\n        this.compound = compound.putFloat(\"AbsorptionAmount\", input);\r\n        markModified();\r\n    }\r\n\r\n    public void setBedSpawnLocation(Location location) {\r\n        if (location == null && !compound.contains(\"SpawnDimension\")) {\r\n            return;\r\n        }\r\n        CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder().put(compound);\r\n        if (location != null) {\r\n            builder.putInt(\"SpawnX\", location.getBlockX())\r\n                    .putInt(\"SpawnY\", location.getBlockY())\r\n                    .putInt(\"SpawnZ\", location.getBlockZ())\r\n                    .putFloat(\"SpawnAngle\", location.getYaw())\r\n                    .putString(\"SpawnDimension\", location.getWorld().getKey().toString());\r\n        }\r\n        else {\r\n            builder.remove(\"SpawnX\").remove(\"SpawnY\").remove(\"SpawnZ\").remove(\"SpawnAngle\").remove(\"SpawnDimension\");\r\n        }\r\n        this.compound = builder.build();\r\n        markModified();\r\n        // Must save to file immediately to work with normal Spigot OfflinePlayer API\r\n        saveToFile();\r\n    }\r\n\r\n    public boolean isSpawnForced() {\r\n        return this.compound.getBoolean(\"SpawnForced\");\r\n    }\r\n\r\n    public void setSpawnForced(boolean spawnForced) {\r\n        this.compound = compound.putBoolean(\"SpawnForced\", spawnForced);\r\n        markModified();\r\n    }\r\n\r\n    public float getExhaustion() {\r\n        return this.compound.getFloat(\"foodExhaustionLevel\");\r\n    }\r\n\r\n    public void setExhaustion(float input) {\r\n        this.compound = compound.putFloat(\"foodExhaustionLevel\", input);\r\n        markModified();\r\n    }\r\n\r\n    public float getExp() {\r\n        return this.compound.getFloat(\"XpP\");\r\n    }\r\n\r\n    public void setExp(float input) {\r\n        this.compound = compound.putFloat(\"XpP\", input);\r\n        markModified();\r\n    }\r\n\r\n    public float getFallDistance() {\r\n        return this.compound.getFloat(\"FallDistance\");\r\n    }\r\n\r\n    public void setFallDistance(float input) {\r\n        this.compound = compound.putFloat(\"FallDistance\", input);\r\n        markModified();\r\n    }\r\n\r\n    public int getFireTicks() {\r\n        return this.compound.getShort(\"Fire\");\r\n    }\r\n\r\n    public void setFireTicks(int input) {\r\n        this.compound = compound.putShort(\"Fire\", (short) input);\r\n        markModified();\r\n    }\r\n\r\n    public float getFlySpeed() {\r\n        return this.compound.getCompound(\"abilities\").getFloat(\"flySpeed\") * 2;\r\n    }\r\n\r\n    public void setFlySpeed(float speed) {\r\n        modifyAbilities(abilitiesTag -> abilitiesTag.putFloat(\"flySpeed\", speed / 2));\r\n    }\r\n\r\n    public int getFoodLevel() {\r\n        return this.compound.getInt(\"foodLevel\");\r\n    }\r\n\r\n    public void setFoodLevel(int input) {\r\n        this.compound = compound.putInt(\"foodLevel\", input);\r\n        markModified();\r\n    }\r\n\r\n    public GameMode getGameMode() {\r\n        return GameMode.getByValue(this.compound.getInt(\"playerGameType\"));\r\n    }\r\n\r\n    public void setGameMode(GameMode input) {\r\n        this.compound = compound.putInt(\"playerGameType\", input.getValue());\r\n        markModified();\r\n    }\r\n\r\n    public boolean getIsOnGround() {\r\n        return compound.getBoolean(\"OnGround\");\r\n    }\r\n\r\n    public void setIsOnGround(boolean input) {\r\n        this.compound = compound.putBoolean(\"OnGround\", input);\r\n        markModified();\r\n    }\r\n\r\n    public int getItemInHand() {\r\n        return this.compound.getInt(\"SelectedItemSlot\");\r\n    }\r\n\r\n    public void setItemInHand(int input) {\r\n        this.compound = compound.putInt(\"SelectedItemSlot\", input);\r\n        markModified();\r\n    }\r\n\r\n    public int getLevel() {\r\n        return this.compound.getInt(\"XpLevel\");\r\n    }\r\n\r\n    public void setLevel(int input) {\r\n        this.compound = compound.putInt(\"XpLevel\", input);\r\n        markModified();\r\n    }\r\n\r\n    public UUID getUniqueId() {\r\n        return this.player;\r\n    }\r\n\r\n    public int getRemainingAir() {\r\n        return this.compound.getShort(\"Air\");\r\n    }\r\n\r\n    public void setRemainingAir(int input) {\r\n        this.compound = compound.putShort(\"Air\", (short) input);\r\n        markModified();\r\n    }\r\n\r\n    public float getSaturation() {\r\n        return this.compound.getFloat(\"foodSaturationLevel\");\r\n    }\r\n\r\n    public void setSaturation(float input) {\r\n        this.compound = compound.putFloat(\"foodSaturationLevel\", input);\r\n        markModified();\r\n    }\r\n\r\n    public float getScore() {\r\n        return this.compound.getFloat(\"foodSaturationLevel\");\r\n    }\r\n\r\n    public void setScore(int input) {\r\n        this.compound = compound.putInt(\"Score\", input);\r\n        markModified();\r\n    }\r\n\r\n    public short getTimeAttack() {\r\n        return this.compound.getShort(\"AttackTime\");\r\n    }\r\n\r\n    public void setTimeAttack(short input) {\r\n        this.compound = compound.putShort(\"AttackTime\", input);\r\n        markModified();\r\n    }\r\n\r\n    public short getTimeDeath() {\r\n        return this.compound.getShort(\"DeathTime\");\r\n    }\r\n\r\n    public void setTimeDeath(short input) {\r\n        this.compound = compound.putShort(\"DeathTime\", input);\r\n        markModified();\r\n    }\r\n\r\n    public short getTimeHurt() {\r\n        return this.compound.getShort(\"HurtTime\");\r\n    }\r\n\r\n    public void setTimeHurt(short input) {\r\n        this.compound = compound.putShort(\"HurtTime\", input);\r\n        markModified();\r\n    }\r\n\r\n    public short getTimeSleep() {\r\n        return this.compound.getShort(\"SleepTimer\");\r\n    }\r\n\r\n    public void setTimeSleep(short input) {\r\n        this.compound = compound.putShort(\"SleepTimer\", input);\r\n        markModified();\r\n    }\r\n\r\n    public int getTotalExperience() {\r\n        return this.compound.getInt(\"XpTotal\");\r\n    }\r\n\r\n    public void setTotalExperience(int input) {\r\n        this.compound = compound.putInt(\"XpTotal\", input);\r\n        markModified();\r\n    }\r\n\r\n    public Vector getVelocity() {\r\n        ListBinaryTag list = this.compound.getList(\"Motion\");\r\n        return new Vector(list.getDouble(0), list.getDouble(1), list.getDouble(2));\r\n    }\r\n\r\n    public void setVelocity(Vector vector) {\r\n        ListBinaryTag.Builder<DoubleBinaryTag> motionBuilder = ListBinaryTag.builder(BinaryTagTypes.DOUBLE);\r\n        motionBuilder.add(DoubleBinaryTag.doubleBinaryTag(vector.getX()));\r\n        motionBuilder.add(DoubleBinaryTag.doubleBinaryTag(vector.getY()));\r\n        motionBuilder.add(DoubleBinaryTag.doubleBinaryTag(vector.getZ()));\r\n        this.compound = compound.put(\"Motion\", motionBuilder.build());\r\n        markModified();\r\n    }\r\n\r\n    public float getWalkSpeed() {\r\n        return this.compound.getCompound(\"abilities\").getFloat(\"walkSpeed\") * 2;\r\n    }\r\n\r\n    public void setWalkSpeed(float speed) {\r\n        modifyAbilities(abilitiesTag -> abilitiesTag.putFloat(\"walkSpeed\", speed / 2));\r\n    }\r\n\r\n    public boolean getAllowFlight() {\r\n        return this.compound.getCompound(\"abilities\").getBoolean(\"mayfly\");\r\n    }\r\n\r\n    public void setAllowFlight(boolean allow) {\r\n        modifyAbilities(abilitiesTag -> abilitiesTag.putBoolean(\"mayfly\", allow));\r\n    }\r\n\r\n    public void setLastDeathLocation(Location deathLoc) {\r\n        CompoundBinaryTag compoundTag = this.compound.getCompound(\"LastDeathLocation\");\r\n        compoundTag = CompoundBinaryTag.builder().put(compoundTag)\r\n                .putIntArray(\"pos\", new int[] {deathLoc.getBlockX(), deathLoc.getBlockY(), deathLoc.getBlockZ()})\r\n                .putString(\"dimension\", deathLoc.getWorld().getKey().toString()).build();\r\n        this.compound = compound.put(\"LastDeathLocation\", compoundTag);\r\n        markModified();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/abstracts/ProfileEditor.java",
    "content": "package com.denizenscript.denizen.nms.abstracts;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerQuitEvent;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.HashSet;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic abstract class ProfileEditor {\r\n\r\n    public static final String EMPTY_NAME = \"\";\r\n    public static final UUID NIL_UUID = new UUID(0L, 0L);\r\n\r\n    public final static Map<UUID, PlayerProfile> fakeProfiles = new HashMap<>();\r\n\r\n    public final static HashSet<UUID> mirrorUUIDs = new HashSet<>();\r\n\r\n    public ProfileEditor() {\r\n        Bukkit.getServer().getPluginManager().registerEvents(new PlayerProfileEditorListener(), NMSHandler.getJavaPlugin());\r\n    }\r\n\r\n    public void setPlayerName(Player player, String name) {\r\n        NetworkInterceptHelper.enable();\r\n        PlayerProfile profile = getFakeProfile(player, true);\r\n        profile.setName(name);\r\n        updatePlayer(player, false);\r\n    }\r\n\r\n    public String getPlayerName(Player player) {\r\n        return getFakeProfile(player, false).getName();\r\n    }\r\n\r\n    public void setPlayerSkin(Player player, String name) {\r\n        NetworkInterceptHelper.enable();\r\n        PlayerProfile profile = getFakeProfile(player, true);\r\n        PlayerProfile skinProfile = NMSHandler.instance.fillPlayerProfile(new PlayerProfile(name, null));\r\n        if (skinProfile == null) {\r\n            return;\r\n        }\r\n        if (skinProfile.getTexture() != null) {\r\n            profile.setTexture(skinProfile.getTexture());\r\n            profile.setTextureSignature(skinProfile.getTextureSignature());\r\n            updatePlayer(player, true);\r\n        }\r\n    }\r\n    // <--[language]\r\n    // @name Player Entity Skins (Skin Blobs)\r\n    // @group Minecraft Logic\r\n    // @description\r\n    // Player entities (that is, both real players and player-type NPCs), in addition to Player_Head items,\r\n    // use something called a \"skin blob\" to determine what skin to show.\r\n    // A skin blob consists of an identifier (usually the player's UUID, sometimes the name), a \"texture\", and a \"signature\".\r\n    //\r\n    // The \"texture\" portion, despite the name, does not contain actual texture data - it contains a Base64 encoding of a JSON section that contains outlinks to real skin data.\r\n    // That might be a bit confusing, so here's an example:\r\n    //\r\n    // The raw \"texture\" data for \"mcmonkey4eva\"'s skin is:\r\n    // eyJ0aW1lc3RhbXAiOjE1NjU1NjUyNDA4NTMsInByb2ZpbGVJZCI6IjQ2MGU5NmI5N2EwZTQxNmRiMmMzNDUwODE2NGI4YjFiIiwicHJvZmlsZU5hbWUiOiJtY21vbmtleTRldmEiLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dH\r\n    // VyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2ZkMzRiM2UyN2EzZmU1MzgyN2IzN2FkNTk1NmFjY2EwOGYyODYzYzY5MjZjYzk3MTE2ZGRhMzM0ODY5N2E1YTkifX19\r\n    //\r\n    // This is base64 encoding, which is just a way of controlling the format of binary data. When passed through a Base64 decoder, that says:\r\n    // {\"timestamp\":1565565240853,\"profileId\":\"460e96b97a0e416db2c34508164b8b1b\",\"profileName\":\"mcmonkey4eva\",\"signatureRequired\":true,\r\n    // \"textures\":{\"SKIN\":{\"url\":\"http://textures.minecraft.net/texture/fd34b3e27a3fe53827b37ad5956acca08f2863c6926cc97116dda3348697a5a9\"}}}\r\n    //\r\n    // As you can see, it contains: the timestamp of when the skin was added, the UUID and name, and a link to the actual skin file on Mojang's servers.\r\n    //\r\n    // The \"signature\" portion is a digitally encrypted signature that is used to verify a skin really was generated by Mojang.\r\n    // It is also represented as Base64, but cannot be decoded to anything readable.\r\n    // The \"signature\" is required for Player entity skins, but can generally be left off from Player_Head items (meaning, you can generate your own Player_Head items by base64 encoding your own JSON).\r\n    //\r\n    // The website <@link url https://mineskin.org/> can be used to generate full texture+signature data from any skin file\r\n    // (it does this by automatically uploading the skin image to Mojang's servers for processing and signing, using a donated Minecraft account).\r\n    //\r\n    // In terms of general actual usage, skin blobs are generally meant to be read from tags and applied with mechanisms, never directly written out or read by a human, due to their complexity.\r\n    //\r\n    // A skin_blob always represents a single actual skin, as opposed to a player name/uuid, where the account owner might choose to change their skin.\r\n    //\r\n    // It should be noted that any time a skin is applied to a Player, NPC, Or Player_Head item using just a name or UUID, the server must contact Mojang's servers to requst the skin blob for that given name/uuid.\r\n    // With a skin blob, however, the server does not need to make any remote calls, and can apply the skin directly (However note that the client will still download the image from Mojang's servers).\r\n    // -->\r\n\r\n    public void setPlayerSkinBlob(Player player, String blob) {\r\n        NetworkInterceptHelper.enable();\r\n        PlayerProfile profile = getFakeProfile(player, true);\r\n        String[] split = blob.split(\";\");\r\n        profile.setTexture(split[0]);\r\n        profile.setTextureSignature(split.length > 1 ? split[1] : null);\r\n        updatePlayer(player, true);\r\n    }\r\n\r\n    public String getPlayerSkinBlob(Player player) {\r\n        PlayerProfile prof = getFakeProfile(player, false);\r\n        return prof.getTexture() + \";\" + prof.getTextureSignature();\r\n    }\r\n\r\n    protected abstract void updatePlayer(Player player, boolean isSkinChanging);\r\n\r\n    private PlayerProfile getFakeProfile(Player player, boolean createCache) {\r\n        UUID uuid = player.getUniqueId();\r\n        if (fakeProfiles.containsKey(uuid)) {\r\n            return fakeProfiles.get(uuid);\r\n        }\r\n        else {\r\n            PlayerProfile fakeProfile = NMSHandler.instance.getPlayerProfile(player);\r\n            if (createCache) {\r\n                fakeProfiles.put(uuid, fakeProfile);\r\n            }\r\n            return fakeProfile;\r\n        }\r\n    }\r\n\r\n    private static class PlayerProfileEditorListener implements Listener {\r\n        @EventHandler\r\n        public void onPlayerQuit(PlayerQuitEvent event) {\r\n            UUID uuid = event.getPlayer().getUniqueId();\r\n            fakeProfiles.remove(uuid);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/abstracts/Sidebar.java",
    "content": "package com.denizenscript.denizen.nms.abstracts;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\n\r\npublic abstract class Sidebar {\r\n\r\n    public static class SidebarLine {\r\n\r\n        public SidebarLine(String _text, int _score) {\r\n            text = CoreUtilities.clearNBSPs(_text);\r\n            score = _score;\r\n        }\r\n\r\n        public String text;\r\n\r\n        public int score;\r\n    }\r\n\r\n    public static final int MAX_LENGTH = 15;\r\n    public static final String[] firstIds = new String[MAX_LENGTH];\r\n    public static final String[] secondIds = new String[MAX_LENGTH];\r\n\r\n    static {\r\n        for (int i = 0; i < MAX_LENGTH; i++) {\r\n            firstIds[i] = Utilities.generateRandomColors(8);\r\n            secondIds[i] = Utilities.generateRandomColors(8);\r\n        }\r\n    }\r\n\r\n    protected final Player player;\r\n    protected String title;\r\n    protected String[] lines = new String[MAX_LENGTH];\r\n    protected int[] scores = new int[MAX_LENGTH];\r\n    protected String[] currentIds = null;\r\n    public int setCount = 0;\r\n\r\n    public Sidebar(Player player) {\r\n        this.player = player;\r\n        setTitle(\"\");\r\n    }\r\n\r\n    public String[] getIds() {\r\n        currentIds = currentIds == firstIds ? secondIds : firstIds;\r\n        return currentIds;\r\n    }\r\n\r\n    public String getTitle() {\r\n        return title;\r\n    }\r\n\r\n    public List<SidebarLine> getLines() {\r\n        List<SidebarLine> toReturn = new ArrayList<>(MAX_LENGTH);\r\n        for (int i = 0; i < setCount; i++) {\r\n            toReturn.add(new SidebarLine(lines[i], scores[i]));\r\n        }\r\n        return toReturn;\r\n    }\r\n\r\n    public List<String> getLinesText() {\r\n        return new ArrayList<>(Arrays.asList(lines));\r\n    }\r\n\r\n    public int[] getScores() {\r\n        return scores;\r\n    }\r\n\r\n    public final void setTitle(String title) {\r\n        if (this.title == null || !this.title.equals(title)) {\r\n            this.title = title;\r\n            setDisplayName(title);\r\n        }\r\n    }\r\n\r\n    protected abstract void setDisplayName(String title);\r\n\r\n    public void setLines(List<SidebarLine> lines) {\r\n        setCount = Math.min(lines.size(), MAX_LENGTH);\r\n        for (int i = 0; i < setCount; i++) {\r\n            String line = lines.get(i).text;\r\n            this.lines[i] = line;\r\n            this.scores[i] = lines.get(i).score;\r\n        }\r\n        for (int i = setCount; i < MAX_LENGTH; i++) {\r\n            this.lines[i] = null;\r\n            this.scores[i] = 0;\r\n        }\r\n    }\r\n\r\n    public abstract void sendUpdate();\r\n\r\n    public abstract void remove();\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/enums/CustomEntityType.java",
    "content": "package com.denizenscript.denizen.nms.enums;\r\n\r\npublic enum CustomEntityType {\r\n\r\n    FAKE_ARROW,\r\n    FAKE_PLAYER,\r\n    ITEM_PROJECTILE\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/AdvancementHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport com.denizenscript.denizen.nms.util.Advancement;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\n\r\npublic abstract class AdvancementHelper {\r\n\r\n    public static org.bukkit.advancement.Advancement getAdvancement(String name) {\r\n        return Bukkit.getAdvancement(Utilities.parseNamespacedKey(name));\r\n    }\r\n\r\n    public abstract void register(Advancement advancement);\r\n\r\n    public abstract void unregister(Advancement advancement);\r\n\r\n    public void grantPartial(Advancement advancement, Player player, int len) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract void grant(Advancement advancement, Player player);\r\n\r\n    public void revokePartial(Advancement advancement, Player player, int len) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract void revoke(Advancement advancement, Player player);\r\n\r\n    public abstract void update(Player player);\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/BlockHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.*;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.Set;\r\n\r\npublic interface BlockHelper {\r\n\r\n\r\n    void applyPhysics(Location location);\r\n\r\n    PlayerProfile getPlayerProfile(Skull skull);\r\n\r\n    void setPlayerProfile(Skull skull, PlayerProfile playerProfile);\r\n\r\n    CompoundBinaryTag getNbtData(Block block);\r\n\r\n    void setNbtData(Block block, CompoundBinaryTag compoundTag);\r\n\r\n    boolean setBlockResistance(Material material, float resistance);\r\n\r\n    float getBlockResistance(Material material);\r\n\r\n    enum PistonPushReaction {\r\n        NORMAL, DESTROY, BLOCK, IGNORE, PUSH_ONLY;\r\n        public static final PistonPushReaction[] VALUES = values();\r\n    }\r\n\r\n    default PistonPushReaction getPushReaction(Material mat) { // TODO: once minimum version is 1.19, remove from NMS\r\n        return PistonPushReaction.VALUES[mat.createBlockData().getPistonMoveReaction().ordinal()];\r\n    }\r\n\r\n    void setPushReaction(Material mat, PistonPushReaction reaction);\r\n\r\n    float getBlockStrength(Material mat);\r\n\r\n    void setBlockStrength(Material mat, float strength);\r\n\r\n    static String getMaterialNameFromBlockData(String text) {\r\n        int openBracket = text.indexOf('[');\r\n        String material = text;\r\n        if (openBracket > 0) {\r\n            material = text.substring(0, openBracket);\r\n        }\r\n        if (material.startsWith(\"minecraft:\")) {\r\n            material = material.substring(\"minecraft:\".length());\r\n        }\r\n        return material;\r\n    }\r\n\r\n    default BlockData parseBlockData(String text) {\r\n        int openBracket = text.indexOf('[');\r\n        String material = text;\r\n        String otherData = null;\r\n        if (openBracket > 0) {\r\n            material = text.substring(0, openBracket);\r\n            otherData = text.substring(openBracket);\r\n        }\r\n        return Material.matchMaterial(material).createBlockData(otherData);\r\n    }\r\n\r\n    default void makeBlockStateRaw(BlockState state) {} // TODO: once 1.19 is the minimum supported version, remove this\r\n\r\n    void doRandomTick(Location location);\r\n\r\n    Instrument getInstrumentFor(Material mat);\r\n\r\n    default void ringBell(Bell bell) { /// TODO: once minimum version is 1.19, remove from NMS\r\n        bell.ring();\r\n    }\r\n\r\n    int getExpDrop(Block block, ItemStack item);\r\n\r\n    default void setSpawnerCustomRules(CreatureSpawner spawner, int skyMin, int skyMax, int blockMin, int blockMax) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    default void setSpawnerSpawnedType(CreatureSpawner spawner, EntityTag entity) {\r\n        spawner.setSpawnedType(entity.getBukkitEntityType());\r\n    }\r\n\r\n    default Color getMapColor(Block block) { // TODO: once 1.20 is the minimum supported version, remove from NMS\r\n        return block.getBlockData().getMapColor();\r\n    }\r\n\r\n    default void setVanillaTags(Material material, Set<NamespacedKey> tags) { // TODO: once 1.21 is the minimum supported version, remove this\r\n        throw new UnsupportedOperationException();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/ChunkHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.World;\r\n\r\npublic interface ChunkHelper {\r\n\r\n    default void refreshChunkSections(Chunk chunk) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    int[] getHeightMap(Chunk chunk);\r\n\r\n    default void changeChunkServerThread(World world) {\r\n        // Do nothing by default.\r\n    }\r\n\r\n    default void restoreServerThread(World world) {\r\n        // Do nothing by default.\r\n    }\r\n\r\n    default void setAllBiomes(Chunk chunk, BiomeNMS biome) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/CustomEntity.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport org.bukkit.entity.Entity;\r\n\r\npublic interface CustomEntity extends Entity {\r\n\r\n    String getEntityTypeName();\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/CustomEntityHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport org.bukkit.Location;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic interface CustomEntityHelper {\r\n\r\n    FakeArrow spawnFakeArrow(Location location);\r\n\r\n    ItemProjectile spawnItemProjectile(Location location, ItemStack itemStack);\r\n\r\n    FakePlayer spawnFakePlayer(Location location, String name, String skin, String blob, boolean doAdd);\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/EnchantmentHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport com.denizenscript.denizen.scripts.containers.core.EnchantmentScriptContainer;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\n\r\npublic class EnchantmentHelper {\r\n\r\n    public Enchantment registerFakeEnchantment(EnchantmentScriptContainer.EnchantmentReference script) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public String getRarity(Enchantment enchantment) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public boolean isDiscoverable(Enchantment enchantment) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public boolean isTradable(Enchantment enchantment) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public boolean isCurse(Enchantment enchantment) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public int getMinCost(Enchantment enchantment, int level) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public int getMaxCost(Enchantment enchantment, int level) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public String getFullName(Enchantment enchantment, int level) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public float getDamageBonus(Enchantment enchantment, int level, String type) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public int getDamageProtection(Enchantment enchantment, int level, EntityDamageEvent.DamageCause type, Entity attacker) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/EntityAnimation.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport org.bukkit.entity.Entity;\r\n\r\npublic interface EntityAnimation {\r\n\r\n    void play(Entity entity);\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/EntityHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport com.denizenscript.denizen.events.entity.EntityEntersVehicleScriptEvent;\r\nimport com.denizenscript.denizen.events.entity.EntityExitsVehicleScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityState;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport org.bukkit.*;\r\nimport org.bukkit.attribute.Attribute;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.entity.*;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.inventory.meta.MapMeta;\r\nimport org.bukkit.util.BoundingBox;\r\nimport org.bukkit.util.RayTraceResult;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic abstract class EntityHelper {\r\n\r\n    public static Attribute ATTRIBUTE_ARMOR = Utilities.findBestEnumlike(Attribute.class, \"ARMOR\", \"GENERIC_ARMOR\");\r\n    public static Attribute ATTRIBUTE_STEP_HEIGHT = Utilities.findBestEnumlike(Attribute.class, \"STEP_HEIGHT\", \"GENERIC_STEP_HEIGHT\");\r\n    public static Attribute ATTRIBUTE_MOVEMENT_SPEED = Utilities.findBestEnumlike(Attribute.class, \"MOVEMENT_SPEED\", \"GENERIC_MOVEMENT_SPEED\");\r\n\r\n    // TODO: once 1.21 is the minimum supported version, remove these\r\n    public int getBlockHeight(Art art) {\r\n        return art.getBlockHeight();\r\n    }\r\n\r\n    // TODO: once 1.21 is the minimum supported version, remove these\r\n    public int getBlockWidth(Art art) {\r\n        return art.getBlockWidth();\r\n    }\r\n\r\n    public abstract void setInvisible(Entity entity, boolean invisible);\r\n\r\n    public abstract boolean isInvisible(Entity entity);\r\n\r\n    public abstract void setPose(Entity entity, Pose pose);\r\n\r\n    public void setSneaking(Entity entity, boolean sneak) {\r\n        if (entity instanceof Player player) {\r\n            player.setSneaking(sneak);\r\n        }\r\n        setPose(entity, sneak ? Pose.SNEAKING : Pose.STANDING);\r\n    }\r\n\r\n    public abstract double getDamageTo(LivingEntity attacker, Entity target);\r\n\r\n    public abstract void setRiptide(Entity entity, boolean state);\r\n\r\n    public abstract void forceInteraction(Player player, Location location);\r\n\r\n    public abstract CompoundBinaryTag getNbtData(Entity entity);\r\n\r\n    public abstract void setNbtData(Entity entity, CompoundBinaryTag compoundTag);\r\n\r\n    public abstract void stopFollowing(Entity follower);\r\n\r\n    public abstract void stopWalking(Entity entity);\r\n\r\n    public abstract void follow(final Entity target, final Entity follower, final double speed, final double lead,\r\n                                final double maxRange, final boolean allowWander, final boolean teleport);\r\n\r\n    public abstract void walkTo(final LivingEntity entity, Location location, Double speed, final Runnable callback);\r\n\r\n    public abstract void sendHidePacket(Player pl, Entity entity);\r\n\r\n    public abstract void sendShowPacket(Player pl, Entity entity);\r\n\r\n    /**\r\n     * Rotates an entity.\r\n     *\r\n     * @param entity The Entity you want to rotate.\r\n     * @param yaw    The new yaw of the entity.\r\n     * @param pitch  The new pitch of the entity.\r\n     */\r\n    public abstract void rotate(Entity entity, float yaw, float pitch);\r\n\r\n    public abstract float getBaseYaw(LivingEntity entity);\r\n\r\n    // Taken from C2 NMS class for less dependency on C2\r\n    public abstract void look(Entity entity, float yaw, float pitch);\r\n\r\n    public MapTag mapTrace(LivingEntity inputEntity) {\r\n        double range = 200;\r\n        Location start = inputEntity.getEyeLocation();\r\n        Vector startVec = start.toVector();\r\n        Vector direction = start.getDirection();\r\n        double bestDist = Double.MAX_VALUE;\r\n        ItemFrame best = null;\r\n        Vector bestHitPos = null;\r\n        BlockFace bestHitFace = null;\r\n        for (Entity entity : start.getWorld().getNearbyEntities(start.clone().add(direction.clone().multiply(50)), 100, 100, 100, (e) -> e instanceof ItemFrame itemFrame && itemFrame.getItem().getType() == Material.FILLED_MAP)) {\r\n            double centerDist = entity.getLocation().distanceSquared(start);\r\n            if (centerDist > bestDist) {\r\n                continue;\r\n            }\r\n            ItemFrame frame = (ItemFrame) entity;\r\n            double EXP_RATE = 0.125;\r\n            double expandX = 0, expandY = 0, expandZ = 0;\r\n            BlockFace face = frame.getFacing();\r\n            switch (face) {\r\n                case SOUTH, NORTH -> {\r\n                    expandX = EXP_RATE;\r\n                    expandY = EXP_RATE;\r\n                }\r\n                case EAST, WEST -> {\r\n                    expandZ = EXP_RATE;\r\n                    expandY = EXP_RATE;\r\n                }\r\n                case UP, DOWN -> {\r\n                    expandX = EXP_RATE;\r\n                    expandZ = EXP_RATE;\r\n                }\r\n            }\r\n            RayTraceResult traced = frame.getBoundingBox().expand(expandX, expandY, expandZ).rayTrace(startVec, direction, range);\r\n            if (traced == null || traced.getHitBlockFace() == null || traced.getHitBlockFace() != face) {\r\n                continue;\r\n            }\r\n            bestDist = centerDist;\r\n            best = frame;\r\n            bestHitPos = traced.getHitPosition();\r\n            bestHitFace = face;\r\n        }\r\n        if (best == null) {\r\n            return null;\r\n        }\r\n        double x = 0;\r\n        double y = 0;\r\n        double basex = bestHitPos.getX() - Math.floor(bestHitPos.getX());\r\n        double basey = bestHitPos.getY() - Math.floor(bestHitPos.getY());\r\n        double basez = bestHitPos.getZ() - Math.floor(bestHitPos.getZ());\r\n        switch (bestHitFace) {\r\n            case NORTH -> {\r\n                x = 128f - (basex * 128f);\r\n                y = 128f - (basey * 128f);\r\n            }\r\n            case SOUTH -> {\r\n                x = basex * 128f;\r\n                y = 128f - (basey * 128f);\r\n            }\r\n            case WEST -> {\r\n                x = basez * 128f;\r\n                y = 128f - (basey * 128f);\r\n            }\r\n            case EAST -> {\r\n                x = 128f - (basez * 128f);\r\n                y = 128f - (basey * 128f);\r\n            }\r\n            case UP -> {\r\n                x = basex * 128f;\r\n                y = basez * 128f;\r\n            }\r\n            case DOWN -> {\r\n                x = basex * 128f;\r\n                y = 128f - (basez * 128f);\r\n            }\r\n        }\r\n        MapMeta map = (MapMeta) best.getItem().getItemMeta();\r\n        switch (best.getRotation()) {\r\n            case CLOCKWISE_45, FLIPPED_45 -> { // 90 deg\r\n                double origX = x;\r\n                x = y;\r\n                y = 128f - origX;\r\n            }\r\n            case CLOCKWISE, COUNTER_CLOCKWISE -> { // 180 deg\r\n                x = 128f - x;\r\n                y = 128f - y;\r\n            }\r\n            case CLOCKWISE_135, COUNTER_CLOCKWISE_45 -> { // 270 deg\r\n                double origX2 = x;\r\n                x = 128f - y;\r\n                y = origX2;\r\n            }\r\n        }\r\n        MapTag result = new MapTag();\r\n        result.putObject(\"x\", new ElementTag(Math.round(x)));\r\n        result.putObject(\"y\", new ElementTag(Math.round(y)));\r\n        result.putObject(\"entity\", new EntityTag(best));\r\n        result.putObject(\"map\", new ElementTag(map.hasMapId() ? map.getMapId() : 0));\r\n        return result;\r\n    }\r\n\r\n    public abstract boolean canTrace(World world, Vector start, Vector end);\r\n\r\n    public Location faceLocation(Location from, Location at) {\r\n        Vector direction = from.toVector().subtract(at.toVector()).normalize();\r\n        Location newLocation = from.clone();\r\n        newLocation.setYaw(180 - (float) Math.toDegrees(Math.atan2(direction.getX(), direction.getZ())));\r\n        newLocation.setPitch(90 - (float) Math.toDegrees(Math.acos(direction.getY())));\r\n        return newLocation;\r\n    }\r\n\r\n    public boolean internalLook(Player player, Location at) {\r\n        return false;\r\n    }\r\n\r\n    /**\r\n     * Changes an entity's yaw and pitch to make it face a location.\r\n     *\r\n     * @param from The Entity whose yaw and pitch you want to change.\r\n     * @param at   The Location it should be looking at.\r\n     */\r\n    public void faceLocation(Entity from, Location at) {\r\n        if (from.getWorld() != at.getWorld()) {\r\n            return;\r\n        }\r\n        if (EntityTag.isPlayer(from)) {\r\n            if (internalLook((Player) from, at)) {\r\n                return;\r\n            }\r\n        }\r\n        Location origin = from instanceof LivingEntity livingEntity ? livingEntity.getEyeLocation()\r\n                : new LocationTag(from.getLocation()).getBlockLocation().add(0.5, 0.5, 0.5);\r\n        Location rotated = faceLocation(origin, at);\r\n        rotate(from, rotated.getYaw(), rotated.getPitch());\r\n    }\r\n\r\n    public boolean isFacingLocation(Location from, Location at, float yawLimitDegrees, float pitchLimitDegrees) {\r\n        Vector direction = from.toVector().subtract(at.toVector()).normalize();\r\n        float pitch = 90 - (float) Math.toDegrees(Math.acos(direction.getY()));\r\n        if (from.getPitch() > pitch + pitchLimitDegrees\r\n                || from.getPitch() < pitch - pitchLimitDegrees) {\r\n            return false;\r\n        }\r\n\r\n        return isFacingLocation(from, at, yawLimitDegrees);\r\n    }\r\n\r\n    /**\r\n     * Checks if a Location's yaw is facing another Location.\r\n     * <p/>\r\n     * Note: do not use a player's location as the first argument,\r\n     * because player yaws need to modified. Use the method\r\n     * below this one instead.\r\n     *\r\n     * @param from        The Location we check.\r\n     * @param at          The Location we want to know if the first Location's yaw\r\n     *                    is facing\r\n     * @param degreeLimit How many degrees can be between the direction the\r\n     *                    first location's yaw is facing and the direction\r\n     *                    we check if it is facing.\r\n     * @return Returns a boolean.\r\n     */\r\n    public boolean isFacingLocation(Location from, Location at, float degreeLimit) {\r\n        double currentYaw = normalizeYaw(from.getYaw());\r\n        double requiredYaw = normalizeYaw(getYaw(at.toVector().subtract(\r\n                from.toVector()).normalize()));\r\n        return (Math.abs(requiredYaw - currentYaw) < degreeLimit ||\r\n                Math.abs(requiredYaw + 360 - currentYaw) < degreeLimit ||\r\n                Math.abs(currentYaw + 360 - requiredYaw) < degreeLimit);\r\n    }\r\n\r\n    /**\r\n     * Checks if an Entity is facing a Location.\r\n     *\r\n     * @param from        The Entity we check.\r\n     * @param at          The Location we want to know if it is looking at.\r\n     * @param degreeLimit How many degrees can be between the direction the\r\n     *                    Entity is facing and the direction we check if it\r\n     *                    is facing.\r\n     * @return Returns a boolean.\r\n     */\r\n    public boolean isFacingLocation(Entity from, Location at, float degreeLimit) {\r\n        return isFacingLocation(from.getLocation(), at, degreeLimit);\r\n    }\r\n\r\n    /**\r\n     * Checks if an Entity is facing another Entity.\r\n     *\r\n     * @param from        The Entity we check.\r\n     * @param at          The Entity we want to know if it is looking at.\r\n     * @param degreeLimit How many degrees can be between the direction the\r\n     *                    Entity is facing and the direction we check if it\r\n     *                    is facing.\r\n     * @return Returns a boolean.\r\n     */\r\n    public boolean isFacingEntity(Entity from, Entity at, float degreeLimit) {\r\n        return isFacingLocation(from.getLocation(), at.getLocation(), degreeLimit);\r\n    }\r\n\r\n    /**\r\n     * Normalizes Mincraft's yaws (which can be negative or can exceed 360)\r\n     * by turning them into proper yaw values that only go from 0 to 359.\r\n     *\r\n     * @param yaw The original yaw.\r\n     * @return The normalized yaw.\r\n     */\r\n    public static float normalizeYaw(float yaw) {\r\n        yaw = yaw % 360;\r\n        if (yaw < 0) {\r\n            yaw += 360;\r\n        }\r\n        return yaw;\r\n    }\r\n\r\n    /**\r\n     * Converts a vector to a yaw.\r\n     * <p/>\r\n     * Thanks to bergerkiller.\r\n     *\r\n     * @param vector The vector you want to get a yaw from.\r\n     * @return The yaw.\r\n     */\r\n    public float getYaw(Vector vector) {\r\n        double dx = vector.getX();\r\n        double dz = vector.getZ();\r\n        double yaw = 0;\r\n        // Set yaw\r\n        if (dx != 0) {\r\n            // Set yaw start value based on dx\r\n            if (dx < 0) {\r\n                yaw = 1.5 * Math.PI;\r\n            }\r\n            else {\r\n                yaw = 0.5 * Math.PI;\r\n            }\r\n            yaw -= Math.atan(dz / dx);\r\n        }\r\n        else if (dz < 0) {\r\n            yaw = Math.PI;\r\n        }\r\n        return (float) (-yaw * 180 / Math.PI);\r\n    }\r\n\r\n    /**\r\n     * Converts a yaw to a cardinal direction name.\r\n     *\r\n     * @param yaw The yaw you want to get a cardinal direction from.\r\n     * @return The name of the cardinal direction as a String.\r\n     */\r\n    public String getCardinal(float yaw) {\r\n        yaw = normalizeYaw(yaw);\r\n        // Compare yaws, return closest direction.\r\n        if (0 <= yaw && yaw < 22.5) {\r\n            return \"south\";\r\n        }\r\n        else if (22.5 <= yaw && yaw < 67.5) {\r\n            return \"southwest\";\r\n        }\r\n        else if (67.5 <= yaw && yaw < 112.5) {\r\n            return \"west\";\r\n        }\r\n        else if (112.5 <= yaw && yaw < 157.5) {\r\n            return \"northwest\";\r\n        }\r\n        else if (157.5 <= yaw && yaw < 202.5) {\r\n            return \"north\";\r\n        }\r\n        else if (202.5 <= yaw && yaw < 247.5) {\r\n            return \"northeast\";\r\n        }\r\n        else if (247.5 <= yaw && yaw < 292.5) {\r\n            return \"east\";\r\n        }\r\n        else if (292.5 <= yaw && yaw < 337.5) {\r\n            return \"southeast\";\r\n        }\r\n        else if (337.5 <= yaw && yaw < 360.0) {\r\n            return \"south\";\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public abstract void snapPositionTo(Entity entity, Vector vector);\r\n\r\n    public abstract void move(Entity entity, Vector vector);\r\n\r\n    public void fakeMove(Entity entity, Vector vector) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void fakeTeleport(Entity entity, Location location) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void clientResetLoc(Entity entity) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract void teleport(Entity entity, Location loc);\r\n\r\n    public abstract void setBoundingBox(Entity entity, BoundingBox box);\r\n\r\n    public List<Player> getPlayersThatSee(Entity entity) { // TODO: once 1.20 is the minimum supported version, remove from NMS\r\n        return List.copyOf(entity.getTrackedBy());\r\n    }\r\n\r\n    public void sendAllUpdatePackets(Entity entity) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract void setTicksLived(Entity entity, int ticks);\r\n\r\n    public abstract void setHeadAngle(LivingEntity entity, float angle);\r\n\r\n    public void setGhastAttacking(Ghast ghast, boolean attacking) { // TODO: once 1.19 is the minimum supported version, remove from NMS\r\n        ghast.setCharging(attacking);\r\n    }\r\n\r\n    public abstract void setEndermanAngry(Enderman enderman, boolean angry);\r\n\r\n    public static EntityDamageEvent fireFakeDamageEvent(Entity target, EntityTag source, Location sourceLoc, EntityDamageEvent.DamageCause cause, float amount) {\r\n        EntityDamageEvent ede;\r\n        if (source != null) {\r\n            ede = new EntityDamageByEntityEvent(source.getBukkitEntity(), target, cause, amount);\r\n        }\r\n        else if (sourceLoc != null) {\r\n            ede = new EntityDamageByBlockEvent(sourceLoc.getBlock(), target, cause, amount);\r\n        }\r\n        else {\r\n            ede = new EntityDamageEvent(target, cause, amount);\r\n        }\r\n        Bukkit.getPluginManager().callEvent(ede);\r\n        return ede;\r\n    }\r\n\r\n    public abstract void damage(LivingEntity target, float amount, EntityTag source, Location sourceLoc, EntityDamageEvent.DamageCause cause);\r\n\r\n    public abstract void setLastHurtBy(LivingEntity mob, LivingEntity damager);\r\n\r\n    public abstract void setFallingBlockType(FallingBlock fallingBlock, BlockData block);\r\n\r\n    public abstract EntityTag getMobSpawnerDisplayEntity(CreatureSpawner spawner);\r\n\r\n    public void setFireworkLifetime(Firework firework, int ticks) { // TODO: once minimum version is 1.19, remove from NMS\r\n        firework.setMaxLife(ticks);\r\n    }\r\n\r\n    public int getFireworkLifetime(Firework firework) { // TODO: once minimum version is 1.19, remove from NMS\r\n        return firework.getMaxLife();\r\n    }\r\n\r\n    public abstract int getInWaterTime(Zombie zombie);\r\n\r\n    public abstract void setInWaterTime(Zombie zombie, int ticks);\r\n\r\n    public abstract void setTrackingRange(Entity entity, int range);\r\n\r\n    public abstract boolean isAggressive(Mob mob);\r\n\r\n    public abstract void setAggressive(Mob mob, boolean aggressive);\r\n\r\n    public void setUUID(Entity entity, UUID id) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public float getStepHeight(Entity entity) {\r\n        return entity instanceof LivingEntity livingEntity ? (float) livingEntity.getAttribute(ATTRIBUTE_STEP_HEIGHT).getBaseValue() : 0;\r\n    }\r\n\r\n    public void setStepHeight(Entity entity, float stepHeight) {\r\n        if (entity instanceof LivingEntity livingEntity) {\r\n            livingEntity.getAttribute(ATTRIBUTE_STEP_HEIGHT).setBaseValue(stepHeight);\r\n        }\r\n    }\r\n\r\n    public List<Object> convertInternalEntityDataValues(Entity entity, MapTag internalData) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void modifyInternalEntityData(Entity entity, MapTag internalData) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void startUsingItem(LivingEntity entity, EquipmentSlot hand) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void stopUsingItem(LivingEntity entity) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract void openHorseInventory(Player player, AbstractHorse horse);\r\n\r\n    public CompoundBinaryTag getRawNBT(Entity entity) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void modifyRawNBT(Entity entity, CompoundBinaryTag tag) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public static class EntityEntersVehicleScriptEventImpl extends EntityEntersVehicleScriptEvent {\r\n        @EventHandler\r\n        public void onEntityMount(EntityMountEvent event) {\r\n            fire(event, event.getMount());\r\n        }\r\n    }\r\n\r\n    public Class<? extends EntityEntersVehicleScriptEvent> getEntersVehicleEventImpl() { // TODO: once 1.20 is the minimum supported version, implement in the ScriptEvent class as usual\r\n        return EntityEntersVehicleScriptEventImpl.class;\r\n    }\r\n\r\n    public static class EntityExitsVehicleScriptEventImpl extends EntityExitsVehicleScriptEvent {\r\n        @EventHandler\r\n        public void onEntityMount(EntityDismountEvent event) {\r\n            fire(event, event.getDismounted());\r\n        }\r\n    }\r\n\r\n    public Class<? extends EntityExitsVehicleScriptEvent> getExitsVehicleEventImpl() { // TODO: once 1.20 is the minimum supported version, implement in the ScriptEvent class as usual\r\n        return EntityExitsVehicleScriptEventImpl.class;\r\n    }\r\n\r\n    public EntityState.ArmadilloState getArmadilloState(Armadillo entity) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void setArmadilloState(Armadillo entity, EntityState.ArmadilloState state) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/FakeArrow.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport org.bukkit.entity.Vehicle;\r\n\r\npublic interface FakeArrow extends CustomEntity, Vehicle {\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/FakePlayer.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport org.bukkit.entity.Player;\r\n\r\npublic interface FakePlayer extends CustomEntity, Player {\r\n\r\n    String getFullName();\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/FishingHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.FishHook;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic interface FishingHelper {\r\n\r\n    enum CatchType {NONE, DEFAULT, JUNK, TREASURE, FISH}\r\n\r\n    ItemStack getResult(FishHook fishHook, CatchType catchType);\r\n\r\n    FishHook spawnHook(Location location, Player player);\r\n\r\n    FishHook getHookFrom(Player player);\r\n\r\n    void setNibble(FishHook hook, int ticks);\r\n\r\n    void setHookTime(FishHook hook, int ticks);\r\n\r\n    int getLureTime(FishHook hook);\r\n\r\n    void setLureTime(FishHook hook, int ticks);\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/ItemHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.nbt.CustomNBT;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.google.gson.JsonObject;\r\nimport net.kyori.adventure.nbt.BinaryTag;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport org.bukkit.DyeColor;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.Banner;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.RecipeChoice;\r\nimport org.bukkit.inventory.ShapedRecipe;\r\nimport org.bukkit.inventory.meta.BlockStateMeta;\r\nimport org.bukkit.inventory.meta.ShieldMeta;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.function.Consumer;\r\n\r\npublic abstract class ItemHelper {\r\n\r\n    public abstract void setMaxStackSize(Material material, int size);\r\n\r\n    public abstract Integer burnTime(Material material);\r\n\r\n    public abstract void registerStonecuttingRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, boolean exact);\r\n\r\n    public abstract void registerFurnaceRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, float exp, int time, String type, boolean exact, String category);\r\n\r\n    public abstract void registerShapelessRecipe(String keyName, String group, ItemStack result, List<ItemStack[]> ingredients, boolean[] exact, String category);\r\n\r\n    public abstract void setShapedRecipeIngredient(ShapedRecipe recipe, char c, ItemStack[] item, boolean exact);\r\n\r\n    public abstract String getJsonString(ItemStack itemStack);\r\n\r\n    public String getLegacyHoverNbt(ItemTag item) { // TODO: once 1.20 is the minimum supported version, remove this\r\n        return item.getItemMeta().getAsString();\r\n    }\r\n\r\n    public JsonObject getRawHoverComponentsJson(ItemStack item) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public ItemStack applyRawHoverComponentsJson(ItemStack item, JsonObject components) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract PlayerProfile getSkullSkin(ItemStack itemStack);\r\n\r\n    public abstract ItemStack setSkullSkin(ItemStack itemStack, PlayerProfile playerProfile);\r\n\r\n    public abstract ItemStack addNbtData(ItemStack itemStack, String key, BinaryTag value);\r\n\r\n    public abstract CompoundBinaryTag getNbtData(ItemStack itemStack);\r\n\r\n    public abstract ItemStack setNbtData(ItemStack itemStack, CompoundBinaryTag compoundTag);\r\n\r\n    public CompoundBinaryTag getCustomData(ItemStack item) { // TODO: once 1.20 is the minimum supported version, remove default impl\r\n        return getNbtData(item);\r\n    }\r\n\r\n    public ItemStack setCustomData(ItemStack item, CompoundBinaryTag data) { // TODO: once 1.20 is the minimum supported version, remove default impl\r\n        return setNbtData(item, data);\r\n    }\r\n\r\n    public ItemStack setPartialOldNbt(ItemStack item, CompoundBinaryTag oldTag) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public CompoundBinaryTag getEntityData(ItemStack item) { // TODO: once 1.20 is the minimum supported version, remove default impl\r\n        return getNbtData(item).getCompound(\"EntityTag\", null);\r\n    }\r\n\r\n    public ItemStack setEntityData(ItemStack item, CompoundBinaryTag entityNbt, EntityType entityType) { // TODO: once 1.20 is the minimum supported version, remove default impl\r\n        boolean shouldRemove = entityNbt == null || entityNbt.isEmpty();\r\n        CompoundBinaryTag nbt = getNbtData(item);\r\n        if (shouldRemove && !nbt.contains(\"EntityTag\")) {\r\n            return item;\r\n        }\r\n        if (shouldRemove) {\r\n            nbt = nbt.remove(\"EntityTag\");\r\n        }\r\n        else {\r\n            nbt = nbt.put(\"EntityTag\", entityNbt);\r\n        }\r\n        return setNbtData(item, nbt);\r\n    }\r\n\r\n    public List<Material> getCanPlaceOn(ItemStack item) { // TODO: once 1.20 is the minimum supported version, remove default impl\r\n        return CustomNBT.getNBTMaterials(item, CustomNBT.KEY_CAN_PLACE_ON);\r\n    }\r\n\r\n    public ItemStack setCanPlaceOn(ItemStack item, List<Material> canPlaceOn) { // TODO: once 1.20 is the minimum supported version, remove default impl\r\n        if (canPlaceOn == null) {\r\n            return CustomNBT.clearNBT(item, CustomNBT.KEY_CAN_PLACE_ON);\r\n        }\r\n        return CustomNBT.setNBTMaterials(item, CustomNBT.KEY_CAN_PLACE_ON, canPlaceOn);\r\n    }\r\n\r\n    public List<Material> getCanBreak(ItemStack item) { // TODO: once 1.20 is the minimum supported version, remove default impl\r\n        return CustomNBT.getNBTMaterials(item, CustomNBT.KEY_CAN_DESTROY);\r\n    }\r\n\r\n    public ItemStack setCanBreak(ItemStack item, List<Material> canBreak) { // TODO: once 1.20 is the minimum supported version, remove default impl\r\n        if (canBreak == null) {\r\n            return CustomNBT.clearNBT(item, CustomNBT.KEY_CAN_DESTROY);\r\n        }\r\n        return CustomNBT.setNBTMaterials(item, CustomNBT.KEY_CAN_DESTROY, canBreak);\r\n    }\r\n\r\n    public MapTag getRawComponentsPatch(ItemStack item, boolean excludeHandled) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public ItemStack setRawComponentsPatch(ItemStack item, MapTag rawComponentsMap, int dataVersion, Consumer<String> errorHandler) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract void registerSmithingRecipe(String keyName, ItemStack result, ItemStack[] baseItem, boolean baseExact, ItemStack[] upgradeItem, boolean upgradeExact, ItemStack[] templateItem, boolean templateExact);\r\n\r\n    public abstract void setInventoryItem(Inventory inventory, ItemStack item, int slot);\r\n\r\n    public abstract String getDisplayName(ItemTag item);\r\n\r\n    public abstract List<String> getLore(ItemTag item);\r\n\r\n    public abstract void setDisplayName(ItemTag item, String name);\r\n\r\n    public abstract void setLore(ItemTag item, List<String> lore);\r\n\r\n    public boolean renderEntireMap(int mapId, int xMin, int zMin, int xMax, int zMax) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public BlockData getPlacedBlock(Material material) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public abstract boolean isValidMix(ItemStack input, ItemStack ingredient);\r\n\r\n    public record BrewingRecipe(RecipeChoice input, RecipeChoice ingredient, ItemStack result) {}\r\n\r\n    public Map<NamespacedKey, BrewingRecipe> getCustomBrewingRecipes() {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public byte[] renderMap(MapView mapView, Player player) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public int getFoodPoints(Material itemType) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public DyeColor getShieldColor(ItemStack item) { // TODO: once 1.21 is the minimum supported version, remove from NMS\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            return ((ShieldMeta) item.getItemMeta()).getBaseColor();\r\n        }\r\n        // TODO: once 1.20 is the minimum supported version, remove legacy code ↓\r\n        BlockStateMeta stateMeta = (BlockStateMeta) item.getItemMeta();\r\n        return stateMeta.hasBlockState() ? ((Banner) stateMeta.getBlockState()).getBaseColor() : null;\r\n    }\r\n\r\n    public ItemStack setShieldColor(ItemStack item, DyeColor color) { // TODO: once 1.21 is the minimum supported version, remove from NMS\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            ShieldMeta shieldMeta = (ShieldMeta) item.getItemMeta();\r\n            shieldMeta.setBaseColor(color);\r\n            item.setItemMeta(shieldMeta);\r\n            return item;\r\n        }\r\n        // TODO: once 1.20 is the minimum supported version, remove legacy code ↓\r\n        if (color == null) {\r\n            CompoundBinaryTag noStateNbt = getNbtData(item).remove(\"BlockEntityTag\");\r\n            return setNbtData(item, noStateNbt);\r\n        }\r\n        BlockStateMeta stateMeta = (BlockStateMeta) item.getItemMeta();\r\n        Banner banner = (Banner) stateMeta.getBlockState();\r\n        banner.setBaseColor(color);\r\n        stateMeta.setBlockState(banner);\r\n        item.setItemMeta(stateMeta);\r\n        return item;\r\n    }\r\n\r\n    public void blockRecipeFinalization() {\r\n    }\r\n\r\n    public void restoreRecipeFinalization() {\r\n    }\r\n\r\n    public void removeRecipes(List<NamespacedKey> keys) {\r\n    }\r\n\r\n    public void registerOtherRecipe(org.bukkit.inventory.Recipe recipe) {\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/ItemProjectile.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport org.bukkit.entity.Item;\r\nimport org.bukkit.entity.Projectile;\r\n\r\npublic interface ItemProjectile extends CustomEntity, Item, Projectile {\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/PacketHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport com.denizenscript.denizen.scripts.commands.entity.TeleportCommand;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.maps.MapImage;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.EntityEffect;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.WorldBorder;\r\nimport org.bukkit.block.Banner;\r\nimport org.bukkit.block.banner.Pattern;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.EntityEquipment;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapPalette;\r\n\r\nimport java.util.EnumMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic interface PacketHelper {\r\n\r\n    void setFakeAbsorption(Player player, float value);\r\n\r\n    default void resetWorldBorder(Player player) { // TODO: once minimum version is 1.18 or higher, remove from NMS\r\n        player.setWorldBorder(null);\r\n    }\r\n\r\n    default void setWorldBorder(Player player, Location center, double size, double currSize, long time, int warningDistance, int warningTime) { // TODO: once minimum version is 1.18 or higher, remove from NMS\r\n        WorldBorder border = Bukkit.createWorldBorder();\r\n        border.setCenter(center);\r\n        if (time > 0) {\r\n            border.setSize(currSize);\r\n            border.setSize(size, time / 1000);\r\n        }\r\n        else {\r\n            border.setSize(size);\r\n        }\r\n        border.setWarningDistance(warningDistance);\r\n        border.setWarningTime(warningTime);\r\n        player.setWorldBorder(border);\r\n    }\r\n\r\n    void setSlot(Player player, int slot, ItemStack itemStack, boolean playerOnly);\r\n\r\n    void setFieldOfView(Player player, float fov);\r\n\r\n    void respawn(Player player);\r\n\r\n    void setVision(Player player, EntityType entityType);\r\n\r\n    default void showDemoScreen(Player player) { // TODO: once minimum version is 1.18 or higher, remove from NMS\r\n        player.showDemoScreen();\r\n    }\r\n\r\n    void showBlockAction(Player player, Location location, int action, int state);\r\n\r\n    default void showBlockCrack(Player player, int id, Location location, int progress) {\r\n        float progressFloat = 0;\r\n        if (progress >= 0 && progress <= 9) {\r\n            // Spigot treats 0 as -1, so replace 0 with 0.1 which will then get floored\r\n            progressFloat = Math.max(progress, 0.1f) / 9f;\r\n        }\r\n        player.sendBlockDamage(location, progressFloat, id);\r\n    }\r\n\r\n    default void showTileEntityData(Player player, Location location, int action, CompoundBinaryTag compoundTag) { // TODO: once minimum version is 1.20, remove in favor of Player#sendBlockUpdate\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    default void showBannerUpdate(Player player, Location location, List<Pattern> patterns) { // TODO: once minimum version is 1.20, remove from NMS\r\n        Banner banner = (Banner) location.getBlock().getState();\r\n        banner.setPatterns(patterns);\r\n        player.sendBlockUpdate(location, banner);\r\n    }\r\n\r\n    void showTabListHeaderFooter(Player player, String header, String footer);\r\n\r\n    void showTitle(Player player, String title, String subtitle, int fadeIn, int stay, int fadeOut);\r\n\r\n    default void showEquipment(Player player, LivingEntity entity, EquipmentSlot equipmentSlot, ItemStack itemStack) { // TODO: once minimum version is 1.18 or higher, remove from NMS\r\n        player.sendEquipmentChange(entity, equipmentSlot, itemStack);\r\n    }\r\n\r\n    default void resetEquipment(Player player, LivingEntity entity) { // TODO: once 1.19 is the minimum supported version, remove from NMS\r\n        EntityEquipment equipment = entity.getEquipment();\r\n        Map<EquipmentSlot, ItemStack> equipmentMap = new EnumMap<>(EquipmentSlot.class);\r\n        for (EquipmentSlot slot : EquipmentSlot.values()) {\r\n            if (PaperAPITools.instance.canUseEquipmentSlot(entity, slot)) {\r\n                equipmentMap.put(slot, equipment.getItem(slot));\r\n            }\r\n        }\r\n        player.sendEquipmentChange(entity, equipmentMap);\r\n    }\r\n\r\n    default void showHealth(Player player, float health, int food, float saturation) { // TODO: once minimum version is 1.20, remove from NMS\r\n        player.sendHealthUpdate(health, food, saturation);\r\n    }\r\n\r\n    void showMobHealth(Player player, LivingEntity mob, double health, double maxHealth);\r\n\r\n    default void resetHealth(Player player) { // TODO: once minimum version is 1.20, remove from NMS\r\n        player.sendHealthUpdate(player.getHealth(), player.getFoodLevel(), player.getSaturation());\r\n    }\r\n\r\n    void showSignEditor(Player player, Location location); // TODO: once minimum version is 1.18 or higher, change to \"showFakeSignEditor\" and remove location param\r\n\r\n    void forceSpectate(Player player, Entity entity);\r\n\r\n    void setNetworkManagerFor(Player player);\r\n\r\n    default void enableNetworkManager() {\r\n        // Pre-1.18 do nothing\r\n    }\r\n\r\n    void sendRename(Player player, Entity entity, String name, boolean listMode);\r\n\r\n    void generateNoCollideTeam(Player player, UUID noCollide);\r\n\r\n    void removeNoCollideTeam(Player player, UUID noCollide);\r\n\r\n    void sendEntityMetadataFlagsUpdate(Player player, Entity entity);\r\n\r\n    void sendEntityEffect(Player player, Entity entity, EntityEffect effect);\r\n\r\n    int getPacketStats(Player player, boolean sent);\r\n\r\n    default void setMapData(MapCanvas canvas, byte[] bytes, int x, int y, MapImage image) {\r\n        int width = image.width, height = image.height;\r\n        for (int x2 = 0; x2 < width; ++x2) {\r\n            for (int y2 = 0; y2 < height; ++y2) {\r\n                byte p = bytes[y2 * width + x2];\r\n                if (p != MapPalette.TRANSPARENT) {\r\n                    canvas.setPixel(x + x2, y + y2, p);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    void showDebugTestMarker(Player player, Location location, ColorTag color, String name, int time);\r\n\r\n    void clearDebugTestMarker(Player player);\r\n\r\n    void sendBrand(Player player, String brand);\r\n\r\n    default void sendCollectItemEntity(Player player, Entity taker, Entity item, int amount) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    default void sendRelativePositionPacket(Player player, double x, double y, double z, float yaw, float pitch, List<TeleportCommand.Relative> relativeMovement) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    default void sendRelativeLookPacket(Player player, float yaw, float pitch) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    default void sendEntityDataPacket(List<Player> players, Entity entity, List<Object> data) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/PlayerHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.entity.DenizenEntityType;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport org.bukkit.*;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.EnumSet;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic abstract class PlayerHelper {\r\n\r\n    public abstract void stopSound(Player player, NamespacedKey sound, SoundCategory category); // TODO: remove the category param once 1.19 is the minimum version\r\n\r\n    public abstract FakeEntity sendEntitySpawn(List<PlayerTag> players, DenizenEntityType entityType, LocationTag location, ArrayList<Mechanism> mechanisms, int customId, UUID customUUID, boolean autoTrack);\r\n\r\n    public abstract void deTrackEntity(Player player, Entity entity);\r\n\r\n    public abstract void sendEntityDestroy(Player player, Entity entity);\r\n\r\n    public abstract int getFlyKickCooldown(Player player);\r\n\r\n    public abstract void setFlyKickCooldown(Player player, int ticks);\r\n\r\n    public abstract int ticksPassedDuringCooldown(Player player);\r\n\r\n    public abstract float getMaxAttackCooldownTicks(Player player);\r\n\r\n    public abstract void setAttackCooldown(Player player, int ticks);\r\n\r\n    public abstract boolean hasChunkLoaded(Player player, Chunk chunk);\r\n\r\n    public abstract void setTemporaryOp(Player player, boolean op);\r\n\r\n    public abstract void showEndCredits(Player player);\r\n\r\n    public abstract ImprovedOfflinePlayer getOfflineData(UUID uuid);\r\n\r\n    public abstract void resendDiscoveredRecipes(Player player);\r\n\r\n    public abstract void quietlyAddRecipe(Player player, NamespacedKey key);\r\n\r\n    public abstract void resendRecipeDetails(Player player);\r\n\r\n    // TODO: once 1.20 is the minimum supported version, remove from NMS in favor of Paper API\r\n    public String getClientBrand(Player player) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public enum SkinLayer {\r\n        CAPE(0),\r\n        HAT(6),\r\n        JACKET(1),\r\n        LEFT_PANTS(4),\r\n        LEFT_SLEEVE(2),\r\n        RIGHT_PANTS(5),\r\n        RIGHT_SLEEVE(3);\r\n\r\n        public final int flag;\r\n\r\n        SkinLayer(int offset) {\r\n            this.flag = 1 << offset;\r\n        }\r\n    }\r\n    public abstract byte getSkinLayers(Player player);\r\n\r\n    public abstract void setSkinLayers(Player player, byte flags);\r\n\r\n    public abstract void setBossBarTitle(BossBar bar, String title);\r\n\r\n    public abstract boolean getSpawnForced(Player player);\r\n\r\n    public abstract void setSpawnForced(Player player, boolean forced);\r\n\r\n    public abstract Location getBedSpawnLocation(Player player);\r\n\r\n    public abstract long getLastActionTime(Player player);\r\n\r\n    public enum ProfileEditMode { ADD, UPDATE_DISPLAY, UPDATE_LATENCY, UPDATE_GAME_MODE, UPDATE_LISTED }\r\n\r\n    public void sendPlayerInfoAddPacket(Player player, EnumSet<ProfileEditMode> editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) { // TODO: once 1.19 is the minimum supported version, rename to 'sendPlayerInfoUpdatePacket'\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void sendPlayerInfoRemovePacket(Player player, UUID id) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void sendClimbableMaterials(Player player, List<Material> materials) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void addFakePassenger(List<PlayerTag> players, Entity entity, FakeEntity fakeEntity) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void refreshPlayer(Player player) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/WorldHelper.java",
    "content": "package com.denizenscript.denizen.nms.interfaces;\r\n\r\nimport com.denizenscript.denizen.objects.BiomeTag;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\n\r\npublic interface WorldHelper {\r\n\r\n    boolean isStatic(World world);\r\n\r\n    void setStatic(World world, boolean isStatic);\r\n\r\n    float getLocalDifficulty(Location location);\r\n\r\n    default Location getNearestBiomeLocation(Location start, BiomeTag biome) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    boolean areEnoughSleeping(World world, int percentage);\r\n\r\n    boolean areEnoughDeepSleeping(World world, int percentage);\r\n\r\n    int getSkyDarken(World world);\r\n\r\n    boolean isDay(World world);\r\n\r\n    boolean isNight(World world);\r\n\r\n    /** for setting the time without firing a CUSTOM TimeSkipEvent */\r\n    void setDayTime(World world, long time);\r\n\r\n    void wakeUpAllPlayers(World world);\r\n\r\n    /** for clearing weather without ignoring possible raised event results */\r\n    void clearWeather(World world);\r\n\r\n    default void setGameTime(World world, long time) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/packets/PacketInResourcePackStatus.java",
    "content": "package com.denizenscript.denizen.nms.interfaces.packets;\r\n\r\npublic interface PacketInResourcePackStatus {\r\n\r\n    String getStatus();\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/packets/PacketInSteerVehicle.java",
    "content": "package com.denizenscript.denizen.nms.interfaces.packets;\r\n\r\npublic interface PacketInSteerVehicle {\r\n\r\n    float getLeftwardInput();\r\n\r\n    float getForwardInput();\r\n\r\n    boolean getJumpInput();\r\n\r\n    boolean getDismountInput();\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/packets/PacketOutChat.java",
    "content": "package com.denizenscript.denizen.nms.interfaces.packets;\r\n\r\nimport java.util.function.Function;\r\n\r\npublic abstract class PacketOutChat {\r\n\r\n    public abstract boolean isSystem();\r\n\r\n    public abstract boolean isActionbar();\r\n\r\n    public abstract String getMessage();\r\n\r\n    public abstract String getRawJson();\r\n\r\n    // TODO: once 1.20 is the minimum supported version, remove this\r\n    public static Function<Object, String> convertComponentToJsonString;\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/packets/PacketOutSetSlot.java",
    "content": "package com.denizenscript.denizen.nms.interfaces.packets;\r\n\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic interface PacketOutSetSlot {\r\n\r\n    ItemStack getItemStack();\r\n\r\n    void setItemStack(ItemStack itemStack);\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/packets/PacketOutSpawnEntity.java",
    "content": "package com.denizenscript.denizen.nms.interfaces.packets;\r\n\r\npublic interface PacketOutSpawnEntity {\r\n\r\n    int getEntityId();\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/packets/PacketOutTradeList.java",
    "content": "package com.denizenscript.denizen.nms.interfaces.packets;\r\n\r\nimport com.denizenscript.denizen.nms.util.TradeOffer;\r\n\r\nimport java.util.List;\r\n\r\npublic interface PacketOutTradeList {\r\n\r\n    List<TradeOffer> getTradeOffers();\r\n\r\n    void setTradeOffers(List<TradeOffer> tradeOffers);\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/packets/PacketOutWindowItems.java",
    "content": "package com.denizenscript.denizen.nms.interfaces.packets;\r\n\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic interface PacketOutWindowItems {\r\n\r\n    ItemStack[] getContents();\r\n\r\n    void setContents(ItemStack[] contents);\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/util/Advancement.java",
    "content": "package com.denizenscript.denizen.nms.util;\r\n\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic class Advancement {\r\n\r\n    public enum Frame {TASK, CHALLENGE, GOAL}\r\n\r\n    public boolean temporary;\r\n    public NamespacedKey key;\r\n    public NamespacedKey parent;\r\n    public ItemStack icon;\r\n    public String title;\r\n    public String description;\r\n    public NamespacedKey background;\r\n    public Frame frame;\r\n    public boolean toast;\r\n    public boolean announceToChat;\r\n    public boolean hidden;\r\n\r\n    public float xOffset;\r\n    public float yOffset;\r\n\r\n    public int length;\r\n\r\n    public boolean registered;\r\n\r\n    public Advancement(boolean temporary, NamespacedKey key, NamespacedKey parent, ItemStack icon,\r\n                       String title, String description, NamespacedKey background, Frame frame,\r\n                       boolean toast, boolean announceToChat, boolean hidden, float xOffset, float yOffset, int length) {\r\n        this.temporary = temporary;\r\n        this.key = key;\r\n        this.parent = parent;\r\n        this.icon = icon;\r\n        this.title = title;\r\n        this.description = description;\r\n        this.background = background;\r\n        this.frame = frame;\r\n        this.toast = toast;\r\n        this.announceToChat = announceToChat;\r\n        this.hidden = hidden;\r\n        this.xOffset = xOffset;\r\n        this.yOffset = yOffset;\r\n        this.length = length;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/util/PlayerProfile.java",
    "content": "package com.denizenscript.denizen.nms.util;\r\n\r\nimport java.util.UUID;\r\n\r\npublic class PlayerProfile {\r\n\r\n    private String name;\r\n    private UUID uuid;\r\n    private String texture;\r\n    private String textureSignature;\r\n\r\n    public PlayerProfile(String name, UUID uuid) {\r\n        this(name, uuid, null, null);\r\n    }\r\n\r\n    public PlayerProfile(String name, UUID uuid, String texture) {\r\n        this(name, uuid, texture, null);\r\n    }\r\n\r\n    public PlayerProfile(String name, UUID uuid, String texture, String textureSignature) {\r\n        this.name = name;\r\n        this.uuid = uuid;\r\n        this.texture = texture;\r\n        this.textureSignature = textureSignature;\r\n    }\r\n\r\n    public String getName() {\r\n        return name;\r\n    }\r\n\r\n    public void setName(String name) {\r\n        this.name = name;\r\n    }\r\n\r\n    public UUID getUniqueId() {\r\n        return uuid;\r\n    }\r\n\r\n    public void setUniqueId(UUID uuid) {\r\n        this.uuid = uuid;\r\n    }\r\n\r\n    public String getTexture() {\r\n        return texture;\r\n    }\r\n\r\n    public void setTexture(String texture) {\r\n        this.texture = texture;\r\n    }\r\n\r\n    public boolean hasTexture() {\r\n        return texture != null;\r\n    }\r\n\r\n    public String getTextureSignature() {\r\n        return textureSignature;\r\n    }\r\n\r\n    public void setTextureSignature(String textureSignature) {\r\n        this.textureSignature = textureSignature;\r\n    }\r\n\r\n    public boolean hasTextureSignature() {\r\n        return textureSignature != null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/nms/util/TradeOffer.java",
    "content": "package com.denizenscript.denizen.nms.util;\r\n\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic class TradeOffer {\r\n\r\n    private ItemStack product;\r\n    private ItemStack firstCost;\r\n    private ItemStack secondCost;\r\n    private boolean usedMaxTimes;\r\n    private int currentUses;\r\n    private int maxUses;\r\n\r\n    // 1.14\r\n    public boolean rewardExp;\r\n    public int xp;\r\n    public float priceMultiplier;\r\n\r\n    public TradeOffer(ItemStack product, ItemStack firstCost, ItemStack secondCost,\r\n                      boolean usedMaxTimes, int currentUses, int maxUses) {\r\n        this.product = product;\r\n        this.firstCost = firstCost;\r\n        this.secondCost = secondCost;\r\n        this.usedMaxTimes = usedMaxTimes;\r\n        this.currentUses = currentUses;\r\n        this.maxUses = maxUses;\r\n    }\r\n\r\n    public TradeOffer(ItemStack product, ItemStack firstCost, ItemStack secondCost,\r\n                      boolean usedMaxTimes, int currentUses, int maxUses,\r\n                      boolean rewardExp, int xp, float priceMultiplier) {\r\n        this(product, firstCost, secondCost, usedMaxTimes, currentUses, maxUses);\r\n        this.rewardExp = rewardExp;\r\n        this.xp = xp;\r\n        this.priceMultiplier = priceMultiplier;\r\n    }\r\n\r\n    public ItemStack getProduct() {\r\n        return product;\r\n    }\r\n\r\n    public void setProduct(ItemStack product) {\r\n        this.product = product;\r\n    }\r\n\r\n    public ItemStack getFirstCost() {\r\n        return firstCost;\r\n    }\r\n\r\n    public void setFirstCost(ItemStack firstCost) {\r\n        this.firstCost = firstCost;\r\n    }\r\n\r\n    public boolean hasSecondCost() {\r\n        return secondCost != null;\r\n    }\r\n\r\n    public ItemStack getSecondCost() {\r\n        return secondCost;\r\n    }\r\n\r\n    public void setSecondCost(ItemStack secondCost) {\r\n        this.secondCost = secondCost;\r\n    }\r\n\r\n    public boolean isUsedMaxTimes() {\r\n        return usedMaxTimes;\r\n    }\r\n\r\n    public int getCurrentUses() {\r\n        return currentUses;\r\n    }\r\n\r\n    public int getMaxUses() {\r\n        return maxUses;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/DenizenNPCHelper.java",
    "content": "package com.denizenscript.denizen.npc;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.events.entity.EntityDespawnScriptEvent;\nimport com.denizenscript.denizen.npc.actions.ActionHandler;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.utilities.depends.Depends;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport net.citizensnpcs.api.event.NPCDespawnEvent;\nimport net.citizensnpcs.api.event.NPCRemoveEvent;\nimport net.citizensnpcs.api.event.NPCSpawnEvent;\nimport net.citizensnpcs.api.npc.NPC;\nimport org.bukkit.Bukkit;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.inventory.InventoryType;\nimport org.bukkit.inventory.Inventory;\nimport org.bukkit.inventory.InventoryHolder;\n\nimport java.util.*;\n\npublic class DenizenNPCHelper implements Listener {\n\n    private ActionHandler actionHandler;\n\n    public DenizenNPCHelper() {\n        if (Depends.citizens != null) {\n            Bukkit.getPluginManager().registerEvents(this, Denizen.getInstance());\n        }\n        actionHandler = new ActionHandler();\n    }\n\n    /**\n     * Gets the currently loaded instance of the ActionHandler\n     *\n     * @return ActionHandler\n     */\n    public ActionHandler getActionHandler() {\n        return actionHandler;\n    }\n\n    /**\n     * Returns a InventoryTag object from the Inventory trait of a valid NPC.\n     *\n     * @param npc the Citizens NPC\n     * @return the NPC's InventoryTag\n     */\n    public static Inventory getInventory(NPC npc) {\n        if (npc == null) {\n            return null;\n        }\n        if (npc.isSpawned() && npc.getEntity() instanceof InventoryHolder) {\n            return ((InventoryHolder) npc.getEntity()).getInventory();\n        }\n        else {\n            try {\n                NPCTag npcTag = new NPCTag(npc);\n                Inventory inv = npcTag.getInventoryTrait().getInventoryView();\n                if (inv != null) {\n                    return inv;\n                }\n                else {\n                    // TODO: ???\n                    Inventory npcInventory = Bukkit.getServer().createInventory(npcTag, InventoryType.PLAYER);\n                    npcInventory.setContents(Arrays.copyOf(npcTag.getInventoryTrait().getContents(), npcInventory.getSize()));\n                    return npcInventory;\n                }\n            }\n            catch (Exception e) {\n                Debug.echoError(e);\n                return null;\n            }\n        }\n    }\n\n    // <--[action]\n    // @Actions\n    // spawn\n    // @Triggers when the NPC is spawned.\n    // This will fire whenever an NPC's chunk is loaded, or a spawn command is issued.\n    //\n    // @Context\n    // None\n    // -->\n\n    /**\n     * Fires the 'On Spawn:' action in an NPCs Assignment, if set.\n     *\n     * @param event NPCSpawnEvent\n     */\n    @EventHandler\n    public void onSpawn(NPCSpawnEvent event) {\n        if (event.getNPC() == null) {\n            Debug.echoError(\"Null NPC spawned!\");\n            return;\n        }\n        // On Spawn action\n        new NPCTag(event.getNPC()).action(\"spawn\", null);\n    }\n\n    // <--[action]\n    // @Actions\n    // despawn\n    // @Triggers when the NPC is despawned.\n    // This can be because a command was issued, or a chunk has been unloaded.\n    //\n    // @Context\n    // None\n    // -->\n\n    /**\n     * Fires a world script event and then NPC action when the NPC despawns.\n     *\n     * @param event NPCDespawnEvent\n     */\n    @EventHandler\n    public void despawn(NPCDespawnEvent event) {\n        NPCTag npc = new NPCTag(event.getNPC());\n        if (npc.isValid()) {\n            EntityDespawnScriptEvent.instance.entity = new EntityTag(event.getNPC().getEntity());\n            EntityDespawnScriptEvent.instance.cause = new ElementTag(\"CITIZENS\");\n            EntityDespawnScriptEvent.instance.fire(event);\n            npc.action(\"despawn\", null);\n        }\n    }\n\n    // <--[action]\n    // @Actions\n    // remove\n    // @Triggers when the NPC is removed.\n    //\n    // @Context\n    // None\n    // -->\n\n    /**\n     * Removes an NPC from the Registry when removed from Citizens.\n     *\n     * @param event NPCRemoveEvent\n     */\n    @EventHandler\n    public void onRemove(NPCRemoveEvent event) {\n        NPC npc = event.getNPC();\n        new NPCTag(npc).action(\"remove\", null);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/TraitRegistry.java",
    "content": "package com.denizenscript.denizen.npc;\r\n\r\nimport com.denizenscript.denizen.npc.traits.*;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.trait.TraitInfo;\r\n\r\npublic class TraitRegistry {\r\n\r\n    public static void registerMainTraits() {\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(AssignmentTrait.class).withName(\"assignment\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(ConstantsTrait.class).withName(\"constants\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(DenizenFlagsTrait.class).withName(\"denizen_flags\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(FishingTrait.class).withName(\"fishing\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(HealthTrait.class).withName(\"health\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(HungerTrait.class).withName(\"hunger\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(InvisibleTrait.class).withName(\"invisible\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(MirrorEquipmentTrait.class).withName(\"mirrorequipment\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(MirrorNameTrait.class).withName(\"mirrorname\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(MirrorTrait.class).withName(\"mirror\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(MobproxTrait.class).withName(\"mobprox\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(NicknameTrait.class).withName(\"nickname\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(PushableTrait.class).withName(\"pushable\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(SittingTrait.class).withName(\"sitting\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(SleepingTrait.class).withName(\"sleeping\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(SneakingTrait.class).withName(\"sneaking\"));\r\n        CitizensAPI.getTraitFactory().registerTrait(TraitInfo.create(TriggerTrait.class).withName(\"triggers\"));\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/actions/ActionHandler.java",
    "content": "package com.denizenscript.denizen.npc.actions;\r\n\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.AssignmentScriptContainer;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.queues.ContextSource;\r\nimport com.denizenscript.denizencore.scripts.queues.ScriptQueue;\r\nimport com.denizenscript.denizencore.scripts.queues.core.InstantQueue;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug.DebugElement;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class ActionHandler {\r\n\r\n    public ActionHandler() {\r\n    }\r\n\r\n    public ListTag doAction(String actionName, NPCTag npc, PlayerTag player, AssignmentScriptContainer assignment, Map<String, ObjectTag> context) {\r\n        if (context == null) {\r\n            context = new HashMap<>();\r\n        }\r\n        if (assignment == null) {\r\n            return null;\r\n        }\r\n        if (!assignment.containsScriptSection(\"actions.on \" + actionName)) {\r\n            return null;\r\n        }\r\n        boolean shouldDebug = Debug.shouldDebug(assignment);\r\n        if (shouldDebug) {\r\n            Debug.report(assignment, \"Action\", ArgumentHelper.debugObj(\"Type\", \"On \" + actionName), npc, assignment.getAsScriptArg(), player);\r\n        }\r\n        // Fetch script from Actions\r\n        List<ScriptEntry> script = assignment.getEntries(new BukkitScriptEntryData(player, npc), \"actions.on \" + actionName);\r\n        if (script.isEmpty()) {\r\n            return null;\r\n        }\r\n        if (shouldDebug) {\r\n            Debug.echoDebug(assignment, DebugElement.Header, \"Building action 'On \" + CoreUtilities.toUpperCase(actionName) + \"' for \" + npc.toString());\r\n        }\r\n        // Add entries and context to the queue\r\n        ScriptQueue queue = new InstantQueue(assignment.getName());\r\n        queue.addEntries(script);\r\n        ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n        src.contexts = context;\r\n        src.contexts.put(\"event_header\", new ElementTag(actionName));\r\n        queue.setContextSource(src);\r\n        // Start the queue!\r\n        queue.start();\r\n        return queue.determinations;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/speech/DenizenSpeechContext.java",
    "content": "package com.denizenscript.denizen.npc.speech;\r\n\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport net.citizensnpcs.api.ai.speech.SpeechContext;\r\n\r\npublic class DenizenSpeechContext extends SpeechContext {\r\n\r\n    private final ScriptEntry scriptEntry;\r\n    private final double chatRange;\r\n\r\n    public DenizenSpeechContext(String message, ScriptEntry scriptEntry, double chatRange) {\r\n        super(message);\r\n        this.scriptEntry = scriptEntry;\r\n        this.chatRange = chatRange;\r\n    }\r\n\r\n    public ScriptEntry getScriptEntry() {\r\n        return scriptEntry;\r\n    }\r\n\r\n    public boolean isBystandersEnabled() {\r\n        return chatRange >= 0;\r\n    }\r\n\r\n    public double getChatRange() {\r\n        return chatRange;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/AssignmentTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.events.bukkit.ScriptReloadEvent;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.scripts.containers.core.AssignmentScriptContainer;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.utilities.Settings;\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport net.citizensnpcs.api.persistence.Persist;\nimport net.citizensnpcs.api.trait.Trait;\nimport net.citizensnpcs.api.util.DataKey;\nimport org.bukkit.event.EventHandler;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\npublic class AssignmentTrait extends Trait {\n\n    @Persist(\"assignment\")\n    public String legacyAssignment = \"\";\n\n    @Persist(\"assignment_list\")\n    public ArrayList<String> assignments = new ArrayList<>();\n\n    public ArrayList<AssignmentScriptContainer> containerCache = new ArrayList<>();\n\n    public AssignmentTrait() {\n        super(\"assignment\");\n    }\n\n    /**\n     * Checks to see if the NPCs assignment is still a valid script on load of NPC.\n     */\n    @Override\n    public void load(DataKey key) {\n        if (legacyAssignment != null && !legacyAssignment.isEmpty()) {\n            assignments.add(CoreUtilities.toLowerCase(legacyAssignment));\n            legacyAssignment = \"\";\n        }\n        buildCache();\n        if (assignments.isEmpty()) {\n            Debug.echoError(\"NPC \" + npc.getId() + \" had assignment trait without any assignments? Removing.\");\n            npc.removeTrait(AssignmentTrait.class);\n            return;\n        }\n        // Fix legacy assignments that might not be lowercased\n        List<String> fixedAssignments = assignments.stream().map(CoreUtilities::toLowerCase).collect(Collectors.toList());\n        assignments.clear();\n        assignments.addAll(fixedAssignments);\n        npc.getOrAddTrait(ConstantsTrait.class).rebuildAssignmentConstants();\n    }\n\n    public void buildCache() {\n        containerCache.clear();\n        for (String assignment : assignments) {\n            AssignmentScriptContainer container = ScriptRegistry.getScriptContainer(assignment);\n            containerCache.add(container);\n            if (container == null) {\n                Debug.echoError(\"NPC \" + npc.getId() + \" has assignment '\" + assignment + \"' which does not exist.\");\n            }\n        }\n    }\n\n    @EventHandler\n    public void onReload(ScriptReloadEvent event) {\n        buildCache();\n    }\n\n    // <--[action]\n    // @Actions\n    // assignment\n    //\n    // @Triggers when the assignment script is added to an NPC.\n    //\n    // @Context\n    // None\n    //\n    // -->\n    public boolean addAssignmentScript(AssignmentScriptContainer script, PlayerTag player) {\n        String name = CoreUtilities.toLowerCase(script.getName());\n        if (assignments.contains(name)) {\n            return false;\n        }\n        assignments.add(name);\n        containerCache.add(script);\n        ensureDefaultTraits();\n        Denizen.getInstance().npcHelper.getActionHandler().doAction(\"assignment\", new NPCTag(npc), player, script, null);\n        return true;\n    }\n\n    // <--[action]\n    // @Actions\n    // remove assignment\n    //\n    // @Triggers when the assignment script is removed from an NPC.\n    //\n    // @Context\n    // None\n    //\n    // -->\n    public boolean removeAssignmentScript(String name, PlayerTag player) {\n        int index = assignments.indexOf(CoreUtilities.toLowerCase(name));\n        if (index == -1) {\n            return false;\n        }\n        assignments.remove(index);\n        AssignmentScriptContainer container = containerCache.remove(index);\n        if (container != null) {\n            Denizen.getInstance().npcHelper.getActionHandler().doAction(\"remove assignment\", new NPCTag(npc), player, container, null);\n        }\n        return true;\n    }\n\n    public void clearAssignments(PlayerTag player) {\n        for (String assign : new ArrayList<>(assignments)) {\n            removeAssignmentScript(assign, player);\n        }\n    }\n\n    public void checkAutoRemove() {\n        if (assignments.isEmpty()) {\n            npc.removeTrait(AssignmentTrait.class);\n        }\n    }\n\n    public void ensureDefaultTraits() {\n        npc.getOrAddTrait(TriggerTrait.class);\n        npc.getOrAddTrait(ConstantsTrait.class).rebuildAssignmentConstants();\n        if (Settings.healthTraitEnabledByDefault()) {\n            npc.getOrAddTrait(HealthTrait.class);\n        }\n    }\n\n    public boolean isAssigned(AssignmentScriptContainer container) {\n        return containerCache.contains(container);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/ConstantsTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.events.bukkit.ScriptReloadEvent;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.AssignmentScriptContainer;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport org.bukkit.event.EventHandler;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class ConstantsTrait extends Trait {\r\n\r\n    // Saved to C2 saves.yml\r\n    @Persist(value = \"\", collectionType = HashMap.class)\r\n    private Map<String, String> constants = new HashMap<>();\r\n\r\n    // Used internally\r\n    private Map<String, String> assignmentConstants = new HashMap<>();\r\n\r\n    public ConstantsTrait() {\r\n        super(\"constants\");\r\n    }\r\n\r\n    public String getConstant(String name) {\r\n        if (constants.containsKey(CoreUtilities.toLowerCase(name))) {\r\n            return TagManager.tag(constants.get(CoreUtilities.toLowerCase(name)), new BukkitTagContext(null, new NPCTag(npc), null, true, null));\r\n        }\r\n        else if (assignmentConstants.containsKey(CoreUtilities.toLowerCase(name))) {\r\n            return TagManager.tag(assignmentConstants.get(CoreUtilities.toLowerCase(name)), new BukkitTagContext(null, new NPCTag(npc), null, true, null));\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public void setConstant(String name, String value) {\r\n        constants.put(CoreUtilities.toLowerCase(name), value);\r\n    }\r\n\r\n    public void removeConstant(String name) {\r\n        constants.remove(CoreUtilities.toLowerCase(name));\r\n    }\r\n\r\n    public void rebuildAssignmentConstants() {\r\n        assignmentConstants.clear();\r\n        if (!npc.hasTrait(AssignmentTrait.class)) {\r\n            npc.removeTrait(ConstantsTrait.class);\r\n            return;\r\n        }\r\n        AssignmentTrait trait = npc.getOrAddTrait(AssignmentTrait.class);\r\n        for (AssignmentScriptContainer container : trait.containerCache) {\r\n            if (container != null) {\r\n                if (container.contains(\"default constants\", Map.class)) {\r\n                    for (StringHolder constant : container.getConfigurationSection(\"default constants\").getKeys(false)) {\r\n                        assignmentConstants.put(CoreUtilities.toLowerCase(constant.str), container.getString(\"default constants.\" + constant.str.toUpperCase(), \"\"));\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void onScriptsReload(ScriptReloadEvent event) {\r\n        rebuildAssignmentConstants();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/DenizenFlagsTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizencore.flags.SavableMapFlagTracker;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport com.google.common.collect.Iterators;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.persistence.PersistenceLoader;\r\nimport net.citizensnpcs.api.persistence.Persister;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport net.citizensnpcs.api.util.DataKey;\r\n\r\nimport java.util.Map;\r\n\r\npublic class DenizenFlagsTrait extends Trait {\r\n\r\n    @Persist(\"full_flag_data\")\r\n    public SavableMapFlagTracker fullFlagData = new SavableMapFlagTracker();\r\n\r\n\r\n    public static class MapTagFlagTrackerPersister implements Persister<SavableMapFlagTracker> {\r\n        @Override\r\n        public SavableMapFlagTracker create(DataKey dataKey) {\r\n            SavableMapFlagTracker toRet = new SavableMapFlagTracker();\r\n            for (DataKey key : dataKey.getSubKeys()) {\r\n                SavableMapFlagTracker.SaveOptimizedFlag flag = new SavableMapFlagTracker.SaveOptimizedFlag();\r\n                flag.string = key.getString(\"\");\r\n                flag.canExpire = flag.string.startsWith(\"map@\");\r\n                toRet.map.put(new StringHolder(key.name()), flag);\r\n            }\r\n            if (!CoreConfiguration.skipAllFlagCleanings) {\r\n                toRet.doTotalClean();\r\n            }\r\n            return toRet;\r\n        }\r\n\r\n        @Override\r\n        public void save(SavableMapFlagTracker o, DataKey dataKey) {\r\n            for (DataKey subkey : Iterators.toArray(dataKey.getSubKeys().iterator(), DataKey.class)) {\r\n                dataKey.removeKey(subkey.name());\r\n            }\r\n            for (Map.Entry<StringHolder, SavableMapFlagTracker.SaveOptimizedFlag> flag : o.map.entrySet()) {\r\n                dataKey.setString(flag.getKey().str, flag.getValue().getString());\r\n            }\r\n        }\r\n    }\r\n\r\n    static {\r\n        PersistenceLoader.registerPersistDelegate(SavableMapFlagTracker.class, MapTagFlagTrackerPersister.class);\r\n    }\r\n\r\n    public DenizenFlagsTrait() {\r\n        super(\"denizen_flags\");\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/FishingTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.FishingHelper;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport net.citizensnpcs.util.PlayerAnimation;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.entity.FishHook;\r\nimport org.bukkit.entity.Item;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\nimport org.bukkit.util.Vector;\r\n\r\npublic class FishingTrait extends Trait {\r\n\r\n    @Persist(\"fishing\")\r\n    public boolean fishing = false;\r\n    @Persist(\"catch type\")\r\n    public FishingHelper.CatchType catchType = FishingHelper.CatchType.NONE;\r\n\r\n    @Persist(\"fishing spot\")\r\n    public Location fishingLocation = null;\r\n\r\n    public FishHook fishHook = null;\r\n    public Item fish = null;\r\n\r\n    @Persist(\"catch chance\")\r\n    public int catchPercent = 65;\r\n\r\n    @Persist(\"reel tick rate\")\r\n    public int reelTickRate = 400;\r\n\r\n    @Persist(\"cast tick rate\")\r\n    public int castTickRate = 75;\r\n\r\n    int reelCount = 100;\r\n    int castCount = 0;\r\n\r\n    public boolean isCast = false;\r\n\r\n    @Override\r\n    public void onSpawn() {\r\n        isCast = false;\r\n    }\r\n\r\n    @Override\r\n    public void run() {\r\n        if (fish != null) {\r\n            if (fish.getLocation().distance(npc.getStoredLocation()) < 3) {\r\n                try {\r\n                    fish.remove();\r\n                }\r\n                catch (Exception e) {\r\n                }\r\n            }\r\n        }\r\n        if (!fishing) {\r\n            isCast = false;\r\n            return;\r\n        }\r\n        if (isCast) {\r\n            reelCount++;\r\n            if (reelCount >= reelTickRate) {\r\n                reel();\r\n                reelCount = 0;\r\n                castCount = 0;\r\n            }\r\n        }\r\n        else {\r\n            castCount++;\r\n            if (castCount >= castTickRate) {\r\n                cast();\r\n                castCount = 0;\r\n            }\r\n        }\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // start fishing\r\n    //\r\n    // @Triggers when the NPC starts fishing. See also <@link command fish>.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n\r\n    /**\r\n     * Makes the NPC fish at the specified location\r\n     * <p/>\r\n     * TODO Reimplement variance, so each cast doesn't land in the exact same spot.\r\n     *\r\n     * @param location the location to fish at\r\n     */\r\n    public void startFishing(Location location) {\r\n        new NPCTag(npc).action(\"start fishing\", null);\r\n        fishingLocation = location.clone();\r\n        cast();\r\n        fishing = true;\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // stop fishing\r\n    //\r\n    // @Triggers when the NPC stops fishing. See also <@link command fish>.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n\r\n    /**\r\n     * Makes the stop fishing.\r\n     */\r\n    public void stopFishing() {\r\n        new NPCTag(npc).action(\"stop fishing\", null);\r\n        reel();\r\n        reelCount = 100;\r\n        castCount = 0;\r\n        fishingLocation = null;\r\n        fishing = false;\r\n    }\r\n\r\n    public boolean scanForFishSpot(Location near, boolean horizontal) {\r\n        Block block = near.getBlock();\r\n        if (block.getType() == Material.WATER) {\r\n            fishingLocation = near.clone();\r\n            return true;\r\n        }\r\n        else if (block.getRelative(BlockFace.DOWN).getType() == Material.WATER) {\r\n            fishingLocation = near.clone().add(0, -1, 0);\r\n            return true;\r\n        }\r\n        else if (block.getRelative(BlockFace.DOWN).getRelative(BlockFace.DOWN).getType() == Material.WATER) {\r\n            fishingLocation = near.clone().add(0, -2, 0);\r\n            return true;\r\n        }\r\n        if (horizontal) {\r\n            return scanForFishSpot(near.clone().add(1, 0, 0), false)\r\n                    || scanForFishSpot(near.clone().add(-1, 0, 0), false)\r\n                    || scanForFishSpot(near.clone().add(0, 0, 1), false)\r\n                    || scanForFishSpot(near.clone().add(0, 0, -1), false);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void startFishing() {\r\n        fishing = true;\r\n        Location search = npc.getStoredLocation().clone();\r\n        Vector direction = npc.getStoredLocation().getDirection().clone();\r\n        if (direction.getY() > -0.1) {\r\n            direction.setY(-0.1);\r\n        }\r\n        for (int i = 0; i < 10; i++) {\r\n            search.add(direction.clone());\r\n            if (scanForFishSpot(search, true)) {\r\n                break;\r\n            }\r\n        }\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // cast fishing rod\r\n    //\r\n    // @Triggers when the NPC casts a fishing rod. See also <@link command fish>.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n    private void cast() {\r\n        new NPCTag(npc).action(\"cast fishing rod\", null);\r\n        if (fishingLocation == null || fishingLocation.getWorld() == null || !fishingLocation.getWorld().equals(npc.getEntity().getWorld())) {\r\n            Debug.echoError(\"Fishing location not found!\");\r\n            return;\r\n        }\r\n        isCast = true;\r\n        double v = 34;\r\n        double g = 20;\r\n        Location from = npc.getStoredLocation();\r\n        from = from.add(0, 0.33, 0);\r\n        Location to = fishingLocation;\r\n        Vector test = to.clone().subtract(from).toVector();\r\n        double elev = test.getY();\r\n        Double testAngle = launchAngle(from, to, v, elev, g);\r\n        if (testAngle == null) {\r\n            return;\r\n        }\r\n        double hangtime = hangtime(testAngle, v, elev, g);\r\n        Vector victor = to.clone().subtract(from).toVector();\r\n        double dist = Math.sqrt(Math.pow(victor.getX(), 2) + Math.pow(victor.getZ(), 2));\r\n        elev = victor.getY();\r\n        if (dist == 0) {\r\n            return;\r\n        }\r\n        Double launchAngle = launchAngle(from, to, v, elev, g);\r\n        if (launchAngle == null) {\r\n            return;\r\n        }\r\n        victor.setY(Math.tan(launchAngle) * dist);\r\n        victor = normalizeVector(victor);\r\n        v += 0.5 * Math.pow(hangtime, 2);\r\n        v += (CoreUtilities.getRandom().nextDouble() - 0.8) / 2;\r\n        victor = victor.multiply(v / 20.0);\r\n\r\n        if (npc.getEntity() instanceof Player) {\r\n            fishHook = NMSHandler.fishingHelper.spawnHook(from, (Player) npc.getEntity());\r\n            fishHook.setShooter((ProjectileSource) npc.getEntity());\r\n            fishHook.setVelocity(victor);\r\n            PlayerAnimation.ARM_SWING.play((Player) npc.getEntity());\r\n        }\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // reel in fishing rod\r\n    //\r\n    // @Triggers when the NPC reels in its fishing rod. See also <@link command fish>.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // catch fish\r\n    //\r\n    // @Triggers when the NPC catches a fish. See also <@link command fish>.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n    private void reel() {\r\n        isCast = false;\r\n        new NPCTag(npc).action(\"reel in fishing rod\", null);\r\n        int chance = (int) (Math.random() * 100);\r\n        if (catchPercent > chance && fishHook != null && catchType != FishingHelper.CatchType.NONE) {\r\n            try {\r\n                fish.remove();\r\n            }\r\n            catch (Exception e) {\r\n            }\r\n            Location location = fishHook.getLocation();\r\n            ItemStack result = NMSHandler.fishingHelper.getResult(fishHook, catchType);\r\n            if (result != null) {\r\n                fish = location.getWorld().dropItem(location, result);\r\n                Location npcLocation = npc.getStoredLocation();\r\n                double d5 = npcLocation.getX() - location.getX();\r\n                double d6 = npcLocation.getY() - location.getY();\r\n                double d7 = npcLocation.getZ() - location.getZ();\r\n                double d8 = Math.sqrt(d5 * d5 + d6 * d6 + d7 * d7);\r\n                double d9 = 0.1D;\r\n                fish.setVelocity(new Vector(d5 * d9, d6 * d9 + Math.sqrt(d8) * 0.08D, d7 * d9));\r\n            }\r\n            new NPCTag(npc).action(\"catch fish\", null);\r\n        }\r\n        if (fishHook != null && fishHook.isValid()) {\r\n            fishHook.remove();\r\n            fishHook = null;\r\n        }\r\n        if (npc.getEntity() instanceof Player) {\r\n            PlayerAnimation.ARM_SWING.play((Player) npc.getEntity());\r\n        }\r\n    }\r\n\r\n    public boolean isFishing() {\r\n        return fishing;\r\n    }\r\n\r\n    public FishingTrait() {\r\n        super(\"fishing\");\r\n    }\r\n\r\n    public static Double launchAngle(Location from, Location to, double v, double elev, double g) {\r\n        Vector victor = from.clone().subtract(to).toVector();\r\n        double dist = Math.sqrt(Math.pow(victor.getX(), 2) + Math.pow(victor.getZ(), 2));\r\n        double v2 = Math.pow(v, 2);\r\n        double v4 = Math.pow(v, 4);\r\n        double derp = g * (g * Math.pow(dist, 2) + 2 * elev * v2);\r\n\r\n        if (v4 < derp) {\r\n            return null;\r\n        }\r\n        else {\r\n            return Math.atan((v2 - Math.sqrt(v4 - derp)) / (g * dist));\r\n        }\r\n    }\r\n\r\n    public static double hangtime(double launchAngle, double v, double elev, double g) {\r\n        double a = v * Math.sin(launchAngle);\r\n        double b = -2 * g * elev;\r\n\r\n        if (Math.pow(a, 2) + b < 0) {\r\n            return 0;\r\n        }\r\n\r\n        return (a + Math.sqrt(Math.pow(a, 2) + b)) / g;\r\n    }\r\n\r\n    public static Vector normalizeVector(Vector victor) {\r\n        double mag = Math.sqrt(Math.pow(victor.getX(), 2) + Math.pow(victor.getY(), 2) + Math.pow(victor.getZ(), 2));\r\n        if (mag != 0) {\r\n            return victor.multiply(1 / mag);\r\n        }\r\n        return victor.multiply(0);\r\n    }\r\n\r\n    public void setCatchType(FishingHelper.CatchType catchType) {\r\n        this.catchType = catchType;\r\n    }\r\n\r\n    public void setCatchPercent(int catchPercent) {\r\n        this.catchPercent = catchPercent;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/HealthTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizen.utilities.Settings;\nimport com.denizenscript.denizen.tags.BukkitTagContext;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.tags.TagManager;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport net.citizensnpcs.api.CitizensAPI;\nimport net.citizensnpcs.api.event.DespawnReason;\nimport net.citizensnpcs.api.persistence.Persist;\nimport net.citizensnpcs.api.trait.Trait;\nimport org.bukkit.Bukkit;\nimport org.bukkit.Location;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.EventPriority;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.entity.EntityDamageEvent;\nimport org.bukkit.event.entity.EntityDeathEvent;\n\nimport java.util.UUID;\n\npublic class HealthTrait extends Trait implements Listener {\n\n    // <--[language]\n    // @name Health Trait\n    // @group NPC Traits\n    // @description\n    // By default, NPCs are invulnerable, unable to be damaged. If you want your NPC\n    // to be able to take damage, or use the left click as an interaction, it must\n    // have the health trait. The Health trait is automatically enabled if you set\n    // the damage trigger state to true.\n    //\n    // You can use the denizen vulnerable command to make your NPCs react to left\n    // click, but not take damage. - vulnerable state:false\n    //\n    // Enable Damage trigger via dScript: - trigger name:damage state:true\n    // Enable Health trait via dScript: - trait state:true health\n    // Enable Health trait via npc command: /npc health --set # (-r)\n    //\n    // Enable automatic respawn (default delay 300t): /npc health --respawndelay [delay as a duration]\n    // Set respawn location: - flag <npc> respawn_location:<location>\n    //\n    // Related Tags\n    // <@link tag EntityTag.health>\n    // <@link tag EntityTag.formatted_health>\n    // <@link tag EntityTag.health_max>\n    // <@link tag EntityTag.health_percentage>\n    // <@link tag NPCTag.has_trait[health]>\n    //\n    // Related Mechanisms\n    // <@link mechanism EntityTag.health>\n    // <@link mechanism EntityTag.max_health>\n    //\n    // Related Commands\n    // <@link command heal>\n    // <@link command health>\n    // <@link command vulnerable>\n    //\n    // Related Actions\n    // <@link action on damage>\n    // <@link action on damaged>\n    // <@link action on no damage trigger>\n    //\n    // -->\n\n    // Saved to the C2 saves.yml\n    @Persist(\"animatedeath\")\n    private boolean animatedeath = Settings.healthTraitAnimatedDeathEnabled();\n\n    @Persist(\"respawnondeath\")\n    private boolean respawn = Settings.healthTraitRespawnEnabled();\n\n    @Persist(\"respawndelayinseconds\")\n    private String respawnDelay = Settings.healthTraitRespawnDelay();\n\n    @Persist(\"respawnlocation\")\n    private String respawnLocation = \"<npc.flag[respawn_location].if_null[<npc.location>]>\";\n\n    @Persist(\"blockdrops\")\n    private boolean blockDrops = Settings.healthTraitBlockDrops();\n\n    // internal\n    private boolean dying = false;\n    private Location loc;\n    private UUID entityId = null;\n\n    public DurationTag getRespawnDelay() {\n        return DurationTag.valueOf(respawnDelay, CoreUtilities.basicContext);\n    }\n\n    public void setRespawnLocation(String string) {\n        respawnLocation = string;\n    }\n\n    public void setRespawnDelay(int seconds) {\n        respawnDelay = String.valueOf(seconds);\n    }\n\n    public void setRespawnDelay(String string) {\n        respawnDelay = string;\n    }\n\n    public String getRespawnLocationAsString() {\n        return respawnLocation;\n    }\n\n    public Location getRespawnLocation() {\n        TagContext context = new BukkitTagContext(null, new NPCTag(npc), null, false, null);\n        return LocationTag.valueOf(TagManager.tag(respawnLocation, context), context);\n    }\n\n    public void setRespawnable(boolean respawnable) {\n        respawn = respawnable;\n    }\n\n    public boolean isRespawnable() {\n        return respawn;\n    }\n\n    public void animateOnDeath(boolean animate) {\n        animatedeath = animate;\n    }\n\n    public boolean animatesOnDeath() {\n        return animatedeath;\n    }\n\n    public Integer void_watcher_task = null;\n\n    /**\n     * Listens for spawn of an NPC and updates its health with the max health\n     * information for this trait.\n     */\n    @Override\n    public void onSpawn() {\n        dying = false;\n        setHealth();\n\n        void_watcher_task = Bukkit.getScheduler().scheduleSyncRepeatingTask(Denizen.getInstance(), () -> {\n            if (!npc.isSpawned()) {\n                Bukkit.getScheduler().cancelTask(void_watcher_task);\n                return;\n            }\n            if (npc.getStoredLocation().getY() < -1000) {\n                npc.despawn(DespawnReason.DEATH);\n                if (respawn) {\n                    Location res = getRespawnLocation();\n                    if (res.getY() < 1) {\n                        res.setY(res.getWorld().getHighestBlockYAt(res.getBlockX(), res.getBlockZ()));\n                    }\n                    if (npc.isSpawned()) {\n                        npc.getEntity().teleport(res);\n                    }\n                    else {\n                        npc.spawn(res);\n                    }\n                }\n            }\n        }, 200, 200);\n\n    }\n\n    public HealthTrait() {\n        super(\"health\");\n    }\n\n    /**\n     * Gets the current health of this NPC.\n     *\n     * @return current health points\n     */\n    public double getHealth() {\n        if (!npc.isSpawned()) {\n            return 0;\n        }\n        else {\n            return ((LivingEntity) npc.getEntity()).getHealth();\n        }\n    }\n\n    /**\n     * Sets the maximum health for this NPC. Default max is 20.\n     *\n     * @param newMax new maximum health\n     */\n    public void setMaxhealth(int newMax) {\n        ((LivingEntity) npc.getEntity()).setMaxHealth(newMax);\n    }\n\n    /**\n     * Gets the maximum health for this NPC.\n     *\n     * @return maximum health\n     */\n    public double getMaxhealth() {\n        return ((LivingEntity) npc.getEntity()).getMaxHealth();\n    }\n\n    /**\n     * Heals the NPC.\n     *\n     * @param health number of health points to heal\n     */\n    public void heal(int health) {\n        setHealth(getHealth() + health);\n    }\n\n    /**\n     * Sets the NPCs health to maximum.\n     */\n    public void setHealth() {\n        setHealth(((LivingEntity) npc.getEntity()).getMaxHealth());\n    }\n\n    /**\n     * Sets the NPCs health to a specific amount.\n     *\n     * @param health total health points\n     */\n    public void setHealth(double health) {\n        if (npc.getEntity() != null) {\n            ((LivingEntity) npc.getEntity()).setHealth(health);\n        }\n    }\n\n    public void die() {\n        ((LivingEntity) npc.getEntity()).damage(((LivingEntity) npc.getEntity()).getHealth());\n    }\n\n    // Listen for deaths to clear drops\n    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\n    public void onDeath(EntityDeathEvent event) {\n\n        if (entityId == null || !event.getEntity().getUniqueId().equals(entityId)) {\n            return;\n        }\n\n        if (blockDrops) {\n            event.getDrops().clear();\n        }\n    }\n\n    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\n    public void onDamage(EntityDamageEvent event) {\n        // Check if the event pertains to this NPC\n        if (event.getEntity() != npc.getEntity() || dying) {\n            return;\n        }\n\n        // Make sure this is a killing blow\n        if (this.getHealth() - event.getFinalDamage() > 0) {\n            return;\n        }\n\n        dying = true;\n\n        // Save entityId for EntityDeath event\n        entityId = npc.getEntity().getUniqueId();\n\n        if (npc.getEntity() == null) {\n            return;\n        }\n\n        TagContext context = new BukkitTagContext(null, new NPCTag(npc), null, true, null);\n        loc = getRespawnLocation();// TODO: debug option?\n\n        if (loc == null) {\n            loc = npc.getStoredLocation();\n        }\n\n        if (animatedeath) {\n            // Cancel navigation to keep the NPC from damaging players\n            // while the death animation is being carried out.\n            npc.getNavigator().cancelNavigation();\n            // Reset health now to avoid the death from happening instantly\n            //setHealth();\n            // Play animation (TODO)\n            // playDeathAnimation(npc.getEntity());\n\n        }\n\n        //die();\n\n        if (respawn && (getRespawnDelay().getTicks() > 0)) {\n            Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(),\n                    () -> {\n                        if (CitizensAPI.getNPCRegistry().getById(npc.getId()) == null || npc.isSpawned()) {\n                            return;\n                        }\n                        else {\n                            npc.spawn(loc);\n                        }\n                    }, (getRespawnDelay().getTicks()));\n        }\n\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/HungerTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.events.bukkit.ExhaustedNPCEvent;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport net.citizensnpcs.api.ai.event.NavigationBeginEvent;\r\nimport net.citizensnpcs.api.ai.event.NavigationCancelEvent;\r\nimport net.citizensnpcs.api.ai.event.NavigationCompleteEvent;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class HungerTrait extends Trait implements Listener {\r\n\r\n    // Saved to the C2 saves.yml\r\n    @Persist(\"maxhunger\")\r\n    private double maxhunger = 20.0;\r\n    @Persist(\"currenthunger\")\r\n    private double currenthunger = 0.0;\r\n    @Persist(\"multiplier\")\r\n    private int multiplier = 1;\r\n    @Persist(\"allowexhaustion\")\r\n    private boolean allowexhaustion = false;\r\n\r\n    // Used internally\r\n    private boolean listening = false;\r\n    private Location location = null;\r\n    private int count = 0;\r\n\r\n    /**\r\n     * Watches the NPCs movement to calculate hunger loss. Loses 0.01 hunger points\r\n     * per block moved, unless a modifier is used.\r\n     */\r\n    @Override\r\n    public void run() {\r\n        if (!listening) {\r\n            return;\r\n        }\r\n        // We'll only actually calculate hunger-loss once per second\r\n        count++;\r\n        if (count >= 20) {\r\n            // Reset counter\r\n            count = 0;\r\n            double td = getDistance(npc.getStoredLocation());\r\n            if (td > 0) {\r\n                location = npc.getStoredLocation().clone();\r\n                currenthunger = currenthunger - (td * 0.01 * multiplier);\r\n            }\r\n        }\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // exhausted\r\n    //\r\n    // @Triggers when the NPC is exhausted (Requires the Hunger trait)\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n\r\n    /**\r\n     * Listens for the NPC to move so hunger-loss can be calculated.\r\n     * Cuts down on processing since loss is only calculated when moving.\r\n     * Also checks for exhaustion, if enabled. If a NPC is exhausted, that is,\r\n     * if currenthunger >= maxhunger, the NPC cannot move and a\r\n     * NPCExhaustedEvent and 'On Exhausted:' action will fire.\r\n     */\r\n    @EventHandler\r\n    public void onMove(NavigationBeginEvent event) {\r\n\r\n        // TODO: Check if NPC == this NPC?\r\n        if (allowexhaustion) {\r\n            if (isStarving()) {\r\n                // Create NPCExhaustedEvent, give chance for outside plugins to cancel.\r\n                ExhaustedNPCEvent e = new ExhaustedNPCEvent(npc);\r\n                Bukkit.getServer().getPluginManager().callEvent(e);\r\n\r\n                // If still exhausted, cancel navigation and fire 'On Exhausted:' action\r\n                if (!e.isCancelled()) {\r\n                    npc.getNavigator().cancelNavigation();\r\n                    new NPCTag(npc).action(\"exhausted\", null);\r\n\r\n                    // No need to progress any further.\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n\r\n        location = npc.getStoredLocation().clone();\r\n        listening = true;\r\n    }\r\n\r\n    /**\r\n     * Stops the listening process for hunger-loss since the NPC is no longer moving.\r\n     */\r\n    @EventHandler\r\n    public void onCancel(NavigationCancelEvent event) {\r\n        listening = false;\r\n    }\r\n\r\n    /**\r\n     * Stops the listening process for hunger-loss since the NPC is no longer moving.\r\n     */\r\n    @EventHandler\r\n    public void onCancel(NavigationCompleteEvent event) {\r\n        listening = false;\r\n    }\r\n\r\n    public HungerTrait() {\r\n        super(\"hunger\");\r\n    }\r\n\r\n    /**\r\n     * Gets the NPCs current hunger level. 0.00 = no hunger, NPC is satiated.\r\n     *\r\n     * @return current hunger level\r\n     */\r\n    public double getHunger() {\r\n        return currenthunger;\r\n    }\r\n\r\n    /**\r\n     * Gets the upper bounds of the hunger level. Default is 20.0. Can be set higher\r\n     * or lower to require more or less 'feeding'.\r\n     *\r\n     * @return max hunger level\r\n     */\r\n    public double getMaxHunger() {\r\n        return maxhunger;\r\n    }\r\n\r\n    /**\r\n     * Gets the percentage of hunger based on currenthunger and maxhunger. 100 = npc\r\n     * is starving. 0 = npc is satiated.\r\n     *\r\n     * @return hunger percentage\r\n     */\r\n    public int getHungerPercentage() {\r\n        return (int) ((int) currenthunger / maxhunger);\r\n    }\r\n\r\n    /**\r\n     * Gets the multiplier used to calculate hunger loss. Default is 1. Setting a higher value\r\n     * reduces hunger quicker.\r\n     *\r\n     * @return current hunger multiplier\r\n     */\r\n    public int getHungerMultiplier() {\r\n        return multiplier;\r\n    }\r\n\r\n    /**\r\n     * Sets the multiplier used to calculate hunger loss. Default is 1. Setting a higher value\r\n     * reduces hunger quicker. Lower value, in turn, makes hunger loss slower.\r\n     *\r\n     * @param multiplier new multiplier\r\n     */\r\n    public void setHungerMultiplier(int multiplier) {\r\n        this.multiplier = multiplier;\r\n    }\r\n\r\n    /**\r\n     * Sets the current hunger level.\r\n     *\r\n     * @param hunger new hunger level\r\n     */\r\n    public void setHunger(double hunger) {\r\n        if (currenthunger > maxhunger) {\r\n            currenthunger = maxhunger;\r\n        }\r\n        else {\r\n            currenthunger = hunger;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * \"Feeds\" the NPC. The value used will reduce the total currenthunger value.\r\n     *\r\n     * @param hunger amount of hunger-points to reduce currenthunger by\r\n     */\r\n    public void feed(double hunger) {\r\n        currenthunger = currenthunger - hunger;\r\n        if (currenthunger < 0) {\r\n            currenthunger = 0;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Sets the max hunger value. Once hunger reaches this level the NPC is starving and\r\n     * may not be able to move. This method does not change the NPC's currenthunger.\r\n     *\r\n     * @param hunger new max hunger value\r\n     */\r\n    public void setMaxhunger(double hunger) {\r\n        maxhunger = hunger;\r\n    }\r\n\r\n    /**\r\n     * Checks to see if the NPC is starving. If currenthunger >= maxhunger, the NPC is starving.\r\n     *\r\n     * @return true if NPC is starving\r\n     */\r\n    public boolean isStarving() {\r\n        return currenthunger >= maxhunger;\r\n    }\r\n\r\n    /**\r\n     * Checks to see if the NPC is hungry. If currenthunger is 10% or more of maxhunger,\r\n     * the NPC is hungry. A NPC that is starving is also hungry.\r\n     *\r\n     * @return true if the NPC is hungry\r\n     */\r\n    public boolean isHungry() {\r\n        return currenthunger > (maxhunger / 10);\r\n    }\r\n\r\n    // Used internally\r\n    private double getDistance(Location location) {\r\n        if (!npc.getEntity().getWorld().equals(location.getWorld())) {\r\n            // World change, update location\r\n            this.location = npc.getStoredLocation();\r\n            return 0;\r\n        }\r\n        return location.distance(this.location);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/InvisibleTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport net.citizensnpcs.trait.ArmorStandTrait;\r\nimport net.citizensnpcs.util.NMS;\r\nimport org.bukkit.entity.ArmorStand;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.potion.PotionEffect;\r\nimport org.bukkit.potion.PotionEffectType;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\npublic class InvisibleTrait extends Trait implements Listener {\r\n\r\n    // <--[language]\r\n    // @name Invisible Trait\r\n    // @group NPC Traits\r\n    // @description\r\n    // The invisible trait will allow a NPC to remain invisible, even after a server restart.\r\n    // It permanently applies the invisible potion effect.\r\n    // Use '/npc invisible' or the 'invisible' script command to toggle this trait.\r\n    //\r\n    // Note that player-type NPCs must have '/npc playerlist' toggled to be turned invisible.\r\n    // Once invisible, the player-type NPCs can be taken off the playerlist.\r\n    // This only applies specifically to player-type NPCs.\r\n    // Playerlist will be enabled automatically if not set in advance, but not automatically removed.\r\n    // -->\r\n\r\n    @Persist(\"\")\r\n    private boolean invisible = true;\r\n\r\n    private static PotionEffect invis = new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 1, false, false);\r\n\r\n    public InvisibleTrait() {\r\n        super(\"invisible\");\r\n    }\r\n\r\n    public void setInvisible(boolean invisible) {\r\n        this.invisible = invisible;\r\n        if (npc.isSpawned() && npc.getEntity() instanceof LivingEntity) {\r\n            setInvisible((LivingEntity) npc.getEntity(), npc, invisible);\r\n        }\r\n    }\r\n\r\n    public static void setInvisible(LivingEntity ent, NPC npc, boolean invisible) {\r\n        if (invisible) {\r\n            setInvisible(ent, npc);\r\n        }\r\n        else {\r\n            setVisible(ent, npc);\r\n        }\r\n    }\r\n\r\n    public static void setVisible(LivingEntity ent, NPC npc) {\r\n        if (ent.hasPotionEffect(PotionEffectType.INVISIBILITY)) {\r\n            ent.removePotionEffect(PotionEffectType.INVISIBILITY);\r\n        }\r\n        if (ent.getType() == EntityType.ARMOR_STAND) {\r\n            ((ArmorStand) ent).setVisible(true);\r\n            if (npc != null) {\r\n                npc.getOrAddTrait(ArmorStandTrait.class).setVisible(true);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void setInvisible(LivingEntity ent, NPC npc) {\r\n        // Apply NPC Playerlist if necessary\r\n        if (npc != null && ent.getType() == EntityType.PLAYER) {\r\n            npc.data().setPersistent(\"removefromplayerlist\", false);\r\n            NMS.addOrRemoveFromPlayerList(ent, false);\r\n        }\r\n        if (ent.getType() == EntityType.ARMOR_STAND) {\r\n            ((ArmorStand) ent).setVisible(false);\r\n            if (npc != null) {\r\n                npc.getOrAddTrait(ArmorStandTrait.class).setVisible(false);\r\n            }\r\n        }\r\n        else {\r\n            invis.apply(ent);\r\n        }\r\n    }\r\n\r\n    private void setInvisible() {\r\n        if (invisible && npc.isSpawned() && npc.getEntity() instanceof LivingEntity) {\r\n            setInvisible((LivingEntity) npc.getEntity(), npc);\r\n        }\r\n    }\r\n\r\n    public boolean isInvisible() {\r\n        return invisible;\r\n    }\r\n\r\n    @Override\r\n    public void onSpawn() {\r\n        if (invisible) {\r\n            setInvisible();\r\n            // Workaround bug in Citizens on Paper servers - NPCs don't seem to actually spawn at startup til several ticks *after* the spawn event (2022/03/12)\r\n            if (!npc.isSpawned()) {\r\n                new BukkitRunnable() {\r\n                    int ticks = 0;\r\n                    @Override\r\n                    public void run() {\r\n                        if (ticks++ > 80) {\r\n                            return;\r\n                        }\r\n                        if (npc.isSpawned()) {\r\n                            setInvisible();\r\n                            cancel();\r\n                        }\r\n                    }\r\n                }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n            }\r\n        }\r\n    }\r\n\r\n    public boolean toggle() {\r\n        setInvisible(!invisible);\r\n        return invisible;\r\n    }\r\n\r\n    @Override\r\n    public void onRemove() {\r\n        setInvisible(false);\r\n    }\r\n\r\n    @Override\r\n    public void onAttach() {\r\n        setInvisible(invisible);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/MirrorEquipmentTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\r\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.block.BlockDispenseArmorEvent;\r\nimport org.bukkit.event.inventory.InventoryClickEvent;\r\nimport org.bukkit.event.player.PlayerInteractEvent;\r\nimport org.bukkit.inventory.EntityEquipment;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.UUID;\r\n\r\npublic class MirrorEquipmentTrait extends Trait {\r\n\r\n    @Persist(\"\")\r\n    public boolean mirror = true;\r\n\r\n    public UUID mirroredUUID = null;\r\n\r\n    public MirrorEquipmentTrait() {\r\n        super(\"mirrorequipment\");\r\n    }\r\n\r\n    public static class MirrorOverride extends FakeEquipCommand.EquipmentOverride {\r\n\r\n        @Override\r\n        public FakeEquipCommand.EquipmentOverride getVariantFor(Player player) {\r\n            FakeEquipCommand.EquipmentOverride result = new FakeEquipCommand.EquipmentOverride();\r\n            EntityEquipment playerEquip = player.getEquipment();\r\n            result.hand = new ItemTag(playerEquip.getItemInMainHand());\r\n            result.offhand = new ItemTag(playerEquip.getItemInOffHand());\r\n            result.head = new ItemTag(playerEquip.getHelmet());\r\n            result.chest = new ItemTag(playerEquip.getChestplate());\r\n            result.legs = new ItemTag(playerEquip.getLeggings());\r\n            result.boots = new ItemTag(playerEquip.getBoots());\r\n            return result;\r\n        }\r\n    }\r\n\r\n    public static MirrorOverride overrideInstance = new MirrorOverride();\r\n\r\n    public void resend() {\r\n        for (Player player : NMSHandler.entityHelper.getPlayersThatSee(npc.getEntity())) {\r\n            NMSHandler.packetHelper.resetEquipment(player, (LivingEntity) npc.getEntity());\r\n        }\r\n    }\r\n\r\n    public void resendIfNeeded(Player player) {\r\n        if (player == null || !mirror || !npc.isSpawned()) {\r\n            return;\r\n        }\r\n        if (!player.getWorld().equals(npc.getEntity().getWorld())) {\r\n            return;\r\n        }\r\n        if (player.getLocation().distanceSquared(npc.getStoredLocation()) > 100 * 100) {\r\n            return;\r\n        }\r\n        if (!NMSHandler.entityHelper.getPlayersThatSee(npc.getEntity()).contains(player)) {\r\n            return;\r\n        }\r\n        NMSHandler.packetHelper.resetEquipment(player, (LivingEntity) npc.getEntity());\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerInventoryClick(InventoryClickEvent event) {\r\n        resendIfNeeded((Player) event.getWhoClicked());\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerInteract(PlayerInteractEvent event) {\r\n        resendIfNeeded(event.getPlayer());\r\n    }\r\n\r\n    @EventHandler\r\n    public void onArmorDispense(BlockDispenseArmorEvent event) {\r\n        if (event.getTargetEntity() instanceof Player) {\r\n            resendIfNeeded((Player) event.getTargetEntity());\r\n        }\r\n    }\r\n\r\n    public void mirrorOn() {\r\n        NetworkInterceptHelper.enable();\r\n        if (!npc.isSpawned()) {\r\n            return;\r\n        }\r\n        if (!(npc.getEntity() instanceof LivingEntity)) {\r\n            mirrorOff();\r\n            return;\r\n        }\r\n        mirroredUUID = npc.getEntity().getUniqueId();\r\n        HashMap<UUID, FakeEquipCommand.EquipmentOverride> mapping = FakeEquipCommand.overrides.computeIfAbsent(null, k -> new HashMap<>());\r\n        mapping.put(mirroredUUID, overrideInstance);\r\n    }\r\n\r\n    public void mirrorOff() {\r\n        if (mirroredUUID == null) {\r\n            return;\r\n        }\r\n        HashMap<UUID, FakeEquipCommand.EquipmentOverride> mapping = FakeEquipCommand.overrides.get(null);\r\n        if (mapping == null) {\r\n            return;\r\n        }\r\n        if (mapping.remove(mirroredUUID) != null && npc.isSpawned()) {\r\n            resend();\r\n        }\r\n    }\r\n\r\n    public void enableMirror() {\r\n        mirror = true;\r\n        mirrorOn();\r\n        if (npc.isSpawned()) {\r\n            resend();\r\n        }\r\n    }\r\n\r\n    public void disableMirror() {\r\n        mirror = false;\r\n        mirrorOff();\r\n        if (RenameCommand.customNames.remove(mirroredUUID) != null && npc.isSpawned()) {\r\n            resend();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onSpawn() {\r\n        if (mirror) {\r\n            mirrorOn();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onRemove() {\r\n        if (mirror) {\r\n            mirrorOff();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onAttach() {\r\n        if (mirror) {\r\n            mirrorOn();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/MirrorNameTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport net.citizensnpcs.api.event.DespawnReason;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.UUID;\r\n\r\npublic class MirrorNameTrait extends Trait {\r\n\r\n    @Persist(\"\")\r\n    public boolean mirror = true;\r\n\r\n    public UUID mirroredUUID = null;\r\n\r\n    public MirrorNameTrait() {\r\n        super(\"mirrorname\");\r\n    }\r\n\r\n    public void respawn() {\r\n        if (!npc.isSpawned() || npc.getEntity().getType() != EntityType.PLAYER) {\r\n            return;\r\n        }\r\n        Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> {\r\n            if (npc.isSpawned()) {\r\n                Location loc = npc.getStoredLocation().clone();\r\n                npc.despawn(DespawnReason.PENDING_RESPAWN);\r\n                Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> {\r\n                    npc.spawn(loc);\r\n                });\r\n            }\r\n        });\r\n    }\r\n\r\n    public void mirrorOn() {\r\n        NetworkInterceptHelper.enable();\r\n        if (!npc.isSpawned()) {\r\n            return;\r\n        }\r\n        mirroredUUID = npc.getEntity().getUniqueId();\r\n        RenameCommand.RenameData renamer = new RenameCommand.RenameData();\r\n        renamer.nameFunction = Player::getName;\r\n        RenameCommand.addDynamicRename(npc.getEntity(), null, renamer);\r\n    }\r\n\r\n    public void mirrorOff() {\r\n        if (mirroredUUID == null) {\r\n            return;\r\n        }\r\n        if (RenameCommand.customNames.remove(mirroredUUID) != null && npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) {\r\n            respawn();\r\n        }\r\n    }\r\n\r\n    public void enableMirror() {\r\n        mirror = true;\r\n        mirrorOn();\r\n        if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) {\r\n            respawn();\r\n        }\r\n    }\r\n\r\n    public void disableMirror() {\r\n        mirror = false;\r\n        mirrorOff();\r\n    }\r\n\r\n    @Override\r\n    public void onSpawn() {\r\n        if (mirror) {\r\n            mirrorOn();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onRemove() {\r\n        if (mirror) {\r\n            mirrorOff();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onAttach() {\r\n        if (mirror) {\r\n            mirrorOn();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/MirrorTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport net.citizensnpcs.api.event.DespawnReason;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\n\r\nimport java.util.UUID;\r\n\r\npublic class MirrorTrait extends Trait {\r\n\r\n    @Persist(\"\")\r\n    public boolean mirror = true;\r\n\r\n    public MirrorTrait() {\r\n        super(\"mirror\");\r\n    }\r\n\r\n    public void respawn() {\r\n        Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> {\r\n            if (npc.isSpawned()) {\r\n                Location loc = npc.getStoredLocation().clone();\r\n                npc.despawn(DespawnReason.PENDING_RESPAWN);\r\n                Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> {\r\n                    npc.spawn(loc);\r\n                });\r\n            }\r\n        });\r\n    }\r\n\r\n    public void mirrorOn() {\r\n        NetworkInterceptHelper.enable();\r\n        UUID uuid = npc.getMinecraftUniqueId();\r\n        if (!ProfileEditor.mirrorUUIDs.contains(uuid)) {\r\n            ProfileEditor.mirrorUUIDs.add(uuid);\r\n            respawn();\r\n        }\r\n    }\r\n\r\n    public void mirrorOff() {\r\n        UUID uuid = npc.getMinecraftUniqueId();\r\n        if (ProfileEditor.mirrorUUIDs.contains(uuid)) {\r\n            ProfileEditor.mirrorUUIDs.remove(uuid);\r\n            respawn();\r\n        }\r\n    }\r\n\r\n    public void enableMirror() {\r\n        mirror = true;\r\n        mirrorOn();\r\n    }\r\n\r\n    public void disableMirror() {\r\n        mirror = false;\r\n        mirrorOff();\r\n    }\r\n\r\n    @Override\r\n    public void onSpawn() {\r\n        if (mirror) {\r\n            mirrorOn();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onRemove() {\r\n        if (mirror) {\r\n            mirrorOff();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onAttach() {\r\n        if (mirror) {\r\n            mirrorOn();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/MobproxTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport net.citizensnpcs.api.event.NPCTraitCommandAttachEvent;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\n// TODO: Documenting language docs\r\n\r\npublic class MobproxTrait extends Trait {\r\n\r\n    public MobproxTrait() {\r\n        super(\"mobprox\");\r\n    }\r\n\r\n    int checkTimer = 0;\r\n    int timerBounce = 0;\r\n    LivingEntity liveEnt;\r\n    NPCTag dnpc;\r\n    List<Entity> inrange = new ArrayList<>();\r\n\r\n    @Override\r\n    public void run() {\r\n        checkTimer++;\r\n        if (checkTimer == 10) {\r\n            checkTimer = 0;\r\n            timerBounce++;\r\n            if (timerBounce >= getTimer()) {\r\n                timerBounce = 0;\r\n                if (getNPC().isSpawned()) {\r\n                    int range = getRange();\r\n                    boolean acceptnpc = acceptNpcs();\r\n                    List<Entity> nearby = liveEnt.getNearbyEntities(range, range, range);\r\n                    List<Entity> removeme = new ArrayList<>(inrange);\r\n                    for (Entity ent : nearby) {\r\n                        if (ent instanceof LivingEntity && (!(ent instanceof Player) || EntityTag.isCitizensNPC(ent))\r\n                                && (acceptnpc || (!EntityTag.isCitizensNPC(ent)))) {\r\n                            removeme.remove(ent);\r\n                            if (!inrange.contains(ent)) {\r\n                                inrange.add(ent);\r\n                                callAction(\"enter\", ent);\r\n                            }\r\n                            else {\r\n                                callAction(\"move\", ent);\r\n                            }\r\n                        }\r\n                    }\r\n                    for (Entity ent : removeme) {\r\n                        inrange.remove(ent);\r\n                        callAction(\"exit\", ent);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // mob enter proximity\r\n    // <entity> enter proximity\r\n    //\r\n    // @Triggers when a mob enters the proximity of the NPC (Requires MobProx trait).\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the mob that entered the proximity\r\n    //\r\n    // -->\r\n    // <--[action]\r\n    // @Actions\r\n    // mob exit proximity\r\n    // <entity> exit proximity\r\n    //\r\n    // @Triggers when a mob exits the proximity of the NPC (Requires MobProx trait).\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the mob that exited the proximity\r\n    //\r\n    // -->\r\n    // <--[action]\r\n    // @Actions\r\n    // mob move proximity\r\n    // <entity> move proximity\r\n    //\r\n    // @Triggers when a mob moves in the proximity of the NPC (Requires MobProx trait).\r\n    // (Fires at a rate of specified by the 'mobprox_timer' flag, default of 2 seconds)\r\n    //\r\n    // @Context\r\n    // <context.entity> returns the mob that entered the proximity\r\n    //\r\n    // -->\r\n    private void callAction(String act, Entity ent) {\r\n        Map<String, ObjectTag> context = new HashMap<>();\r\n        context.put(\"entity\", new EntityTag(ent).getDenizenObject());\r\n        dnpc.action(\"mob \" + act + \" proximity\", null, context);\r\n        dnpc.action(ent.getType().name() + \" \" + act + \" proximity\", null, context);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onTraitAttachEvent(NPCTraitCommandAttachEvent event) {\r\n        if (!event.getTraitClass().equals(MobproxTrait.class)) {\r\n            return;\r\n        }\r\n        if (event.getNPC() != getNPC()) {\r\n            return;\r\n        }\r\n        onSpawn();\r\n        if (!dnpc.getCitizen().hasTrait(AssignmentTrait.class)) {\r\n            event.getCommandSender().sendMessage(ChatColor.RED + \"Warning: This NPC doesn't have a script assigned! Mobprox only works with scripted Denizen NPCs!\");\r\n        }\r\n    }\r\n\r\n    public int getRange() {\r\n        // TODO: Make this not flag based.\r\n        ObjectTag range = dnpc.getFlagTracker().getFlagValue(\"mobprox_range\");\r\n        if (range == null) {\r\n            return 10;\r\n        }\r\n        return range.asElement().asInt();\r\n    }\r\n\r\n    public int getTimer() {\r\n        ObjectTag range = dnpc.getFlagTracker().getFlagValue(\"mobprox_timer\");\r\n        if (range == null) {\r\n            return 4;\r\n        }\r\n        return range.asElement().asInt();\r\n    }\r\n\r\n    public boolean acceptNpcs() {\r\n        ObjectTag range = dnpc.getFlagTracker().getFlagValue(\"mobprox_acceptnpcs\");\r\n        if (range == null) {\r\n            return false;\r\n        }\r\n        return range.asElement().asBoolean();\r\n    }\r\n\r\n    @Override\r\n    public void onSpawn() {\r\n        liveEnt = (LivingEntity) getNPC().getEntity();\r\n        dnpc = new NPCTag(getNPC());\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/NicknameTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.event.Listener;\r\n\r\n/**\r\n * <p>Adds the ability to 'nickname' an NPC. This is meant to extend the NPCs real\r\n * name to perhaps add more description. Similar to a Player's 'Display Name', but better.\r\n * Inside Denizen, Nicknames can be utilized containing Replaceable TAGs. Outside Denizen,\r\n * the methods contained in this Trait can be used to get, set, and remove nicknames.</p>\r\n * <p/>\r\n * <p>Nicknames should not used as a static reference to an NPC because of the\r\n * dynamic nature of the Trait. Each time the trait is asked for a nickname, tags are\r\n * replaced. This allows for nicknames to use FLAGs and other dynamically changing\r\n * TAGs and have the linked information updated live.</p>\r\n * <p/>\r\n * <p>Though not in this Trait class, Denizen also provides some Replaceable TAGs\r\n * for getting a NPCs nickname. Use <</p>\r\n */\r\npublic class NicknameTrait extends Trait implements Listener {\r\n\r\n    @Persist(\"\")\r\n    private String nickname = null;\r\n\r\n    public NicknameTrait() {\r\n        super(\"nickname\");\r\n    }\r\n\r\n    /**\r\n     * Sets the nickname of this NPC. When setting, dScript TAGS\r\n     * can be used. This included dScript color codes.\r\n     *\r\n     * @param nickName the new nickname for this NPC\r\n     */\r\n    public void setNickname(String nickName) {\r\n        this.nickname = nickName;\r\n    }\r\n\r\n    /**\r\n     * Gets the current nickname of this NPC. This may include color codes.\r\n     * Note: To strip color codes, use {@link #getUncoloredNickname()}. If\r\n     * this NPC does not have a nickname, its NPC name is returned instead.\r\n     *\r\n     * @return the nickname for this NPC\r\n     */\r\n    public String getNickname() {\r\n        if (nickname == null || nickname.equals(\"\")) {\r\n            return npc.getName();\r\n        }\r\n        else {\r\n            return TagManager.tag(nickname, // TODO: debug option?\r\n                    new BukkitTagContext(null, new NPCTag(npc), null, true, null));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Gets the current nickname of this NPC and strips out all text colors.\r\n     * To get the colored nickname use {@link #getNickname()}.\r\n     *\r\n     * @return The uncolored nickname for this NPC\r\n     */\r\n    public String getUncoloredNickname() {\r\n        return ChatColor.stripColor(getNickname());\r\n    }\r\n\r\n    /**\r\n     * Removes the current nickname from the NPC.\r\n     */\r\n    public void removeNickname() {\r\n        nickname = null;\r\n    }\r\n\r\n    /**\r\n     * Checks if the NPC has a nickname set.\r\n     *\r\n     * @return true if NPC has a nickname\r\n     */\r\n    public boolean hasNickname() {\r\n        return (nickname != null);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/PushableTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.citizensnpcs.api.ai.event.NavigationCompleteEvent;\r\nimport net.citizensnpcs.api.event.NPCPushEvent;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport net.citizensnpcs.util.NMS;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\n// <--[language]\r\n// @name Pushable Trait\r\n// @group NPC Traits\r\n// @description\r\n// By default, NPCs created will allow players to 'noclip' them, or go right through. This is to\r\n// avoid NPCs moving from their set location, but often times, this behavior may be undesired.\r\n// The pushable trait allows NPCs to move when collided with, and optionally return to their\r\n// original location after a specified amount of time.\r\n//\r\n// To enable the trait, use the '/npc pushable' command on any selected NPC. Once the trait is\r\n// enabled, the '-r' option can be used to toggle returnable, and the '--delay #' option can be\r\n// used to specify the number of seconds before the npc returns.\r\n//\r\n// Care should be taken when allowing NPCs to be pushable. Allowing NPCs to be pushed around\r\n// complex structures can result in stuck NPCs. If the NPC is stuck, it may not return. Keeping\r\n// a small delay, in situations like this, can be a good trade-off. Typically the lower the\r\n// delay, the shorter distance a Player is able to push the NPC. The default delay is 2 seconds.\r\n//\r\n// The pushable trait also implements some actions that can be used in assignment scripts.\r\n// This includes 'on push' and 'on push return'.\r\n\r\n// -->\r\n\r\npublic class PushableTrait extends Trait implements Listener {\r\n\r\n    // Saved to the C2 saves.yml\r\n    @Persist(\"toggle\")\r\n    private boolean pushable = true;\r\n    @Persist(\"returnable\")\r\n    private boolean returnable = false;\r\n    @Persist(\"delay\")\r\n    private int delay = 2;\r\n\r\n    // Used internally\r\n    private boolean pushed = false;\r\n    private Location returnLocation = null;\r\n    private long pushedTimer = 0;\r\n\r\n    public PushableTrait() {\r\n        super(\"pushable\");\r\n    }\r\n\r\n    /**\r\n     * Gets the delay, as set by {@link #setDelay(int)}.\r\n     *\r\n     * @return delay, in seconds\r\n     */\r\n    public int getDelay() {\r\n        return delay;\r\n    }\r\n\r\n    /**\r\n     * Checks if this NPCs pushable setting is toggled.\r\n     *\r\n     * @return true if pushable\r\n     */\r\n    public boolean isPushable() {\r\n        return pushable;\r\n    }\r\n\r\n    /**\r\n     * Checks if this NPC is returnable when pushed.\r\n     * Note: Does not take into account whether the NPC\r\n     * is pushable, use {@link #isPushable()}\r\n     *\r\n     * @return true if returnable\r\n     */\r\n    public boolean isReturnable() {\r\n        return returnable;\r\n    }\r\n\r\n    /**\r\n     * Sets the delay, in seconds, in which the NPC will\r\n     * return to its position after being pushed.\r\n     * Note: Must be pushable.\r\n     *\r\n     * @param delay time in seconds to return after being pushed\r\n     */\r\n    public void setDelay(int delay) {\r\n        this.delay = delay;\r\n    }\r\n\r\n    /**\r\n     * Indicates that the NPC should be pushable. By default,\r\n     * C2 NPCs are not pushable.\r\n     *\r\n     * @param pushable whether the NPC should be pushable\r\n     */\r\n    public void setPushable(boolean pushable) {\r\n        this.pushable = pushable;\r\n    }\r\n\r\n    /**\r\n     * Indicates that the NPC should return to its location\r\n     * after being pushed. Takes into account a delay which\r\n     * can be set with {@link #setDelay(int)} and\r\n     * checked with {@link #getDelay()}}.\r\n     *\r\n     * @param returnable whether the NPC can return\r\n     */\r\n    public void setReturnable(boolean returnable) {\r\n        this.returnable = returnable;\r\n    }\r\n\r\n    /**\r\n     * Toggles the NPCs current pushable setting.\r\n     *\r\n     * @return {@link #isPushable()} after setting\r\n     */\r\n    public boolean toggle() {\r\n        pushable = !pushable;\r\n        if (!pushable) {\r\n            returnable = false;\r\n        }\r\n        return pushable;\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // push\r\n    //\r\n    // @Triggers when the NPC is pushed by a player\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n\r\n    /**\r\n     * Fires an 'On Push:' action upon being pushed.\r\n     */\r\n    @EventHandler\r\n    public void NPCPush(NPCPushEvent event) {\r\n        if (event.getNPC() == npc && pushable) {\r\n            event.setCancelled(false);\r\n            // On Push action / Push Trigger\r\n            if (CoreUtilities.monotonicMillis() > pushedTimer) {\r\n                // Get pusher\r\n                Player pusher = null;\r\n                for (Entity le : event.getNPC().getEntity().getNearbyEntities(1, 1, 1)) {\r\n                    if (le instanceof Player) {\r\n                        pusher = (Player) le;\r\n                    }\r\n                }\r\n                if (pusher != null) {\r\n                    new NPCTag(npc).action(\"push\", PlayerTag.mirrorBukkitPlayer(pusher));\r\n                    pushedTimer = CoreUtilities.monotonicMillis() + ((long) delay * 1000L);\r\n                }\r\n            } // End push action\r\n            if (!pushed && returnable) {\r\n                pushed = true;\r\n                returnLocation = npc.getStoredLocation().clone();\r\n                Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(\r\n                        Denizen.getInstance(), () -> {\r\n                            if (npc.isSpawned()) {\r\n                                navigateBack();\r\n                            }\r\n                        }, delay * 20L);\r\n            }\r\n        }\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // push return\r\n    //\r\n    // @Triggers when the NPC returns to its center after being pushed by a player.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n\r\n    /**\r\n     * Fires a 'On Push Return:' action upon return after being pushed.\r\n     */\r\n    @EventHandler\r\n    public void NPCCompleteDestination(NavigationCompleteEvent event) {\r\n        if (event.getNPC() == npc && pushed) {\r\n            Entity npcEntity = npc.getEntity();\r\n            Location location = npcEntity.getLocation();\r\n            location.setYaw(returnLocation.getYaw());\r\n            location.setPitch(returnLocation.getPitch());\r\n            NMS.setHeadYaw(npcEntity, returnLocation.getYaw());\r\n            pushed = false;\r\n            // Push Return action\r\n            new NPCTag(npc).action(\"push return\", null);\r\n        }\r\n    }\r\n\r\n    protected void navigateBack() {\r\n        if (npc.getNavigator().isNavigating()) {\r\n            pushed = false;\r\n        }\r\n        else if (pushed) {\r\n            pushed = false; // Avoids NPCCompleteDestination from triggering\r\n            npc.getNavigator().setTarget(returnLocation);\r\n            pushed = true;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/SittingTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.ChunkTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.npc.MemoryNPCDataStore;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport net.citizensnpcs.api.npc.NPCRegistry;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport net.citizensnpcs.api.util.Messaging;\r\nimport net.citizensnpcs.trait.ArmorStandTrait;\r\nimport net.citizensnpcs.trait.ClickRedirectTrait;\r\nimport net.citizensnpcs.util.NMS;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.ArmorStand;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockBreakEvent;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\nimport org.bukkit.metadata.FixedMetadataValue;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\npublic class SittingTrait extends Trait implements Listener {\r\n\r\n    @Persist(\"sitting\")\r\n    private boolean sitting = false;\r\n\r\n    @Persist(\"chair location\")\r\n    private Location chairLocation = null;\r\n\r\n    private boolean hasSpawned = false;\r\n\r\n    @Override\r\n    public void run() {\r\n        if (!npc.isSpawned() || chairLocation == null || !hasSpawned) {\r\n            return;\r\n        }\r\n        Location curLoc = npc.getEntity().getLocation();\r\n        if (curLoc.getWorld() != chairLocation.getWorld()) {\r\n            stand();\r\n            Messaging.debug(\"(Denizen/SittingTrait) NPC\", npc.getId(), \"stood up because it change world.\");\r\n            return;\r\n        }\r\n        double xoff = chairLocation.getX() - curLoc.getX(), zoff = chairLocation.getZ() - curLoc.getZ();\r\n        double dist = xoff * xoff + zoff * zoff;\r\n        if (dist > 4) {\r\n            stand();\r\n            Messaging.debug(\"(Denizen/SittingTrait) NPC\", npc.getId(), \"stood up because it moved away:\", xoff, \"on X and\", zoff, \"on Z\");\r\n            return;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onSpawn() {\r\n        if (!sitting) {\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.instance, () -> {\r\n                if (!sitting && npc != null) {\r\n                    npc.removeTrait(SittingTrait.class);\r\n                }\r\n            }, 1);\r\n            return;\r\n        }\r\n        hasSpawned = true;\r\n        if (chairLocation == null) {\r\n            sit();\r\n        }\r\n        else {\r\n            chairLocation = chairLocation.clone();\r\n            chairLocation.setYaw(npc.getStoredLocation().getYaw());\r\n            chairLocation.setPitch(npc.getStoredLocation().getPitch());\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> sit(chairLocation), 1);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onDespawn() {\r\n        hasSpawned = false;\r\n        if (npc == null || npc.getEntity() == null) {\r\n            return;\r\n        }\r\n        Entity vehicle = npc.getEntity().getVehicle();\r\n        if (vehicle != null) {\r\n            vehicle.eject();\r\n            NPC vehicleNPC = CitizensAPI.getNPCRegistry().getNPC(vehicle);\r\n            if (vehicleNPC != null && vehicleNPC.data().get(\"is-denizen-seat\", false)) {\r\n                vehicleNPC.destroy();\r\n            }\r\n        }\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // sit\r\n    //\r\n    // @Triggers when the NPC sits down.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n\r\n    /**\r\n     * Makes the NPC sit\r\n     */\r\n    public void sit() {\r\n        if (!npc.isSpawned()) {\r\n            return;\r\n        }\r\n        new NPCTag(npc).action(\"sit\", null);\r\n        sit(npc.getStoredLocation());\r\n    }\r\n\r\n    private void standInternal() {\r\n        sitting = false;\r\n        safetyCleanup(chairLocation.clone());\r\n        if (!npc.isSpawned()) {\r\n            return;\r\n        }\r\n        forceUnsit(npc.getEntity());\r\n        if (chairLocation == null) {\r\n            return;\r\n        }\r\n        npc.teleport(chairLocation.clone().add(0, 0.3, 0), PlayerTeleportEvent.TeleportCause.PLUGIN);\r\n    }\r\n\r\n    public void sitInternal(Location location) {\r\n        sitting = true;\r\n        safetyCleanup(location.clone());\r\n        if (!npc.isSpawned()) {\r\n            return;\r\n        }\r\n        new NPCTag(npc).action(\"sit\", null);\r\n        npc.teleport(location, PlayerTeleportEvent.TeleportCause.PLUGIN);\r\n        forceEntitySit(npc.getEntity(), location.clone(), 0);\r\n    }\r\n\r\n    public void safetyCleanup(Location loc) {\r\n        if (loc.getWorld() == null) {\r\n            return;\r\n        }\r\n        for (Entity entity : loc.getWorld().getNearbyEntities(loc, 3, 3, 3)) {\r\n            if (entity.getType() == EntityType.ARMOR_STAND && entity.getCustomName() != null && entity.getCustomName().equals(SIT_STAND_NAME) && entity.getPassengers().isEmpty()) {\r\n                ArmorStand stand = (ArmorStand) entity;\r\n                if (stand.isMarker() && stand.isSmall() && !stand.isVisible() && stand.getPassengers().isEmpty()) {\r\n                    NPC npc = CitizensAPI.getNPCRegistry().getNPC(stand);\r\n                    if (npc != null) {\r\n                        npc.destroy();\r\n                    }\r\n                    else {\r\n                        stand.remove();\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Makes the NPC sit at the specified location\r\n     *\r\n     * @param location where to sit\r\n     */\r\n    public void sit(Location location) {\r\n        sitInternal(location.clone());\r\n        chairLocation = location.clone();\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // stand\r\n    //\r\n    // @Triggers when the NPC stands up.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n\r\n    /**\r\n     * Makes the NPC stand\r\n     */\r\n    public void stand() {\r\n        new NPCTag(npc).action(\"stand\", null);\r\n        standInternal();\r\n        standInternal();\r\n        chairLocation = null;\r\n    }\r\n\r\n    /**\r\n     * Checks if the NPC is currently sitting\r\n     *\r\n     * @return boolean\r\n     */\r\n    public boolean isSitting() {\r\n        return sitting;\r\n    }\r\n\r\n    /**\r\n     * If someone tries to break the poor\r\n     * NPC's chair, we need to stop them!\r\n     */\r\n    @EventHandler(ignoreCancelled = true)\r\n    public void onBlockBreak(BlockBreakEvent event) {\r\n        if (chairLocation == null) {\r\n            return;\r\n        }\r\n        if (event.getBlock().getLocation().equals(chairLocation)) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n\r\n    public SittingTrait() {\r\n        super(\"sitting\");\r\n    }\r\n\r\n    public static String SIT_STAND_NAME = ChatColor.BLACK + \"Deniz\" + ChatColor.DARK_GRAY + \"NPCSit\";\r\n\r\n    public void forceUnsit(Entity entity) {\r\n        entity.removeMetadata(\"denizen.sitting\", Denizen.getInstance());\r\n        if (entity.isInsideVehicle()) {\r\n            entity.leaveVehicle();\r\n        }\r\n        if (sitStandNPC != null) {\r\n            sitStandNPC.destroy();\r\n            sitStandNPC = null;\r\n        }\r\n    }\r\n\r\n    public NPC sitStandNPC = null;\r\n\r\n    public void forceEntitySit(Entity entity, Location location, int retryCount) {\r\n        if (sitStandNPC != null) {\r\n            sitStandNPC.destroy();\r\n        }\r\n        entity.setMetadata(\"denizen.sitting\", new FixedMetadataValue(Denizen.getInstance(), true));\r\n        NPCRegistry registry = CitizensAPI.getNamedNPCRegistry(\"DenizenSitRegistry\");\r\n        if (registry == null) {\r\n            registry = CitizensAPI.createNamedNPCRegistry(\"DenizenSitRegistry\", new MemoryNPCDataStore());\r\n        }\r\n        NPC npc = CitizensAPI.getNPCRegistry().getNPC(entity);\r\n        final NPC holder = registry.createNPC(EntityType.ARMOR_STAND, SIT_STAND_NAME);\r\n        sitStandNPC = holder;\r\n        if (npc != null) {\r\n            holder.addTrait(new ClickRedirectTrait(npc));\r\n            Messaging.debug(\"(Denizen/SittingTrait) Spawning chair for\", npc.getId(), \"as id\", holder.getId());\r\n        }\r\n        ArmorStandTrait trait = holder.getOrAddTrait(ArmorStandTrait.class);\r\n        trait.setGravity(false);\r\n        trait.setHasArms(false);\r\n        trait.setHasBaseplate(false);\r\n        trait.setSmall(true);\r\n        trait.setMarker(true);\r\n        trait.setVisible(false);\r\n        holder.data().set(NPC.Metadata.NAMEPLATE_VISIBLE, false);\r\n        holder.data().set(NPC.Metadata.DEFAULT_PROTECTED, true);\r\n        boolean spawned = holder.spawn(location);\r\n        if (!spawned || !holder.isSpawned()) {\r\n            if (retryCount >= 4) {\r\n                Debug.echoError(\"NPC \" + (npc == null ? \"null\" : npc.getId()) + \" sit failed (\" + spawned + \",\" + holder.isSpawned() + \"): cannot spawn chair id \"\r\n                        + holder.getId() + \" at \" + new LocationTag(location).identifySimple() + \" ChunkIsLoaded=\" + new ChunkTag(location).isLoaded());\r\n                holder.destroy();\r\n                sitStandNPC = null;\r\n            }\r\n            else {\r\n                Messaging.debug(\"(Denizen/SittingTrait) retrying failed sit for\", npc.getId());\r\n                Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> { if (npc.isSpawned()) { forceEntitySit(entity, location, retryCount + 1); } }, 5);\r\n            }\r\n            return;\r\n        }\r\n        holder.data().set(\"is-denizen-seat\", true);\r\n        new BukkitRunnable() {\r\n            @Override\r\n            public void cancel() {\r\n                super.cancel();\r\n                if (entity.isValid() && entity.hasMetadata(\"denizen.sitting\")) {\r\n                    entity.removeMetadata(\"denizen.sitting\", Denizen.getInstance());\r\n                }\r\n                if (holder.getTraits().iterator().hasNext()) { // Hacky NPC-already-removed test\r\n                    holder.destroy();\r\n                }\r\n                if (sitStandNPC == holder) {\r\n                    sitStandNPC = null;\r\n                }\r\n            }\r\n\r\n            @Override\r\n            public void run() {\r\n                if (holder != sitStandNPC) {\r\n                    cancel();\r\n                }\r\n                else if (!holder.getTraits().iterator().hasNext()) { // Hacky NPC-already-removed test\r\n                    cancel();\r\n                }\r\n                else if (!entity.isValid() || !entity.hasMetadata(\"denizen.sitting\") || !entity.getMetadata(\"denizen.sitting\").get(0).asBoolean()) {\r\n                    cancel();\r\n                }\r\n                else if (npc != null && !npc.isSpawned()) {\r\n                    cancel();\r\n                }\r\n                else if (!holder.isSpawned()) {\r\n                    cancel();\r\n                }\r\n                else if (!NMS.getPassengers(holder.getEntity()).contains(entity)) {\r\n                    holder.getEntity().addPassenger(entity);\r\n                }\r\n            }\r\n        }.runTaskTimer(Denizen.getInstance(), 0, 1);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/SleepingTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.block.data.type.Bed;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.entity.Pose;\r\nimport org.bukkit.entity.Villager;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.block.BlockBreakEvent;\r\n\r\npublic class SleepingTrait extends Trait {\r\n\r\n    @Persist(\"sleeping\")\r\n    private boolean sleeping = false;\r\n\r\n    @Persist(\"bed location\")\r\n    private Location bedLocation = null;\r\n\r\n    @Override\r\n    public void run() {\r\n        if (npc == null || bedLocation == null || !npc.isSpawned()) {\r\n            return;\r\n        }\r\n        if (!Utilities.checkLocation((LivingEntity) npc.getEntity(), bedLocation, 2)) {\r\n            wakeUp();\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> {\r\n                if (npc.hasTrait(SleepingTrait.class) && !npc.getOrAddTrait(SleepingTrait.class).isSleeping()) {\r\n                    npc.removeTrait(SleepingTrait.class);\r\n                }\r\n            });\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onSpawn() {\r\n        if (sleeping) {\r\n            internalSleepNow();\r\n        }\r\n    }\r\n\r\n    public void internalSleepNow() {\r\n        if (npc.getEntity() instanceof Villager) {\r\n            if (!((Villager) npc.getEntity()).sleep(bedLocation.clone())) {\r\n                return;\r\n            }\r\n        }\r\n        else if (npc.getEntity() instanceof Player) {\r\n            if (bedLocation.getBlock().getBlockData() instanceof Bed) {\r\n                ((Player) npc.getEntity()).sleep(bedLocation.clone(), true);\r\n            }\r\n            else {\r\n                NMSHandler.entityHelper.setPose(npc.getEntity(), Pose.SLEEPING);\r\n            }\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n        sleeping = true;\r\n    }\r\n\r\n    /**\r\n     * Makes the NPC sleep\r\n     */\r\n    public void toSleep() {\r\n        if (sleeping) {\r\n            return;\r\n        }\r\n        if (!npc.isSpawned()) {\r\n            Debug.echoError(\"NPC \" + npc.getId() + \" cannot sleep: not spawned.\");\r\n            return;\r\n        }\r\n        bedLocation = npc.getStoredLocation().clone();\r\n        if (!bedLocation.isWorldLoaded()) {\r\n            Debug.echoError(\"NPC \" + npc.getId() + \" cannot sleep: invalid bed location.\");\r\n            return;\r\n        }\r\n        internalSleepNow();\r\n    }\r\n\r\n    /**\r\n     * Makes the NPC sleep at the specified location\r\n     *\r\n     * @param location where to sleep at\r\n     */\r\n    public void toSleep(Location location) {\r\n        if (sleeping) {\r\n            return;\r\n        }\r\n        if (!npc.isSpawned()) {\r\n            Debug.echoError(\"NPC \" + npc.getId() + \" cannot sleep: not spawned.\");\r\n            return;\r\n        }\r\n        if (!location.isWorldLoaded()) {\r\n            Debug.echoError(\"NPC \" + npc.getId() + \" cannot sleep: invalid bed location.\");\r\n            return;\r\n        }\r\n        npc.getEntity().teleport(location.clone());\r\n        bedLocation = location.clone();\r\n        internalSleepNow();\r\n    }\r\n\r\n    /**\r\n     * Makes the NPC wake up\r\n     */\r\n    public void wakeUp() {\r\n        if (!sleeping) {\r\n            return;\r\n        }\r\n        sleeping = false;\r\n        if (npc.getEntity() instanceof Villager) {\r\n            ((Villager) npc.getEntity()).wakeup();\r\n        }\r\n        else {\r\n            if (((Player) npc.getEntity()).isSleeping()) {\r\n                ((Player) npc.getEntity()).wakeup(false);\r\n            }\r\n            NMSHandler.entityHelper.setPose(npc.getEntity(), Pose.STANDING);\r\n        }\r\n        bedLocation = null;\r\n    }\r\n\r\n    /**\r\n     * Checks if the NPC is currently sleeping\r\n     *\r\n     * @return boolean\r\n     */\r\n    public boolean isSleeping() {\r\n        return sleeping;\r\n    }\r\n\r\n    /**\r\n     * Gets the bed the NPC is sleeping on\r\n     * Returns null if the NPC isnt sleeping\r\n     *\r\n     * @return Location\r\n     */\r\n    public Location getBed() {\r\n        return bedLocation;\r\n    }\r\n\r\n    /**\r\n     * If someone tries to break the poor\r\n     * NPC's bed, we need to stop them!\r\n     */\r\n    @EventHandler(ignoreCancelled = true)\r\n    public void onBlockBreak(BlockBreakEvent event) {\r\n        if (bedLocation == null) {\r\n            return;\r\n        }\r\n        if (event.getBlock().getLocation().equals(bedLocation)) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n\r\n    public SleepingTrait() {\r\n        super(\"sleeping\");\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/SneakingTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class SneakingTrait extends Trait implements Listener {\r\n\r\n    @Persist(\"sneaking\")\r\n    private boolean sneaking = false;\r\n\r\n    @Override\r\n    public void onSpawn() {\r\n        if (sneaking) {\r\n            sneak();\r\n        }\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // sneak\r\n    //\r\n    // @Triggers when the NPC starts sneaking.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n\r\n    /**\r\n     * Makes the NPC sneak\r\n     */\r\n    public void sneak() {\r\n        new NPCTag(npc).action(\"sneak\", null);\r\n        if (npc.getEntity().getType() != EntityType.PLAYER) {\r\n            return;\r\n        }\r\n        NMSHandler.entityHelper.setSneaking(npc.getEntity(), true);\r\n        sneaking = true;\r\n    }\r\n\r\n    /**\r\n     * Makes the NPC stand\r\n     */\r\n    public void stand() {\r\n        // Notated in SittingTrait\r\n        new NPCTag(npc).action(\"stand\", null);\r\n        NMSHandler.entityHelper.setSneaking(npc.getEntity(), false);\r\n        sneaking = false;\r\n    }\r\n\r\n    /**\r\n     * Checks if the NPC is currently sneaking\r\n     *\r\n     * @return boolean\r\n     */\r\n    public boolean isSneaking() {\r\n        return sneaking;\r\n    }\r\n\r\n    public SneakingTrait() {\r\n        super(\"sneaking\");\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/npc/traits/TriggerTrait.java",
    "content": "package com.denizenscript.denizen.npc.traits;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.commands.npc.EngageCommand;\r\nimport com.denizenscript.denizen.scripts.triggers.AbstractTrigger;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport net.citizensnpcs.api.command.exception.CommandException;\r\nimport net.citizensnpcs.api.persistence.Persist;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport net.citizensnpcs.api.util.DataKey;\r\nimport net.citizensnpcs.api.util.Paginator;\r\nimport net.citizensnpcs.util.Messages;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.event.Listener;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.Map.Entry;\r\n\r\npublic class TriggerTrait extends Trait implements Listener {\r\n\r\n    @Persist(value = \"enabled\", collectionType = HashMap.class)\r\n    public Map<String, Boolean> enabled = new HashMap<>();\r\n    @Persist(value = \"properly_set\", collectionType = HashMap.class)\r\n    public Map<String, Boolean> properly_set = new HashMap<>();\r\n    @Persist(value = \"duration\", collectionType = HashMap.class)\r\n    private Map<String, Double> duration = new HashMap<>();\r\n    @Persist(value = \"radius\", collectionType = HashMap.class)\r\n    private Map<String, Integer> radius = new HashMap<>();\r\n\r\n    public TriggerTrait() {\r\n        super(\"triggers\");\r\n        for (Map.Entry<String, Boolean> entry : enabled.entrySet()) {\r\n            if (!properly_set.containsKey(entry.getKey())) {\r\n                properly_set.put(entry.getKey(), entry.getValue());\r\n            }\r\n        }\r\n        for (String triggerName : Denizen.getInstance().triggerRegistry.list().keySet()) {\r\n            if (!enabled.containsKey(triggerName)) {\r\n                enabled.put(triggerName, Settings.triggerEnabled(triggerName));\r\n                properly_set.put(triggerName, false);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onSpawn() {\r\n        if (!npc.hasTrait(AssignmentTrait.class)) {\r\n            npc.removeTrait(TriggerTrait.class);\r\n            return;\r\n        }\r\n        for (Map.Entry<String, AbstractTrigger> trigger : Denizen.getInstance().triggerRegistry.list().entrySet()) {\r\n            if (!enabled.containsKey(trigger.getKey())) {\r\n                enabled.put(trigger.getKey(), Settings.triggerEnabled(trigger.getKey()));\r\n            }\r\n            if (enabled.get(trigger.getKey())) {\r\n                trigger.getValue().timesUsed++;\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void load(DataKey key) {\r\n        if (!key.keyExists(\"properly_set\") && key.keyExists(\"enabled\")) {\r\n            for (final String triggerName : Denizen.getInstance().triggerRegistry.list().keySet()) {\r\n                Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> properly_set.put(triggerName, key.getBoolean(\"enabled.\" + triggerName)));\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Toggles a trigger on or off for this NPC.\r\n     *\r\n     * @param triggerName name of the Trigger, as specified by the Trigger. Case in-sensitive.\r\n     * @param toggle      new state of the trigger\r\n     * @return output debug information.\r\n     */\r\n    public String toggleTrigger(String triggerName, boolean toggle) {\r\n        if (enabled.containsKey(triggerName.toUpperCase())) {\r\n            Denizen.getInstance().triggerRegistry.get(triggerName).timesUsed++;\r\n            enabled.put(triggerName.toUpperCase(), toggle);\r\n            properly_set.put(triggerName.toUpperCase(), true);\r\n            return triggerName + \" trigger is now \" + (toggle ? \"enabled.\" : \"disabled.\");\r\n        }\r\n        else {\r\n            return triggerName + \" trigger not found!\";\r\n        }\r\n    }\r\n\r\n    public boolean triggerNameIsValid(String triggerName) {\r\n        return enabled.containsKey(triggerName.toUpperCase());\r\n    }\r\n\r\n    public String toggleTrigger(String triggerName) {\r\n        if (enabled.containsKey(triggerName.toUpperCase())) {\r\n            if (enabled.get(triggerName.toUpperCase())) {\r\n                enabled.put(triggerName.toUpperCase(), false);\r\n                return triggerName + \" trigger is now disabled.\";\r\n            }\r\n            else {\r\n                enabled.put(triggerName.toUpperCase(), true);\r\n                properly_set.put(triggerName.toUpperCase(), true);\r\n                return triggerName + \" trigger is now enabled.\";\r\n            }\r\n        }\r\n        else {\r\n            return triggerName + \" trigger not found!\";\r\n        }\r\n    }\r\n\r\n    public boolean hasTrigger(String triggerName) {\r\n        return enabled.containsKey(triggerName.toUpperCase()) && enabled.get(triggerName.toUpperCase());\r\n    }\r\n\r\n    public boolean isEnabled(String triggerName) {\r\n        if (!npc.hasTrait(AssignmentTrait.class)) {\r\n            return false;\r\n        }\r\n        return enabled.getOrDefault(triggerName.toUpperCase(), false);\r\n    }\r\n\r\n    public void setLocalCooldown(String triggerName, double value) {\r\n        if (value < 0) {\r\n            value = 0;\r\n        }\r\n        duration.put(triggerName.toUpperCase(), value);\r\n    }\r\n\r\n    public double getCooldownDuration(String triggerName) {\r\n        if (duration.containsKey(triggerName.toUpperCase())) {\r\n            return duration.get(triggerName.toUpperCase());\r\n        }\r\n        else {\r\n            return Settings.triggerDefaultCooldown(triggerName);\r\n        }\r\n    }\r\n\r\n    public void setLocalRadius(String triggerName, int value) {\r\n        radius.put(triggerName.toUpperCase(), value);\r\n    }\r\n\r\n    public double getRadius(String triggerName) {\r\n        if (radius.containsKey(triggerName.toUpperCase())) {\r\n            return radius.get(triggerName.toUpperCase());\r\n        }\r\n        else {\r\n            return Settings.triggerDefaultRange(triggerName);\r\n        }\r\n    }\r\n\r\n    public void describe(CommandSender sender, int page) throws CommandException {\r\n        Paginator paginator = new Paginator().header(\"Triggers\");\r\n        paginator.addLine(\"<e>Key: <a>Name  <b>Status  <c>Cooldown  <d>Cooldown Type  <e>(Radius)\");\r\n        for (Entry<String, Boolean> entry : enabled.entrySet()) {\r\n            String line = \"<a> \" + entry.getKey()\r\n                    + \"<b> \" + (entry.getValue() ? \"Enabled\" : \"Disabled\")\r\n                    + \"<c> \" + getCooldownDuration(entry.getKey())\r\n                    + \"<e> \" + (getRadius(entry.getKey()) == -1 ? \"\" : getRadius(entry.getKey()));\r\n            paginator.addLine(line);\r\n        }\r\n        if (!paginator.sendPage(sender, page)) {\r\n            throw new CommandException(Messages.COMMAND_PAGE_MISSING, page);\r\n        }\r\n    }\r\n\r\n    public boolean triggerCooldownOnly(AbstractTrigger triggerClass, PlayerTag player) {\r\n        // Check cool down, return false if not yet met\r\n        if (!Denizen.getInstance().triggerRegistry.checkCooldown(npc, player, triggerClass)) {\r\n            return false;\r\n        }\r\n        // Check engaged\r\n        if (EngageCommand.getEngaged(npc, player)) {\r\n            return false;\r\n        }\r\n        // Set cool down\r\n        Denizen.getInstance().triggerRegistry.setCooldown(npc, player, triggerClass, getCooldownDuration(triggerClass.getName()));\r\n        return true;\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // unavailable\r\n    //\r\n    // @Triggers when a trigger fires but the NPC is engaged.\r\n    //\r\n    // @Context\r\n    // <context.trigger_type> return the type of trigger fired\r\n    //\r\n    // -->\r\n    public TriggerContext trigger(AbstractTrigger triggerClass, PlayerTag player) {\r\n        return trigger(triggerClass, player, null);\r\n    }\r\n\r\n    public TriggerContext trigger(AbstractTrigger triggerClass, PlayerTag player, Map<String, ObjectTag> context) {\r\n        String trigger_type = triggerClass.getName();\r\n        if (!Denizen.getInstance().triggerRegistry.checkCooldown(npc, player, triggerClass)) {\r\n            return new TriggerContext(false);\r\n        }\r\n        if (context == null) {\r\n            context = new HashMap<>();\r\n        }\r\n        if (EngageCommand.getEngaged(npc, player)) {\r\n            context.put(\"trigger_type\", new ElementTag(trigger_type));\r\n            // TODO: Should this be refactored?\r\n            if (new NPCTag(npc).action(\"unavailable\", player, context).containsCaseInsensitive(\"available\")) {\r\n                // If determined available, continue on...\r\n                // else, return a 'non-triggered' state.\r\n            }\r\n            else {\r\n                return new TriggerContext(false);\r\n            }\r\n        }\r\n        Denizen.getInstance().triggerRegistry.setCooldown(npc, player, triggerClass, getCooldownDuration(trigger_type));\r\n        ListTag determination = new NPCTag(npc).action(trigger_type, player, context);\r\n        return new TriggerContext(determination, true);\r\n    }\r\n\r\n    /**\r\n     * Contains whether the trigger successfully 'triggered' and any context that was\r\n     * available while triggering or attempting to trigger.\r\n     */\r\n    public static class TriggerContext {\r\n\r\n        public TriggerContext(boolean triggered) {\r\n            this.triggered = triggered;\r\n        }\r\n\r\n        public TriggerContext(ListTag determination, boolean triggered) {\r\n            this.determination = determination;\r\n            this.triggered = triggered;\r\n        }\r\n\r\n        ListTag determination;\r\n        boolean triggered;\r\n\r\n        public boolean hasDetermination() {\r\n            return determination != null && !determination.isEmpty();\r\n        }\r\n\r\n        public ListTag getDeterminations() {\r\n            return determination;\r\n        }\r\n\r\n        public boolean wasTriggered() {\r\n            return triggered;\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/AreaContainmentObject.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.utilities.NotedAreaTracker;\r\nimport com.denizenscript.denizen.utilities.blocks.SpawnableHelper;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.utilities.flags.LocationFlagSearchHelper;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.LivingEntity;\r\n\r\nimport java.util.function.Predicate;\r\n\r\npublic interface AreaContainmentObject extends ObjectTag {\r\n\r\n    // <--[ObjectType]\r\n    // @name AreaObject\r\n    // @ExampleTagBase cuboid[my_noted_cuboid]\r\n    // @ExampleValues my_cuboid_note\r\n    // @ExampleForReturns\r\n    // - note %VALUE% as:my_new_area\r\n    // @prefix None\r\n    // @base None\r\n    // @format\r\n    // N/A\r\n    //\r\n    // @description\r\n    // \"AreaObject\" is a pseudo-ObjectType that represents any object that indicates a world-space area, such as a CuboidTag.\r\n    //\r\n    // @Matchable\r\n    // AreaObject matchers (applies to CuboidTag, EllipsoidTag, PolygonTag, ...), sometimes identified as \"<area>\":\r\n    // \"cuboid\" plaintext: matches if the area is a CuboidTag.\r\n    // \"ellipsoid\" plaintext: matches if the area is an EllipsoidTag.\r\n    // \"polygon\" plaintext: matches if the area is a PolygonTag.\r\n    // \"area_flagged:<flag>\": a Flag Matchable for AreaObject flags.\r\n    // Area note name: matches if the AreaObject's note name matches the given advanced matcher.\r\n    //\r\n    // -->\r\n\r\n    // <--[extension]\r\n    // @name Note Command Extension\r\n    // @target_type command\r\n    // @target_name Note\r\n    // @Description\r\n    // Notable object types: CuboidTag, EllipsoidTag, PolygonTag, LocationTag, InventoryTag\r\n    // @Tags\r\n    // <server.notes[<type>]>\r\n    // <CuboidTag.note_name>\r\n    // <EllipsoidTag.note_name>\r\n    // <PolygonTag.note_name>\r\n    // <InventoryTag.note_name>\r\n    // <LocationTag.note_name>\r\n    // -->\r\n\r\n    String getNoteName();\r\n\r\n    boolean doesContainLocation(Location loc);\r\n\r\n    CuboidTag getCuboidBoundary();\r\n\r\n    WorldTag getWorld();\r\n\r\n    ListTag getShell();\r\n\r\n    ListTag getBlocks(Predicate<Location> test);\r\n\r\n    AreaContainmentObject withWorld(WorldTag world);\r\n\r\n    default ListTag getBlocksFlagged(String flagName, Attribute attribute) {\r\n        CuboidTag cuboid = getCuboidBoundary();\r\n        ListTag blocks = new ListTag();\r\n        for (CuboidTag.LocationPair pair : cuboid.pairs) {\r\n            ChunkTag minChunk = new ChunkTag(pair.low);\r\n            ChunkTag maxChunk = new ChunkTag(pair.high);\r\n            ChunkTag subChunk = new ChunkTag(pair.low);\r\n            for (int x = minChunk.getX(); x <= maxChunk.getX(); x++) {\r\n                subChunk.chunkX = x;\r\n                for (int z = minChunk.getZ(); z <= maxChunk.getZ(); z++) {\r\n                    subChunk.chunkZ = z;\r\n                    subChunk.cachedChunk = null;\r\n                    if (subChunk.isLoadedSafe()) {\r\n                        LocationFlagSearchHelper.getFlaggedLocations(subChunk.getChunkForTag(attribute), flagName, (loc) -> {\r\n                            if (doesContainLocation(loc)) {\r\n                                blocks.addObject(new LocationTag(loc));\r\n                            }\r\n                        });\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        return blocks;\r\n    }\r\n\r\n    static <T extends AreaContainmentObject> void register(Class<T> type, ObjectTagProcessor<T> processor) {\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.bounding_box>\r\n        // @returns CuboidTag\r\n        // @description\r\n        // Returns a cuboid approximately representing the maximal bounding box of the area (anything this cuboid does not contain, is also not contained by the area, but not vice versa).\r\n        // For single-member CuboidTags, this tag returns a copy of the cuboid.\r\n        // @example\r\n        // # Notes the polygon's bounding box to efficiently check when things are near the polygon, even if not exactly inside.\r\n        // - note <polygon[my_poly].bounding_box> my_bound_box\r\n        // -->\r\n        processor.registerTag(CuboidTag.class, \"bounding_box\", (attribute, area) -> {\r\n            return area.getCuboidBoundary();\r\n        });\r\n\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.world>\r\n        // @returns WorldTag\r\n        // @description\r\n        // Returns the area's world.\r\n        // @example\r\n        // - narrate \"The cuboid, 'my_cuboid', is in world: <cuboid[my_cuboid].world.name>!\"\r\n        // -->\r\n        processor.registerTag(WorldTag.class, \"world\", (attribute, area) -> {\r\n            return area.getWorld();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.players>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Gets a list of all players currently within the area.\r\n        // @example\r\n        // # Narrates a list of players' names that are within the area.\r\n        // # For example: \"List of players in 'my_cuboid': steve, alex, john, jane\"\r\n        // - narrate \"List of players in 'my_cuboid': <cuboid[my_cuboid].players.formatted>\"\r\n        // -->\r\n        processor.registerTag(ListTag.class, \"players\", (attribute, area) -> {\r\n            return new ListTag(Bukkit.getOnlinePlayers(), player -> area.doesContainLocation(player.getLocation()), PlayerTag::new);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.npcs>\r\n        // @returns ListTag(NPCTag)\r\n        // @description\r\n        // Gets a list of all NPCs currently within the area.\r\n        // @example\r\n        // # Narrates a list of NPCs' names that are within the area.\r\n        // # For example: \"List of NPCs in 'my_cuboid': steve, alex, john, jane\"\r\n        // - narrate \"List of NPCs in 'my_cuboid': <cuboid[my_cuboid].npcs.formatted>\"\r\n        // -->\r\n        if (Depends.citizens != null) {\r\n            processor.registerTag(ListTag.class, \"npcs\", (attribute, area) -> {\r\n                ListTag result = new ListTag();\r\n                for (NPC npc : CitizensAPI.getNPCRegistry()) {\r\n                    NPCTag dnpc = new NPCTag(npc);\r\n                    if (area.doesContainLocation(dnpc.getLocation())) {\r\n                        result.addObject(dnpc);\r\n                    }\r\n                }\r\n                return result;\r\n            });\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.entities[(<matcher>)]>\r\n        // @returns ListTag(EntityTag)\r\n        // @description\r\n        // Gets a list of all entities currently within the area, with an optional search parameter for the entity.\r\n        // @example\r\n        // # Spawns a flash particle at the location of every axolotl in the cuboid.\r\n        // - foreach <cuboid[my_cuboid].entities[axolotl]> as:entity:\r\n        //     - playeffect effect:flash at:<[entity].location>\r\n        // -->\r\n        processor.registerTag(ListTag.class, \"entities\", (attribute, area) -> {\r\n            String matcher = attribute.hasParam() ? attribute.getParam() : null;\r\n            ListTag entities = new ListTag();\r\n            for (Entity ent : area.getCuboidBoundary().getEntitiesPossiblyWithinForTag()) {\r\n                if (area.doesContainLocation(ent.getLocation())) {\r\n                    EntityTag current = new EntityTag(ent);\r\n                    if (matcher == null || current.tryAdvancedMatcher(matcher, attribute.context)) {\r\n                        entities.addObject(current.getDenizenObject());\r\n                    }\r\n                }\r\n            }\r\n            return entities;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.living_entities>\r\n        // @returns ListTag(EntityTag)\r\n        // @description\r\n        // Gets a list of all living entities currently within the area.\r\n        // This includes Players, mobs, NPCs, etc., but excludes dropped items, experience orbs, etc.\r\n        // @example\r\n        // # Narrates the name of all the living entities within the area.\r\n        // - foreach <cuboid[my_cuboid].living_entities> as:entity:\r\n        //      - narrate <[entity].name>\r\n        // -->\r\n        processor.registerTag(ListTag.class, \"living_entities\", (attribute, area) -> {\r\n            return new ListTag(area.getCuboidBoundary().getEntitiesPossiblyWithinForTag(),\r\n                    entity -> entity instanceof LivingEntity && !EntityTag.isCitizensNPC(entity) && area.doesContainLocation(entity.getLocation()),\r\n                    EntityTag::mirrorBukkitEntity);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.contains[<location>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns a boolean indicating whether the specified location is inside this area.\r\n        // @example\r\n        // # Checks to see if \"my_cuboid\" contains the player's location.\r\n        // - if <cuboid[my_cuboid].contains[<player.location>]>:\r\n        //      - narrate \"You are within 'my_cuboid'!\"\r\n        // - else:\r\n        //      - narrate \"You are NOT within 'my_cuboid'!\"\r\n        // -->\r\n        processor.registerTag(ElementTag.class, LocationTag.class, \"contains\", (attribute, area, loc) -> {\r\n            return new ElementTag(area.doesContainLocation(loc));\r\n        }, \"contains_location\");\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.blocks[(<matcher>)]>\r\n        // @returns ListTag(LocationTag)\r\n        // @description\r\n        // Returns each block location within the area.\r\n        // Optionally, specify a material matcher to only return locations with that block type.\r\n        // @example\r\n        // # Spawns a debugblock to highlight every plank-type block in the area.\r\n        // - debugblock <cuboid[my_cuboid].blocks[*planks]>\r\n        // -->\r\n        processor.registerTag(ListTag.class, \"blocks\", (attribute, area) -> {\r\n            if (attribute.hasParam()) {\r\n                NMSHandler.chunkHelper.changeChunkServerThread(area.getWorld().getWorld());\r\n                try {\r\n                    String matcher = attribute.getParam();\r\n                    Predicate<Location> predicate = (l) -> new LocationTag(l).tryAdvancedMatcher(matcher, attribute.context);\r\n                    return area.getBlocks(predicate);\r\n                }\r\n                finally {\r\n                    NMSHandler.chunkHelper.restoreServerThread(area.getWorld().getWorld());\r\n                }\r\n            }\r\n            return area.getBlocks(null);\r\n        }, \"get_blocks\");\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.spawnable_blocks[(<matcher>)]>\r\n        // @returns ListTag(LocationTag)\r\n        // @description\r\n        // Returns each LocationTag within the area that is safe for players or similar entities to spawn in.\r\n        // Optionally, specify a material matcher to only return locations with that block type.\r\n        // Uses the same spawnable check as <@link tag LocationTag.is_spawnable>\r\n        // @example\r\n        // # Spawns a creeper at a random spawnable block within the area.\r\n        // - spawn creeper <cuboid[my_cuboid].spawnable_blocks.random>\r\n        // -->\r\n        processor.registerTag(ListTag.class, \"spawnable_blocks\", (attribute, area) -> {\r\n            NMSHandler.chunkHelper.changeChunkServerThread(area.getWorld().getWorld());\r\n            try {\r\n                if (attribute.hasParam()) {\r\n                    String matcher = attribute.getParam();\r\n                    Predicate<Location> predicate = (l) -> SpawnableHelper.isSpawnable(l) && new LocationTag(l.getBlock().getRelative(0, -1, 0).getLocation()).tryAdvancedMatcher(matcher, attribute.context);\r\n                    return area.getBlocks(predicate);\r\n                }\r\n                return area.getBlocks(SpawnableHelper::isSpawnable);\r\n            }\r\n            finally {\r\n                NMSHandler.chunkHelper.restoreServerThread(area.getWorld().getWorld());\r\n            }\r\n        }, \"get_spawnable_blocks\");\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.blocks_flagged[<flag_name>]>\r\n        // @returns ListTag(LocationTag)\r\n        // @description\r\n        // Gets a list of all block locations with a specified flag within the area.\r\n        // Searches the internal flag lists, rather than through all possible blocks.\r\n        // @example\r\n        // # Spawns a debugblock to highlight every block in the cuboid that has the location flag 'my_flag'.\r\n        // - debugblock <cuboid[my_cuboid].blocks_flagged[my_flag]>\r\n        // -->\r\n        processor.registerTag(ListTag.class, ElementTag.class, \"blocks_flagged\", (attribute, area, flagName) -> {\r\n            return area.getBlocksFlagged(CoreUtilities.toLowerCase(flagName.toString()), attribute);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.shell>\r\n        // @returns ListTag(LocationTag)\r\n        // @description\r\n        // Returns each block location on the 3D outer shell of the area.\r\n        // This tag is useful for displaying particles or blocks to mark the boundary of the area.\r\n        // @example\r\n        // # Spawns a hollow sphere of fire around the player.\r\n        // - playeffect effect:flame at:<player.location.to_ellipsoid[5,5,5].shell> offset:0\r\n        // -->\r\n        processor.registerTag(ListTag.class, \"shell\", (attribute, area) -> {\r\n            return area.getShell();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.is_within[<cuboid>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether this area is fully inside another cuboid.\r\n        // @example\r\n        // # Checks to see if \"my_cuboid\" is within \"my_bigger_cuboid\".\r\n        // - if <cuboid[my_cuboid].is_within[<cuboid[my_bigger_cuboid]>]>:\r\n        //      - narrate \"It is fully within 'my_bigger_cuboid'!\"\r\n        // - else:\r\n        //      - narrate \"It is not fully within 'my_bigger_cuboid'!\"\r\n        // -->\r\n        processor.registerTag(ElementTag.class, CuboidTag.class, \"is_within\", (attribute, area, cub2) -> {\r\n            CuboidTag cuboid = area instanceof CuboidTag cuboidTag ? cuboidTag : area.getCuboidBoundary();\r\n            if (cub2 != null) {\r\n                boolean contains = true;\r\n                for (CuboidTag.LocationPair pair2 : cuboid.pairs) {\r\n                    boolean contained = false;\r\n                    for (CuboidTag.LocationPair pair : cub2.pairs) {\r\n                        if (!pair.low.getWorld().equals(pair2.low.getWorld())) {\r\n                            return new ElementTag(false);\r\n                        }\r\n                        if (pair2.low.getX() >= pair.low.getX()\r\n                                && pair2.low.getY() >= pair.low.getY()\r\n                                && pair2.low.getZ() >= pair.low.getZ()\r\n                                && pair2.high.getX() <= pair.high.getX()\r\n                                && pair2.high.getY() <= pair.high.getY()\r\n                                && pair2.high.getZ() <= pair.high.getZ()) {\r\n                            contained = true;\r\n                            break;\r\n                        }\r\n                    }\r\n                    if (!contained) {\r\n                        contains = false;\r\n                        break;\r\n                    }\r\n                }\r\n                return new ElementTag(contains);\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.with_world[<world>]>\r\n        // @returns AreaObject\r\n        // @description\r\n        // Returns a copy of the area, with the specified world.\r\n        // @example\r\n        // # Notes a copy of \"my_cuboid\" with the same coordinates transposed from the overworld to the end world.\r\n        // - note my_new_cuboid <cuboid[my_cuboid].with_world[world_the_end]>\r\n        // -->\r\n        processor.registerTag(type, WorldTag.class, \"with_world\", (attribute, area, world) -> {\r\n            return (T) area.withWorld(world);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <AreaObject.approximate_overlap_areas>\r\n        // @returns ListTag(AreaObject)\r\n        // @description\r\n        // Returns a list of all noted areas that approximately overlap this area.\r\n        // May be inaccurate for objects with complex shapes.\r\n        // Errs on the side of over-inclusion (ie areas that don't overlap may be in the list, but areas that do overlap will never be excluded).\r\n        // -->\r\n        processor.registerTag(ListTag.class, \"approximate_overlap_areas\", (attribute, area) -> {\r\n            ListTag list = new ListTag();\r\n            CuboidTag.LocationPair pair = area.getCuboidBoundary().pairs.get(0);\r\n            NotedAreaTracker.forEachAreaThatIntersects(pair.low, pair.high, list::addObject);\r\n            return list;\r\n        });\r\n    }\r\n\r\n    default boolean areaBaseAdvancedMatches(String matcher) {\r\n        return getNoteName() != null && BukkitScriptEvent.createMatcher(matcher).doesMatch(getNoteName(), text -> {\r\n            if (this instanceof FlaggableObject flaggableObject && text.startsWith(\"area_flagged:\")) {\r\n                AbstractFlagTracker tracker = flaggableObject.getFlagTracker();\r\n                return tracker != null && tracker.hasFlag(text.substring(\"area_flagged:\".length()));\r\n            }\r\n            return false;\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/BiomeTag.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.flags.RedirectionFlagTracker;\r\nimport com.denizenscript.denizencore.objects.Adjustable;\r\nimport com.denizenscript.denizencore.objects.Fetchable;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Keyed;\r\nimport org.bukkit.World;\r\nimport org.bukkit.block.Biome;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.util.List;\r\n\r\npublic class BiomeTag implements ObjectTag, Adjustable, FlaggableObject {\r\n\r\n    // <--[ObjectType]\r\n    // @name BiomeTag\r\n    // @prefix b\r\n    // @base ElementTag\r\n    // @implements FlaggableObject\r\n    // @ExampleTagBase biome[desert]\r\n    // @ExampleValues desert\r\n    // @ExampleForReturns\r\n    // - adjust <player.location.to_ellipsoid[60,3,10].blocks> biome:%VALUE%\r\n    // @ExampleForReturns\r\n    // - adjust <player.location.chunk> set_all_biomes:%VALUE%\r\n    // @format\r\n    // The identity format for biomes is a world name, then a comma, then the biome key. For example: 'hub,desert', or 'space,minecraft:desert'.\r\n    //\r\n    // @description\r\n    // A BiomeTag represents a world biome type. Vanilla biomes are globally available, however some biomes are world-specific when added by datapacks.\r\n    //\r\n    // A list of all vanilla biomes can be found at <@link url https://minecraft.wiki/w/Biome#Biome_IDs>.\r\n    //\r\n    // BiomeTags without a specific world will work as though they are in the server's default world.\r\n    //\r\n    // This object type is flaggable.\r\n    // Flags on this object type will be stored in the server saves file, under special sub-key \"__biomes\"\r\n    //\r\n    // -->\r\n\r\n    //////////////////\r\n    //    OBJECT FETCHER\r\n    ////////////////\r\n\r\n    @Fetchable(\"b\")\r\n    public static BiomeTag valueOf(String string, TagContext context) {\r\n        if (string.startsWith(\"b@\")) {\r\n            string = string.substring(2);\r\n        }\r\n        string = CoreUtilities.toLowerCase(string);\r\n        int comma = string.indexOf(',');\r\n        String worldName = null, biomeName = string;\r\n        if (comma != -1) {\r\n            worldName = string.substring(0, comma);\r\n            biomeName = string.substring(comma + 1);\r\n        }\r\n        World world = Bukkit.getWorlds().get(0);\r\n        if (worldName != null) {\r\n            WorldTag worldTag = WorldTag.valueOf(worldName, context);\r\n            if (worldTag == null || worldTag.getWorld() == null) {\r\n                return null;\r\n            }\r\n            world = worldTag.getWorld();\r\n        }\r\n        BiomeNMS biome = NMSHandler.instance.getBiomeNMS(world, Utilities.parseNamespacedKey(biomeName));\r\n        if (biome == null) {\r\n            return null;\r\n        }\r\n        return new BiomeTag(biome);\r\n    }\r\n\r\n    public static boolean matches(String arg) {\r\n        if (arg.startsWith(\"b@\")) {\r\n            return true;\r\n        }\r\n        return valueOf(arg, CoreUtilities.noDebugContext) != null;\r\n    }\r\n\r\n    ///////////////\r\n    //   Constructors\r\n    /////////////\r\n\r\n    public BiomeTag(Biome biome) {\r\n        this.biome = NMSHandler.instance.getBiomeNMS(Bukkit.getWorlds().get(0), ((Keyed) biome).getKey());\r\n    }\r\n\r\n    public BiomeTag(BiomeNMS biome) {\r\n        this.biome = biome;\r\n    }\r\n\r\n    /////////////////////\r\n    //   INSTANCE FIELDS/METHODS\r\n    /////////////////\r\n\r\n    private BiomeNMS biome;\r\n\r\n    public BiomeNMS getBiome() {\r\n        return biome;\r\n    }\r\n\r\n    String prefix = \"biome\";\r\n\r\n    @Override\r\n    public String getPrefix() {\r\n        return prefix;\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnique() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public String identify() {\r\n        return \"b@\" + biome.world.getName() + \",\" + Utilities.namespacedKeyToString(biome.getKey());\r\n    }\r\n\r\n    @Override\r\n    public String identifySimple() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag setPrefix(String prefix) {\r\n        if (prefix != null) {\r\n            this.prefix = prefix;\r\n        }\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public AbstractFlagTracker getFlagTracker() {\r\n        return new RedirectionFlagTracker(DenizenCore.serverFlagMap, \"__biomes.\" + Utilities.namespacedKeyToString(biome.getKey()).replace(\".\", \"&dot\"));\r\n    }\r\n\r\n    @Override\r\n    public void reapplyTracker(AbstractFlagTracker tracker) {\r\n        // Nothing to do.\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\r\n\r\n        // <--[tag]\r\n        // @attribute <BiomeTag.downfall_type>\r\n        // @returns ElementTag\r\n        // @mechanism BiomeTag.downfall_type\r\n        // @deprecated Minecraft changed the way biome downfall works, use <@link tag BiomeTag.downfall_at> on 1.19+.\r\n        // @description\r\n        // Deprecated in favor of <@link tag BiomeTag.downfall_at> on 1.19+, as downfall is block-specific now.\r\n        // Returns this biome's downfall type for when a world has weather.\r\n        // This can be RAIN, SNOW, or NONE.\r\n        // @example\r\n        // # In a plains biome, this fills with 'RAIN'.\r\n        // - narrate \"The downfall type in plains biomes is: <biome[plains].downfall_type>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"downfall_type\", (attribute, object) -> {\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n                BukkitImplDeprecations.biomeGlobalDownfallType.warn(attribute.context);\r\n            }\r\n            return new ElementTag(object.biome.getDownfallType());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <BiomeTag.name>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns this biome's name.\r\n        // @example\r\n        // # In a plains biome, this fills with 'plains'.\r\n        // - narrate \"You are currently in a <biome[plains].name> biome!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"name\", (attribute, object) -> {\r\n            return new ElementTag(Utilities.namespacedKeyToString(object.biome.getKey()), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <BiomeTag.humidity>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism BiomeTag.humidity\r\n        // @description\r\n        // Returns the humidity of this biome.\r\n        // @example\r\n        // # In a plains biome, this fills with '0.4'.\r\n        // - narrate \"Humidity in a plains biome is <biome[plains].humidity>! So humid!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"humidity\", (attribute, object) -> {\r\n            return new ElementTag(object.biome.getHumidity());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <BiomeTag.base_temperature>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism BiomeTag.base_temperature\r\n        // @description\r\n        // Returns the base temperature of this biome, which is used for per-location temperature calculations (see <@link tag BiomeTag.temperature_at>).\r\n        // @example\r\n        // # In a plains biome, this fills with '0.8'.\r\n        // - narrate \"Stay warm! In a plains biome, the base temperature is <biome[plains].base_temperature>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"base_temperature\", (attribute, object) -> {\r\n            return new ElementTag(object.biome.getBaseTemperature());\r\n        }, \"temperature\");\r\n\r\n        // <--[tag]\r\n        // @attribute <BiomeTag.spawnable_entities[(<type>)]>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns all entities that spawn naturally in this biome.\r\n        // Optionally specify a type as: AMBIENT, CREATURES, MONSTERS, WATER, or ALL.\r\n        // (By default, will be \"ALL\").\r\n        // @example\r\n        // # Narrates the types of entities of type MONSTERS that can spawn in the player's biome.\r\n        // # For example, in a plains biome this could contain \"SPIDER\", \"ZOMBIE\", \"CREEPER\", etc.\r\n        // - narrate <player.location.biome.spawnable_entities[MONSTERS].formatted>\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"spawnable_entities\", (attribute, object) -> {\r\n            List<EntityType> entityTypes;\r\n            if (attribute.startsWith(\"ambient\", 2)) {\r\n                BukkitImplDeprecations.biomeSpawnableTag.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                entityTypes = object.biome.getAmbientEntities();\r\n            }\r\n            else if (attribute.startsWith(\"creatures\", 2)) {\r\n                BukkitImplDeprecations.biomeSpawnableTag.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                entityTypes = object.biome.getCreatureEntities();\r\n            }\r\n            else if (attribute.startsWith(\"monsters\", 2)) {\r\n                BukkitImplDeprecations.biomeSpawnableTag.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                entityTypes = object.biome.getMonsterEntities();\r\n            }\r\n            else if (attribute.startsWith(\"water\", 2)) {\r\n                BukkitImplDeprecations.biomeSpawnableTag.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                entityTypes = object.biome.getWaterEntities();\r\n            }\r\n            else {\r\n                String type = attribute.hasParam() ? CoreUtilities.toLowerCase(attribute.getParam()) : \"all\";\r\n                entityTypes = switch (type) {\r\n                    case \"ambient\" -> object.biome.getAmbientEntities();\r\n                    case \"creatures\" -> object.biome.getCreatureEntities();\r\n                    case \"monsters\" -> object.biome.getMonsterEntities();\r\n                    case \"water\" -> object.biome.getWaterEntities();\r\n                    default -> object.biome.getAllEntities();\r\n                };\r\n            }\r\n            return new ListTag(entityTypes, ElementTag::new);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <BiomeTag.foliage_color>\r\n        // @returns ColorTag\r\n        // @mechanism BiomeTag.foliage_color\r\n        // @description\r\n        // Returns the approximate foliage color of this biome. Foliage includes leaves and vines.\r\n        // The \"swamp\", \"mangrove_swamp\", \"badlands\", \"wooded_badlands\", and \"eroded_badlands\" biomes are the only biomes with hard-coded foliage colors.\r\n        // Biomes with no set foliage color already will have their foliage colors based on temperature and humidity of the biome.\r\n        // -->\r\n        tagProcessor.registerTag(ColorTag.class, \"foliage_color\", (attribute, object) -> {\r\n            return ColorTag.fromRGB(object.biome.getFoliageColor());\r\n        });\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <BiomeTag.temperature_at[<location>]>\r\n            // @returns ElementTag(Decimal)\r\n            // @description\r\n            // Returns the temperature of a specific location in this biome.\r\n            // If this is less than 0.15, snow will form on the ground when weather occurs in the world and a layer of ice will form over water.\r\n            // Generally <@link tag LocationTag.temperature> should be preferred, other than some special cases.\r\n            // @example\r\n            // # Gives the player water if they are standing in a warm location.\r\n            // - if <player.location.biome.temperature_at[<player.location]> > 0.5:\r\n            //   - give water_bucket\r\n            // -->\r\n            tagProcessor.registerTag(ElementTag.class, LocationTag.class, \"temperature_at\", (attribute, object, param) -> {\r\n                return new ElementTag(object.biome.getTemperatureAt(param));\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <BiomeTag.downfall_at[<location>]>\r\n            // @returns ElementTag\r\n            // @description\r\n            // Returns this biome's downfall type at a location (for when a world has weather).\r\n            // This can be RAIN, SNOW, or NONE.\r\n            // Generally <@link tag LocationTag.downfall_type> should be preferred, other than some special cases.\r\n            // @example\r\n            // # Tells the linked player what the downfall type at their location is.\r\n            // - narrate \"The downfall type at your location is: <player.location.biome.downfall_at[<player.location>]>!\"\r\n            // -->\r\n            tagProcessor.registerTag(ElementTag.class, LocationTag.class, \"downfall_at\", (attribute, object, param) -> {\r\n                return new ElementTag(object.biome.getDownfallTypeAt(param));\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <BiomeTag.has_downfall>\r\n            // @returns ElementTag(Boolean)\r\n            // @mechanism BiomeTag.has_downfall\r\n            // @description\r\n            // Returns whether the biome has downfall (rain/snow).\r\n            // @example\r\n            // # Tells the linked player whether there's a possibility of rain.\r\n            // - if <player.location.biome.has_downfall>:\r\n            //   - narrate \"It might rain or snow!\"\r\n            // - else:\r\n            //   - narrate \"It will be dry.\"\r\n            // -->\r\n            tagProcessor.registerTag(ElementTag.class, \"has_downfall\", (attribute, object) -> {\r\n                return new ElementTag(object.biome.hasDownfall());\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <BiomeTag.fog_color>\r\n            // @returns ColorTag\r\n            // @mechanism BiomeTag.fog_color\r\n            // @description\r\n            // Returns the biome's fog color, which is visible when outside water (see also <@link tag BiomeTag.water_fog_color>).\r\n            // @example\r\n            // # Sends the player a message in their current biome's fog color.\r\n            // - narrate \"You are currently seeing fog that looks like <&color[<player.location.biome.fog_color>]>this!\"\r\n            // -->\r\n            tagProcessor.registerTag(ColorTag.class, \"fog_color\", (attribute, object) -> {\r\n                return ColorTag.fromRGB(object.biome.getFogColor());\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <BiomeTag.water_fog_color>\r\n            // @returns ColorTag\r\n            // @mechanism BiomeTag.water_fog_color\r\n            // @description\r\n            // Returns the biome's water fog color, which is visible when underwater (see also <@link tag BiomeTag.fog_color>).\r\n            // @example\r\n            // # Sends the player a message in their current biome's water fog color.\r\n            // - narrate \"If you are underwater, everything looks like <&color[<player.location.biome.water_fog_color>]>this!\"\r\n            // -->\r\n            tagProcessor.registerTag(ColorTag.class, \"water_fog_color\", (attribute, object) -> {\r\n                return ColorTag.fromRGB(object.biome.getWaterFogColor());\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object BiomeTag\r\n            // @name fog_color\r\n            // @input ColorTag\r\n            // @description\r\n            // Sets the biome's fog color, which is visible when outside water (see also <@link mechanism BiomeTag.water_fog_color>).\r\n            // @tags\r\n            // <BiomeTag.fog_color>\r\n            // @example\r\n            // # Makes the plains biome's fog color red permanently, using a server start event to keep it applied.\r\n            // on server start:\r\n            // - adjust <biome[plains]> fog_color:red\r\n            // -->\r\n            tagProcessor.registerMechanism(\"fog_color\", false, ColorTag.class, (object, mechanism, input) -> {\r\n                object.biome.setFogColor(input.asRGB());\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object BiomeTag\r\n            // @name water_fog_color\r\n            // @input ColorTag\r\n            // @description\r\n            // Sets the biome's water fog color, which is visible when underwater (see also <@link mechanism BiomeTag.fog_color>).\r\n            // @tags\r\n            // <BiomeTag.water_fog_color>\r\n            // @example\r\n            // # Makes the plains biome's water fog color fuchsia permanently, using a server start event to keep it applied.\r\n            // on server start:\r\n            // - adjust <biome[plains]> water_fog_color:fuchsia\r\n            // -->\r\n            tagProcessor.registerMechanism(\"water_fog_color\", false, ColorTag.class, (object, mechanism, input) -> {\r\n                object.biome.setWaterFogColor(input.asRGB());\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object BiomeTag\r\n            // @name has_downfall\r\n            // @input ElementTag(Boolean)\r\n            // @description\r\n            // Sets whether the biome has downfall (rain/snow).\r\n            // @tags\r\n            // <BiomeTag.has_downfall>\r\n            // @example\r\n            // # Disables downfall for the plains biome permanently, using a server start event to keep it applied.\r\n            // on server start:\r\n            // - adjust <biome[plains]> has_downfall:false\r\n            // -->\r\n            tagProcessor.registerMechanism(\"has_downfall\", false, ElementTag.class, (object, mechanism, input) -> {\r\n                if (mechanism.requireBoolean()) {\r\n                    object.biome.setHasDownfall(input.asBoolean());\r\n                }\r\n            });\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object BiomeTag\r\n        // @name foliage_color\r\n        // @input ColorTag\r\n        // @description\r\n        // Sets the foliage color of this biome. Foliage includes leaves and vines.\r\n        // Colors reset on server restart. For the change to take effect on the players' clients, they must quit and rejoin the server.\r\n        // @tags\r\n        // <BiomeTag.foliage_color>\r\n        // @example\r\n        // # Adjusts the foliage color of the plains biome permanently, using a server start event to keep it applied.\r\n        // # Now the leaves and vines will be a nice salmon-pink!\r\n        // on server start:\r\n        // - adjust <biome[plains]> foliage_color:#F48D8D\r\n        // -->\r\n        tagProcessor.registerMechanism(\"foliage_color\", false, ColorTag.class, (object, mechanism, color) -> {\r\n            object.biome.setFoliageColor(color.asRGB());\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object BiomeTag\r\n        // @name humidity\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the humidity for this biome server-wide.\r\n        // If this is greater than 0.85, fire has less chance to spread in this biome.\r\n        // Resets on server restart.\r\n        // @tags\r\n        // <BiomeTag.humidity>\r\n        // @example\r\n        // # Adjusts the humidity of the plains biome permanently, using a server start event to keep it applied.\r\n        // on server start:\r\n        // - adjust <biome[plains]> humidity:0.5\r\n        // -->\r\n        tagProcessor.registerMechanism(\"humidity\", false, ElementTag.class, (object, mechanism, input) -> {\r\n            if (mechanism.requireFloat()) {\r\n                object.biome.setHumidity(input.asFloat());\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object BiomeTag\r\n        // @name base_temperature\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the base temperature for this biome server-wide.\r\n        // This is used as a base for temperature calculations, but the end temperature is calculated per-location (see <@link tag BiomeTag.temperature_at>).\r\n        // Resets on server restart.\r\n        // @tags\r\n        // <BiomeTag.base_temperature>\r\n        // @example\r\n        // # Adjusts the temperature of the plains biome permanently, using a server start event to keep it applied.\r\n        // on server start:\r\n        // - adjust <biome[plains]> temperature:0.5\r\n        // -->\r\n        tagProcessor.registerMechanism(\"base_temperature\", false, ElementTag.class, (object, mechanism, input) -> {\r\n            if (mechanism.requireFloat()) {\r\n                object.biome.setBaseTemperature(input.asFloat());\r\n            }\r\n        }, \"temperature\");\r\n\r\n        // <--[mechanism]\r\n        // @object BiomeTag\r\n        // @name downfall_type\r\n        // @input ElementTag\r\n        // @deprecated This functionality was removed from Minecraft as of 1.19.\r\n        // @description\r\n        // Deprecated on 1.19+, as Minecraft removed the ability to set this value.\r\n        // Sets the downfall-type for this biome server-wide.\r\n        // This can be RAIN, SNOW, or NONE.\r\n        // Resets on server restart.\r\n        // @tags\r\n        // <BiomeTag.base_temperature>\r\n        // @example\r\n        // # Adjusts the downfall type of the plains biome permanently, using a server start event to keep it applied.\r\n        // on server start:\r\n        // - adjust <biome[plains]> temperature:-0.2\r\n        // - adjust <biome[plains]> downfall_type:SNOW\r\n        // -->\r\n        tagProcessor.registerMechanism(\"downfall_type\", false, ElementTag.class, (object, mechanism, input) -> {\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n                BukkitImplDeprecations.biomeSettingDownfallType.warn(mechanism.context);\r\n                return;\r\n            }\r\n            if (mechanism.requireEnum(BiomeNMS.DownfallType.class)) {\r\n                object.biome.setPrecipitation(input.asEnum(BiomeNMS.DownfallType.class));\r\n            }\r\n        });\r\n    }\r\n\r\n    public static ObjectTagProcessor<BiomeTag> tagProcessor = new ObjectTagProcessor<>();\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return tagProcessor.getObjectAttribute(this, attribute);\r\n    }\r\n\r\n    @Override\r\n    public void applyProperty(Mechanism mechanism) {\r\n        mechanism.echoError(\"Cannot apply properties to a biome!\");\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n        tagProcessor.processMechanism(this, mechanism);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/ChunkTag.java",
    "content": "package com.denizenscript.denizen.objects;\n\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.utilities.flags.DataPersistenceFlagTracker;\nimport com.denizenscript.denizen.utilities.flags.LocationFlagSearchHelper;\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\nimport com.denizenscript.denizencore.flags.FlaggableObject;\nimport com.denizenscript.denizencore.objects.*;\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport org.bukkit.Chunk;\nimport org.bukkit.ChunkSnapshot;\nimport org.bukkit.Location;\nimport org.bukkit.World;\nimport org.bukkit.block.BlockState;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.entity.Player;\nimport org.bukkit.plugin.Plugin;\n\nimport java.util.*;\n\npublic class ChunkTag implements ObjectTag, Adjustable, FlaggableObject {\n\n    // <--[ObjectType]\n    // @name ChunkTag\n    // @prefix ch\n    // @base ElementTag\n    // @implements FlaggableObject\n    // @ExampleTagBase player.location.chunk\n    // @ExampleValues <player.location.chunk>\n    // @ExampleForReturns\n    // - chunkload %VALUE%\n    // @ExampleForReturns\n    // - adjust %VALUE% set_all_biomes:desert\n    // @format\n    // The identity format for chunks is <x>,<z>,<world>\n    // For example, 'ch@5,3,world'.\n    //\n    // @description\n    // A ChunkTag represents a chunk in the world.\n    //\n    // Note that the X/Z pair are chunk coordinates, not block coordinates.\n    // To convert from block coordinates to chunk coordinates, divide by 16 and round downward.\n    // Note that negative chunks are one unit lower than you might expect.\n    // To understand why, simply look at chunks on a number line...\n    //  x      x      x      x      x\n    // -2     -1    b 0 a    1      2\n    // The block 'a' at block position '1' is in chunk '0', but the block 'b' at block position '-1' is in chunk '-1'.\n    // As otherwise (if 'b' was in chunk '0'), chunk '0' would be double-wide (32 blocks wide instead of the standard 16).\n    //\n    // For example, block at X,Z 32,67 is in the chunk at X,Z 2,4\n    // And the block at X,Z -32,-67 is in the chunk at X,Z -2,-5\n    //\n    // This object type is flaggable.\n    // Flags on this object type will be stored in the chunk file inside the world folder.\n    //\n    // -->\n\n    //////////////////\n    //    OBJECT FETCHER\n    ////////////////\n\n    @Fetchable(\"ch\")\n    public static ChunkTag valueOf(String string, TagContext context) {\n        if (string == null) {\n            return null;\n        }\n        string = CoreUtilities.toLowerCase(string).replace(\"ch@\", \"\");\n        String[] parts = string.split(\",\");\n        if (parts.length != 3) {\n            if (context == null || context.showErrors()) {\n                Debug.log(\"Minor: valueOf ChunkTag unable to handle malformed format: \" + \"ch@\" + string);\n            }\n            return null;\n        }\n        try {\n            return new ChunkTag(new WorldTag(parts[2]), Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));\n        }\n        catch (Exception e) {\n            if (context == null || context.showErrors()) {\n                Debug.log(\"Minor: valueOf ChunkTag returning null: \" + \"ch@\" + string);\n            }\n            return null;\n        }\n    }\n\n    public static boolean matches(String string) {\n        return valueOf(string, CoreUtilities.noDebugContext) != null;\n    }\n\n    int chunkX, chunkZ;\n\n    WorldTag world;\n\n    Chunk cachedChunk;\n\n    public Chunk getChunkForTag(Attribute attribute) {\n        if (cachedChunk != null) {\n            return cachedChunk;\n        }\n        NMSHandler.chunkHelper.changeChunkServerThread(getBukkitWorld());\n        try {\n            if (!isLoaded()) {\n                if (!attribute.hasAlternative()) {\n                    Debug.echoError(\"Cannot get chunk at \" + chunkX + \", \" + chunkZ + \": Chunk is not loaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                }\n                return null;\n            }\n            return getChunk();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getBukkitWorld());\n        }\n    }\n\n    public Chunk getChunk() {\n        if (cachedChunk == null) {\n            cachedChunk = getBukkitWorld().getChunkAt(chunkX, chunkZ);\n        }\n        return cachedChunk;\n    }\n\n    /**\n     * ChunkTag can be constructed with a Chunk\n     *\n     * @param chunk The chunk to use.\n     */\n    public ChunkTag(Chunk chunk) {\n        this.cachedChunk = chunk;\n        world = new WorldTag(chunk.getWorld());\n        chunkX = chunk.getX();\n        chunkZ = chunk.getZ();\n    }\n\n    public ChunkTag(WorldTag world, int x, int z) {\n        this.world = world;\n        chunkX = x;\n        chunkZ = z;\n    }\n\n    /**\n     * ChunkTag can be constructed with a Location (or LocationTag)\n     *\n     * @param location The location of the chunk.\n     */\n    public ChunkTag(Location location) {\n        world = location instanceof LocationTag ? new WorldTag(((LocationTag) location).getWorldName()) : new WorldTag(location.getWorld());\n        chunkX = location.getBlockX() >> 4;\n        chunkZ = location.getBlockZ() >> 4;\n    }\n\n    public LocationTag getCenter() {\n        return new LocationTag(getWorldName(), getX() * 16 + 8, 128, getZ() * 16 + 8, 0, 0);\n    }\n\n    public int getX() {\n        return chunkX;\n    }\n\n    public int getZ() {\n        return chunkZ;\n    }\n\n    public String getWorldName() {\n        return world.getName();\n    }\n\n    public World getBukkitWorld() {\n        return world.getWorld();\n    }\n\n    String prefix = \"Chunk\";\n\n    @Override\n    public String getPrefix() {\n        return prefix;\n    }\n\n    @Override\n    public ChunkTag setPrefix(String prefix) {\n        this.prefix = prefix;\n        return this;\n    }\n\n    @Override\n    public boolean isUnique() {\n        return true;\n    }\n\n    @Override\n    public String identify() {\n        return \"ch@\" + getX() + ',' + getZ() + ',' + getWorldName();\n    }\n\n    @Override\n    public String identifySimple() {\n        return identify();\n    }\n\n    @Override\n    public String toString() {\n        return identify();\n    }\n\n    @Override\n    public Object getJavaObject() {\n        return getChunk();\n    }\n\n    public boolean isLoaded() {\n        if (getBukkitWorld() == null) {\n            return false;\n        }\n        return getBukkitWorld().isChunkLoaded(chunkX, chunkZ);\n    }\n\n    public boolean isLoadedSafe() {\n        try {\n            NMSHandler.chunkHelper.changeChunkServerThread(getBukkitWorld());\n            return isLoaded();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getBukkitWorld());\n        }\n    }\n\n    @Override\n    public AbstractFlagTracker getFlagTracker() {\n        return new DataPersistenceFlagTracker(getChunk(), \"flag_chunk_\");\n    }\n\n    @Override\n    public AbstractFlagTracker getFlagTrackerForTag() {\n        if (!isLoadedSafe()) {\n            return null;\n        }\n        return getFlagTracker();\n    }\n\n    @Override\n    public void reapplyTracker(AbstractFlagTracker tracker) {\n        // Nothing to do.\n    }\n\n    @Override\n    public String getReasonNotFlaggable() {\n        return \"is the chunk loaded?\";\n    }\n\n    public static void register() {\n\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\n\n        // <--[tag]\n        // @attribute <ChunkTag.add[<#>,<#>]>\n        // @returns ChunkTag\n        // @description\n        // Returns the chunk with the specified coordinates added to it.\n        // @example\n        // # Adds 10 to the X and Z coordinates of the player's current chunk and loads it.\n        // - chunkload <player.location.chunk.add[10,10]>\n        // -->\n        tagProcessor.registerTag(ChunkTag.class, ElementTag.class, \"add\", (attribute, object, addCoords) -> {\n            List<String> coords = CoreUtilities.split(addCoords.toString(), ',');\n            if (coords.size() < 2) {\n                attribute.echoError(\"The tag ChunkTag.add[<#>,<#>] requires two values!\");\n                return null;\n            }\n            if (!ArgumentHelper.matchesInteger(coords.get(0)) || !ArgumentHelper.matchesInteger(coords.get(1))) {\n                attribute.echoError(\"Input to ChunkTag.add[x,z] is not a valid integer pair!\");\n                return null;\n            }\n            int x = Integer.parseInt(coords.get(0));\n            int z = Integer.parseInt(coords.get(1));\n\n            return new ChunkTag(object.world, object.chunkX + x, object.chunkZ + z);\n\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.sub[<#>,<#>]>\n        // @returns ChunkTag\n        // @description\n        // Returns the chunk with the specified coordinates subtracted from it.\n        // @example\n        // # Subtracts 10 from the X and Z coordinates of the player's current chunk and loads it.\n        // - chunkload <player.location.chunk.sub[10,10]>\n        // -->\n        tagProcessor.registerTag(ChunkTag.class, ElementTag.class, \"sub\", (attribute, object, subCoords) -> {\n            List<String> coords = CoreUtilities.split(subCoords.toString(), ',');\n            if (coords.size() < 2) {\n                attribute.echoError(\"The tag ChunkTag.sub[<#>,<#>] requires two values!\");\n                return null;\n            }\n            if (!ArgumentHelper.matchesInteger(coords.get(0)) || !ArgumentHelper.matchesInteger(coords.get(1))) {\n                attribute.echoError(\"Input to ChunkTag.sub[x,z] is not a valid integer pair!\");\n                return null;\n            }\n            int x = Integer.parseInt(coords.get(0));\n            int z = Integer.parseInt(coords.get(1));\n\n            return new ChunkTag(object.world, object.chunkX - x, object.chunkZ - z);\n\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.is_generated>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns true if the chunk has already been generated.\n        // @example\n        // # Loops though the chunks surrounding the player in a 20x20 radius and loads the chunk if it has not been generated yet.\n        // # Loading the chunk will generate it if it has not been generated already.\n        // - repeat 20 from:-10 as:x:\n        //     - repeat 20 from:-10 as:z:\n        //         - if !<player.location.chunk.add[<[x]>,<[z]>].is_generated>:\n        //             - chunkload <player.location.chunk.add[<[x]>,<[z]>]>\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_generated\", (attribute, object) -> {\n            return new ElementTag(object.getBukkitWorld().isChunkGenerated(object.chunkX, object.chunkZ));\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.is_loaded>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns true if the chunk is currently loaded into memory.\n        // @example\n        // # Loops though the chunks surrounding the player in a 20x20 radius and loads the chunk if it is not already loaded.\n        // - repeat 20 from:-10 as:x:\n        //     - repeat 20 from:-10 as:z:\n        //         - if !<player.location.chunk.add[<[x]>,<[z]>].is_loaded>:\n        //             - chunkload <player.location.chunk.add[<[x]>,<[z]>]>\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_loaded\", (attribute, object) -> {\n            return new ElementTag(object.isLoadedSafe());\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.force_loaded>\n        // @returns ElementTag(Boolean)\n        // @mechanism ChunkTag.force_loaded\n        // @description\n        // Returns whether the chunk is forced to stay loaded at all times.\n        // @example\n        // - if <player.location.chunk.force_loaded>:\n        //     - narrate \"This chunk is being forced to stay loaded!\"\n        // - else:\n        //     - narrate \"This chunk is NOT being forced to stay loaded!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"force_loaded\", (attribute, object) -> {\n            if (!object.isLoadedSafe()) {\n                return new ElementTag(false);\n            }\n            Chunk chunk = object.getChunkForTag(attribute);\n            return new ElementTag(chunk != null && chunk.isForceLoaded());\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.plugin_tickets>\n        // @returns ListTag(PluginTag)\n        // @mechanism ChunkTag.clear_plugin_tickets\n        // @description\n        // Returns a list of plugins that are keeping this chunk loaded.\n        // This is related to the <@link command chunkload> command.\n        // @example\n        // # Narrates the list of plugin names keeping the player's chunk loaded formatted into readable format.\n        // # Example: \"Plugins keeping your chunk loaded: Denizen and Citizens\".\n        // - narrate \"Plugins keeping your chunk loaded: <player.location.chunk.plugin_tickets.formatted>\"\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"plugin_tickets\", (attribute, object) -> {\n            if (!object.isLoadedSafe()) {\n                return new ListTag();\n            }\n            Chunk chunk = object.getChunkForTag(attribute);\n            ListTag result = new ListTag();\n            for (Plugin plugin : chunk.getPluginChunkTickets()) {\n                result.addObject(new PluginTag(plugin));\n            }\n            return result;\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.x>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the x coordinate of the chunk.\n        // @example\n        // # Narrates the player's chunk's X coordinate.\n        // # For example, if the player was in <chunk[5,10,world]>, the X coordinate would be \"5\".\n        // - narrate \"Your current X chunk coordinate is: <player.location.chunk.x>!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"x\", (attribute, object) -> {\n            return new ElementTag(object.chunkX);\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.z>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the z coordinate of the chunk.\n        // @example\n        // # Narrates the player's chunk's Z coordinate.\n        // # For example, if the player was in <chunk[5,10,world]>, the Z coordinate would be \"10\".\n        // - narrate \"Your current Z chunk coordinate is: <player.location.chunk.z>!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"z\", (attribute, object) -> {\n            return new ElementTag(object.chunkZ);\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.world>\n        // @returns WorldTag\n        // @description\n        // Returns the world associated with the chunk.\n        // @example\n        // # Narrates the name of the player's chunk's associated world.\n        // # For example, if the player was in <chunk[5,10,world]>, the world the chunk is in would be \"world\".\n        // - narrate \"The world your chunk is in is: <player.location.chunk.world.name>!\"\n        // -->\n        tagProcessor.registerTag(WorldTag.class, \"world\", (attribute, object) -> {\n            return object.world;\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.xz>\n        // @returns ElementTag\n        // @description\n        // Returns the X,Z coordinates of the chunk in the format \"X,Z\".\n        // @example\n        // # Narrates the player's chunk's X,Z coordinate pair.\n        // # For example, if the player was in <chunk[5,10,world]>, this will be \"5,10\"\n        // - narrate \"Your current chunk coordinate is: <player.location.chunk.xz>!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"xz\", (attribute, object) -> {\n            return new ElementTag(object.chunkX + \",\" + object.chunkZ);\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.simple>\n        // @returns ElementTag\n        // @description\n        // Returns the X,Z coordinates and world name of the chunk in the format \"X,Z,world\".\n        // @example\n        // # Narrates the player's chunk's X,Z,World coordinates.\n        // # For example, if the player was in <chunk[5,10,world]>, this will be \"5,10,world\"\n        // - narrate \"Your current chunk is: <player.location.chunk.simple>!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"simple\", (attribute, object) -> {\n            return new ElementTag(object.chunkX + \",\" + object.chunkZ + \",\" + object.getWorldName());\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.cuboid>\n        // @returns CuboidTag\n        // @description\n        // Returns a cuboid of this chunk.\n        // @example\n        // # Plays the \"flame\" effect at the player's chunk as a cuboid outlined along the player's Y coordinate.\n        // - playeffect effect:flame at:<player.location.chunk.cuboid.outline_2d[<player.location.y>]> offset:0.0\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"cuboid\", (attribute, object) -> {\n            int yMin = 0, yMax = 255;\n            World world = object.getBukkitWorld();\n            if (world != null) {\n                yMin = world.getMinHeight();\n                yMax = world.getMaxHeight();\n            }\n            return new CuboidTag(new LocationTag(object.getWorldName(), object.getX() * 16, yMin, object.getZ() * 16, 0, 0),\n                    new LocationTag(object.getWorldName(), object.getX() * 16 + 15, yMax, object.getZ() * 16 + 15, 0, 0));\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.tile_entities>\n        // @returns ListTag(LocationTag)\n        // @description\n        // Returns a list of tile entity locations in the chunk.\n        // @example\n        // # Spawns a debugblock to highlight every tile entity in the chunk.\n        // - debugblock <player.location.chunk.tile_entities>\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"tile_entities\", (attribute, object) -> {\n            ListTag tiles = new ListTag();\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            try {\n                NMSHandler.chunkHelper.changeChunkServerThread(object.getBukkitWorld());\n                for (BlockState block : chunk.getTileEntities()) {\n                    tiles.addObject(new LocationTag(block.getLocation()));\n                }\n            }\n            finally {\n                NMSHandler.chunkHelper.restoreServerThread(object.getBukkitWorld());\n            }\n            return tiles;\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.entities[(<entity>|...)]>\n        // @returns ListTag(EntityTag)\n        // @description\n        // Returns a list of entities in the chunk.\n        // Optionally specify entity types to filter down to.\n        // @example\n        // # Loops though the entities found in the player's chunk and narrates the name and location of it.\n        // - foreach <player.location.chunk.entities> as:entity:\n        //     - narrate \"Found a <[entity].name> at <[entity].location.simple>!\"\n        // @example\n        // # Loops though the axolotls found in the player's chunk and narrates the name and location of it.\n        // - foreach <player.location.chunk.entities[axolotl]> as:entity:\n        //     - narrate \"Found an axolotl at <[entity].location.simple>!\"\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"entities\", (attribute, object) -> {\n            ListTag entities = new ListTag();\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            ListTag typeFilter = attribute.hasParam() ? attribute.paramAsType(ListTag.class) : null;\n            try {\n                NMSHandler.chunkHelper.changeChunkServerThread(object.getBukkitWorld());\n                for (Entity entity : chunk.getEntities()) {\n                    EntityTag current = new EntityTag(entity);\n                    if (typeFilter != null) {\n                        for (String type : typeFilter) {\n                            if (current.comparedTo(type)) {\n                                entities.addObject(current.getDenizenObject());\n                                break;\n                            }\n                        }\n                    }\n                    else {\n                        entities.addObject(current.getDenizenObject());\n                    }\n                }\n            }\n            finally {\n                NMSHandler.chunkHelper.restoreServerThread(object.getBukkitWorld());\n            }\n            return entities;\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.living_entities>\n        // @returns ListTag(EntityTag)\n        // @description\n        // Returns a list of living entities in the chunk.\n        // This includes Players, mobs, NPCs, etc., but excludes dropped items, experience orbs, etc.\n        // @example\n        // # Loops though the living entities found in the player's chunk and narrates the name and location of it.\n        // - foreach <player.location.chunk.living_entities> as:entity:\n        //     - narrate \"Found a <[entity].name> at <[entity].location.simple>!\"\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"living_entities\", (attribute, object) -> {\n            ListTag entities = new ListTag();\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            try {\n                NMSHandler.chunkHelper.changeChunkServerThread(object.getBukkitWorld());\n                for (Entity ent : chunk.getEntities()) {\n                    if (ent instanceof LivingEntity) {\n                        entities.addObject(new EntityTag(ent).getDenizenObject());\n                    }\n                }\n            }\n            finally {\n                NMSHandler.chunkHelper.restoreServerThread(object.getBukkitWorld());\n            }\n            return entities;\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.players>\n        // @returns ListTag(PlayerTag)\n        // @description\n        // Returns a list of players in the chunk.\n        // @example\n        // # Narrates a list of players excluding the original player in the chunk formatted into a readable format.\n        // # For example, this can return: \"steve, alex, john, and jane\".\n        // - narrate \"Wow! Look at all these players in the same chunk as you: <player.location.chunk.players.exclude[<player>].formatted>!\"\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"players\", (attribute, object) -> {\n            ListTag entities = new ListTag();\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            for (Entity ent : chunk.getEntities()) {\n                if (EntityTag.isPlayer(ent)) {\n                    entities.addObject(new PlayerTag((Player) ent));\n                }\n            }\n            return entities;\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.height_map>\n        // @returns ListTag\n        // @description\n        // Returns a list of the height of each block in the chunk.\n        // @example\n        // # Narrates the height as a number of the highest block in the chunk.\n        // - narrate \"The block with the tallest height has a height of <player.location.chunk.height_map.highest>!\"\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"height_map\", (attribute, object) -> {\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            int[] heightMap = NMSHandler.chunkHelper.getHeightMap(chunk);\n            List<String> height_map = new ArrayList<>(heightMap.length);\n            for (int i : heightMap) {\n                height_map.add(String.valueOf(i));\n            }\n            return new ListTag(height_map);\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.average_height>\n        // @returns ElementTag(Decimal)\n        // @description\n        // Returns the average height of the blocks in the chunk.\n        // @example\n        // # Narrates the average height of each block in the player's chunk rounded.\n        // - narrate \"The average height of blocks in this chunk is <player.location.chunk.average_height.round>!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"average_height\", (attribute, object) -> {\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            int[] heightMap = NMSHandler.chunkHelper.getHeightMap(chunk);\n            int sum = 0;\n            for (int i : heightMap) {\n                sum += i;\n            }\n            return new ElementTag(((double) sum) / heightMap.length);\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.is_flat[(<#>)]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Scans the heights of the blocks to check variance between them.\n        // If no number is supplied, is_flat will return true if all the blocks are less than 2 blocks apart in height.\n        // Specifying a number will modify the number criteria for determining if it is flat.\n        // @example\n        // - if <player.location.chunk.is_flat>:\n        //     - narrate \"Wow! That is a flat chunk!\"\n        // - else:\n        //     - narrate \"Watch out! This chunk has a more rugged terrain!\"\n        // @example\n        // - if <player.location.chunk.is_flat[4]>:\n        //     - narrate \"Wow! This chunk's blocks are all less than 4 blocks apart in height!\"\n        // - else:\n        //     - narrate \"This chunk's blocks do not meet the criteria of being flat.\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_flat\", (attribute, object) -> {\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            int[] heightMap = NMSHandler.chunkHelper.getHeightMap(chunk);\n            int tolerance = 2;\n            if (attribute.hasParam() && ArgumentHelper.matchesInteger(attribute.getParam())) {\n                tolerance = attribute.getIntParam();\n            }\n            int x = heightMap[0];\n            for (int i : heightMap) {\n                if (Math.abs(x - i) > tolerance) {\n                    return new ElementTag(false);\n                }\n            }\n\n            return new ElementTag(true);\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.surface_blocks>\n        // @returns ListTag(LocationTag)\n        // @description\n        // Returns a list of the highest non-air surface blocks in the chunk.\n        // @example\n        // # Spawns a creeper above a random surface block to prevent the creeper from suffocating.\n        // - spawn creeper <player.location.chunk.surface_blocks.random.above>\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"surface_blocks\", (attribute, object) -> {\n            ListTag surface_blocks = new ListTag();\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            ChunkSnapshot snapshot = chunk.getChunkSnapshot();\n            int sub = NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18) ? 0 : 1;\n            for (int x = 0; x < 16; x++) {\n                for (int z = 0; z < 16; z++) {\n                    surface_blocks.addObject(new LocationTag(chunk.getWorld(), chunk.getX() << 4 | x, snapshot.getHighestBlockYAt(x, z) - sub, chunk.getZ() << 4 | z));\n                }\n            }\n            return surface_blocks;\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.blocks_flagged[<flag_name>]>\n        // @returns ListTag(LocationTag)\n        // @description\n        // Gets a list of all block locations with a specified flag within the CuboidTag.\n        // Searches the internal flag lists, rather than through all possible blocks.\n        // @example\n        // # Spawns a debugblock to highlight every block in the chunk flagged \"my_flag\"\n        // - debugblock <player.location.chunk.blocks_flagged[my_flag]>\n        // -->\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"blocks_flagged\", (attribute, object, flagNameInput) -> {\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            String flagName = CoreUtilities.toLowerCase(flagNameInput.toString());\n            ListTag blocks = new ListTag();\n            LocationFlagSearchHelper.getFlaggedLocations(chunk, flagName, (loc) -> {\n                blocks.addObject(new LocationTag(loc));\n            });\n            return blocks;\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.spawn_slimes>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the chunk is a specially located 'slime spawner' chunk.\n        // @example\n        // - if <player.location.chunk.spawn_slimes>:\n        //     - narrate \"Watch out! Slimes can spawn here!\"\n        // - else:\n        //     - narrate \"No slimes can spawn here! You are safe for now!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"spawn_slimes\", (attribute, object) -> {\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            return new ElementTag(chunk.isSlimeChunk());\n        });\n\n        // <--[tag]\n        // @attribute <ChunkTag.inhabited_time>\n        // @returns DurationTag\n        // @mechanism ChunkTag.inhabited_time\n        // @description\n        // Returns the total time the chunk has been inhabited for.\n        // This is a primary deciding factor in the \"local difficulty\" setting.\n        // @example\n        // # Narrates the time inhabited in the chunk by the player formatted into words.\n        // # For example: \"You have been in this chunk for a total of 2 hours 26 minutes!\"\n        // - narrate \"You have been in this chunk for a total of <player.location.chunk.inhabited_time.formatted_words>!\"\n        // -->\n        tagProcessor.registerTag(DurationTag.class, \"inhabited_time\", (attribute, object) -> {\n            Chunk chunk = object.getChunkForTag(attribute);\n            if (chunk == null) {\n                return null;\n            }\n            return new DurationTag(chunk.getInhabitedTime());\n        });\n    }\n\n    public static ObjectTagProcessor<ChunkTag> tagProcessor = new ObjectTagProcessor<>();\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n        return tagProcessor.getObjectAttribute(this, attribute);\n    }\n\n    public void applyProperty(Mechanism mechanism) {\n        mechanism.echoError(\"Cannot apply properties to a chunk!\");\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ChunkTag\n        // @name inhabited_time\n        // @input DurationTag\n        // @description\n        // Changes the amount of time the chunk has been inhabited for.\n        // This is a primary deciding factor in the \"local difficulty\" setting.\n        // @tags\n        // <ChunkTag.inhabited_time>\n        // @example\n        // - adjust <player.location.chunk> inhabited_time:5d\n        // -->\n        if (mechanism.matches(\"inhabited_time\") && mechanism.requireObject(DurationTag.class)) {\n            getChunk().setInhabitedTime(mechanism.valueAsType(DurationTag.class).getTicks());\n        }\n\n        // <--[mechanism]\n        // @object ChunkTag\n        // @name unload\n        // @input None\n        // @description\n        // Removes a chunk from memory.\n        // @tags\n        // <ChunkTag.is_loaded>\n        // @example\n        // - adjust <player.location.chunk.add[20,20]> unload\n        // -->\n        if (mechanism.matches(\"unload\")) {\n            getBukkitWorld().unloadChunk(getX(), getZ(), true);\n        }\n\n        // <--[mechanism]\n        // @object ChunkTag\n        // @name unload_without_saving\n        // @input None\n        // @description\n        // Removes a chunk from memory without saving any recent changes.\n        // @tags\n        // <chunk.is_loaded>\n        // @example\n        // - adjust <player.location.chunk.add[20,20]> unload_without_saving\n        // -->\n        if (mechanism.matches(\"unload_without_saving\")) {\n            getBukkitWorld().unloadChunk(getX(), getZ(), false);\n        }\n\n        // <--[mechanism]\n        // @object ChunkTag\n        // @name force_loaded\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether this plugin is force-loaded or not.\n        // Unless you have a specific reason to use this, prefer <@link command chunkload>.\n        // @tags\n        // <ChunkTag.force_loaded>\n        // @example\n        // - adjust <player.location.chunk> force_loaded:true\n        // -->\n        if (mechanism.matches(\"force_loaded\") && mechanism.requireBoolean()) {\n            getChunk().setForceLoaded(mechanism.getValue().asBoolean());\n        }\n\n        // <--[mechanism]\n        // @object ChunkTag\n        // @name clear_plugin_tickets\n        // @input None\n        // @description\n        // Forcibly removes all plugin tickets from this chunk, usually allowing it to unload.\n        // This is usually a bad idea.\n        // @tags\n        // <ChunkTag.plugin_tickets>\n        // @example\n        // # Make sure you know what you are doing before using this mechanism.\n        // - adjust <player.location.chunk> clear_plugin_tickets\n        // -->\n        if (mechanism.matches(\"clear_plugin_tickets\")) {\n            for (Plugin plugin : new ArrayList<>(getChunk().getPluginChunkTickets())) {\n                getChunk().removePluginChunkTicket(plugin);\n            }\n        }\n\n        // <--[mechanism]\n        // @object ChunkTag\n        // @name load\n        // @input None\n        // @description\n        // Loads a chunk into memory.\n        // @tags\n        // <ChunkTag.is_loaded>\n        // @example\n        // - adjust <player.location.chunk.add[100,0]> load\n        // -->\n        if (mechanism.matches(\"load\")) {\n            getChunk().load(true);\n        }\n\n        // <--[mechanism]\n        // @object ChunkTag\n        // @name regenerate\n        // @input None\n        // @description\n        // Causes the chunk to be entirely deleted and reformed from the world's seed.\n        // At time of writing this method only works as expected on Paper, and will error on Spigot.\n        // @example\n        // - adjust <player.location.chunk> regenerate\n        // -->\n        if (mechanism.matches(\"regenerate\")) {\n            getBukkitWorld().regenerateChunk(getX(), getZ());\n        }\n\n        // <--[mechanism]\n        // @object ChunkTag\n        // @name refresh_chunk\n        // @input None\n        // @description\n        // Refreshes the chunk, sending any changed properties to players.\n        // @example\n        // - adjust <player.location.chunk> refresh_chunk\n        // -->\n        if (mechanism.matches(\"refresh_chunk\")) {\n            final int chunkX = getX();\n            final int chunkZ = getZ();\n            getBukkitWorld().refreshChunk(chunkX, chunkZ);\n        }\n\n        // <--[mechanism]\n        // @object ChunkTag\n        // @name refresh_chunk_sections\n        // @input None\n        // @deprecated for MC 1.18+, use 'refresh_chunk'\n        // @description\n        // Refreshes all 16x16x16 chunk sections within the chunk.\n        // For MC 1.18+, prefer <@link mechanism ChunkTag.refresh_chunk>\n        // @example\n        // - adjust <player.location.chunk> refresh_chunk_sections\n        // -->\n        if (mechanism.matches(\"refresh_chunk_sections\")) {\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\n                BukkitImplDeprecations.chunkRefreshSections.warn(mechanism.context);\n                getBukkitWorld().refreshChunk(chunkX, chunkZ);\n            }\n            else {\n                NMSHandler.chunkHelper.refreshChunkSections(getChunk());\n            }\n        }\n\n        // <--[mechanism]\n        // @object ChunkTag\n        // @name set_all_biomes\n        // @input BiomeTag\n        // @description\n        // Sets all biomes in the chunk to the given biome.\n        // @example\n        // - adjust <player.location.chunk> set_all_biomes:<biome[savanna]>\n        // # Allow players to see the biome change:\n        // - adjust <player.location.chunk> refresh_chunk\n        // -->\n        if (mechanism.matches(\"set_all_biomes\") && mechanism.requireObject(BiomeTag.class)) {\n            NMSHandler.chunkHelper.setAllBiomes(getChunk(), mechanism.valueAsType(BiomeTag.class).getBiome());\n        }\n\n        tagProcessor.processMechanism(this, mechanism);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/CuboidTag.java",
    "content": "package com.denizenscript.denizen.objects;\n\nimport com.denizenscript.denizen.utilities.NotedAreaTracker;\nimport com.denizenscript.denizencore.tags.TagManager;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\nimport com.denizenscript.denizencore.flags.FlaggableObject;\nimport com.denizenscript.denizencore.flags.SavableMapFlagTracker;\nimport com.denizenscript.denizencore.objects.*;\nimport com.denizenscript.denizen.utilities.Settings;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.notable.Notable;\nimport com.denizenscript.denizencore.objects.notable.Note;\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\nimport org.bukkit.Location;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.util.BoundingBox;\nimport org.bukkit.util.Vector;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.function.Predicate;\n\npublic class CuboidTag implements ObjectTag, Cloneable, Notable, Adjustable, AreaContainmentObject, FlaggableObject {\n\n    // <--[ObjectType]\n    // @name CuboidTag\n    // @prefix cu\n    // @base ElementTag\n    // @implements FlaggableObject, AreaObject\n    // @ExampleTagBase cuboid[my_noted_cuboid]\n    // @ExampleValues my_cuboid_note\n    // @ExampleForReturns\n    // - note %VALUE% as:my_new_cuboid\n    // @format\n    // The identity format for cuboids is <world>,<x1>,<y1>,<z1>,<x2>,<y2>,<z2>\n    // Multi-member cuboids can simply continue listing x,y,z pairs.\n    // For example, 'cu@space,1,2,3,4,5,6'.\n    //\n    // @description\n    // A CuboidTag represents a cuboidal region in the world.\n    //\n    // The word 'cuboid' means a less strict cube.\n    // Basically: a \"cuboid\" is to a 3D \"cube\" what a \"rectangle\" is to a 2D \"square\".\n    //\n    // One 'cuboid' consists of two points: the low point and a high point.\n    // a CuboidTag can contain as many cuboids within itself as needed (this allows forming more complex shapes from a single CuboidTag).\n    //\n    // Note that the coordinates used are inclusive, meaning that a CuboidTag always includes the blocks identified as the low and high corner points.\n    // This means for example that a cuboid from \"5,5,5\" to \"5,5,5\" will contain one full block, and have a size of \"1,1,1\".\n    //\n    // This object type can be noted.\n    //\n    // This object type is flaggable when it is noted.\n    // Flags on this object type will be stored in the notables.yml file.\n    //\n    // @Matchable\n    // Refer to <@link objecttype areaobject>'s matchable list.\n    // -->\n\n    @Override\n    public CuboidTag clone() {\n        CuboidTag cuboid;\n        try {\n            cuboid = (CuboidTag) super.clone();\n        }\n        catch (CloneNotSupportedException ex) { // Should never happen.\n            Debug.echoError(ex);\n            cuboid = new CuboidTag();\n        }\n        cuboid.noteName = null;\n        cuboid.priorNoteName = null;\n        cuboid.flagTracker = null;\n        cuboid.pairs = new ArrayList<>(pairs.size());\n        for (LocationPair pair : pairs) {\n            cuboid.pairs.add(new LocationPair(pair.low.clone(), pair.high.clone()));\n        }\n        return cuboid;\n    }\n\n    @Fetchable(\"cu\")\n    public static CuboidTag valueOf(String string, TagContext context) {\n        if (string == null) {\n            return null;\n        }\n        if (CoreUtilities.toLowerCase(string).startsWith(\"cu@\")) {\n            string = string.substring(\"cu@\".length());\n        }\n        if (!TagManager.isStaticParsing) {\n            Notable noted = NoteManager.getSavedObject(string);\n            if (noted instanceof CuboidTag) {\n                return (CuboidTag) noted;\n            }\n        }\n        if (CoreUtilities.contains(string, '@')) {\n            if (CoreUtilities.contains(string, '|') && string.contains(\"l@\")) {\n                Debug.echoError(\"Warning: likely improperly constructed CuboidTag '\" + string + \"' - use to_cuboid\");\n            }\n            else {\n                return null;\n            }\n        }\n        if (CoreUtilities.contains(string, '|')) {\n            ListTag positions = ListTag.valueOf(string, context);\n            if (positions.size() > 1\n                    && LocationTag.matches(positions.get(0))\n                    && LocationTag.matches(positions.get(1))) {\n                if (positions.size() % 2 != 0) {\n                    if (context == null || context.showErrors()) {\n                        Debug.echoError(\"valueOf CuboidTag returning null (Uneven number of locations): '\" + string + \"'.\");\n                    }\n                    return null;\n                }\n                CuboidTag toReturn = new CuboidTag();\n                for (int i = 0; i < positions.size(); i += 2) {\n                    LocationTag pos_1 = LocationTag.valueOf(positions.get(i), context);\n                    LocationTag pos_2 = LocationTag.valueOf(positions.get(i + 1), context);\n                    if (pos_1 == null || pos_2 == null) {\n                        if ((context == null || context.showErrors()) && !TagManager.isStaticParsing) {\n                            Debug.echoError(\"valueOf in CuboidTag returning null (null locations): '\" + string + \"'.\");\n                        }\n                        return null;\n                    }\n                    if (pos_1.getWorldName() == null || pos_2.getWorldName() == null) {\n                        if ((context == null || context.showErrors()) && !TagManager.isStaticParsing) {\n                            Debug.echoError(\"valueOf in CuboidTag returning null (null worlds): '\" + string + \"'.\");\n                        }\n                        return null;\n                    }\n                    toReturn.addPair(pos_1, pos_2);\n                }\n                if (toReturn.pairs.size() > 0) {\n                    return toReturn;\n                }\n            }\n        }\n        else if (CoreUtilities.contains(string, ',')) {\n            List<String> subStrs = CoreUtilities.split(string, ',');\n            if (subStrs.size() < 7 || (subStrs.size() - 1) % 6 != 0) {\n                if ((context == null || context.showErrors()) && !TagManager.isStaticParsing) {\n                    Debug.echoError(\"valueOf CuboidTag returning null (Improper number of commas): '\" + string + \"'.\");\n                }\n                return null;\n            }\n            CuboidTag toReturn = new CuboidTag();\n            String worldName = subStrs.get(0);\n            if (worldName.startsWith(\"w@\")) {\n                worldName = worldName.substring(\"w@\".length());\n            }\n            try {\n                for (int i = 0; i < subStrs.size() - 1; i += 6) {\n                    LocationTag locationOne = new LocationTag(parseRoundDouble(subStrs.get(i + 1)),\n                            parseRoundDouble(subStrs.get(i + 2)), parseRoundDouble(subStrs.get(i + 3)), worldName);\n                    LocationTag locationTwo = new LocationTag(parseRoundDouble(subStrs.get(i + 4)),\n                            parseRoundDouble(subStrs.get(i + 5)), parseRoundDouble(subStrs.get(i + 6)), worldName);\n                    toReturn.addPair(locationOne, locationTwo);\n                }\n            }\n            catch (NumberFormatException ex) {\n                if ((context == null || context.showErrors()) && !TagManager.isStaticParsing) {\n                    Debug.echoError(\"valueOf CuboidTag returning null (Improper number value inputs): '\" + ex.getMessage() + \"'.\");\n                }\n                return null;\n            }\n            if (toReturn.pairs.size() > 0) {\n                return toReturn;\n            }\n        }\n        if ((context == null || context.showErrors()) && !TagManager.isStaticParsing) {\n            Debug.echoError(\"Minor: valueOf CuboidTag returning null: \" + string);\n        }\n        return null;\n    }\n\n    public static double parseRoundDouble(String str) {\n        return Math.floor(Double.parseDouble(str));\n    }\n\n    public static boolean matches(String string) {\n        if (valueOf(string, CoreUtilities.noDebugContext) != null) {\n            return true;\n        }\n        return false;\n    }\n\n    @Override\n    public CuboidTag duplicate() {\n        CuboidTag self = refreshState();\n        if (self.noteName != null) {\n            return this;\n        }\n        return self.clone();\n    }\n\n    @Override\n    public int hashCode() {\n        if (noteName != null) {\n            return noteName.hashCode();\n        }\n        return pairs.size() + pairs.get(0).low.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object other) {\n        if (!(other instanceof CuboidTag)) {\n            return false;\n        }\n        CuboidTag cuboid2 = (CuboidTag) other;\n        if (cuboid2.pairs.size() != pairs.size()) {\n            return false;\n        }\n        if ((noteName == null) != (cuboid2.noteName == null)) {\n            return false;\n        }\n        if (noteName != null) {\n            return noteName.equals(cuboid2.noteName);\n        }\n        for (int i = 0; i < pairs.size(); i++) {\n            LocationPair pair1 = pairs.get(i);\n            LocationPair pair2 = cuboid2.pairs.get(i);\n            if (!pair1.low.getWorldName().equals(pair2.low.getWorldName())) {\n                return false;\n            }\n            if (pair1.low.distanceSquared(pair2.low) >= 0.5) {\n                return false;\n            }\n            if (pair1.high.distanceSquared(pair2.high) >= 0.5) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public String getNoteName() {\n        return noteName;\n    }\n\n    @Override\n    public boolean doesContainLocation(Location loc) {\n        return isInsideCuboid(loc);\n    }\n\n    @Override\n    public CuboidTag getCuboidBoundary() {\n        CuboidTag result = new CuboidTag(getLow(0), getHigh(0));\n        for (int i = 1; i < pairs.size(); i++) {\n            result = result.including(getLow(i)).including(getHigh(i));\n        }\n        return result;\n    }\n\n    public static class LocationPair {\n        public LocationTag low;\n        public LocationTag high;\n\n        public int xDistance() {\n            return high.getBlockX() - low.getBlockX();\n        }\n\n        public int yDistance() {\n            return high.getBlockY() - low.getBlockY();\n        }\n\n        public int zDistance() {\n            return high.getBlockZ() - low.getBlockZ();\n        }\n\n        public LocationPair(LocationTag point_1, LocationTag point_2) {\n            regenerate(point_1, point_2);\n        }\n\n        public void regenerate(LocationTag point_1, LocationTag point_2) {\n            String world = point_1.getWorldName();\n            int x_high = Math.max(point_1.getBlockX(), point_2.getBlockX());\n            int x_low = Math.min(point_1.getBlockX(), point_2.getBlockX());\n            int y_high = Math.max(point_1.getBlockY(), point_2.getBlockY());\n            int y_low = Math.min(point_1.getBlockY(), point_2.getBlockY());\n            int z_high = Math.max(point_1.getBlockZ(), point_2.getBlockZ());\n            int z_low = Math.min(point_1.getBlockZ(), point_2.getBlockZ());\n            low = new LocationTag(x_low, y_low, z_low, world);\n            high = new LocationTag(x_high, y_high, z_high, world);\n        }\n    }\n\n    public List<LocationPair> pairs = new ArrayList<>();\n\n    public String noteName = null, priorNoteName = null;\n\n    public AbstractFlagTracker flagTracker = null;\n\n    /**\n     * Construct the cuboid without adding pairs\n     * ONLY use this if addPair will be called immediately after!\n     */\n    public CuboidTag() {\n    }\n\n    public CuboidTag(Location point_1, Location point_2) {\n        addPair(new LocationTag(point_1), new LocationTag(point_2));\n    }\n\n    public void addPair(LocationTag point_1, LocationTag point_2) {\n        if (point_1.getWorldName() == null) {\n            Debug.echoError(\"Tried to make cuboid without a world!\");\n            return;\n        }\n        if (!point_1.getWorldName().equals(point_2.getWorldName())) {\n            Debug.echoError(\"Tried to make cross-world cuboid!\");\n            return;\n        }\n        if (pairs.size() > 0 && !point_1.getWorldName().equals(getWorld().getName())) {\n            Debug.echoError(\"Tried to make cross-world cuboid set!\");\n            return;\n        }\n        LocationPair pair = new LocationPair(point_1, point_2);\n        pairs.add(pair);\n    }\n\n    private static boolean isBetween(int low, int high, int pos) {\n        return pos >= low && pos <= high;\n    }\n\n    public boolean isInsideCuboid(Location location) {\n        if (location.getWorld() == null) {\n            return false;\n        }\n        for (LocationPair pair : pairs) {\n            if (location.getWorld().getName().equals(pair.low.getWorldName())\n                && isBetween(pair.low.getBlockX(), pair.high.getBlockX(), location.getBlockX())\n                && isBetween(pair.low.getBlockY(), pair.high.getBlockY(), location.getBlockY())\n                && isBetween(pair.low.getBlockZ(), pair.high.getBlockZ(), location.getBlockZ())) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public ListTag getWalls() {\n        int max = Settings.blockTagsMaxBlocks();\n        int index = 0;\n        ListTag list = new ListTag();\n        for (LocationPair pair : pairs) {\n            LocationTag low = pair.low;\n            LocationTag high = pair.high;\n            int y_distance = pair.yDistance();\n            int z_distance = pair.zDistance();\n            int x_distance = pair.xDistance();\n            for (int y = 0; y <= y_distance; y++) {\n                for (int x = 0; x <= x_distance; x++) {\n                    list.addObject(new LocationTag(low.getWorld(), low.getBlockX() + x, low.getBlockY() + y, low.getBlockZ()));\n                    list.addObject(new LocationTag(low.getWorld(), low.getBlockX() + x, low.getBlockY() + y, high.getBlockZ()));\n                    index++;\n                    if (index > max) {\n                        return list;\n                    }\n                }\n                for (int z = 1; z < z_distance; z++) {\n                    list.addObject(new LocationTag(low.getWorld(), low.getBlockX(), low.getBlockY() + y, low.getBlockZ() + z));\n                    list.addObject(new LocationTag(low.getWorld(), high.getBlockX(), low.getBlockY() + y, low.getBlockZ() + z));\n                    index++;\n                    if (index > max) {\n                        return list;\n                    }\n                }\n            }\n        }\n        return list;\n    }\n\n    @Override\n    public ListTag getShell() {\n        int max = Settings.blockTagsMaxBlocks();\n        ListTag list = getWalls();\n        int index = list.size();\n        for (LocationPair pair : pairs) {\n            LocationTag low = pair.low;\n            LocationTag high = pair.high;\n            int z_distance = pair.zDistance();\n            int x_distance = pair.xDistance();\n            for (int x = 1; x < x_distance; x++) {\n                for (int z = 1; z < z_distance; z++) {\n                    list.addObject(new LocationTag(low.getWorld(), low.getBlockX() + x, low.getBlockY(), low.getBlockZ() + z));\n                    list.addObject(new LocationTag(low.getWorld(), low.getBlockX() + x, high.getBlockY(), low.getBlockZ() + z));\n                    index++;\n                    if (index > max) {\n                        return list;\n                    }\n                }\n            }\n        }\n        return list;\n    }\n\n    public ListTag getOutline2D(double y) {\n        int max = Settings.blockTagsMaxBlocks();\n        int index = 0;\n        ListTag list = new ListTag();\n        for (LocationPair pair : pairs) {\n            LocationTag loc_1 = pair.low;\n            LocationTag loc_2 = pair.high;\n            int z_distance = pair.zDistance();\n            int x_distance = pair.xDistance();\n            list.addObject(new LocationTag(loc_2.getWorld(), loc_2.getBlockX(), y, loc_2.getBlockZ()));\n            for (int x = loc_1.getBlockX(); x < loc_1.getBlockX() + x_distance; x++) {\n                list.addObject(new LocationTag(loc_1.getWorld(), x, y, loc_2.getBlockZ()));\n                list.addObject(new LocationTag(loc_1.getWorld(), x, y, loc_1.getBlockZ()));\n                index++;\n                if (index > max) {\n                    return list;\n                }\n            }\n            for (int z = loc_1.getBlockZ(); z < loc_1.getBlockZ() + z_distance; z++) {\n                list.addObject(new LocationTag(loc_1.getWorld(), loc_2.getBlockX(), y, z));\n                list.addObject(new LocationTag(loc_1.getWorld(), loc_1.getBlockX(), y, z));\n                index++;\n                if (index > max) {\n                    return list;\n                }\n            }\n        }\n        return list;\n    }\n\n    public ListTag getOutline() {\n        int max = Settings.blockTagsMaxBlocks();\n        int index = 0;\n        ListTag list = new ListTag();\n        for (LocationPair pair : pairs) {\n            LocationTag loc_1 = pair.low;\n            LocationTag loc_2 = pair.high;\n            int y_distance = pair.yDistance();\n            int z_distance = pair.zDistance();\n            int x_distance = pair.xDistance();\n            for (int y = loc_1.getBlockY(); y < loc_1.getBlockY() + y_distance; y++) {\n                list.addObject(new LocationTag(loc_1.getWorld(), loc_1.getBlockX(), y, loc_1.getBlockZ()));\n                list.addObject(new LocationTag(loc_1.getWorld(), loc_2.getBlockX(), y, loc_2.getBlockZ()));\n                list.addObject(new LocationTag(loc_1.getWorld(), loc_1.getBlockX(), y, loc_2.getBlockZ()));\n                list.addObject(new LocationTag(loc_1.getWorld(), loc_2.getBlockX(), y, loc_1.getBlockZ()));\n                index++;\n                if (index > max) {\n                    return list;\n                }\n            }\n            for (int x = loc_1.getBlockX(); x < loc_1.getBlockX() + x_distance; x++) {\n                list.addObject(new LocationTag(loc_1.getWorld(), x, loc_1.getBlockY(), loc_1.getBlockZ()));\n                list.addObject(new LocationTag(loc_1.getWorld(), x, loc_1.getBlockY(), loc_2.getBlockZ()));\n                list.addObject(new LocationTag(loc_1.getWorld(), x, loc_2.getBlockY(), loc_2.getBlockZ()));\n                list.addObject(new LocationTag(loc_1.getWorld(), x, loc_2.getBlockY(), loc_1.getBlockZ()));\n                index++;\n                if (index > max) {\n                    return list;\n                }\n            }\n            for (int z = loc_1.getBlockZ(); z < loc_1.getBlockZ() + z_distance; z++) {\n                list.addObject(new LocationTag(loc_1.getWorld(), loc_1.getBlockX(), loc_1.getBlockY(), z));\n                list.addObject(new LocationTag(loc_1.getWorld(), loc_2.getBlockX(), loc_2.getBlockY(), z));\n                list.addObject(new LocationTag(loc_1.getWorld(), loc_1.getBlockX(), loc_2.getBlockY(), z));\n                list.addObject(new LocationTag(loc_1.getWorld(), loc_2.getBlockX(), loc_1.getBlockY(), z));\n                index++;\n                if (index > max) {\n                    return list;\n                }\n            }\n            list.addObject(pair.high);\n        }\n        return list;\n    }\n\n    @Override\n    public ListTag getBlocks(Predicate<Location> test) {\n        List<LocationTag> locs = getBlocks_internal(test);\n        ListTag list = new ListTag();\n        for (LocationTag loc : locs) {\n            list.addObject(loc);\n        }\n        return list;\n    }\n\n    public List<LocationTag> getBlocks_internal(Predicate<Location> test) {\n        if (test == null) {\n            return getBlockLocationsUnfiltered(true);\n        }\n        int yMin = getWorld().getWorld().getMinHeight(), yMax = getWorld().getWorld().getMaxHeight();\n        int max = Settings.blockTagsMaxBlocks();\n        LocationTag loc;\n        List<LocationTag> list = new ArrayList<>();\n        int index = 0;\n        for (LocationPair pair : pairs) {\n            LocationTag loc_1 = pair.low;\n            int y_distance = pair.yDistance();\n            int z_distance = pair.zDistance();\n            int x_distance = pair.xDistance();\n            for (int x = 0; x != x_distance + 1; x++) {\n                for (int y = 0; y != y_distance + 1; y++) {\n                    if (loc_1.getY() + y < yMin || loc_1.getY() + y > yMax) {\n                        continue;\n                    }\n                    for (int z = 0; z != z_distance + 1; z++) {\n                        loc = new LocationTag(loc_1.clone().add(x, y, z));\n                        if (index++ > max) {\n                            return list;\n                        }\n                        if (test.test(loc)) {\n                            list.add(loc);\n                        }\n                    }\n                }\n            }\n        }\n        return list;\n    }\n\n    public List<LocationTag> getBlockLocationsUnfiltered(boolean doMax) {\n        int max = doMax ? Settings.blockTagsMaxBlocks() : Integer.MAX_VALUE;\n        List<LocationTag> list = new ArrayList<>();\n        int index = 0;\n        for (LocationPair pair : pairs) {\n            LocationTag loc_1 = pair.low;\n            int y_distance = pair.yDistance();\n            int z_distance = pair.zDistance();\n            int x_distance = pair.xDistance();\n            for (int x = 0; x <= x_distance; x++) {\n                for (int y = 0; y <= y_distance; y++) {\n                    for (int z = 0; z <= z_distance; z++) {\n                        LocationTag loc = new LocationTag(loc_1.clone().add(x, y, z));\n                        list.add(loc);\n                        if (index++ > max) {\n                            return list;\n                        }\n                    }\n                }\n            }\n        }\n        return list;\n    }\n\n    public final Collection<Entity> getEntitiesPossiblyWithin() {\n        WorldTag world = getWorld();\n        if (pairs.size() != 1) {\n            return world.getEntities();\n        }\n        BoundingBox box = BoundingBox.of(getLow(0).toVector(), getHigh(0).toVector().add(new Vector(1, 1, 1)));\n        return world.getPossibleEntitiesForBoundary(box);\n    }\n\n    public Collection<Entity> getEntitiesPossiblyWithinForTag() {\n        WorldTag world = getWorld();\n        if (pairs.size() != 1) {\n            return world.getEntitiesForTag();\n        }\n        BoundingBox box = BoundingBox.of(getLow(0).toVector(), getHigh(0).toVector().add(new Vector(1, 1, 1)));\n        return world.getPossibleEntitiesForBoundaryForTag(box);\n    }\n\n    @Override\n    public WorldTag getWorld() {\n        if (pairs.isEmpty()) {\n            return null;\n        }\n        return new WorldTag(pairs.get(0).high.getWorldName());\n    }\n\n    public LocationTag getHigh(int index) {\n        if (index < 0) {\n            return null;\n        }\n        if (index >= pairs.size()) {\n            return null;\n        }\n        return pairs.get(index).high;\n    }\n\n    public LocationTag getLow(int index) {\n        if (index < 0) {\n            return null;\n        }\n        if (index >= pairs.size()) {\n            return null;\n        }\n        return pairs.get(index).low;\n    }\n\n    @Override\n    public boolean isUnique() {\n        return noteName != null;\n    }\n\n    @Override\n    @Note(\"Cuboids\")\n    public Object getSaveObject() {\n        YamlConfiguration section = new YamlConfiguration();\n        section.set(\"object\", identifyFull());\n        section.set(\"flags\", flagTracker.toString());\n        return section;\n    }\n\n    @Override\n    public void makeUnique(String id) {\n        CuboidTag toNote = clone();\n        toNote.noteName = id;\n        toNote.flagTracker = new SavableMapFlagTracker();\n        NoteManager.saveAs(toNote, id);\n        NotedAreaTracker.add(toNote);\n    }\n\n    @Override\n    public void forget() {\n        if (noteName == null) {\n            return;\n        }\n        priorNoteName = noteName;\n        NotedAreaTracker.remove(this);\n        NoteManager.remove(this);\n        noteName = null;\n        flagTracker = null;\n    }\n\n    @Override\n    public CuboidTag refreshState() {\n        if (noteName == null && priorNoteName != null) {\n            Notable note = NoteManager.getSavedObject(priorNoteName);\n            if (note instanceof CuboidTag) {\n                return (CuboidTag) note;\n            }\n            priorNoteName = null;\n        }\n        return this;\n    }\n\n    String prefix = \"Cuboid\";\n\n    @Override\n    public String getPrefix() {\n        return prefix;\n    }\n\n    @Override\n    public CuboidTag setPrefix(String prefix) {\n        this.prefix = prefix;\n        return this;\n    }\n\n    @Override\n    public String debuggable() {\n        CuboidTag self = refreshState();\n        if (self.isUnique()) {\n            return \"<LG>cu@<Y>\" + self.noteName + \" <GR>(\" + self.identifyFull() + \")\";\n        }\n        else {\n            return self.identifyFull();\n        }\n    }\n\n    @Override\n    public String identify() {\n        CuboidTag self = refreshState();\n        if (self.isUnique()) {\n            return \"cu@\" + self.noteName;\n        }\n        else {\n            return self.identifyFull();\n        }\n    }\n\n    @Override\n    public String identifySimple() {\n        return identify();\n    }\n\n    public String identifyFull() {\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"cu@\").append(pairs.get(0).low.getWorldName());\n        for (LocationPair pair : pairs) {\n            sb.append(',').append(pair.low.getBlockX()).append(',').append(pair.low.getBlockY()).append(',').append(pair.low.getBlockZ())\n                    .append(',').append(pair.high.getBlockX()).append(',').append(pair.high.getBlockY()).append(',').append(pair.high.getBlockZ());\n        }\n        return sb.toString();\n    }\n\n    @Override\n    public String toString() {\n        return identify();\n    }\n\n    @Override\n    public AbstractFlagTracker getFlagTracker() {\n        return flagTracker;\n    }\n\n    @Override\n    public void reapplyTracker(AbstractFlagTracker tracker) {\n        if (noteName != null) {\n            this.flagTracker = tracker;\n        }\n    }\n\n    @Override\n    public String getReasonNotFlaggable() {\n        if (noteName == null) {\n            return \"the area is not noted - only noted areas can hold flags\";\n        }\n        return \"unknown reason - something went wrong\";\n    }\n\n    @Override\n    public CuboidTag withWorld(WorldTag world) {\n        CuboidTag newCuboid = clone();\n        for (LocationPair pair : newCuboid.pairs) {\n            pair.low.setWorld(world.getWorld());\n            pair.high.setWorld(world.getWorld());\n        }\n        return newCuboid;\n    }\n\n    public static void register() {\n\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\n        AreaContainmentObject.register(CuboidTag.class, tagProcessor);\n\n        // <--[tag]\n        // @attribute <CuboidTag.random>\n        // @returns LocationTag\n        // @description\n        // Returns a random block location within the cuboid.\n        // (Note: random selection will not be fairly weighted for multi-member cuboids).\n        // @example\n        // # Spawns a debugblock at a random block in the cuboid \"my_cuboid\".\n        // - debugblock <cuboid[my_cuboid].random>\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"random\", (attribute, cuboid) -> {\n            LocationPair pair = cuboid.pairs.get(CoreUtilities.getRandom().nextInt(cuboid.pairs.size()));\n            Vector range = pair.high.toVector().subtract(pair.low.toVector()).add(new Vector(1, 1, 1));\n            range.setX(CoreUtilities.getRandom().nextInt(range.getBlockX()));\n            range.setY(CoreUtilities.getRandom().nextInt(range.getBlockY()));\n            range.setZ(CoreUtilities.getRandom().nextInt(range.getBlockZ()));\n            LocationTag out = pair.low.clone();\n            out.add(range);\n            return out;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.members_size>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the number of cuboids defined in the CuboidTag.\n        // @example\n        // # Narrates the amount of cuboids that are defined in \"my_cuboid\".\n        // # For example, if there are 3 cuboids defined in \"my_cuboid\", this will return \"3\".\n        // - narrate \"The cuboid, 'my_cuboid', has <cuboid[my_cuboid].members_size> members!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"members_size\", (attribute, cuboid) -> {\n            return new ElementTag(cuboid.pairs.size());\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.outline>\n        // @returns ListTag(LocationTag)\n        // @description\n        // Returns each block location on the outline of the CuboidTag.\n        // @example\n        // # Plays the \"flame\" effect at the outline of the cuboid.\n        // - playeffect effect:flame at:<cuboid[my_cuboid].outline> offset:0.0\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"outline\", (attribute, cuboid) -> {\n            return cuboid.getOutline();\n        }, \"get_outline\");\n\n        // <--[tag]\n        // @attribute <CuboidTag.outline_2d[<#.#>]>\n        // @returns ListTag(LocationTag)\n        // @description\n        // Returns a list of block locations along the 2D outline of this CuboidTag, at the specified Y level.\n        // @example\n        // # Plays the \"flame\" effect at the 2D outline of the cuboid on the player's Y level.\n        // - playeffect effect:flame at:<cuboid[my_cuboid].outline_2d[<player.location.y>]> offset:0.0\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"outline_2d\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"CuboidTag.outline_2d[...] tag must have an input.\");\n                return null;\n            }\n            double y = attribute.getDoubleParam();\n            return cuboid.getOutline2D(y);\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.walls>\n        // @returns ListTag(LocationTag)\n        // @description\n        // Returns each block location on the walls of the CuboidTag - that is, the shell minus top and bottom.\n        // @example\n        // # Plays the \"flame\" effect at the walls of the cuboid.\n        // - playeffect effect:flame at:<cuboid[my_cuboid].walls> offset:0.0\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"walls\", (attribute, cuboid) -> {\n            return cuboid.getWalls();\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.contains_cuboid[<cuboid>]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether this cuboid fully contains another cuboid.\n        // @example\n        // # Checks if the cuboid, \"my_cuboid\", contains \"my_second_cuboid\".\n        // - if <cuboid[my_cuboid].contains_cuboid[my_second_cuboid]>:\n        //     - narrate \"My_Cuboid contains Second!\"\n        // - else:\n        //     - narrate \"Second is NOT contained!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, CuboidTag.class, \"contains_cuboid\", (attribute, cuboid, cub2) -> {\n            return new ElementTag(cuboid.containsCuboid(cub2));\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.intersects[<cuboid>]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether this cuboid and another intersect.\n        // @example\n        // # Checks if the cuboid, \"my_cuboid\", intersects \"my_second_cuboid\".\n        // - if <cuboid[my_cuboid].intersects[my_second_cuboid]>:\n        //     - narrate \"Both cuboids intersect each other!\"\n        // - else:\n        //     - narrate \"These cuboids do NOT intersect each other!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, CuboidTag.class, \"intersects\", (attribute, cuboid, cub2) -> {\n            return new ElementTag(cuboid.intersects(cub2));\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.intersection[<cuboid>]>\n        // @returns CuboidTag\n        // @description\n        // Returns the intersection of two intersecting cuboids - in other words, returns a cuboid of just the overlap between the two cuboids.\n        // Returns null if the cuboids do not intersect.\n        // @example\n        // # Notes the intersection as \"intersecting_area\".\n        // - note <cuboid[my_cuboid].intersection[my_second_cuboid]> as:intersecting_area\n        // @example\n        // # Highlights the outline of the intersecting_area.\n        // - debugblock <cuboid[intersecting_area].outline>\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, CuboidTag.class, \"intersection\", (attribute, cuboid, cub2) -> {\n            if (!cuboid.intersects(cub2)) {\n                attribute.echoError(\"Cannot return intersection: The cuboids do not intersect.\");\n                return null;\n            }\n            LocationPair pair = cuboid.pairs.get(0);\n            LocationPair pair2 = cub2.pairs.get(0);\n            int xHigh = Math.min(pair.high.getBlockX(), pair2.high.getBlockX());\n            int yHigh = Math.min(pair.high.getBlockY(), pair2.high.getBlockY());\n            int zHigh = Math.min(pair.high.getBlockZ(), pair2.high.getBlockZ());\n            int xLow = Math.max(pair.low.getBlockX(), pair2.low.getBlockX());\n            int yLow = Math.max(pair.low.getBlockY(), pair2.low.getBlockY());\n            int zLow = Math.max(pair.low.getBlockZ(), pair2.low.getBlockZ());\n            LocationTag locationMin = new LocationTag(xLow, yLow, zLow, pair.low.getWorldName());\n            LocationTag locationMax = new LocationTag(xHigh, yHigh, zHigh, pair.low.getWorldName());\n            return new CuboidTag(locationMin, locationMax);\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.list_members>\n        // @returns ListTag(CuboidTag)\n        // @description\n        // Returns a list of all sub-cuboids in this CuboidTag (for cuboids that contain multiple sub-cuboids).\n        // @example\n        // # Loops through the members of the cuboid and plays a \"flame\" effect at their outlines.\n        // - foreach <cuboid[my_cuboid].list_members> as:member:\n        //     - playeffect effect:flame at:<[member].outline> offset:0.0\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"list_members\", (attribute, cuboid) -> {\n            List<LocationPair> pairs = cuboid.pairs;\n            ListTag list = new ListTag();\n            for (LocationPair pair : pairs) {\n                list.addObject(new CuboidTag(pair.low.clone(), pair.high.clone()));\n            }\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.get[<index>]>\n        // @returns CuboidTag\n        // @description\n        // Returns a cuboid representing the one component of this cuboid (for cuboids that contain multiple sub-cuboids).\n        // @example\n        // # Displays a debugblock at the corners of the second member of \"my_cuboid\".\n        // - debugblock <cuboid[my_cuboid].get[2].corners>\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"get\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.get[...] must have a value.\");\n                return null;\n            }\n            else {\n                int member = attribute.getIntParam();\n                if (member < 1) {\n                    member = 1;\n                }\n                if (member > cuboid.pairs.size()) {\n                    member = cuboid.pairs.size();\n                }\n                LocationPair pair = cuboid.pairs.get(member - 1);\n                return new CuboidTag(pair.low.clone(), pair.high.clone());\n            }\n        }, \"member\", \"get_member\");\n\n        // <--[tag]\n        // @attribute <CuboidTag.set[<cuboid>].at[<index>]>\n        // @returns CuboidTag\n        // @mechanism CuboidTag.set_member\n        // @description\n        // Returns a modified copy of this cuboid, with the specific sub-cuboid index changed to hold the input cuboid.\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"set\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.set[...] must have a value.\");\n                return null;\n            }\n            else {\n                CuboidTag subCuboid = attribute.paramAsType(CuboidTag.class);\n                if (!attribute.startsWith(\"at\", 2)) {\n                    attribute.echoError(\"The tag CuboidTag.set[...] must be followed by an 'at'.\");\n                    return null;\n                }\n                if (!attribute.hasContext(2)) {\n                    attribute.echoError(\"The tag CuboidTag.set[...].at[...] must have an 'at' value.\");\n                    return null;\n                }\n                int member = attribute.getIntContext(2);\n                if (member < 1) {\n                    member = 1;\n                }\n                if (member > cuboid.pairs.size()) {\n                    member = cuboid.pairs.size();\n                }\n                attribute.fulfill(1);\n                LocationPair pair = subCuboid.pairs.get(0);\n                CuboidTag cloned = cuboid.clone();\n                cloned.pairs.set(member - 1, new LocationPair(pair.low.clone(), pair.high.clone()));\n                return cloned;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.add_member[<cuboid>|...]>\n        // @returns CuboidTag\n        // @mechanism CuboidTag.add_member\n        // @description\n        // Returns a modified copy of this cuboid, with the input cuboid(s) added at the end.\n        // @example\n        // # Creates a new cuboid named \"my_third_cuboid\" and adds \"my_cuboid\" and \"my_second_cuboid\" as members.\n        // # You can also use the \"add_member\" mechanism.\n        // - note <cuboid[my_cuboid].add_member[my_second_cuboid]> as:my_third_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"add_member\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.add_member[...] must have a value.\");\n                return null;\n            }\n            cuboid = cuboid.clone();\n            int member = cuboid.pairs.size();\n            ObjectTag param = attribute.getParamObject();\n\n            // <--[tag]\n            // @attribute <CuboidTag.add_member[<cuboid>|...].at[<index>]>\n            // @returns CuboidTag\n            // @mechanism CuboidTag.add_member\n            // @description\n            // Returns a modified copy of this cuboid, with the input cuboid(s) added at the specified index.\n            // @example\n            // # Adds \"my_second_cuboid\" as a member of \"my_cuboid\" at an index of 3.\n            // # You can also use the \"add_member\" mechanism.\n            // - note <cuboid[my_cuboid].add_member[my_second_cuboid].at[3]> as:my_third_cuboid\n            // -->\n            if (attribute.startsWith(\"at\", 2)) {\n                if (!attribute.hasContext(2)) {\n                    attribute.echoError(\"The tag CuboidTag.add_member[...].at[...] must have an 'at' value.\");\n                    return null;\n                }\n                member = attribute.getIntContext(2) - 1;\n                attribute.fulfill(1);\n            }\n            if (member < 0) {\n                member = 0;\n            }\n            if (member > cuboid.pairs.size()) {\n                member = cuboid.pairs.size();\n            }\n            if (!(param instanceof CuboidTag) && param.toString().startsWith(\"li@\")) { // Old cuboid identity used '|' symbol, so require 'li@' to be a list\n                for (CuboidTag subCuboid : param.asType(ListTag.class, attribute.context).filter(CuboidTag.class, attribute.context)) {\n                    LocationPair pair = subCuboid.pairs.get(0);\n                    cuboid.pairs.add(member, new LocationPair(pair.low.clone(), pair.high.clone()));\n                    member++;\n                }\n            }\n            else {\n                CuboidTag subCuboid = param.asType(CuboidTag.class, attribute.context);\n                LocationPair pair = subCuboid.pairs.get(0);\n                cuboid.pairs.add(member, new LocationPair(pair.low.clone(), pair.high.clone()));\n            }\n            return cuboid;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.remove_member[<#>]>\n        // @returns CuboidTag\n        // @mechanism CuboidTag.remove_member\n        // @description\n        // Returns a modified copy of this cuboid, with member at the input index removed.\n        // @example\n        // # Removes the third member in the cuboid \"my_cuboid\" and notes it as \"my_new_cuboid\".\n        // # You can also use the \"remove_member\" mechanism.\n        // - note <cuboid[my_cuboid].remove_member[3]> as:my_new_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"remove_member\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.remove_member[...] must have a value.\");\n                return null;\n            }\n            cuboid = cuboid.clone();\n            int member = attribute.getIntParam();\n            if (member < 1) {\n                member = 1;\n            }\n            if (member > cuboid.pairs.size() + 1) {\n                member = cuboid.pairs.size() + 1;\n            }\n            cuboid.pairs.remove(member - 1);\n            if (cuboid.pairs.isEmpty()) {\n                return null;\n            }\n            return cuboid;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.center>\n        // @returns LocationTag\n        // @description\n        // Returns the location of the exact center of the cuboid.\n        // Not valid for multi-member CuboidTags.\n        // @example\n        // # Displays a debugblock at the center of the cuboid.\n        // - debugblock <cuboid[my_cuboid].center>\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"center\", (attribute, cuboid) -> {\n            LocationPair pair;\n            if (!attribute.hasParam()) {\n                pair = cuboid.pairs.get(0);\n            }\n            else { // legacy\n                int member = attribute.getIntParam();\n                if (member < 1) {\n                    member = 1;\n                }\n                if (member > cuboid.pairs.size()) {\n                    member = cuboid.pairs.size();\n                }\n                pair = cuboid.pairs.get(member - 1);\n            }\n            LocationTag base = pair.high.clone().add(pair.low).add(1.0, 1.0, 1.0);\n            base.setX(base.getX() / 2.0);\n            base.setY(base.getY() / 2.0);\n            base.setZ(base.getZ() / 2.0);\n            return base;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.volume>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the volume of the cuboid.\n        // Effectively equivalent to: (size.x * size.y * size.z).\n        // Not valid for multi-member CuboidTags.\n        // @example\n        // # For example, a cuboid with a size of \"6,7,8\" will have a volume of \"336\". 6 * 7 * 8 = 336.\n        // - narrate \"The volume of the cuboid 'my_cuboid' is: <cuboid[my_cuboid].volume>!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"volume\", (attribute, cuboid) -> {\n            LocationPair pair = cuboid.pairs.get(0);\n            Location base = pair.high.clone().subtract(pair.low.clone()).add(1, 1, 1);\n            return new ElementTag(base.getX() * base.getY() * base.getZ());\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.size>\n        // @returns LocationTag\n        // @description\n        // Returns the size of the cuboid.\n        // Effectively equivalent to: (max - min) + (1,1,1)\n        // Not valid for multi-member CuboidTags.\n        // @example\n        // # For example, this can return \"6,7,8\", meaning the cuboid is 6 blocks wide, 7 blocks high, and 8 blocks deep.\n        // - narrate \"The size of the cuboid 'my_cuboid' is: <cuboid[my_cuboid].size.xyz>!\"\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"size\", (attribute, cuboid) -> {\n            LocationPair pair;\n            if (!attribute.hasParam()) {\n                pair = cuboid.pairs.get(0);\n            }\n            else { // legacy\n                int member = attribute.getIntParam();\n                if (member < 1) {\n                    member = 1;\n                }\n                if (member > cuboid.pairs.size()) {\n                    member = cuboid.pairs.size();\n                }\n                pair = cuboid.pairs.get(member - 1);\n            }\n            Location base = pair.high.clone().subtract(pair.low.clone()).add(1, 1, 1);\n            return new LocationTag(base);\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.max>\n        // @returns LocationTag\n        // @description\n        // Returns the highest-numbered (maximum) corner location.\n        // Not valid for multi-member CuboidTags.\n        // @example\n        // # Displays a glowstone \"block_marker\" effect at maximum corner location of the cuboid.\n        // - playeffect effect:block_marker special_data:glowstone at:<cuboid[my_cuboid].max> offset:0.0\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"max\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                return cuboid.pairs.get(0).high;\n            }\n            else { // legacy\n                int member = attribute.getIntParam();\n                if (member < 1) {\n                    member = 1;\n                }\n                if (member > cuboid.pairs.size()) {\n                    member = cuboid.pairs.size();\n                }\n                return cuboid.pairs.get(member - 1).high;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.min>\n        // @returns LocationTag\n        // @description\n        // Returns the lowest-numbered (minimum) corner location.\n        // Not valid for multi-member CuboidTags.\n        // @example\n        // # Displays a glowstone \"block_marker\" effect at minimum corner location of the cuboid.\n        // - playeffect effect:block_marker special_data:glowstone at:<cuboid[my_cuboid].min> offset:0.0\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"min\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                return cuboid.pairs.get(0).low;\n            }\n            else { // legacy\n                int member = attribute.getIntParam();\n                if (member < 1) {\n                    member = 1;\n                }\n                if (member > cuboid.pairs.size()) {\n                    member = cuboid.pairs.size();\n                }\n                return cuboid.pairs.get(member - 1).low;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.corners>\n        // @returns ListTag(LocationTag)\n        // @description\n        // Returns all 8 corners of the cuboid.\n        // The 4 low corners, then the 4 high corners.\n        // In order X-Z-, X+Z-, X-Z+, X+Z+\n        // If the object is a multi-member cuboid, returns corners for all members in sequence.\n        // @example\n        // # Displays a glowstone block marker effect at each corner of the cuboid.\n        // - playeffect effect:block_marker special_data:glowstone at:<cuboid[my_cuboid].corners> offset:0.0\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"corners\", (attribute, cuboid) -> {\n            ListTag output = new ListTag();\n            for (LocationPair pair : cuboid.pairs) {\n                output.addObject(new LocationTag(pair.low.getX(), pair.low.getY(), pair.low.getZ(), pair.low.getWorldName()));\n                output.addObject(new LocationTag(pair.high.getX(), pair.low.getY(), pair.low.getZ(), pair.low.getWorldName()));\n                output.addObject(new LocationTag(pair.low.getX(), pair.low.getY(), pair.high.getZ(), pair.low.getWorldName()));\n                output.addObject(new LocationTag(pair.high.getX(), pair.low.getY(), pair.high.getZ(), pair.low.getWorldName()));\n                output.addObject(new LocationTag(pair.low.getX(), pair.high.getY(), pair.low.getZ(), pair.low.getWorldName()));\n                output.addObject(new LocationTag(pair.high.getX(), pair.high.getY(), pair.low.getZ(), pair.low.getWorldName()));\n                output.addObject(new LocationTag(pair.low.getX(), pair.high.getY(), pair.high.getZ(), pair.low.getWorldName()));\n                output.addObject(new LocationTag(pair.high.getX(), pair.high.getY(), pair.high.getZ(), pair.low.getWorldName()));\n            }\n            return output;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.shift[<vector>]>\n        // @returns CuboidTag\n        // @description\n        // Returns a copy of this cuboid, with all members shifted by the given vector LocationTag.\n        // For example, a cuboid from 5,5,5 to 10,10,10, shifted 100,0,100, would return a cuboid from 105,5,105 to 110,10,110.\n        // @example\n        // # Notes the cuboid \"my_cuboid\" as \"my_shifted_cuboid\" but shifted over by 25,25,25.\n        // - note <cuboid[my_cuboid].shift[25,25,25]> as:my_shifted_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"shift\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.shift[...] must have a value.\");\n                return null;\n            }\n            LocationTag vector = attribute.paramAsType(LocationTag.class);\n            if (vector != null) {\n                return cuboid.shifted(vector);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.include[<location>/<cuboid>]>\n        // @returns CuboidTag\n        // @description\n        // Expands the first member of the CuboidTag to contain the given location (or entire cuboid), and returns the expanded cuboid.\n        // @example\n        // # Expands the cuboid to contain the player's location and notes it as \"my_new_cuboid\".\n        // - note <cuboid[my_cuboid].include[<player.location>]> as:my_new_cuboid\n        // @example\n        // # Expands the cuboid to contain the cuboid \"my_second_cuboid\" and notes it as \"my_new_cuboid\".\n        // - note <cuboid[my_cuboid].include[my_second_cuboid]> as:my_new_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"include\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.include[...] must have a value.\");\n                return null;\n            }\n            CuboidTag newCuboid = CuboidTag.valueOf(attribute.getParam(), CoreUtilities.noDebugContext);\n            if (newCuboid != null) {\n                return cuboid.including(newCuboid.getLow(0)).including(newCuboid.getHigh(0));\n            }\n            LocationTag loc = attribute.paramAsType(LocationTag.class);\n            if (loc != null) {\n                return cuboid.including(loc);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.include_x[<number>]>\n        // @returns CuboidTag\n        // @description\n        // Expands the first member of the CuboidTag to contain the given X value, and returns the expanded cuboid.\n        // @example\n        // # Expands the cuboid to include a block with an X location of 25 and notes it as \"my_expanded_cuboid\".\n        // - note <cuboid[my_cuboid].include_x[25]> as:my_expanded_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"include_x\", (attribute, cuboid) -> {\n            cuboid = cuboid.clone();\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.include_x[...] must have a value.\");\n                return null;\n            }\n            double x = attribute.getDoubleParam();\n            if (x < cuboid.pairs.get(0).low.getX()) {\n                cuboid.pairs.get(0).low = new LocationTag(cuboid.pairs.get(0).low.getWorld(), x, cuboid.pairs.get(0).low.getY(), cuboid.pairs.get(0).low.getZ());\n            }\n            if (x > cuboid.pairs.get(0).high.getX()) {\n                cuboid.pairs.get(0).high = new LocationTag(cuboid.pairs.get(0).high.getWorld(), x, cuboid.pairs.get(0).high.getY(), cuboid.pairs.get(0).high.getZ());\n            }\n            return cuboid;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.include_y[<number>]>\n        // @returns CuboidTag\n        // @description\n        // Expands the first member of the CuboidTag to contain the given Y value, and returns the expanded cuboid.\n        // @example\n        // # Expands the cuboid to include a block with a Y location of 25 and notes it as \"my_expanded_cuboid\".\n        // - note <cuboid[my_cuboid].include_y[25]> as:my_expanded_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"include_y\", (attribute, cuboid) -> {\n            cuboid = cuboid.clone();\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.include_y[...] must have a value.\");\n                return null;\n            }\n            double y = attribute.getDoubleParam();\n            if (y < cuboid.pairs.get(0).low.getY()) {\n                cuboid.pairs.get(0).low = new LocationTag(cuboid.pairs.get(0).low.getWorld(), cuboid.pairs.get(0).low.getX(), y, cuboid.pairs.get(0).low.getZ());\n            }\n            if (y > cuboid.pairs.get(0).high.getY()) {\n                cuboid.pairs.get(0).high = new LocationTag(cuboid.pairs.get(0).high.getWorld(), cuboid.pairs.get(0).high.getX(), y, cuboid.pairs.get(0).high.getZ());\n            }\n            return cuboid;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.include_z[<number>]>\n        // @returns CuboidTag\n        // @description\n        // Expands the first member of the CuboidTag to contain the given Z value, and returns the expanded cuboid.\n        // @example\n        // # Expands the cuboid to include a block with a Z location of 25 and notes it as \"my_expanded_cuboid\".\n        // - note <cuboid[my_cuboid].include_z[25]> as:my_expanded_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"include_z\", (attribute, cuboid) -> {\n            cuboid = cuboid.clone();\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.include_z[...] must have a value.\");\n                return null;\n            }\n            double z = attribute.getDoubleParam();\n            if (z < cuboid.pairs.get(0).low.getZ()) {\n                cuboid.pairs.get(0).low = new LocationTag(cuboid.pairs.get(0).low.getWorld(), cuboid.pairs.get(0).low.getX(), cuboid.pairs.get(0).low.getY(), z);\n            }\n            if (z > cuboid.pairs.get(0).high.getZ()) {\n                cuboid.pairs.get(0).high = new LocationTag(cuboid.pairs.get(0).high.getWorld(), cuboid.pairs.get(0).high.getX(), cuboid.pairs.get(0).high.getY(), z);\n            }\n            return cuboid;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.with_min[<location>]>\n        // @returns CuboidTag\n        // @description\n        // Changes the cuboid to have the given minimum location, and returns the changed cuboid.\n        // If values in the new min are higher than the existing max, the output max will contain the new min values,\n        // and the output min will contain the old max values.\n        // Note that this is equivalent to constructing a cuboid with the input value and the original cuboids max value.\n        // Not valid for multi-member CuboidTags.\n        // @example\n        // # Notes a new cuboid with the player's location as the new minimum location of the cuboid.\n        // # For example: if \"my_cuboid\" spans from 10,10,10 (max) to 5,5,5 (min) and the player had a location of -5,5,-5,\n        // # then \"my_new_cuboid\" will have a minimum location of -5,5,-5.\n        // - note <cuboid[my_cuboid].with_min[<player.location>]> as:my_new_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"with_min\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.with_min[...] must have a value.\");\n                return null;\n            }\n            LocationTag location = attribute.paramAsType(LocationTag.class);\n            return new CuboidTag(location, cuboid.pairs.get(0).high);\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.with_max[<location>]>\n        // @returns CuboidTag\n        // @description\n        // Changes the cuboid to have the given maximum location, and returns the changed cuboid.\n        // If values in the new max are lower than the existing min, the output min will contain the new max values,\n        // and the output max will contain the old min values.\n        // Note that this is equivalent to constructing a cuboid with the input value and the original cuboids min value.\n        // Not valid for multi-member CuboidTags.\n        // @example\n        // # Notes a new cuboid with the player's location as the new maximum location of the cuboid.\n        // # For example: if \"my_cuboid\" spans from 10,10,10 (max) to 5,5,5 (min) and the player had a location of 15,10,15,\n        // # then \"my_new_cuboid\" will span from 15,10,15 (max) to 5,5,5 (min).\n        // - note <cuboid[my_cuboid].with_max[<player.location>]> as:my_new_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"with_max\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.with_max[...] must have a value.\");\n                return null;\n            }\n            LocationTag location = attribute.paramAsType(LocationTag.class);\n            return new CuboidTag(location, cuboid.pairs.get(0).low);\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.expand[<location>]>\n        // @returns CuboidTag\n        // @description\n        // Expands the cuboid by the given amount, and returns the changed cuboid.\n        // This will decrease the min coordinates by the given vector location, and increase the max coordinates by it.\n        // Supplying a negative input will therefore contract the cuboid.\n        // Note that you can also specify a single number to expand all coordinates by the same amount (equivalent to specifying a location that is that value on X, Y, and Z).\n        // Not valid for multi-member CuboidTags.\n        // @example\n        // # If \"my_cuboid\" spans from 10,10,10 to 5,5,5 and gets expanded by 15 (15,15,15),\n        // # then \"my_expanded_cuboid\" will span -10,-10,-10 (min) to 25,25,25 (max).\n        // - note <cuboid[my_cuboid].expand[15]> as:my_expanded_cuboid\n        // @example\n        // # If \"my_cuboid\" spans from 10,10,10 to 5,5,5 and gets expanded by 15,20,25,\n        // # then \"my_expanded_cuboid\" will span -10,-15,-20 (min) to 25,30,35 (max).\n        // - note <cuboid[my_cuboid].expand[15,20,25]> as:my_expanded_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"expand\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.expand[...] must have a value.\");\n                return null;\n            }\n            Vector expandBy;\n            if (ArgumentHelper.matchesInteger(attribute.getParam())) {\n                int val = attribute.getIntParam();\n                expandBy = new Vector(val, val, val);\n            }\n            else {\n                expandBy = attribute.paramAsType(LocationTag.class).toVector();\n            }\n            LocationPair pair = cuboid.pairs.get(0);\n            return new CuboidTag(pair.low.clone().subtract(expandBy), pair.high.clone().add(expandBy));\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.expand_one_side[<location>]>\n        // @returns CuboidTag\n        // @description\n        // Expands the cuboid by the given amount in just one direction, and returns the changed cuboid.\n        // If a coordinate is positive, it will expand the high value. If it is negative, it will expand the low value.\n        // Note that you can also specify a single number to expand all coordinates by the same amount (equivalent to specifying a location that is that value on X, Y, and Z).\n        // Inverted by <@link tag CuboidTag.shrink_one_side>\n        // Not valid for multi-member CuboidTags.\n        // @example\n        // # Expands the high value of the cuboid \"my_cuboid\" by 15,16,17 on one side and notes it as \"my_expanded_cuboid\".\n        // # If \"my_cuboid\" spans from 10,10,10 (max) to 5,5,5 (min), then expanding it by 15,16,17 will make \"my_expanded_cuboid\"\n        // # span from 25,26,27 (max) to 5,5,5 (min).\n        // - note <cuboid[my_cuboid].expand_one_side[15,16,17]> as:my_expanded_cuboid\n        // @example\n        // # Expands the low value of the cuboid \"my_cuboid\" by -15 (-15,-15,-15) on one side and notes it as \"my_expanded_cuboid\".\n        // # If \"my_cuboid\" spans from 10,10,10 (max) to 5,5,5 (min), then shrinking it by -15 will make \"my_expanded_cuboid\"\n        // # span from 10,10,10 (max) to -10,-10,-10 (min).\n        // - note <cuboid[my_cuboid].expand_one_side[-15]> as:my_expanded_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"expand_one_side\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.expand_one_side[...] must have a value.\");\n                return null;\n            }\n            Vector expandBy;\n            if (ArgumentHelper.matchesInteger(attribute.getParam())) {\n                int val = attribute.getIntParam();\n                expandBy = new Vector(val, val, val);\n            }\n            else {\n                expandBy = attribute.paramAsType(LocationTag.class).toVector();\n            }\n            LocationPair pair = cuboid.pairs.get(0);\n            LocationTag low = pair.low.clone();\n            LocationTag high = pair.high.clone();\n            if (expandBy.getBlockX() < 0) {\n                low.setX(low.getBlockX() + expandBy.getBlockX());\n            }\n            else {\n                high.setX(high.getBlockX() + expandBy.getBlockX());\n            }\n            if (expandBy.getBlockY() < 0) {\n                low.setY(low.getBlockY() + expandBy.getBlockY());\n            }\n            else {\n                high.setY(high.getBlockY() + expandBy.getBlockY());\n            }\n            if (expandBy.getBlockZ() < 0) {\n                low.setZ(low.getBlockZ() + expandBy.getBlockZ());\n            }\n            else {\n                high.setZ(high.getBlockZ() + expandBy.getBlockZ());\n            }\n            return new CuboidTag(low, high);\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.shrink_one_side[<location>]>\n        // @returns CuboidTag\n        // @description\n        // Shrinks the cuboid by the given amount in just one direction, and returns the changed cuboid.\n        // If a coordinate is positive, it will shrink the high value. If it is negative, it will shrink the low value.\n        // Note that you can also specify a single number to expand all coordinates by the same amount (equivalent to specifying a location that is that value on X, Y, and Z).\n        // Inverted by <@link tag CuboidTag.expand_one_side>\n        // Not valid for multi-member CuboidTags.\n        // If you shrink past the limits of the cuboid's size, the cuboid will flip and start expanding the opposite direction.\n        // @example\n        // # Shrinks the high value of the cuboid \"my_cuboid\" by 15,16,17 on one side and notes it as \"my_smaller_cuboid\".\n        // # If \"my_cuboid\" spans from 10,10,10 (max) to 5,5,5 (min), then shrinking it by 15,16,17 will make \"my_smaller_cuboid\"\n        // # span from 5,5,5 (max) to -5,-6,-7 (min).\n        // - note <cuboid[my_cuboid].shrink_one_side[15,16,17]> as:my_smaller_cuboid\n        // @example\n        // # Shrinks the low value of the cuboid \"my_cuboid\" by -15 (-15,-15,-15) on one side and notes it as \"my_smaller_cuboid\".\n        // # If \"my_cuboid\" spans from 10,10,10 (max) to 5,5,5 (min), then shrinking it by -15 will make \"my_smaller_cuboid\"\n        // # span from 20,20,20 (max) to 10,10,10 (min).\n        // - note <cuboid[my_cuboid].shrink_one_side[-15]> as:my_smaller_cuboid\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"shrink_one_side\", (attribute, cuboid) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag CuboidTag.shrink_one_side[...] must have a value.\");\n                return null;\n            }\n            Vector expandBy;\n            if (ArgumentHelper.matchesInteger(attribute.getParam())) {\n                int val = attribute.getIntParam();\n                expandBy = new Vector(val, val, val);\n            }\n            else {\n                expandBy = attribute.paramAsType(LocationTag.class).toVector();\n            }\n            LocationPair pair = cuboid.pairs.get(0);\n            LocationTag low = pair.low.clone();\n            LocationTag high = pair.high.clone();\n            if (expandBy.getBlockX() < 0) {\n                low.setX(low.getBlockX() - expandBy.getBlockX());\n            }\n            else {\n                high.setX(high.getBlockX() - expandBy.getBlockX());\n            }\n            if (expandBy.getBlockY() < 0) {\n                low.setY(low.getBlockY() - expandBy.getBlockY());\n            }\n            else {\n                high.setY(high.getBlockY() - expandBy.getBlockY());\n            }\n            if (expandBy.getBlockZ() < 0) {\n                low.setZ(low.getBlockZ() - expandBy.getBlockZ());\n            }\n            else {\n                high.setZ(high.getBlockZ() - expandBy.getBlockZ());\n            }\n            return new CuboidTag(low, high);\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.chunks>\n        // @returns ListTag(ChunkTag)\n        // @description\n        // Gets a list of all chunks entirely within the CuboidTag (ignoring the Y axis).\n        // @example\n        // # Loads the chunks that are fully within the cuboid \"my_cuboid\".\n        // # If \"my_cuboid\" spans from \"15,50,15\" (max) to \"7,64,5\" (min), then this will return an empty list and not load any chunks\n        // # because no chunks are fully enclosed within it. But, for example, if it spans from \"21,70,21\" (max) to \"-10,64,-9\" (min),\n        // # then this will return a list with chunk 0,0 and load it because the cuboid surrounds that chunk.\n        // - chunkload <cuboid[my_cuboid].chunks>\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"chunks\", (attribute, cuboid) -> {\n            ListTag chunks = new ListTag();\n            for (LocationPair pair : cuboid.pairs) {\n                int minY = pair.low.getBlockY();\n                ChunkTag minChunk = new ChunkTag(pair.low);\n                int minX = minChunk.getX();\n                int minZ = minChunk.getZ();\n                if (!cuboid.isInsideCuboid(new Location(cuboid.getWorld().getWorld(), minChunk.getX() * 16, minY, minChunk.getZ() * 16))) {\n                    minX++;\n                    minZ++;\n                }\n                ChunkTag maxChunk = new ChunkTag(pair.high);\n                int maxX = maxChunk.getX();\n                int maxZ = maxChunk.getZ();\n                if (cuboid.isInsideCuboid(new Location(cuboid.getWorld().getWorld(), maxChunk.getX() * 16 + 15, minY, maxChunk.getZ() * 16 + 15))) {\n                    maxX++;\n                    maxZ++;\n                }\n                for (int x = minX; x < maxX; x++) {\n                    for (int z = minZ; z < maxZ; z++) {\n                        chunks.addObject(new ChunkTag(cuboid.getWorld(), x, z));\n                    }\n                }\n            }\n            return chunks.deduplicate();\n        }, \"list_chunks\");\n\n        // <--[tag]\n        // @attribute <CuboidTag.partial_chunks>\n        // @returns ListTag(ChunkTag)\n        // @description\n        // Gets a list of all chunks partially or entirely within the CuboidTag.\n        // @example\n        // # Loads the chunks that are within the cuboid \"my_cuboid\", even if they are partially within the cuboid.\n        // # If \"my_cuboid\" spans from \"15,50,15\" (max) to \"7,64,5\" (min), then this will return a list with chunk 0,0\n        // # in it, because the chunk is partially contained by the cuboid.\n        // - chunkload <cuboid[my_cuboid].partial_chunks>\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"partial_chunks\", (attribute, cuboid) -> {\n            ListTag chunks = new ListTag();\n            for (LocationPair pair : cuboid.pairs) {\n                ChunkTag minChunk = new ChunkTag(pair.low);\n                ChunkTag maxChunk = new ChunkTag(pair.high);\n                for (int x = minChunk.getX(); x <= maxChunk.getX(); x++) {\n                    for (int z = minChunk.getZ(); z <= maxChunk.getZ(); z++) {\n                        chunks.addObject(new ChunkTag(cuboid.getWorld(), x, z));\n                    }\n                }\n            }\n            return chunks;\n        }, \"list_partial_chunks\");\n\n        // <--[tag]\n        // @attribute <CuboidTag.note_name>\n        // @returns ElementTag\n        // @description\n        // Gets the name of a noted CuboidTag. If the cuboid isn't noted, this is null.\n        // @example\n        // # For example, this might return something like:\n        // # \"The cuboid you are currently in is noted as: my_cuboid!\"\n        // - narrate \"The cuboid you are currently in is noted as: <player.location.areas[cuboid].first.note_name.if_null[null! You aren't in a cuboid]>!\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"note_name\", (attribute, cuboid) -> {\n            String noteName = NoteManager.getSavedId(cuboid);\n            if (noteName == null) {\n                return null;\n            }\n            return new ElementTag(noteName);\n        }, \"notable_name\");\n\n        // <--[tag]\n        // @attribute <CuboidTag.contained_cuboids>\n        // @returns ListTag(CuboidTag)\n        // @description\n        // Returns a list of all noted cuboid areas that this cuboid fully contains.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"contained_cuboids\", (attribute, cuboid) -> {\n            ListTag list = new ListTag();\n            HashSet<String> antidup = new HashSet<>();\n            for (LocationPair pair : cuboid.pairs) {\n                NotedAreaTracker.forEachAreaThatIntersects(pair.low, pair.high, a -> {\n                    if (a instanceof CuboidTag cub2 && cuboid.containsCuboid(cub2) && antidup.add(cub2.noteName)) {\n                        list.addObject(cub2);\n                    }\n                });\n            }\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <CuboidTag.intersecting_cuboids>\n        // @returns ListTag(CuboidTag)\n        // @description\n        // Returns a list of all noted cuboid areas that this cuboid intersects with.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"intersecting_cuboids\", (attribute, cuboid) -> {\n            ListTag list = new ListTag();\n            HashSet<String> antidup = new HashSet<>();\n            for (LocationPair pair : cuboid.pairs) {\n                NotedAreaTracker.forEachAreaThatIntersects(pair.low, pair.high, a -> {\n                    if (a instanceof CuboidTag cub2 && cuboid.intersects(cub2) && antidup.add(cub2.noteName)) {\n                        list.addObject(cub2);\n                    }\n                });\n            }\n            return list;\n        });\n\n        tagProcessor.registerTag(ElementTag.class, \"full\", (attribute, cuboid) -> {\n            BukkitImplDeprecations.cuboidFullTag.warn(attribute.context);\n            return new ElementTag(cuboid.identifyFull());\n        });\n    }\n\n    public boolean containsCuboid(CuboidTag cub2) {\n        for (LocationPair pair2 : cub2.pairs) {\n            boolean containedPair = false;\n            for (LocationPair pair : pairs) {\n                if (!pair.low.getWorldName().equalsIgnoreCase(pair2.low.getWorldName())) {\n                    continue;\n                }\n                if (pair2.low.getX() >= pair.low.getX()\n                        && pair2.low.getY() >= pair.low.getY()\n                        && pair2.low.getZ() >= pair.low.getZ()\n                        && pair2.high.getX() <= pair.high.getX()\n                        && pair2.high.getY() <= pair.high.getY()\n                        && pair2.high.getZ() <= pair.high.getZ()) {\n                    containedPair = true;\n                    break;\n                }\n            }\n            if (!containedPair) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public boolean intersects(CuboidTag cub2) {\n        for (LocationPair pair : pairs) {\n            for (LocationPair pair2 : cub2.pairs) {\n                if (!pair.low.getWorldName().equalsIgnoreCase(pair2.low.getWorldName())) {\n                    return false;\n                }\n                if (pair2.low.getX() <= pair.high.getX()\n                        && pair2.low.getY() <= pair.high.getY()\n                        && pair2.low.getZ() <= pair.high.getZ()\n                        && pair2.high.getX() >= pair.low.getX()\n                        && pair2.high.getY() >= pair.low.getY()\n                        && pair2.high.getZ() >= pair.low.getZ()) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    public CuboidTag shifted(LocationTag vec) {\n        CuboidTag cuboid = clone();\n        for (LocationPair pair : cuboid.pairs) {\n            LocationTag low = pair.low.clone().add(vec.toVector());\n            LocationTag high = pair.high.clone().add(vec.toVector());\n            pair.regenerate(low, high);\n        }\n        return cuboid;\n    }\n\n    public CuboidTag including(Location loc) {\n        loc = loc.clone();\n        CuboidTag cuboid = clone();\n        LocationTag low = cuboid.pairs.get(0).low;\n        LocationTag high = cuboid.pairs.get(0).high;\n        if (loc.getX() < low.getX()) {\n            low = new LocationTag(low.getWorld(), loc.getX(), low.getY(), low.getZ());\n        }\n        if (loc.getY() < low.getY()) {\n            low = new LocationTag(low.getWorld(), low.getX(), loc.getY(), low.getZ());\n        }\n        if (loc.getZ() < low.getZ()) {\n            low = new LocationTag(low.getWorld(), low.getX(), low.getY(), loc.getZ());\n        }\n        if (loc.getX() > high.getX()) {\n            high = new LocationTag(high.getWorld(), loc.getX(), high.getY(), high.getZ());\n        }\n        if (loc.getY() > high.getY()) {\n            high = new LocationTag(high.getWorld(), high.getX(), loc.getY(), high.getZ());\n        }\n        if (loc.getZ() > high.getZ()) {\n            high = new LocationTag(high.getWorld(), high.getX(), high.getY(), loc.getZ());\n        }\n        cuboid.pairs.get(0).regenerate(low, high);\n        return cuboid;\n    }\n\n    public static ObjectTagProcessor<CuboidTag> tagProcessor = new ObjectTagProcessor<>();\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n        return tagProcessor.getObjectAttribute(this, attribute);\n    }\n\n    public void applyProperty(Mechanism mechanism) {\n        if (noteName != null) {\n            mechanism.echoError(\"Cannot apply properties to noted objects.\");\n            return;\n        }\n        adjust(mechanism);\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object CuboidTag\n        // @name set_member\n        // @input (#,)CuboidTag\n        // @description\n        // Sets a given sub-cuboid of the cuboid.\n        // Input is of the form like \"2,cu@...\" where 2 is the sub-cuboid index, or just a direct CuboidTag input.\n        // The default index, if unspecified, is 1 (ie the first member).\n        // @tags\n        // <CuboidTag.get>\n        // <CuboidTag.set[<cuboid>].at[<#>]>\n        // -->\n        if (mechanism.matches(\"set_member\")) {\n            if (noteName != null) {\n                NotedAreaTracker.remove(this);\n            }\n            String value = mechanism.getValue().asString();\n            int comma = value.indexOf(',');\n            int member = 1;\n            if (comma > 0 && !value.startsWith(\"cu@\")) {\n                member = new ElementTag(value.substring(0, comma)).asInt();\n                value = value.substring(comma + 1);\n            }\n            CuboidTag subCuboid = CuboidTag.valueOf(value, mechanism.context);\n            if (member < 1) {\n                member = 1;\n            }\n            if (member > pairs.size()) {\n                member = pairs.size();\n            }\n            LocationPair pair = subCuboid.pairs.get(0);\n            pairs.set(member - 1, new LocationPair(pair.low.clone(), pair.high.clone()));\n            if (noteName != null) {\n                NotedAreaTracker.add(this);\n            }\n        }\n\n        // <--[mechanism]\n        // @object CuboidTag\n        // @name add_member\n        // @input (#,)CuboidTag\n        // @description\n        // Adds a sub-member to the cuboid (optionally at a specified index - otherwise, at the end).\n        // Input is of the form like \"2,cu@...\" where 2 is the sub-cuboid index, or just a direct CuboidTag input.\n        // Note that the index is where the member will end up. So, index 1 will add the cuboid as the very first member (moving the rest up +1 index value).\n        // @tags\n        // <CuboidTag.get>\n        // <CuboidTag.add_member[<cuboid>]>\n        // <CuboidTag.add_member[<cuboid>].at[<#>]>\n        // @example\n        // # Adds \"my_second_cuboid\" as a member to \"my_cuboid\" and narrates a formatted list of members.\n        // # For example, if \"my_cuboid\" is \"world,5,5,5,10,10,10\" and \"my_second_cuboid\" is \"world,12,12,12,22,22,22\",\n        // # then this will narrate \"world,5,5,5,10,10,10 and world,12,12,12,22,22,22\".\n        // - adjust <cuboid[my_cuboid]> add_member:my_second_cuboid\n        // - narrate <cuboid[my_cuboid].list_members.formatted>\n        // @example\n        // # Adds \"my_second_cuboid\" as a member to \"my_cuboid\" at the second index.\n        // - adjust <cuboid[my_cuboid]> add_member:2,my_second_cuboid\n        // -->\n        if (mechanism.matches(\"add_member\")) {\n            if (noteName != null) {\n                NotedAreaTracker.remove(this);\n            }\n            String value = mechanism.getValue().asString();\n            int comma = value.indexOf(',');\n            int member = pairs.size();\n            if (comma > 0 && !value.startsWith(\"cu@\")) {\n                member = new ElementTag(value.substring(0, comma)).asInt() - 1;\n                value = value.substring(comma + 1);\n            }\n            CuboidTag subCuboid = CuboidTag.valueOf(value, mechanism.context);\n            if (member < 0) {\n                member = 0;\n            }\n            if (member > pairs.size()) {\n                member = pairs.size();\n            }\n            LocationPair pair = subCuboid.pairs.get(0);\n            pairs.add(member, new LocationPair(pair.low.clone(), pair.high.clone()));\n            if (noteName != null) {\n                NotedAreaTracker.add(this);\n            }\n        }\n\n        // <--[mechanism]\n        // @object CuboidTag\n        // @name remove_member\n        // @input ElementTag(Number)\n        // @description\n        // Remove a sub-member from the cuboid at the specified index.\n        // @tags\n        // <CuboidTag.remove_member[<#>]>\n        // @example\n        // # Removes the second member from \"my_cuboid\" and narrates a formatted list of members.\n        // # For example, if \"my_cuboid\" is \"world,5,5,5,10,10,10\" and it's second member is \"world,12,12,12,22,22,22\",\n        // # after the member is removed then this will narrate \"world,5,5,5,10,10,10\".\n        // - adjust <cuboid[my_cuboid]> remove_member:2\n        // - narrate <cuboid[my_cuboid].list_members.formatted>\n        // -->\n        if (mechanism.matches(\"remove_member\") && mechanism.requireInteger()) {\n            if (pairs.size() == 1) {\n                Debug.echoError(\"Cannot remove_member: CuboidTag only has 1 member left.\");\n                return;\n            }\n            if (noteName != null) {\n                NotedAreaTracker.remove(this);\n            }\n            int member = mechanism.getValue().asInt();\n            if (member < 1) {\n                member = 1;\n            }\n            if (member > pairs.size()) {\n                member = pairs.size();\n            }\n            pairs.remove(member - 1);\n            if (noteName != null) {\n                NotedAreaTracker.add(this);\n            }\n        }\n\n        tagProcessor.processMechanism(this, mechanism);\n    }\n\n    @Override\n    public boolean advancedMatches(String matcher, TagContext context) {\n        String matcherLow = CoreUtilities.toLowerCase(matcher);\n        if (matcherLow.equals(\"cuboid\")) {\n            return true;\n        }\n        return areaBaseAdvancedMatches(matcher);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/EllipsoidTag.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizen.utilities.NotedAreaTracker;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.flags.SavableMapFlagTracker;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.objects.notable.Notable;\r\nimport com.denizenscript.denizencore.objects.notable.Note;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.function.Predicate;\r\n\r\npublic class EllipsoidTag implements ObjectTag, Notable, Cloneable, AreaContainmentObject, FlaggableObject, Adjustable {\r\n\r\n    // <--[ObjectType]\r\n    // @name EllipsoidTag\r\n    // @prefix ellipsoid\r\n    // @base ElementTag\r\n    // @implements FlaggableObject, AreaObject\r\n    // @ExampleTagBase ellipsoid[my_noted_ellipsoid]\r\n    // @ExampleValues my_ellipsoid_note\r\n    // @ExampleForReturns\r\n    // - note %VALUE% as:my_new_ellipsoid\r\n    // @format\r\n    // The identity format for ellipsoids is <x>,<y>,<z>,<world>,<x-radius>,<y-radius>,<z-radius>\r\n    // For example, 'ellipsoid@1,2,3,space,7,7,7'.\r\n    //\r\n    // @description\r\n    // An EllipsoidTag represents an ellipsoidal region in the world.\r\n    //\r\n    // The word 'ellipsoid' means a less strict sphere.\r\n    // Basically: an \"ellipsoid\" is to a 3D \"sphere\" what an \"ellipse\" (or \"oval\") is to a 2D \"circle\".\r\n    //\r\n    // This object type can be noted.\r\n    //\r\n    // This object type is flaggable when it is noted.\r\n    // Flags on this object type will be stored in the notables.yml file.\r\n    //\r\n    // @Matchable\r\n    // Refer to <@link objecttype areaobject>'s matchable list.\r\n    //\r\n    // -->\r\n\r\n    //////////////////\r\n    //    OBJECT FETCHER\r\n    ////////////////\r\n\r\n    @Fetchable(\"ellipsoid\")\r\n    public static EllipsoidTag valueOf(String string, TagContext context) {\r\n        if (string.startsWith(\"ellipsoid@\")) {\r\n            string = string.substring(10);\r\n        }\r\n        if (string.contains(\"@\")) {\r\n            return null;\r\n        }\r\n        if (!TagManager.isStaticParsing) {\r\n            Notable noted = NoteManager.getSavedObject(string);\r\n            if (noted instanceof EllipsoidTag) {\r\n                return (EllipsoidTag) noted;\r\n            }\r\n        }\r\n        List<String> split = CoreUtilities.split(string, ',');\r\n        if (split.size() != 7) {\r\n            return null;\r\n        }\r\n        String worldName = split.get(3);\r\n        for (int i = 0; i < 7; i++) {\r\n            if (i != 3 && !ArgumentHelper.matchesDouble(split.get(i))) {\r\n                if (context == null || context.showErrors()) {\r\n                    Debug.echoError(\"EllipsoidTag input is not a valid decimal number: \" + split.get(i));\r\n                    return null;\r\n                }\r\n            }\r\n        }\r\n        LocationTag location = new LocationTag(Double.parseDouble(split.get(0)), Double.parseDouble(split.get(1)), Double.parseDouble(split.get(2)), worldName);\r\n        LocationTag size = new LocationTag(null, Double.parseDouble(split.get(4)), Double.parseDouble(split.get(5)), Double.parseDouble(split.get(6)));\r\n        return new EllipsoidTag(location, size);\r\n    }\r\n\r\n    /**\r\n     * Determines whether a string is a valid ellipsoid.\r\n     *\r\n     * @param arg the string\r\n     * @return true if matched, otherwise false\r\n     */\r\n    public static boolean matches(String arg) {\r\n        try {\r\n            return EllipsoidTag.valueOf(arg, CoreUtilities.noDebugContext) != null;\r\n        }\r\n        catch (Exception e) {\r\n            return false;\r\n        }\r\n    }\r\n    @Override\r\n    public EllipsoidTag clone() {\r\n        return new EllipsoidTag(center.clone(), size.clone());\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag duplicate() {\r\n        EllipsoidTag self = refreshState();\r\n        if (self.noteName != null) {\r\n            return this;\r\n        }\r\n        return self.clone();\r\n    }\r\n\r\n    ///////////////\r\n    //   Constructors\r\n    /////////////\r\n\r\n    public EllipsoidTag(LocationTag center, LocationTag size) {\r\n        this.center = center;\r\n        this.size = size;\r\n    }\r\n\r\n    /////////////////////\r\n    //   INSTANCE FIELDS/METHODS\r\n    /////////////////\r\n\r\n    public LocationTag center;\r\n\r\n    public LocationTag size;\r\n\r\n    public String noteName = null, priorNoteName = null;\r\n\r\n    public AbstractFlagTracker flagTracker = null;\r\n\r\n    @Override\r\n    public ListTag getBlocks(Predicate<Location> test) {\r\n        return getCuboidBoundary().getBlocks(test == null ? this::contains : (l) -> (test.test(l) && contains(l)));\r\n    }\r\n\r\n    public List<LocationTag> getBlockLocationsUnfiltered(boolean doMax) {\r\n        List<LocationTag> initial = getCuboidBoundary().getBlockLocationsUnfiltered(doMax);\r\n        List<LocationTag> locations = new ArrayList<>();\r\n        for (LocationTag loc : initial) {\r\n            if (contains(loc)) {\r\n                locations.add(loc);\r\n            }\r\n        }\r\n        return locations;\r\n    }\r\n\r\n    @Override\r\n    public ListTag getShell() {\r\n        ListTag output = new ListTag();\r\n        double yScale = size.getY();\r\n        int maxY = (int) Math.floor(yScale);\r\n        output.addObject(new LocationTag(center.getBlockX(), center.getBlockY() - maxY, center.getBlockZ(), center.getWorldName()));\r\n        if (maxY != 0) {\r\n            output.addObject(new LocationTag(center.getBlockX(), center.getBlockY() + maxY, center.getBlockZ(), center.getWorldName()));\r\n        }\r\n        for (int y = -maxY; y <= maxY; y++) {\r\n            double yProgMin = Math.min(1.0, (Math.abs(y) + 1) / yScale);\r\n            double yProgMax = Math.abs(y) / yScale;\r\n            double minSubWidth = Math.sqrt(1.0 - yProgMin * yProgMin);\r\n            double maxSubWidth = Math.sqrt(1.0 - yProgMax * yProgMax);\r\n            double minX = size.getX() * minSubWidth - 1;\r\n            double minZ = size.getZ() * minSubWidth - 1;\r\n            double maxX = size.getX() * maxSubWidth;\r\n            double maxZ = size.getZ() * maxSubWidth;\r\n            for (int x = 0; x < maxX; x++) {\r\n                for (int z = 0; z < maxZ; z++) {\r\n                    double scaleTestMin = (x * x) / (minX * minX) + (z * z) / (minZ * minZ);\r\n                    double scaleTestMax = (x * x) / (maxX * maxX) + (z * z) / (maxZ * maxZ);\r\n                    if (scaleTestMin >= 1.0 && scaleTestMax <= 1.0) {\r\n                        output.addObject(new LocationTag(center.getBlockX() + x, center.getBlockY() + y, center.getBlockZ() + z, center.getWorldName()));\r\n                        if (x != 0) {\r\n                            output.addObject(new LocationTag(center.getBlockX() - x, center.getBlockY() + y, center.getBlockZ() + z, center.getWorldName()));\r\n                        }\r\n                        if (z != 0) {\r\n                            output.addObject(new LocationTag(center.getBlockX() + x, center.getBlockY() + y, center.getBlockZ() - z, center.getWorldName()));\r\n                        }\r\n                        if (x != 0 && z != 0) {\r\n                            output.addObject(new LocationTag(center.getBlockX() - x, center.getBlockY() + y, center.getBlockZ() - z, center.getWorldName()));\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        return output;\r\n    }\r\n\r\n    public boolean contains(Location test) {\r\n        if (test.getWorld() == null || !test.getWorld().getName().equals(center.getWorld().getName())) {\r\n            return false;\r\n        }\r\n        double xbase = test.getX() - center.getX();\r\n        double ybase = test.getY() - center.getY();\r\n        double zbase = test.getZ() - center.getZ();\r\n        return ((xbase * xbase) / (size.getX() * size.getX())\r\n                + (ybase * ybase) / (size.getY() * size.getY())\r\n                + (zbase * zbase) / (size.getZ() * size.getZ()) <= 1);\r\n    }\r\n\r\n    public boolean intersects(ChunkTag chunk) {\r\n        int xMin = chunk.getX() * 16;\r\n        int zMin = chunk.getZ() * 16;\r\n        LocationTag locTest = chunk.getCenter();\r\n        // This mess gets a position within the chunk that is as closes as possible to the ellipsoid's center\r\n        locTest.setY(center.getY());\r\n        if (center.getX() > xMin) {\r\n            if (center.getX() < xMin + 16) {\r\n                locTest.setX(center.getX());\r\n            }\r\n            else {\r\n                locTest.setX(center.getX());\r\n            }\r\n        }\r\n        if (center.getZ() > zMin) {\r\n            if (center.getZ() < zMin + 16) {\r\n                locTest.setZ(center.getZ());\r\n            }\r\n            else {\r\n                locTest.setZ(center.getZ());\r\n            }\r\n        }\r\n        return contains(locTest);\r\n    }\r\n\r\n    String prefix = \"ellipsoid\";\r\n\r\n    @Override\r\n    public String getPrefix() {\r\n        return prefix;\r\n    }\r\n\r\n    @Override\r\n    public String debuggable() {\r\n        EllipsoidTag self = refreshState();\r\n        if (self.isUnique()) {\r\n            return \"<LG>ellipsoid@<Y>\" + self.noteName + \"<GR> (\" + self.identifyFull() + \")\";\r\n        }\r\n        else {\r\n            return self.identifyFull();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnique() {\r\n        return noteName != null;\r\n    }\r\n\r\n    @Override\r\n    @Note(\"Ellipsoids\")\r\n    public Object getSaveObject() {\r\n        YamlConfiguration section = new YamlConfiguration();\r\n        section.set(\"object\", identifyFull());\r\n        section.set(\"flags\", flagTracker.toString());\r\n        return section;\r\n    }\r\n\r\n    @Override\r\n    public void makeUnique(String id) {\r\n        EllipsoidTag toNote = clone();\r\n        toNote.noteName = id;\r\n        toNote.flagTracker = new SavableMapFlagTracker();\r\n        NoteManager.saveAs(toNote, id);\r\n        NotedAreaTracker.add(toNote);\r\n    }\r\n\r\n    @Override\r\n    public void forget() {\r\n        if (noteName == null) {\r\n            return;\r\n        }\r\n        priorNoteName = noteName;\r\n        NotedAreaTracker.remove(this);\r\n        NoteManager.remove(this);\r\n        noteName = null;\r\n        flagTracker = null;\r\n    }\r\n\r\n    @Override\r\n    public EllipsoidTag refreshState() {\r\n        if (noteName == null && priorNoteName != null) {\r\n            Notable note = NoteManager.getSavedObject(priorNoteName);\r\n            if (note instanceof EllipsoidTag) {\r\n                return (EllipsoidTag) note;\r\n            }\r\n            priorNoteName = null;\r\n        }\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public int hashCode() {\r\n        if (noteName != null) {\r\n            return noteName.hashCode();\r\n        }\r\n        return center.hashCode() + size.hashCode();\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object other) {\r\n        if (!(other instanceof EllipsoidTag)) {\r\n            return false;\r\n        }\r\n        EllipsoidTag ellipsoid2 = (EllipsoidTag) other;\r\n        if ((noteName == null) != (ellipsoid2.noteName == null)) {\r\n            return false;\r\n        }\r\n        if (noteName != null) {\r\n            return noteName.equals(ellipsoid2.noteName);\r\n        }\r\n        if (!center.getWorldName().equals(ellipsoid2.center.getWorldName())) {\r\n            return false;\r\n        }\r\n        if (center.distanceSquaredNoWorld(ellipsoid2.center) >= 0.25) {\r\n            return false;\r\n        }\r\n        if (size.distanceSquaredNoWorld(ellipsoid2.size) >= 0.25) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public String identify() {\r\n        EllipsoidTag self = refreshState();\r\n        if (self.isUnique()) {\r\n            return \"ellipsoid@\" + self.noteName;\r\n        }\r\n        else {\r\n            return self.identifyFull();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String identifySimple() {\r\n        return identify();\r\n    }\r\n\r\n    public String identifyFull() {\r\n        return \"ellipsoid@\" + center.getX() + \",\" + center.getY() + \",\" + center.getZ() + \",\" + center.getWorldName()\r\n                + \",\" + size.getX() + \",\" + size.getY() + \",\" + size.getZ();\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag setPrefix(String prefix) {\r\n        if (prefix != null) {\r\n            this.prefix = prefix;\r\n        }\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public AbstractFlagTracker getFlagTracker() {\r\n        return flagTracker;\r\n    }\r\n\r\n    @Override\r\n    public void reapplyTracker(AbstractFlagTracker tracker) {\r\n        if (noteName != null) {\r\n            this.flagTracker = tracker;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getReasonNotFlaggable() {\r\n        if (noteName == null) {\r\n            return \"the area is not noted - only noted areas can hold flags\";\r\n        }\r\n        return \"unknown reason - something went wrong\";\r\n    }\r\n\r\n    @Override\r\n    public CuboidTag getCuboidBoundary() {\r\n        return new CuboidTag(center.clone().subtract(size.toVector()), center.clone().add(size.toVector()));\r\n    }\r\n\r\n    @Override\r\n    public WorldTag getWorld() {\r\n        return new WorldTag(center.getWorldName());\r\n    }\r\n\r\n    @Override\r\n    public EllipsoidTag withWorld(WorldTag world) {\r\n        LocationTag loc = center.clone();\r\n        loc.setWorld(world.getWorld());\r\n        return new EllipsoidTag(loc, size.clone());\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\r\n        AreaContainmentObject.register(EllipsoidTag.class, tagProcessor);\r\n\r\n        // <--[tag]\r\n        // @attribute <EllipsoidTag.random>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns a random decimal location within the ellipsoid.\r\n        // Note that distribution of results will not be completely even.\r\n        // @example\r\n        // # Displays a debugblock at a random location within the ellipsoid \"my_ellipsoid\".\r\n        // - debugblock <ellipsoid[my_ellipsoid].random>\r\n        // -->\r\n        tagProcessor.registerTag(LocationTag.class, \"random\", (attribute, object) -> {\r\n            // This is an awkward hack to try to weight towards the center a bit (to counteract the weight-away-from-center that would otherwise happen).\r\n            double y = (Math.sqrt(CoreUtilities.getRandom().nextDouble()) * 2 - 1) * object.size.getY();\r\n            Vector result = new Vector();\r\n            result.setY(y);\r\n            double yProg = Math.abs(y) / object.size.getY();\r\n            double subWidth = Math.sqrt(1.0 - yProg * yProg);\r\n            double maxX = object.size.getX() * subWidth;\r\n            double maxZ = object.size.getZ() * subWidth;\r\n            result.setX(maxX * (CoreUtilities.getRandom().nextDouble() * 2 - 1));\r\n            result.setZ(maxZ * (CoreUtilities.getRandom().nextDouble() * 2 - 1));\r\n            LocationTag out = object.center.clone();\r\n            out.add(result);\r\n            return out;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EllipsoidTag.location>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns the center location of the ellipsoid.\r\n        // @example\r\n        // # Displays a debugblock at center location of the ellipsoid \"my_ellipsoid\".\r\n        // - debugblock <ellipsoid[my_ellipsoid].location>\r\n        // -->\r\n        tagProcessor.registerTag(LocationTag.class, \"location\", (attribute, object) -> {\r\n            return object.center;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EllipsoidTag.size>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns the size of the ellipsoid.\r\n        // @example\r\n        // # For example, this can return: \"The size of the ellipsoid 'my_ellipsoid' is: 10,10,10!\"\r\n        // - narrate \"The size of the ellipsoid 'my_ellipsoid' is: <ellipsoid[my_ellipsoid].size.xyz>!\"\r\n        // -->\r\n        tagProcessor.registerTag(LocationTag.class, \"size\", (attribute, object) -> {\r\n            return object.size;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EllipsoidTag.add[<location>]>\r\n        // @returns EllipsoidTag\r\n        // @description\r\n        // Returns a copy of this ellipsoid, shifted by the input location.\r\n        // @example\r\n        // # Shifts the ellipsoid by 10,10,10 and notes it as \"my_shifted_ellipsoid\".\r\n        // # For example, if \"my_ellipsoid\" has a location of \"5,5,5\", and it's added by \"10,10,10\",\r\n        // # \"my_shifted_cuboid\" will have a location of \"15,15,15\".\r\n        // - note <ellipsoid[my_ellipsoid].add[10,10,10]> as:my_shifted_ellipsoid\r\n        // -->\r\n        tagProcessor.registerTag(EllipsoidTag.class, \"add\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"ellipsoid.add[...] tag must have an input.\");\r\n                return null;\r\n            }\r\n            return new EllipsoidTag(object.center.clone().add(attribute.paramAsType(LocationTag.class)), object.size.clone());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EllipsoidTag.include[<location>]>\r\n        // @returns EllipsoidTag\r\n        // @description\r\n        // Returns a copy of this ellipsoid, with the size value adapted to include the specified world location.\r\n        // @example\r\n        // # Expands \"my_ellipsoid\" to include the player's location and notes it as \"my_new_ellipsoid\".\r\n        // # For example, if \"my_ellipsoid\" has a location of \"5,5,5\" and a size of \"8,8,8\",\r\n        // # and the player had a location of 20,22,24, then \"my_new_ellipsoid\" will have a location of\r\n        // # \"5,5,5\" and a size of \"26,29,33\" (rounded).\r\n        // - note <ellipsoid[my_ellipsoid].include[<player.location>]> as:my_new_ellipsoid\r\n        // -->\r\n        tagProcessor.registerTag(EllipsoidTag.class, \"include\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"ellipsoid.include[...] tag must have an input.\");\r\n                return null;\r\n            }\r\n            LocationTag target = attribute.paramAsType(LocationTag.class);\r\n            if (object.contains(target)) {\r\n                return object;\r\n            }\r\n            LocationTag size = object.size.clone();\r\n            Vector relative = target.toVector().subtract(object.center.toVector());\r\n            // Cuboid minimum expansion\r\n            size.setX(Math.max(size.getX(), Math.abs(relative.getX())));\r\n            size.setY(Math.max(size.getY(), Math.abs(relative.getY())));\r\n            size.setZ(Math.max(size.getZ(), Math.abs(relative.getZ())));\r\n            EllipsoidTag result = new EllipsoidTag(object.center.clone(), new LocationTag(size));\r\n            if (result.contains(target)) {\r\n                return result;\r\n            }\r\n            double sizeLen = size.length();\r\n            // Ellipsoid additional expand\r\n            while (!result.contains(target)) {\r\n                // I gave up on figuring out the math for this, so here's an awful loop-hack\r\n                double projX = (relative.getX() * relative.getX()) / (size.getX() * size.getX());\r\n                double projY = (relative.getY() * relative.getY()) / (size.getY() * size.getY());\r\n                double projZ = (relative.getZ() * relative.getZ()) / (size.getZ() * size.getZ());\r\n                double scale = Math.max(projX + projY + projZ, sizeLen * 0.01);\r\n                if (projX >= projY && projX >= projZ) {\r\n                    size.setX(size.getX() + scale);\r\n                }\r\n                else if (projY >= projX && projY >= projZ) {\r\n                    size.setY(size.getY() + scale);\r\n                }\r\n                else if (projZ >= projX && projZ >= projY) {\r\n                    size.setZ(size.getZ() + scale);\r\n                }\r\n                else {\r\n                    size = size.add(scale, scale, scale);\r\n                }\r\n                result.size = size;\r\n            }\r\n            return result;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EllipsoidTag.with_location[<location>]>\r\n        // @returns EllipsoidTag\r\n        // @description\r\n        // Returns a copy of this ellipsoid, set to the specified location.\r\n        // @example\r\n        // # Sets the location of \"my_ellipsoid\" to be the player's location.\r\n        // # For example, if the player's location is 10,15,20, then \"my_new_ellipsoid\" will have a location of 10,15,20.\r\n        // - note <ellipsoid[my_ellipsoid].with_location[<player.location>]> as:my_new_ellipsoid\r\n        // -->\r\n        tagProcessor.registerTag(EllipsoidTag.class, \"with_location\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"ellipsoid.with_location[...] tag must have an input.\");\r\n                return null;\r\n            }\r\n            return new EllipsoidTag(attribute.paramAsType(LocationTag.class), object.size.clone());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EllipsoidTag.with_size[<location>]>\r\n        // @returns EllipsoidTag\r\n        // @description\r\n        // Returns a copy of this ellipsoid, set to the specified size.\r\n        // @example\r\n        // # Changes the size of \"my_ellipsoid\" to be 20,20,20 and notes it as \"my_new_ellipsoid\".\r\n        // - note <ellipsoid[my_ellipsoid].with_size[20,20,20]> as:my_new_ellipsoid\r\n        // -->\r\n        tagProcessor.registerTag(EllipsoidTag.class, \"with_size\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"ellipsoid.with_size[...] tag must have an input.\");\r\n                return null;\r\n            }\r\n            return new EllipsoidTag(object.center.clone(), attribute.paramAsType(LocationTag.class));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EllipsoidTag.chunks>\r\n        // @returns ListTag(ChunkTag)\r\n        // @description\r\n        // Returns a list of all chunks that this ellipsoid touches at all (note that no valid ellipsoid tag can ever totally contain a chunk, due to vertical limits and roundness).\r\n        // @example\r\n        // # Loads the chunks that touch the ellipsoid \"my_ellipsoid\".\r\n        // # For example, if \"my_ellipsoid\" had a size of \"9,4,6\" and a location of \"-10,70,-9\",\r\n        // # this will return a list containing chunks -2,-1 and -1,-1.\r\n        // - chunkload <ellipsoid[my_ellipsoid].chunks>\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"chunks\", (attribute, object) -> {\r\n            ListTag chunks = new ListTag();\r\n            double minPossibleX = object.center.getX() - object.size.getX();\r\n            double minPossibleZ = object.center.getZ() - object.size.getZ();\r\n            double maxPossibleX = object.center.getX() + object.size.getX();\r\n            double maxPossibleZ = object.center.getZ() + object.size.getZ();\r\n            int minChunkX = (int) Math.floor(minPossibleX / 16);\r\n            int minChunkZ = (int) Math.floor(minPossibleZ / 16);\r\n            int maxChunkX = (int) Math.ceil(maxPossibleX / 16);\r\n            int maxChunkZ = (int) Math.ceil(maxPossibleZ / 16);\r\n            ChunkTag testChunk = new ChunkTag(object.center);\r\n            for (int x = minChunkX; x <= maxChunkX; x++) {\r\n                testChunk.chunkX = x;\r\n                for (int z = minChunkZ; z <= maxChunkZ; z++) {\r\n                    testChunk.chunkZ = z;\r\n                    if (object.intersects(testChunk)) {\r\n                        chunks.addObject(new ChunkTag(testChunk.world, testChunk.chunkX, testChunk.chunkZ));\r\n                    }\r\n                }\r\n            }\r\n            return chunks;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EllipsoidTag.note_name>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Gets the name of a noted EllipsoidTag. If the ellipsoid isn't noted, this is null.\r\n        // @example\r\n        // # For example, this might return something like:\r\n        // # \"The ellipsoid you are currently in is noted as: my_ellipsoid!\"\r\n        // - narrate \"The ellipsoid you are currently in is noted as: <player.location.areas[ellipsoid].first.note_name.if_null[null! You aren't in an ellipsoid]>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"note_name\", (attribute, ellipsoid) -> {\r\n            String noteName = NoteManager.getSavedId(ellipsoid);\r\n            if (noteName == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(noteName);\r\n        });\r\n    }\r\n\r\n    public static ObjectTagProcessor<EllipsoidTag> tagProcessor = new ObjectTagProcessor<>();\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return tagProcessor.getObjectAttribute(this, attribute);\r\n    }\r\n\r\n    @Override\r\n    public String getNoteName() {\r\n        return noteName;\r\n    }\r\n\r\n    @Override\r\n    public boolean doesContainLocation(Location loc) {\r\n        return contains(loc);\r\n    }\r\n\r\n    public void applyProperty(Mechanism mechanism) {\r\n        if (noteName != null) {\r\n            mechanism.echoError(\"Cannot apply properties to noted objects.\");\r\n            return;\r\n        }\r\n        adjust(mechanism);\r\n    }\r\n\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n        tagProcessor.processMechanism(this, mechanism);\r\n    }\r\n\r\n    @Override\r\n    public boolean advancedMatches(String matcher, TagContext context) {\r\n        String matcherLow = CoreUtilities.toLowerCase(matcher);\r\n        if (matcherLow.equals(\"ellipsoid\")) {\r\n            return true;\r\n        }\r\n        return areaBaseAdvancedMatches(matcher);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/EnchantmentTag.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.scripts.containers.core.EnchantmentScriptContainer;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.flags.RedirectionFlagTracker;\r\nimport com.denizenscript.denizencore.objects.Fetchable;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\n\r\npublic class EnchantmentTag implements ObjectTag, FlaggableObject {\r\n\r\n    // <--[ObjectType]\r\n    // @name EnchantmentTag\r\n    // @prefix enchantment\r\n    // @base ElementTag\r\n    // @implements FlaggableObject\r\n    // @ExampleTagBase enchantment[sharpness]\r\n    // @ExampleValues sharpness\r\n    // @ExampleForReturns\r\n    // - inventory adjust slot:hand enchantments:[%VALUE%=5]\r\n    // @format\r\n    // The identity format for enchantments is the vanilla ID, Denizen ID, or full key. Can also be constructed by Denizen script name.\r\n    // For example, 'enchantment@sharpness', 'enchantment@my_custom_ench', or 'enchantment@otherplugin:customench'.\r\n    //\r\n    // @description\r\n    // An EnchantmentTag represents an item enchantment abstractly (the enchantment itself, like 'sharpness', which *can be* applied to an item, as opposed to the specific reference to an enchantment on a specific item).\r\n    // For enchantment names, check <@link url https://minecraft.wiki/w/Enchanting#Summary_of_enchantments>. Note spaces should swapped to underscores, so for example \"Aqua Affinity\" becomes \"aqua_affinity\".\r\n    //\r\n    // This object type is flaggable.\r\n    // Flags on this object type will be stored in the server saves file, under special sub-key \"__enchantments\"\r\n    //\r\n    // -->\r\n\r\n    //////////////////\r\n    //    Object Fetcher\r\n    ////////////////\r\n\r\n    @Fetchable(\"enchantment\")\r\n    public static EnchantmentTag valueOf(String string, TagContext context) {\r\n        if (string == null) {\r\n            return null;\r\n        }\r\n        string = CoreUtilities.toLowerCase(string);\r\n        if (string.startsWith(\"enchantment@\")) {\r\n            string = string.substring(\"enchantment@\".length());\r\n        }\r\n        Enchantment ench;\r\n        NamespacedKey key = Utilities.parseNamespacedKey(string);\r\n        ench = Enchantment.getByKey(key);\r\n        if (ench == null) {\r\n            ench = Enchantment.getByName(string.toUpperCase());\r\n        }\r\n        if (ench == null) {\r\n            ench = Enchantment.getByKey(new NamespacedKey(\"denizen\", Utilities.cleanseNamespaceID(string)));\r\n        }\r\n        if (ench == null && ScriptRegistry.containsScript(string, EnchantmentScriptContainer.class)) {\r\n            ench = ScriptRegistry.getScriptContainerAs(string, EnchantmentScriptContainer.class).enchantment;\r\n        }\r\n        if (ench == null) {\r\n            if (context == null || context.debug || CoreConfiguration.debugOverride) {\r\n                Debug.echoError(\"Unknown enchantment '\" + string + \"'\");\r\n            }\r\n            return null;\r\n        }\r\n        return new EnchantmentTag(ench);\r\n\r\n    }\r\n\r\n    public static boolean matches(String arg) {\r\n        if (CoreUtilities.toLowerCase(arg).startsWith(\"enchantment@\")) {\r\n            return true;\r\n        }\r\n        return valueOf(arg, CoreUtilities.noDebugContext) != null;\r\n    }\r\n\r\n    public EnchantmentTag(Enchantment enchantment) {\r\n        this.enchantment = enchantment;\r\n    }\r\n\r\n    public Enchantment enchantment;\r\n\r\n    private String prefix = \"Enchantment\";\r\n\r\n    @Override\r\n    public String getPrefix() {\r\n        return prefix;\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnique() {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public String identify() {\r\n        return \"enchantment@\" + getCleanName();\r\n    }\r\n\r\n    @Override\r\n    public String identifySimple() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public Object getJavaObject() {\r\n        return enchantment;\r\n    }\r\n\r\n    @Override\r\n    public EnchantmentTag setPrefix(String prefix) {\r\n        this.prefix = prefix;\r\n        return this;\r\n    }\r\n\r\n    public String getCleanName() {\r\n        NamespacedKey key = enchantment.getKey();\r\n        if (key.getNamespace().equals(\"minecraft\") || key.getNamespace().equals(\"denizen\")) {\r\n            return key.getKey();\r\n        }\r\n        return key.toString();\r\n    }\r\n\r\n    @Override\r\n    public AbstractFlagTracker getFlagTracker() {\r\n        return new RedirectionFlagTracker(DenizenCore.serverFlagMap, \"__enchantments.\" + getCleanName().replace(\".\", \"&dot\"));\r\n    }\r\n\r\n    @Override\r\n    public void reapplyTracker(AbstractFlagTracker tracker) {\r\n        // Nothing to do.\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.name>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Gets the name of this enchantment. For vanilla enchantments, uses the vanilla name like 'sharpness'.\r\n        // For Denizen custom enchantments, returns the 'id' specified in the script.\r\n        // For any other enchantments, returns the full key.\r\n        // @example\r\n        // # This can narrate something like this:\r\n        // # \"The item in your hand's first enchantment is sharpness!\"\r\n        // - narrate \"The item in your hand's first enchantment is <player.item_in_hand.enchantment_types.get[1].name||nothing>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"name\", (attribute, object) -> {\r\n            return new ElementTag(object.getCleanName());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.key>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the full key for this enchantment, like \"minecraft:sharpness\".\r\n        // @example\r\n        // # Narrates \"The key for the flame enchantment is: minecraft:flame!\"\r\n        // - narrate \"The key for the flame enchantment is: <enchantment[flame].key>!\"\r\n        // @example\r\n        // # This example uses a custom Denizen enchantment.\r\n        // # Narrates \"The key for the my_enchantment enchantment is: denizen:my_enchantment!\"\r\n        // - narrate \"The key for the my_enchantment enchantment is: <enchantment[my_enchantment].key>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"key\", (attribute, object) -> {\r\n            return new ElementTag(object.enchantment.getKey().toString());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.full_name[<level>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the full name for this enchantment for a given level, like \"Sharpness V\".\r\n        // For vanilla enchantments, uses language translation keys.\r\n        // @example\r\n        // # Narrates \"You don your Thorns III armor.\"\r\n        // # Note: vanilla enchantments have a color applied to them, such as grey or red.\r\n        // - narrate \"You don your <enchantment[thorns].full_name[3]><reset> armor.\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"full_name\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            return new ElementTag(NMSHandler.enchantmentHelper.getFullName(object.enchantment, attribute.getIntParam()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.script>\r\n        // @returns ScriptTag\r\n        // @description\r\n        // Returns the script that created this enchantment type, if any.\r\n        // @example\r\n        // # This can narrate something like this:\r\n        // # \"The script used to create the my_enchantment enchantment is: my_enchantment_script\"\r\n        // - narrate \"The script used to create the my_enchantment enchantment is: <enchantment[my_enchantment].script.name>\"\r\n        // -->\r\n        tagProcessor.registerTag(ScriptTag.class, \"script\", (attribute, object) -> {\r\n            if (!object.enchantment.getKey().getNamespace().equals(\"denizen\")) {\r\n                return null;\r\n            }\r\n            EnchantmentScriptContainer.EnchantmentReference ref = EnchantmentScriptContainer.registeredEnchantmentContainers.get(object.enchantment.getKey().getKey());\r\n            if (ref == null) {\r\n                return null;\r\n            }\r\n            return new ScriptTag(ref.script);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.min_level>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the minimum level of this enchantment. Usually '1'.\r\n        // @example\r\n        // # Narrates \"The minimum enchantment level that you can get for Feather Falling is: 1!\"\r\n        // - narrate \"The minimum enchantment level that you can get for Feather Falling is: <enchantment[feather_falling].min_level>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"min_level\", (attribute, object) -> {\r\n            return new ElementTag(object.enchantment.getStartLevel());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.max_level>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the maximum level of this enchantment. Usually between 1 and 5.\r\n        // @example\r\n        // # Narrates \"The maximum enchantment level that you can get for Feather Falling is: 4!\"\r\n        // - narrate \"The maximum enchantment level that you can get for Feather Falling is: <enchantment[feather_falling].max_level>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"max_level\", (attribute, object) -> {\r\n            return new ElementTag(object.enchantment.getMaxLevel());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.treasure_only>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether this enchantment is only for spawning as treasure.\r\n        // @example\r\n        // # In the case of the \"loyalty\" enchantment, this will narrate:\r\n        // # \"You cannot find this enchantment as treasure!\"\r\n        // - if <enchantment[loyalty].treasure_only>:\r\n        //     - narrate \"You can only find this enchantment as treasure!\"\r\n        // - else:\r\n        //     - narrate \"You cannot find this enchantment as treasure!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"treasure_only\", (attribute, object) -> {\r\n            return new ElementTag(object.enchantment.isTreasure());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.is_tradable>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether this enchantment is only considered to be tradable. Villagers won't trade this enchantment if set to false.\r\n        // @example\r\n        // # In the case of the \"loyalty\" enchantment, this will narrate:\r\n        // # \"Let's do some haggling to get this enchantment!\"\r\n        // - if <enchantment[loyalty].is_tradable>:\r\n        //     - narrate \"Let's do some haggling to get this enchantment!\"\r\n        // - else:\r\n        //     - narrate \"Villagers don't seem to want to trade this enchantment!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_tradable\", (attribute, object) -> {\r\n            return new ElementTag(NMSHandler.enchantmentHelper.isTradable(object.enchantment));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.is_discoverable>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether this enchantment is only considered to be discoverable.\r\n        // If true, this will spawn from vanilla sources like the enchanting table. If false, it can only be given directly by script.\r\n        // @example\r\n        // # In the case of the \"loyalty\" enchantment, this will narrate:\r\n        // # \"Time to do some discovering!\"\r\n        // - if <enchantment[loyalty].is_discoverable>:\r\n        //     - narrate \"Time to do some discovering!\"\r\n        // - else:\r\n        //     - narrate \"You'll have to be given this enchantment through a script!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_discoverable\", (attribute, object) -> {\r\n            return new ElementTag(NMSHandler.enchantmentHelper.isDiscoverable(object.enchantment));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.is_curse>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether this enchantment is only considered to be a curse. Curses are removed at grindstones, and spread from crafting table repairs.\r\n        // @example\r\n        // # In the case of the \"vanishing_curse\" enchantment, this will narrate:\r\n        // # \"Watch out! This enchantment is a curse!\"\r\n        // - if <enchantment[vanishing_curse].is_curse>:\r\n        //     - narrate \"Watch out! This enchantment is a curse!\"\r\n        // - else:\r\n        //     - narrate \"Phew, this enchantment is not a curse!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_curse\", (attribute, object) -> {\r\n            return new ElementTag(NMSHandler.enchantmentHelper.isCurse(object.enchantment));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.category>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the category of this enchantment. Can be any of: ARMOR, ARMOR_FEET, ARMOR_LEGS, ARMOR_CHEST, ARMOR_HEAD,\r\n        // WEAPON, DIGGER, FISHING_ROD, TRIDENT, BREAKABLE, BOW, WEARABLE, CROSSBOW, VANISHABLE\r\n        // @example\r\n        // # This narrates \"The bane_of_arthropods enchantment is in the WEAPON category!\"\r\n        // - narrate \"The bane_of_arthropods enchantment is in the <enchantment[bane_of_arthropods].category> category!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"category\", (attribute, object) -> {\r\n            return new ElementTag(object.enchantment.getItemTarget());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.rarity>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the rarity of this enchantment. Can be any of: COMMON, UNCOMMON, RARE, VERY_RARE\r\n        // @example\r\n        // # This narrates \"The infinity enchantment is VERY_RARE!\"\r\n        // - narrate \"The infinity enchantment is <enchantment[infinity].rarity>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"rarity\", (attribute, object) -> {\r\n            return new ElementTag(NMSHandler.enchantmentHelper.getRarity(object.enchantment));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.can_enchant[<item>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether this enchantment can enchant the given ItemTag (based on material mainly).\r\n        // This is internally based on multiple factors, such as the enchantment's category and its own specific compatibility checks.\r\n        // @example\r\n        // # In the case of the \"knockback\" enchantment, this narrates\r\n        // # \"You can apply the knockback enchantment in a diamond sword!\"\r\n        // - if <enchantment[knockback].can_enchant[diamond_sword]>:\r\n        //     - narrate \"You can apply the knockback enchantment in a diamond sword!\"\r\n        // - else:\r\n        //     - narrate \"You cannot apply the knockback enchantment in a diamond sword!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"can_enchant\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            return new ElementTag(object.enchantment.canEnchantItem(attribute.paramAsType(ItemTag.class).getItemStack()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.is_compatible[<enchantment>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether this enchantment is compatible with another given enchantment.\r\n        // @example\r\n        // # In the case of the \"silk_touch\" and \"mending\" enchantments, this narrates\r\n        // # \"These enchantments are compatible together!\"\r\n        // - if <enchantment[silk_touch].is_compatible[mending]>:\r\n        //     - narrate \"These enchantments are compatible together!\"\r\n        // - else:\r\n        //     - narrate \"These enchantments are not compatible with each other!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_compatible\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            return new ElementTag(!object.enchantment.conflictsWith(attribute.paramAsType(EnchantmentTag.class).enchantment));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.min_cost[<level>]>\r\n        // @returns ElementTag(Decimal)\r\n        // @description\r\n        // Returns the minimum cost for this enchantment for the given level.\r\n        // @example\r\n        // # This narrates \"The minimum cost for the efficiency 3 enchantment is: 21!\"\r\n        // - narrate \"The minimum cost for the efficiency 3 enchantment is: <enchantment[efficiency].min_cost[3]>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"min_cost\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            return new ElementTag(NMSHandler.enchantmentHelper.getMinCost(object.enchantment, attribute.getIntParam()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.max_cost[<level>]>\r\n        // @returns ElementTag(Decimal)\r\n        // @description\r\n        // Returns the maximum cost for this enchantment for the given level.\r\n        // @example\r\n        // # This narrates \"The maximum cost for the efficiency 3 enchantment is: 81!\"\r\n        // - narrate \"The maximum cost for the efficiency 3 enchantment is: <enchantment[efficiency].max_cost[3]>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"max_cost\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            return new ElementTag(NMSHandler.enchantmentHelper.getMaxCost(object.enchantment, attribute.getIntParam()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.damage_bonus[level=<level>;type=<type>]>\r\n        // @returns ElementTag(Decimal)\r\n        // @description\r\n        // Returns the damage bonus this enchantment applies against the given monster type.\r\n        // The input is a MapTag with a level value and a monster type specified, where the type can be any of: ARTHROPOD, ILLAGER, WATER, UNDEAD, or UNDEFINED\r\n        // See also <@link tag EntityTag.monster_type> for getting the category of another mob.\r\n        // @example\r\n        // # Narrates \"With Bane of Arthropods 2, you get a damage bonus of 5 on arthropods!\"\r\n        // - narrate \"With Bane of Arthropods 2, you get a damage bonus of <enchantment[bane_of_arthropods].damage_bonus[level=2;type=arthropod]> on arthropods!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, MapTag.class, \"damage_bonus\", (attribute, object, map) -> {\r\n            ElementTag level = map.getElement(\"level\");\r\n            ElementTag type = map.getElement(\"type\");\r\n            if (level == null || type == null) {\r\n                attribute.echoError(\"Invalid MapTag input to damage_bonus - missing 'level' or 'type'\");\r\n                return null;\r\n            }\r\n            return new ElementTag(NMSHandler.enchantmentHelper.getDamageBonus(object.enchantment, level.asInt(), CoreUtilities.toLowerCase(type.toString())));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EnchantmentTag.damage_protection[level=<level>;type=<cause>;(attacker=<entity>)]>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the damage protection this enchantment applies against the given damage cause and optional attacker.\r\n        // The input is a MapTag with a level value and a damage type specified, where the damage type must be from <@link language Damage Cause>.\r\n        // For entity damage causes, optionally specify the entity attacker.\r\n        // @example\r\n        // # Narrates \"With the Protection 3 enchantment, there is fall damage protection of 3!\"\r\n        // - narrate \"With the Protection 3 enchantment, there is fall damage protection of <enchantment[protection].damage_protection[level=3;type=fall]>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, MapTag.class, \"damage_protection\", (attribute, object, map) -> {\r\n            ElementTag level = map.getRequiredObjectAs(\"level\", ElementTag.class, attribute);\r\n            ElementTag type = map.getRequiredObjectAs(\"type\", ElementTag.class, attribute);\r\n            if (level == null || type == null) {\r\n                return null;\r\n            }\r\n            EntityDamageEvent.DamageCause cause;\r\n            try {\r\n                cause = EntityDamageEvent.DamageCause.valueOf(type.toString().toUpperCase());\r\n            }\r\n            catch (IllegalArgumentException ex) {\r\n                attribute.echoError(\"Invalid MapTag input to damage_protection - cause '\" + type + \"' is not a valid DamageCause.\");\r\n                return null;\r\n            }\r\n            EntityTag attacker = map.getObjectAs(\"attacker\", EntityTag.class, attribute.context);\r\n            return new ElementTag(NMSHandler.enchantmentHelper.getDamageProtection(object.enchantment, level.asInt(), cause, attacker == null ? null : attacker.getBukkitEntity()));\r\n        });\r\n    }\r\n\r\n    public static ObjectTagProcessor<EnchantmentTag> tagProcessor = new ObjectTagProcessor<>();\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return tagProcessor.getObjectAttribute(this, attribute);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/EntityFormObject.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\n\r\npublic interface EntityFormObject extends ObjectTag {\r\n\r\n    EntityTag getDenizenEntity();\r\n\r\n    default LocationTag getLocation() {\r\n        return getDenizenEntity().getLocation();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.interfaces.EntityAnimation;\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.interfaces.PlayerHelper;\r\nimport com.denizenscript.denizen.npc.traits.MirrorTrait;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityAge;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityColor;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityTame;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\r\nimport com.denizenscript.denizen.scripts.commands.player.DisguiseCommand;\r\nimport com.denizenscript.denizen.scripts.containers.core.EntityScriptContainer;\r\nimport com.denizenscript.denizen.scripts.containers.core.EntityScriptHelper;\r\nimport com.denizenscript.denizen.utilities.*;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.utilities.entity.DenizenEntityType;\r\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizen.utilities.entity.HideEntitiesHelper;\r\nimport com.denizenscript.denizen.utilities.flags.DataPersistenceFlagTracker;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.*;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagRunnable;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport net.citizensnpcs.npc.ai.NPCHolder;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.block.ChiseledBookshelf;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;\r\nimport org.bukkit.inventory.*;\r\nimport org.bukkit.loot.LootTable;\r\nimport org.bukkit.loot.Lootable;\r\nimport org.bukkit.potion.PotionEffect;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\nimport org.bukkit.util.RayTraceResult;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.*;\r\nimport java.util.function.Predicate;\r\nimport java.util.function.Supplier;\r\n\r\npublic class EntityTag implements ObjectTag, Adjustable, EntityFormObject, FlaggableObject, Cloneable {\r\n\r\n    // <--[ObjectType]\r\n    // @name EntityTag\r\n    // @prefix e\r\n    // @base ElementTag\r\n    // @implements FlaggableObject, PropertyHolderObject\r\n    // @ExampleTagBase player\r\n    // @ExampleValues <player>,<npc>\r\n    // @ExampleForReturns\r\n    // - kill %VALUE%\r\n    // @ExampleForReturns\r\n    // - heal %VALUE%\r\n    // @ExampleForReturns\r\n    // - remove %VALUE%\r\n    // @format\r\n    // The identity format for entities is a spawned entity's UUID, or an entity type.\r\n    // For example, 'e@abc123' or 'e@zombie'.\r\n    //\r\n    // @description\r\n    // An EntityTag represents a spawned entity, or a generic entity type.\r\n    //\r\n    // Note that players and NPCs are valid EntityTags, but are generally represented by the more specific\r\n    // PlayerTag and NPCTag objects.\r\n    //\r\n    // Note that a spawned entity can be a living entity (a player, NPC, or mob) or a nonliving entity (a painting, item frame, etc).\r\n    //\r\n    // This object type is flaggable.\r\n    // Flags on this object type will be stored in the world chunk files as a part of the entity's NBT.\r\n    //\r\n    // @Matchable\r\n    // EntityTag matchers, sometimes identified as \"<entity>\", \"<projectile>\", or \"<vehicle>\":\r\n    // \"entity\" plaintext: always matches.\r\n    // \"player\" plaintext: matches any real player (not NPCs).\r\n    // \"npc\" plaintext: matches any Citizens NPC.\r\n    // \"vehicle\" plaintext: matches for any vehicle type (minecarts, boats, horses, etc).\r\n    // \"fish\" plaintext: matches for any fish type (cod, pufferfish, etc).\r\n    // \"projectile\" plaintext: matches for any projectile type (arrow, trident, fish hook, snowball, etc).\r\n    // \"hanging\" plaintext: matches for any hanging type (painting, item_frame, etc).\r\n    // \"monster\" plaintext: matches for any monster type (creepers, zombies, etc).\r\n    // \"animal\" plaintext: matches for any animal type (pigs, cows, etc).\r\n    // \"mob\" plaintext: matches for any mob type (creepers, pigs, etc).\r\n    // \"living\" plaintext: matches for any living type (players, pigs, creepers, etc).\r\n    // \"vanilla_tagged:<tag_name>\": matches if the given vanilla tag applies to the entity. Allows advanced matchers, for example: \"vanilla_tagged:axolotl_*\".\r\n    // \"entity_flagged:<flag>\": a Flag Matchable for EntityTag flags.\r\n    // \"player_flagged:<flag>\": a Flag Matchable for PlayerTag flags (will never match non-players).\r\n    // \"npc_flagged:<flag>\": a Flag Matchable for NPCTag flags (will never match non-NPCs).\r\n    // \"npc_<type>\": matches if the NPC is the given entity type (like \"npc_cow\" or \"npc_mob\" or \"npc_player\").\r\n    // Any entity type name: matches if the entity is of the given type, using advanced matchers.\r\n    //\r\n    // -->\r\n\r\n    /////////////////////\r\n    //   STATIC METHODS\r\n    /////////////////\r\n\r\n    // List a mechanism here if it can be safely run before spawn.\r\n    public static HashSet<String> earlyValidMechanisms = new HashSet<>(Arrays.asList(\r\n            \"max_health\", \"health_data\", \"health\",\r\n            \"visible\", \"armor_pose\", \"arms\", \"base_plate\", \"is_small\", \"marker\",\r\n            \"velocity\", \"age\", \"is_using_riptide\", \"size\", \"item\", \"scale\", \"translation\",\r\n            \"left_rotation\", \"right_rotation\", \"brightness\", \"display\", \"pivot\",\r\n            \"shadow_radius\", \"shadow_strength\"\r\n    ));\r\n    // Definitely not valid: \"item\"\r\n\r\n    private static final Map<UUID, Entity> rememberedEntities = new HashMap<>();\r\n\r\n    public static void rememberEntity(Entity entity) {\r\n        if (entity == null) {\r\n            return;\r\n        }\r\n        rememberedEntities.put(entity.getUniqueId(), entity);\r\n    }\r\n\r\n    public static void forgetEntity(Entity entity) {\r\n        if (entity == null) {\r\n            return;\r\n        }\r\n        rememberedEntities.remove(entity.getUniqueId());\r\n    }\r\n\r\n    public static EntityFormObject mirrorBukkitEntity(Entity entity) {\r\n        return new EntityTag(entity).getDenizenObject();\r\n    }\r\n\r\n    public static boolean isNPC(Entity entity) {\r\n        return entity != null && entity.hasMetadata(\"NPC\") && entity.getMetadata(\"NPC\").get(0).asBoolean();\r\n    }\r\n\r\n    public static boolean isCitizensNPC(Entity entity) {\r\n        if (entity == null) {\r\n            return false;\r\n        }\r\n        if (Depends.citizens == null) {\r\n            return false;\r\n        }\r\n        if (!CitizensAPI.hasImplementation()) {\r\n            return false;\r\n        }\r\n        if (!(entity instanceof NPCHolder)) {\r\n            return false;\r\n        }\r\n        NPC npc = ((NPCHolder) entity).getNPC();\r\n        if (npc == null) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    public static NPCTag getNPCFrom(Entity entity) {\r\n        if (isCitizensNPC(entity)) {\r\n            return NPCTag.fromEntity(entity);\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static boolean isPlayer(Entity entity) {\r\n        return entity instanceof Player && !isNPC(entity);\r\n    }\r\n\r\n    public static PlayerTag getPlayerFrom(Entity entity) {\r\n        if (isPlayer(entity)) {\r\n            return PlayerTag.mirrorBukkitPlayer((Player) entity);\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public ItemTag getItemInHand() {\r\n        if (isLivingEntity() && getLivingEntity().getEquipment() != null) {\r\n            ItemStack its = getLivingEntity().getEquipment().getItemInMainHand();\r\n            if (its == null) {\r\n                return null;\r\n            }\r\n            return new ItemTag(its.clone());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    //////////////////\r\n    //    OBJECT FETCHER\r\n    ////////////////\r\n\r\n    public static boolean allowDespawnedNpcs = false;\r\n\r\n    @Fetchable(\"e\")\r\n    public static EntityTag valueOf(String string, TagContext context) {\r\n        if (string == null) {\r\n            return null;\r\n        }\r\n        UUID id = null;\r\n        if (string.startsWith(\"e@\") && !string.startsWith(\"e@fake\")) {\r\n            int slash = string.indexOf('/');\r\n            if (slash != -1) {\r\n                try {\r\n                    id = UUID.fromString(string.substring(2, slash));\r\n                    string = string.substring(slash + 1);\r\n                    Entity entity = getEntityForID(id);\r\n                    if (entity != null) {\r\n                        EntityTag result = new EntityTag(entity);\r\n                        if (string.equalsIgnoreCase(result.getEntityScript())\r\n                                || string.equalsIgnoreCase(result.getBukkitEntityType().name())) {\r\n                            return result;\r\n                        }\r\n                        else if (context == null || context.showErrors()) {\r\n                            Debug.echoError(\"Invalid EntityTag! ID '\" + id + \"' is valid, but '\" + string + \"' does not match its type data.\");\r\n                        }\r\n                    }\r\n                }\r\n                catch (Exception ex) {\r\n                    // DO NOTHING\r\n                }\r\n            }\r\n        }\r\n        if (ObjectFetcher.isObjectWithProperties(string)) {\r\n            return ObjectFetcher.getObjectFromWithProperties(EntityTag.class, string, context);\r\n        }\r\n        string = CoreUtilities.toLowerCase(string);\r\n        if (string.startsWith(\"e@\")) {\r\n            if (string.startsWith(\"e@fake:\")) {\r\n                try {\r\n                    UUID entityID = UUID.fromString(string.substring(\"e@fake:\".length()));\r\n                    FakeEntity entity = FakeEntity.idsToEntities.get(entityID);\r\n                    if (entity != null) {\r\n                        return entity.entity;\r\n                    }\r\n                    return null;\r\n                }\r\n                catch (Exception ex) {\r\n                    // DO NOTHING\r\n                }\r\n            }\r\n            string = string.substring(\"e@\".length());\r\n        }\r\n        // Choose a random entity type if \"random\" is used\r\n        if (string.equals(\"random\")) {\r\n            EntityType randomType = null;\r\n            // When selecting a random entity type, ignore invalid or inappropriate ones\r\n            while (randomType == null ||\r\n                    randomType.name().matches(\"^(COMPLEX_PART|DROPPED_ITEM|ENDER_CRYSTAL\" +\r\n                            \"|ENDER_DRAGON|FISHING_HOOK|ITEM_FRAME|LEASH_HITCH|LIGHTNING\" +\r\n                            \"|PAINTING|PLAYER|UNKNOWN|WEATHER|WITHER|WITHER_SKULL)$\")) {\r\n\r\n                randomType = EntityType.values()[CoreUtilities.getRandom().nextInt(EntityType.values().length)];\r\n            }\r\n            return new EntityTag(randomType, \"RANDOM\");\r\n        }\r\n        // NPC entity\r\n        if (string.startsWith(\"n@\")) {\r\n            NPCTag npc = NPCTag.valueOf(string, context);\r\n            if (npc != null) {\r\n                if (npc.isSpawned()) {\r\n                    return new EntityTag(npc);\r\n                }\r\n                else {\r\n                    if (!allowDespawnedNpcs && context != null && context.showErrors()) {\r\n                        Debug.echoDebug(context, \"NPC '\" + string + \"' is not spawned, errors may follow!\");\r\n                    }\r\n                    return new EntityTag(npc);\r\n                }\r\n            }\r\n            else {\r\n                if (context == null || context.debug || CoreConfiguration.debugOverride) {\r\n                    Debug.echoError(\"NPC '\" + string + \"' does not exist!\");\r\n                }\r\n            }\r\n        }\r\n        // Player entity\r\n        else if (string.startsWith(\"p@\")) {\r\n            PlayerTag returnable = PlayerTag.valueOf(string, context);\r\n            if (returnable != null && returnable.isOnline()) {\r\n                return new EntityTag(returnable.getPlayerEntity());\r\n            }\r\n            else if (context == null || context.showErrors()) {\r\n                Debug.echoError(\"Invalid Player! '\" + string + \"' could not be found. Has the player logged off?\");\r\n            }\r\n        }\r\n        if (ScriptRegistry.containsScript(string, EntityScriptContainer.class)) {\r\n            // Construct a new custom unspawned entity from script\r\n            EntityTag entity = ScriptRegistry.getScriptContainerAs(string, EntityScriptContainer.class).getEntityFrom();\r\n            entity.uuid = id;\r\n            return entity;\r\n        }\r\n        List<String> data = CoreUtilities.split(string, ',');\r\n        String typeStr = data.get(0);\r\n        // Handle custom DenizenEntityTypes\r\n        DenizenEntityType type = DenizenEntityType.getByName(typeStr);\r\n        if (type == null && Settings.cache_legacySpigotNamesSupport && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            String updatedTypeStr = NMSHandler.instance.updateLegacyName(EntityType.class, typeStr);\r\n            if (!CoreUtilities.equalsIgnoreCase(typeStr, updatedTypeStr)) {\r\n                BukkitImplDeprecations.oldSpigotNames.warn(context);\r\n                type = DenizenEntityType.getByName(updatedTypeStr);\r\n            }\r\n        }\r\n        if (type != null && type.getBukkitEntityType() != EntityType.UNKNOWN) {\r\n            EntityTag entity = new EntityTag(type, data.size() > 1 ? data.get(1) : null);\r\n            entity.uuid = id;\r\n            return entity;\r\n        }\r\n        try {\r\n            UUID entityID = id != null ? id : UUID.fromString(string);\r\n            Entity entity = getEntityForID(entityID);\r\n            if (entity != null) {\r\n                return new EntityTag(entity);\r\n            }\r\n            return null;\r\n        }\r\n        catch (Exception ex) {\r\n            // DO NOTHING\r\n        }\r\n        if (context == null || context.showErrors()) {\r\n            Debug.log(\"valueOf EntityTag returning null: \" + string);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static Entity getEntityForID(UUID id) {\r\n        if (rememberedEntities.containsKey(id)) {\r\n            return rememberedEntities.get(id);\r\n        }\r\n        return Bukkit.getEntity(id);\r\n    }\r\n\r\n    public static boolean matches(String arg) {\r\n        // Accept anything that starts with a valid entity object identifier.\r\n        if (arg.startsWith(\"n@\") || arg.startsWith(\"e@\") || arg.startsWith(\"p@\")) {\r\n            return true;\r\n        }\r\n        // No longer picky about e@.. let's remove it from the arg\r\n        arg = CoreUtilities.toUpperCase(arg.replace(\"e@\", \"\"));\r\n        // Allow 'random'\r\n        if (arg.equals(\"RANDOM\")) {\r\n            return true;\r\n        }\r\n        // Allow any entity script\r\n        if (ScriptRegistry.containsScript(arg, EntityScriptContainer.class)) {\r\n            return true;\r\n        }\r\n        // Check first word with a valid entity_type (other groups are datas used in constructors)\r\n        if (DenizenEntityType.isRegistered(CoreUtilities.split(arg, ',').get(0))) {\r\n            return true;\r\n        }\r\n        // No luck otherwise!\r\n        return false;\r\n    }\r\n\r\n    /////////////////////\r\n    //   CONSTRUCTORS\r\n    //////////////////\r\n\r\n    public EntityTag(Entity entity) {\r\n        if (entity != null) {\r\n            this.entity = entity;\r\n            entityScript = EntityScriptHelper.getEntityScript(entity);\r\n            this.uuid = entity.getUniqueId();\r\n            this.entity_type = DenizenEntityType.getByEntity(entity);\r\n            if (isCitizensNPC(entity)) {\r\n                this.npc = getNPCFrom(entity);\r\n            }\r\n        }\r\n        else {\r\n            Debug.echoError(\"Entity referenced is null!\");\r\n        }\r\n    }\r\n\r\n    public EntityTag(EntityType entityType) {\r\n        if (entityType != null) {\r\n            this.entity = null;\r\n            this.entity_type = DenizenEntityType.getByName(entityType.name());\r\n        }\r\n        else {\r\n            Debug.echoError(\"Entity_type referenced is null!\");\r\n        }\r\n    }\r\n\r\n    public EntityTag(EntityType entityType, ArrayList<Mechanism> mechanisms) {\r\n        this(entityType);\r\n        this.mechanisms = mechanisms;\r\n    }\r\n\r\n    public EntityTag(EntityType entityType, String data1) {\r\n        if (entityType != null) {\r\n            this.entity = null;\r\n            this.entity_type = DenizenEntityType.getByName(entityType.name());\r\n            this.data1 = data1;\r\n        }\r\n        else {\r\n            Debug.echoError(\"Entity_type referenced is null!\");\r\n        }\r\n    }\r\n\r\n    public EntityTag(DenizenEntityType entityType) {\r\n        if (entityType != null) {\r\n            this.entity = null;\r\n            this.entity_type = entityType;\r\n        }\r\n        else {\r\n            Debug.echoError(\"DenizenEntityType referenced is null!\");\r\n        }\r\n    }\r\n\r\n    public EntityTag(DenizenEntityType entityType, ArrayList<Mechanism> mechanisms) {\r\n        this(entityType);\r\n        this.mechanisms = mechanisms;\r\n    }\r\n\r\n    public EntityTag(DenizenEntityType entityType, String data1) {\r\n        if (entityType != null) {\r\n            this.entity = null;\r\n            this.entity_type = entityType;\r\n            this.data1 = data1;\r\n        }\r\n        else {\r\n            Debug.echoError(\"DenizenEntityType referenced is null!\");\r\n        }\r\n    }\r\n\r\n    public EntityTag(NPCTag npc) {\r\n        if (Depends.citizens == null) {\r\n            return;\r\n        }\r\n        if (npc != null) {\r\n            this.npc = npc;\r\n            if (npc.isSpawned()) {\r\n                this.entity = npc.getEntity();\r\n                this.entity_type = DenizenEntityType.getByName(npc.getEntityType().name());\r\n                this.uuid = entity.getUniqueId();\r\n            }\r\n        }\r\n        else {\r\n            Debug.echoError(\"NPC referenced is null!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public EntityTag duplicate() {\r\n        if (isUnique()) {\r\n            return this;\r\n        }\r\n        try {\r\n            EntityTag copy = (EntityTag) clone();\r\n            if (copy.mechanisms != null) {\r\n                copy.mechanisms = new ArrayList<>(copy.mechanisms);\r\n            }\r\n            return copy;\r\n        }\r\n        catch (CloneNotSupportedException ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public AbstractFlagTracker getFlagTracker() {\r\n        if (isCitizensNPC()) {\r\n            return getDenizenNPC().getFlagTracker();\r\n        }\r\n        else if (isPlayer()) {\r\n            return getDenizenPlayer().getFlagTracker();\r\n        }\r\n        Entity ent = getBukkitEntity();\r\n        if (ent != null) {\r\n            return new DataPersistenceFlagTracker(ent);\r\n        }\r\n        else {\r\n            // TODO: Warning?\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getReasonNotFlaggable() {\r\n        if (!isSpawned() || getBukkitEntity() == null) {\r\n            return \"the entity is not spawned\";\r\n        }\r\n        return \"unknown reason - something went wrong\";\r\n    }\r\n\r\n    @Override\r\n    public void reapplyTracker(AbstractFlagTracker tracker) {\r\n        if (CoreConfiguration.skipAllFlagCleanings) {\r\n            return;\r\n        }\r\n        if (cleanRateProtect + 60000 > DenizenCore.serverTimeMillis) {\r\n            tracker.doTotalClean();\r\n            cleanRateProtect = DenizenCore.serverTimeMillis;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean isTruthy() {\r\n        return isSpawnedOrValidForTag();\r\n    }\r\n\r\n    public Entity entity = null;\r\n    public long cleanRateProtect = -60000;\r\n    public DenizenEntityType entity_type = null;\r\n    private String data1 = null;\r\n    private NPCTag npc = null;\r\n    public UUID uuid = null;\r\n    private String entityScript = null;\r\n    public boolean isFake = false;\r\n    public boolean isFakeValid = false;\r\n\r\n    public DenizenEntityType getEntityType() {\r\n        return entity_type;\r\n    }\r\n\r\n    public EntityType getBukkitEntityType() {\r\n        return entity_type.getBukkitEntityType();\r\n    }\r\n\r\n    public void setEntityScript(String entityScript) {\r\n        this.entityScript = entityScript;\r\n    }\r\n\r\n    public String getEntityScript() {\r\n        return entityScript;\r\n    }\r\n\r\n    public UUID getUUID() {\r\n        if (uuid == null && entity != null) {\r\n            uuid = entity.getUniqueId();\r\n        }\r\n        return uuid;\r\n    }\r\n\r\n    @Override\r\n    public EntityTag getDenizenEntity() {\r\n        return this;\r\n    }\r\n\r\n    public EntityFormObject getDenizenObject() {\r\n        if (entity == null && npc == null) {\r\n            return this;\r\n        }\r\n        if (isCitizensNPC()) {\r\n            return getDenizenNPC();\r\n        }\r\n        else if (isPlayer()) {\r\n            return new PlayerTag(getPlayer());\r\n        }\r\n        else {\r\n            return this;\r\n        }\r\n    }\r\n\r\n    public Entity getBukkitEntity() {\r\n        if (uuid != null && (entity == null || !entity.isValid())) {\r\n            if (!isFake) {\r\n                Entity backup = Bukkit.getEntity(uuid);\r\n                if (backup != null) {\r\n                    entity = backup;\r\n                }\r\n            }\r\n        }\r\n        return entity;\r\n    }\r\n\r\n    public LivingEntity getLivingEntity() {\r\n        if (entity instanceof LivingEntity) {\r\n            return (LivingEntity) entity;\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public boolean isLivingEntity() {\r\n        return entity instanceof LivingEntity;\r\n    }\r\n\r\n    public boolean isLivingEntityType() {\r\n        if (getBukkitEntity() == null && entity_type != null) {\r\n            return entity_type.getBukkitEntityType().isAlive();\r\n        }\r\n        return entity instanceof LivingEntity;\r\n    }\r\n\r\n    public boolean isMonsterType() {\r\n        if (getBukkitEntity() == null && entity_type != null) {\r\n            return Monster.class.isAssignableFrom(entity_type.getBukkitEntityType().getEntityClass());\r\n        }\r\n        return getBukkitEntity() instanceof Monster;\r\n    }\r\n\r\n    public boolean isMobType() {\r\n        if (getBukkitEntity() == null && entity_type != null) {\r\n            return Mob.class.isAssignableFrom(entity_type.getBukkitEntityType().getEntityClass());\r\n        }\r\n        return getBukkitEntity() instanceof Mob;\r\n    }\r\n\r\n    public boolean isAnimalType() {\r\n        if (getBukkitEntity() == null && entity_type != null) {\r\n            return Animals.class.isAssignableFrom(entity_type.getBukkitEntityType().getEntityClass());\r\n        }\r\n        return getBukkitEntity() instanceof Animals;\r\n    }\r\n\r\n    public boolean hasInventory() {\r\n        return getBukkitEntity() instanceof InventoryHolder || isCitizensNPC();\r\n    }\r\n\r\n    public NPCTag getDenizenNPC() {\r\n        if (npc != null) {\r\n            return npc;\r\n        }\r\n        else {\r\n            return getNPCFrom(entity);\r\n        }\r\n    }\r\n\r\n    public boolean isNPC() {\r\n        return npc != null || isNPC(entity);\r\n    }\r\n\r\n    public boolean isCitizensNPC() {\r\n        return npc != null || isCitizensNPC(entity);\r\n    }\r\n\r\n    public Player getPlayer() {\r\n        if (isPlayer()) {\r\n            return (Player) entity;\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public PlayerTag getDenizenPlayer() {\r\n        if (isPlayer()) {\r\n            return new PlayerTag(getPlayer());\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public boolean isPlayer() {\r\n        if (entity == null) {\r\n            return entity_type.getBukkitEntityType() == EntityType.PLAYER && npc == null;\r\n        }\r\n        return entity instanceof Player && !isNPC();\r\n    }\r\n\r\n    public <T extends Entity> T as(Class<T> entityClass) {\r\n        return (T) getBukkitEntity();\r\n    }\r\n\r\n    public Projectile getProjectile() {\r\n        return (Projectile) entity;\r\n    }\r\n\r\n    public boolean isProjectile() {\r\n        return entity instanceof Projectile;\r\n    }\r\n\r\n    public EntityTag getShooter() {\r\n        if (getBukkitEntity() instanceof TNTPrimed) {\r\n            Entity source = ((TNTPrimed) getBukkitEntity()).getSource();\r\n            if (source != null) {\r\n                return new EntityTag(source);\r\n            }\r\n        }\r\n        else if (isProjectile()) {\r\n            ProjectileSource shooter = getProjectile().getShooter();\r\n            if (shooter instanceof Entity) {\r\n                return new EntityTag((Entity) shooter);\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public void setShooter(EntityTag shooter) {\r\n        if (getBukkitEntity() instanceof TNTPrimed) {\r\n            ((TNTPrimed) getBukkitEntity()).setSource(shooter.getBukkitEntity());\r\n        }\r\n        else if (isProjectile() && shooter.isLivingEntity()) {\r\n            getProjectile().setShooter(shooter.getLivingEntity());\r\n        }\r\n    }\r\n\r\n    public boolean hasShooter() {\r\n        return getShooter() != null;\r\n    }\r\n\r\n    public Inventory getBukkitInventory() {\r\n        if (hasInventory()) {\r\n            if (!isCitizensNPC()) {\r\n                return ((InventoryHolder) getBukkitEntity()).getInventory();\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public InventoryTag getInventory() {\r\n        return hasInventory() ? isCitizensNPC() ? getDenizenNPC().getDenizenInventory() : InventoryTag.mirrorBukkitInventory(getBukkitInventory()) : null;\r\n    }\r\n\r\n    public String getName() {\r\n        if (isCitizensNPC()) {\r\n            return getDenizenNPC().getCitizen().getName();\r\n        }\r\n        if (entity instanceof FakePlayer) {\r\n            return ((FakePlayer) entity).getFullName();\r\n        }\r\n        if (entity instanceof Player) {\r\n            return entity.getName();\r\n        }\r\n        String customName = entity == null ? null : entity.getCustomName();\r\n        if (customName != null) {\r\n            return customName;\r\n        }\r\n        return entity_type.getName();\r\n    }\r\n\r\n    public ListTag getEquipment() {\r\n        ItemStack[] equipment = getLivingEntity().getEquipment().getArmorContents();\r\n        ListTag equipmentList = new ListTag();\r\n        for (ItemStack item : equipment) {\r\n            equipmentList.addObject(new ItemTag(item));\r\n        }\r\n        return equipmentList;\r\n    }\r\n\r\n    public boolean isGeneric() {\r\n        return !isUnique();\r\n    }\r\n\r\n    @Override\r\n    public LocationTag getLocation() {\r\n        Entity entity = getBukkitEntity();\r\n        if (entity != null) {\r\n            return new LocationTag(entity.getLocation());\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    public LocationTag getEyeLocation() {\r\n        Entity entity = getBukkitEntity();\r\n        if (entity == null) {\r\n            return null;\r\n        }\r\n        if (isPlayer()) {\r\n            return new LocationTag(getPlayer().getEyeLocation());\r\n        }\r\n        else if (!isGeneric() && isLivingEntity()) {\r\n            return new LocationTag(getLivingEntity().getEyeLocation());\r\n        }\r\n        else if (!isGeneric()) {\r\n            return new LocationTag(entity.getLocation());\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    public Location getTargetBlockSafe(Set<Material> mats, int range) {\r\n        try {\r\n            NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\r\n            return getLivingEntity().getTargetBlock(mats, range).getLocation();\r\n        }\r\n        finally {\r\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\r\n        }\r\n    }\r\n\r\n    public Vector getVelocity() {\r\n        Entity entity = getBukkitEntity();\r\n        if (entity == null) {\r\n            return null;\r\n        }\r\n        return entity.getVelocity();\r\n    }\r\n\r\n    public void setVelocity(Vector vector) {\r\n        Entity entity = getBukkitEntity();\r\n        if (entity == null) {\r\n            return;\r\n        }\r\n        entity.setVelocity(vector);\r\n    }\r\n\r\n    public World getWorld() {\r\n        Entity entity = getBukkitEntity();\r\n        if (entity == null) {\r\n            return null;\r\n        }\r\n        return entity.getWorld();\r\n    }\r\n\r\n    public void spawnAt(Location location) {\r\n        spawnAt(location, TeleportCause.PLUGIN, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n    }\r\n\r\n    public void spawnAt(Location location, TeleportCause cause, CreatureSpawnEvent.SpawnReason reason) {\r\n        if (location.getWorld() == null) {\r\n            Debug.echoError(\"Cannot teleport or spawn entity at location '\" + new LocationTag(location) + \"' because it is missing a world.\");\r\n            return;\r\n        }\r\n        // If the entity is already spawned, teleport it.\r\n        if (isCitizensNPC() || (isUnique() && entity != null)) {\r\n            teleport(location, cause);\r\n            return;\r\n        }\r\n        else if (entity_type == null) {\r\n            Debug.echoError(\"Cannot spawn a null EntityTag!\");\r\n            return;\r\n        }\r\n        if (entity_type.getBukkitEntityType() == EntityType.PLAYER && !entity_type.isCustom()) {\r\n            if (Depends.citizens == null) {\r\n                Debug.echoError(\"Cannot spawn entity of type PLAYER!\");\r\n                return;\r\n            }\r\n            NPCTag npc = new NPCTag(net.citizensnpcs.api.CitizensAPI.getNPCRegistry().createNPC(EntityType.PLAYER, data1));\r\n            npc.getCitizen().spawn(location);\r\n            entity = npc.getEntity();\r\n        }\r\n        else if (entity_type.getBukkitEntityType() == EntityType.FALLING_BLOCK) {\r\n            MaterialTag material = null;\r\n            if (data1 != null && MaterialTag.matches(data1)) {\r\n                material = MaterialTag.valueOf(data1, CoreUtilities.basicContext);\r\n                // If we did not get a block with \"RANDOM\", or we got\r\n                // air or portals, keep trying\r\n                while (data1.equalsIgnoreCase(\"RANDOM\") &&\r\n                        ((!material.getMaterial().isBlock()) ||\r\n                                material.getMaterial() == Material.AIR ||\r\n                                material.getMaterial() == Material.NETHER_PORTAL ||\r\n                                material.getMaterial() == Material.END_PORTAL)) {\r\n                    material = MaterialTag.valueOf(data1, CoreUtilities.basicContext);\r\n                }\r\n            }\r\n            else {\r\n                for (Mechanism mech : mechanisms) {\r\n                    if (mech.getName().equals(\"fallingblock_type\")) {\r\n                        material = mech.valueAsType(MaterialTag.class);\r\n                        mechanisms.remove(mech);\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            // If material is null or not a block, default to SAND\r\n            if (material == null || !material.getMaterial().isBlock() || !material.hasModernData()) {\r\n                material = new MaterialTag(Material.SAND);\r\n            }\r\n            // This is currently the only way to spawn a falling block\r\n            entity = location.getWorld().spawnFallingBlock(location, material.getModernData());\r\n        }\r\n        else if (entity_type.getBukkitEntityType() == EntityType.PAINTING) {\r\n            entity = entity_type.spawnNewEntity(location, mechanisms, entityScript, reason);\r\n            location = location.clone();\r\n            Painting painting = (Painting) entity;\r\n            Art art = null;\r\n            BlockFace face = null;\r\n            try {\r\n                for (Mechanism mech : mechanisms) {\r\n                    if (mech.getName().equals(\"painting\")) {\r\n                        art = Registry.ART.get(Utilities.parseNamespacedKey(mech.getValue().asString()));\r\n                    }\r\n                    else if (mech.getName().equals(\"rotation\")) {\r\n                        face = BlockFace.valueOf(mech.getValue().asString().toUpperCase());\r\n                    }\r\n                }\r\n            }\r\n            catch (Exception ex) {\r\n                // ignore\r\n            }\r\n            if (art != null && face != null) { // Paintings are the worst\r\n                if (NMSHandler.entityHelper.getBlockHeight(art) % 2 == 0) {\r\n                    location.subtract(0, 1, 0);\r\n                }\r\n                if (NMSHandler.entityHelper.getBlockWidth(art) % 2 == 0) {\r\n                    if (face == BlockFace.WEST) {\r\n                        location.subtract(0, 0, 1);\r\n                    }\r\n                    else if (face == BlockFace.SOUTH) {\r\n                        location.subtract(1, 0, 0);\r\n                    }\r\n                }\r\n                painting.teleport(location);\r\n                painting.setFacingDirection(face, true);\r\n                painting.setArt(art, true);\r\n            }\r\n        }\r\n        else {\r\n            entity = entity_type.spawnNewEntity(location, mechanisms, entityScript, reason);\r\n        }\r\n        if (entity == null) {\r\n            if (!new LocationTag(location).isChunkLoaded()) {\r\n                Debug.echoError(\"Error spawning entity - tried to spawn in an unloaded chunk.\");\r\n            }\r\n            else {\r\n                Debug.echoError(\"Error spawning entity - bad entity type, or blocked by another plugin?\");\r\n            }\r\n            return;\r\n        }\r\n        uuid = entity.getUniqueId();\r\n        if (entityScript != null) {\r\n            EntityScriptHelper.setEntityScript(entity, entityScript);\r\n        }\r\n        for (Mechanism mechanism : mechanisms) {\r\n            safeAdjust(new Mechanism(mechanism.getName(), mechanism.value, mechanism.context));\r\n        }\r\n        mechanisms.clear();\r\n    }\r\n\r\n    public boolean isSpawnedOrValidForTag() {\r\n        if (isFake) {\r\n            return true;\r\n        }\r\n        if (entity == null) { // Note: this breaks thread-patch for entities that need revalidating\r\n            if (uuid == null) {\r\n                return false;\r\n            }\r\n            return isValid() || rememberedEntities.containsKey(uuid);\r\n        }\r\n        NMSHandler.chunkHelper.changeChunkServerThread(entity.getWorld());\r\n        try {\r\n            return isValid() || rememberedEntities.containsKey(entity.getUniqueId());\r\n        }\r\n        finally {\r\n            NMSHandler.chunkHelper.restoreServerThread(entity.getWorld());\r\n        }\r\n    }\r\n\r\n    public boolean isSpawned() {\r\n        return isValid();\r\n    }\r\n\r\n    public boolean isValid() {\r\n        Entity entity = getBukkitEntity();\r\n        return entity != null && (entity.isValid() || (isFake && isFakeValid));\r\n    }\r\n\r\n    public void remove() {\r\n        entity.remove();\r\n    }\r\n\r\n    public void teleport(Location location) {\r\n        teleport(location, TeleportCause.PLUGIN);\r\n    }\r\n\r\n    public void teleport(Location location, TeleportCause cause) {\r\n        if (location.getWorld() == null) {\r\n            Debug.echoError(\"Cannot teleport or spawn entity at location '\" + new LocationTag(location) + \"' because it is missing a world.\");\r\n            return;\r\n        }\r\n        if (isCitizensNPC()) {\r\n            if (getDenizenNPC().getCitizen().isSpawned()) {\r\n                getDenizenNPC().getCitizen().teleport(location, cause);\r\n            }\r\n            else {\r\n                if (getDenizenNPC().getCitizen().spawn(location)) {\r\n                    entity = getDenizenNPC().getCitizen().getEntity();\r\n                    uuid = getDenizenNPC().getCitizen().getEntity().getUniqueId();\r\n                }\r\n                else {\r\n                    if (new LocationTag(location).isChunkLoaded()) {\r\n                        Debug.echoError(\"Error spawning NPC - tried to spawn in an unloaded chunk.\");\r\n                    }\r\n                    else {\r\n                        Debug.echoError(\"Error spawning NPC - blocked by plugin\");\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        else if (isFake) {\r\n            NMSHandler.entityHelper.snapPositionTo(entity, location.toVector());\r\n            NMSHandler.entityHelper.look(entity, location.getYaw(), location.getPitch());\r\n        }\r\n        else {\r\n            getBukkitEntity().teleport(location, cause);\r\n            if (entity.getWorld().equals(location.getWorld())) { // Force the teleport through (for things like mounts)\r\n                NMSHandler.entityHelper.teleport(entity, location);\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Make this entity target another living entity, attempting both\r\n     * old entity AI and new entity AI targeting methods\r\n     *\r\n     * @param target The LivingEntity target\r\n     */\r\n\r\n    public void target(LivingEntity target) {\r\n        if (!isSpawned()) {\r\n            return;\r\n        }\r\n        if (entity instanceof Mob) {\r\n            ((Mob) entity).setTarget(target);\r\n        }\r\n        else if (entity instanceof ShulkerBullet) {\r\n            ((ShulkerBullet) entity).setTarget(target);\r\n        }\r\n        else {\r\n            Debug.echoError(identify() + \" is not an entity type that can hold a target!\");\r\n        }\r\n    }\r\n\r\n    public void setEntity(Entity entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    public int comparesTo(EntityTag entity) {\r\n        // Never matches a null\r\n        if (entity == null) {\r\n            return 0;\r\n        }\r\n\r\n        // If provided is unique, and both are the same unique entity, return 1.\r\n        if (entity.isUnique() && entity.identify().equals(identify())) {\r\n            return 1;\r\n        }\r\n\r\n        // If provided isn't unique...\r\n        if (!entity.isUnique()) {\r\n            // Return 1 if this object isn't unique either, but matches\r\n            if (!isUnique() && entity.identify().equals(identify())) {\r\n                return 1;\r\n            }\r\n            // Return 1 if the provided object isn't unique, but whose entity_type\r\n            // matches this object, even if this object is unique.\r\n            if (entity_type == entity.entity_type) {\r\n                return 1;\r\n            }\r\n        }\r\n\r\n        return 0;\r\n    }\r\n\r\n    public boolean comparedTo(String compare) {\r\n        compare = CoreUtilities.toLowerCase(compare);\r\n        if (compare.equals(\"entity\")) {\r\n            return true;\r\n        }\r\n        else if (compare.equals(\"player\")) {\r\n            return isPlayer();\r\n        }\r\n        else if (compare.equals(\"npc\")) {\r\n            return isCitizensNPC() || isNPC();\r\n        }\r\n        else if (getEntityScript() != null && compare.equals(CoreUtilities.toLowerCase(getEntityScript()))) {\r\n            return true;\r\n        }\r\n        else if (compare.equals(getEntityType().getLowercaseName())) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    /////////////////////\r\n    //  ObjectTag Methods\r\n    ///////////////////\r\n\r\n    private String prefix = \"Entity\";\r\n\r\n    @Override\r\n    public String getPrefix() {\r\n        return prefix;\r\n    }\r\n\r\n    @Override\r\n    public EntityTag setPrefix(String prefix) {\r\n        this.prefix = prefix;\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public String debuggable() {\r\n        if (npc != null) {\r\n            return npc.debuggable();\r\n        }\r\n        if (entity != null) {\r\n            if (isPlayer()) {\r\n                return getDenizenPlayer().debuggable();\r\n            }\r\n            else if (isFake) {\r\n                return \"<LG>e@<Y>FAKE: \" + getUUID() + \"<GR>(FAKE-\" + entity.getType().name() + \"/\" + entity.getName() + \")\";\r\n            }\r\n            else if (isSpawnedOrValidForTag()) {\r\n                return \"<LG>e@<Y> \" + getUUID() + \"<GR>(\" + entity.getType().name() + \"/\" + entity.getName() + \")\";\r\n            }\r\n        }\r\n        return identify(this::getWaitingMechanismsDebuggable);\r\n    }\r\n\r\n    @Override\r\n    public String savable() {\r\n        if (npc != null) {\r\n            return npc.savable();\r\n        }\r\n        else if (isPlayer()) {\r\n            return getDenizenPlayer().savable();\r\n        }\r\n        else if (isFake) {\r\n            return \"e@fake:\" + getUUID();\r\n        }\r\n        else {\r\n            return identify();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String identify() {\r\n        return identify(this::getWaitingMechanismsString);\r\n    }\r\n\r\n    public String identify(Supplier<String> mechsHandler) {\r\n        if (npc != null) {\r\n            return npc.identify();\r\n        }\r\n        if (isFake) {\r\n            return \"e@fake:\" + getUUID();\r\n        }\r\n        if (getUUID() != null) {\r\n            if (isPlayer()) {\r\n                return getDenizenPlayer().identify();\r\n            }\r\n            if (entityScript != null) {\r\n                return \"e@\" + getUUID() + \"/\" + entityScript;\r\n            }\r\n            if (entity_type != null) {\r\n                return \"e@\" + getUUID() + \"/\" + entity_type.getLowercaseName();\r\n            }\r\n        }\r\n        if (entityScript != null) {\r\n            return \"e@\" + entityScript + mechsHandler.get();\r\n        }\r\n        if (entity_type != null) {\r\n            return \"e@\" + entity_type.getLowercaseName() + mechsHandler.get();\r\n        }\r\n        return \"null\";\r\n    }\r\n\r\n    public String getWaitingMechanismsDebuggable() {\r\n        StringBuilder properties = new StringBuilder();\r\n        for (Mechanism mechanism : mechanisms) {\r\n            properties.append(mechanism.getName()).append(\" <LG>=<Y> \").append(mechanism.getValue().asString()).append(\"<LG>; <Y>\");\r\n        }\r\n        if (properties.length() > 0) {\r\n            return \"<LG>[<Y>\" + properties.substring(0, properties.length() - \"; <Y>\".length()) + \" <LG>]\";\r\n        }\r\n        return \"\";\r\n    }\r\n\r\n    public String getWaitingMechanismsString() {\r\n        StringBuilder properties = new StringBuilder();\r\n        for (Mechanism mechanism : mechanisms) {\r\n            properties.append(PropertyParser.escapePropertyKey(mechanism.getName())).append(\"=\").append(PropertyParser.escapePropertyValue(mechanism.getValue().asString())).append(\";\");\r\n        }\r\n        if (properties.length() > 0) {\r\n            return \"[\" + properties.substring(0, properties.length() - 1) + \"]\";\r\n        }\r\n        return \"\";\r\n    }\r\n\r\n    @Override\r\n    public String identifySimple() {\r\n        if (npc != null && npc.isValid()) {\r\n            return \"n@\" + npc.getId();\r\n        }\r\n        if (isPlayer()) {\r\n            return \"p@\" + getPlayer().getName();\r\n        }\r\n        if (entityScript != null) {\r\n            return \"e@\" + entityScript;\r\n        }\r\n        if (entity_type != null) {\r\n            return \"e@\" + entity_type.getLowercaseName();\r\n        }\r\n        return \"null\";\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public Object getJavaObject() {\r\n        return entity == null ? getBukkitEntityType() : entity;\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnique() {\r\n        return entity != null || uuid != null || isFake || npc != null;\r\n    }\r\n\r\n    public LocationTag doLocationTag(Attribute attribute) {\r\n        if (attribute.startsWith(\"cursor_on\", 2)) {\r\n            BukkitImplDeprecations.entityLocationCursorOnTag.warn(attribute.context);\r\n            int range = attribute.getIntContext(2);\r\n            if (range < 1) {\r\n                range = 50;\r\n            }\r\n            Set<Material> set = new HashSet<>();\r\n            set.add(Material.AIR);\r\n\r\n            if (attribute.startsWith(\"ignore\", 3) && attribute.hasContext(3)) {\r\n                List<MaterialTag> ignoreList = attribute.contextAsType(3, ListTag.class).filter(MaterialTag.class, attribute.context);\r\n                for (MaterialTag material : ignoreList) {\r\n                    set.add(material.getMaterial());\r\n                }\r\n                attribute.fulfill(1);\r\n            }\r\n            attribute.fulfill(1);\r\n            return new LocationTag(getTargetBlockSafe(set, range));\r\n        }\r\n\r\n        if (attribute.startsWith(\"standing_on\", 2)) {\r\n            BukkitImplDeprecations.entityStandingOn.warn(attribute.context);\r\n            attribute.fulfill(1);\r\n            return new LocationTag(getBukkitEntity().getLocation().clone().add(0, -0.5f, 0));\r\n        }\r\n        return new LocationTag(getBukkitEntity().getLocation());\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\r\n        PropertyParser.registerPropertyTagHandlers(EntityTag.class, tagProcessor);\r\n\r\n        /////////////////////\r\n        //   UNSPAWNED ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.entity_type>\r\n        // @returns ElementTag\r\n        // @deprecated Use 'EntityTag.type' on MC 1.20+.\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.type> on MC 1.20+, which returns entity type names as specified by Mojang (scripts using this may need an update when switching).\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"entity_type\", (attribute, object) -> {\r\n            if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\r\n                return new ElementTag(object.getEntityType().getName(), true);\r\n            }\r\n            BukkitImplDeprecations.oldSpigotNames.warn(attribute.context);\r\n            return new ElementTag(switch (object.getEntityType().getName()) {\r\n                case \"ITEM\" -> \"DROPPED_ITEM\";\r\n                case \"LEASH_KNOT\" -> \"LEASH_HITCH\";\r\n                case \"EYE_OF_ENDER\" -> \"ENDER_SIGNAL\";\r\n                case \"POTION\" -> \"SPLASH_POTION\";\r\n                case \"EXPERIENCE_BOTTLE\" -> \"THROWN_EXP_BOTTLE\";\r\n                case \"TNT\" -> \"PRIMED_TNT\";\r\n                case \"FIREWORK_ROCKET\" -> \"FIREWORK\";\r\n                case \"COMMAND_BLOCK_MINECART\" -> \"MINECART_COMMAND\";\r\n                case \"CHEST_MINECART\" -> \"MINECART_CHEST\";\r\n                case \"FURNACE_MINECART\" -> \"MINECART_FURNACE\";\r\n                case \"TNT_MINECART\" -> \"MINECART_TNT\";\r\n                case \"HOPPER_MINECART\" -> \"MINECART_HOPPER\";\r\n                case \"SPAWNER_MINECART\" -> \"MINECART_MOB_SPAWNER\";\r\n                case \"MOOSHROOM\" -> \"MUSHROOM_COW\";\r\n                case \"SNOW_GOLEM\" -> \"SNOWMAN\";\r\n                case \"END_CRYSTAL\" -> \"ENDER_CRYSTAL\";\r\n                case \"FISHING_BOBBER\" -> \"FISHING_HOOK\";\r\n                case \"LIGHTNING_BOLT\" -> \"LIGHTNING\";\r\n                default -> object.getEntityType().getName();\r\n            }, true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.type>\r\n        // @returns ElementTag\r\n        // @group data\r\n        // @description\r\n        // Returns the type of the entity.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"type\", (attribute, object) -> {\r\n            return new ElementTag(object.getEntityType().getLowercaseName(), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.translated_name>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the localized name of the entity.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"translated_name\", (attribute, object) -> {\r\n            String key = object.getBukkitEntityType().getKey().getKey();\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[translate=entity.minecraft.\" + key + \"]\", true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.vanilla_tags>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of vanilla tags that apply to this entity type. See also <@link url https://minecraft.wiki/w/Tag>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"vanilla_tags\", (attribute, object) -> {\r\n            HashSet<String> tags = VanillaTagHelper.tagsByEntity.get(object.getBukkitEntityType());\r\n            if (tags == null) {\r\n                return new ListTag();\r\n            }\r\n            return new ListTag(tags);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_spawned>\r\n        // @returns ElementTag(Boolean)\r\n        // @group data\r\n        // @description\r\n        // Returns whether the entity is spawned.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_spawned\", (attribute, object) -> {\r\n            return new ElementTag(object.isSpawned());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.eid>\r\n        // @returns ElementTag(Number)\r\n        // @group data\r\n        // @description\r\n        // Returns the entity's temporary server entity ID.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"eid\", (attribute, object) -> {\r\n            return new ElementTag(object.getBukkitEntity().getEntityId());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.uuid>\r\n        // @returns ElementTag\r\n        // @group data\r\n        // @description\r\n        // Returns the permanent unique ID of the entity.\r\n        // Works with offline players.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"uuid\", (attribute, object) -> {\r\n            return new ElementTag(object.getUUID().toString(), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.script>\r\n        // @returns ScriptTag\r\n        // @group data\r\n        // @description\r\n        // Returns the entity script that spawned this entity, if any.\r\n        // -->\r\n        tagProcessor.registerTag(ScriptTag.class, \"script\", (attribute, object) -> {\r\n            if (object.entityScript == null) {\r\n                return null;\r\n            }\r\n            return ScriptTag.valueOf(object.entityScript, CoreUtilities.noDebugContext);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.scriptname>\r\n        // @returns ElementTag\r\n        // @deprecated use \".script.name\" instead.\r\n        // @group data\r\n        // @description\r\n        // Use \".script.name\" instead.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"scriptname\", (attribute, object) -> {\r\n            BukkitImplDeprecations.hasScriptTags.warn(attribute.context);\r\n            if (object.entityScript == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(object.entityScript);\r\n        });\r\n\r\n        /////////////////////\r\n        //   IDENTIFICATION ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.name>\r\n        // @returns ElementTag\r\n        // @group data\r\n        // @description\r\n        // Returns the name of the entity.\r\n        // This can be a player name, an NPC name, a custom_name, or the entity type.\r\n        // Works with offline players.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"name\", (attribute, object) -> {\r\n            return new ElementTag(object.getName(), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.monster_type>\r\n        // @returns ElementTag\r\n        // @group data\r\n        // @description\r\n        // Returns the entity's monster type, if it is a monster.\r\n        // This is sometimes called 'mob type' or 'entity category', but it is only applicable to enemy monsters - this is used for enchanted damage bonuses, see <@link tag EnchantmentTag.damage_bonus>.\r\n        // This can be any of undead/water/illager/arthropod, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/EntityCategory.html>.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"monster_type\", (attribute, object) -> {\r\n            EntityCategory category = object.getLivingEntity().getCategory();\r\n            if (category == EntityCategory.NONE) {\r\n                return null;\r\n            }\r\n            return new ElementTag(category);\r\n        });\r\n\r\n        /////////////////////\r\n        //   INVENTORY ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.saddle>\r\n        // @returns ItemTag\r\n        // @group inventory\r\n        // @description\r\n        // If the entity is a horse or pig, returns the saddle as a ItemTag, or air if none.\r\n        // -->\r\n        registerSpawnedOnlyTag(ItemTag.class, \"saddle\", (attribute, object) -> {\r\n            if (object.getLivingEntity() instanceof AbstractHorse) {\r\n                return new ItemTag(((AbstractHorse) object.getLivingEntity()).getInventory().getSaddle());\r\n            }\r\n            else if (object.getLivingEntity() instanceof Steerable) {\r\n                return new ItemTag(((Steerable) object.getLivingEntity()).hasSaddle() ? Material.SADDLE : Material.AIR);\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.horse_armor>\r\n        // @returns ItemTag\r\n        // @deprecated Use 'EntityTag.equipment_map.get[body]' on MC 1.20+.\r\n        // @group inventory\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.equipment_map> with the 'body' key on MC 1.20+.\r\n        // -->\r\n        registerSpawnedOnlyTag(ItemTag.class, \"horse_armor\", (attribute, object) -> {\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                BukkitImplDeprecations.horseArmorTag.warn();\r\n            }\r\n            if (object.getLivingEntity() instanceof Horse horse) {\r\n                return new ItemTag(horse.getInventory().getArmor());\r\n            }\r\n            return null;\r\n        }, \"horse_armour\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.has_saddle>\r\n        // @returns ElementTag(Boolean)\r\n        // @group inventory\r\n        // @description\r\n        // If the entity is a pig or horse, returns whether it has a saddle equipped.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"has_saddle\", (attribute, object) -> {\r\n            if (object.getLivingEntity() instanceof AbstractHorse) {\r\n                return new ElementTag(((AbstractHorse) object.getLivingEntity()).getInventory().getSaddle().getType() == Material.SADDLE);\r\n            }\r\n            else if (object.getLivingEntity() instanceof Steerable) {\r\n                return new ElementTag(((Steerable) object.getLivingEntity()).hasSaddle());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_trading>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the villager entity is trading.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_trading\", (attribute, object) -> {\r\n            if (object.getBukkitEntity() instanceof Merchant) {\r\n                return new ElementTag(((Merchant) object.getBukkitEntity()).isTrading());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.trading_with>\r\n        // @returns PlayerTag\r\n        // @description\r\n        // Returns the player who is trading with the villager entity, or null if it is not trading.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityFormObject.class, \"trading_with\", (attribute, object) -> {\r\n            if (object.getBukkitEntity() instanceof Merchant && ((Merchant) object.getBukkitEntity()).getTrader() != null) {\r\n                return new EntityTag(((Merchant) object.getBukkitEntity()).getTrader()).getDenizenObject();\r\n            }\r\n            return null;\r\n        });\r\n\r\n        /////////////////////\r\n        //   LOCATION ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.trace_framed_map>\r\n        // @returns MapTag\r\n        // @group location\r\n        // @description\r\n        // Returns information at the framed item of a filled map that an entity is currently looking at, if any.\r\n        // The map contains key \"x\" and \"y\" as coordinates in the range of 0 to 128. These will automatically correct for rotation, if the framed item is rotated.\r\n        // The map contains \"entity\" as the EntityTag of the item frame.\r\n        // The map also contains \"map\" as the ID of the targeted map.\r\n        // Returns null if the entity is not looking at an item_frame holding a filled_map.\r\n        // -->\r\n        registerSpawnedOnlyTag(MapTag.class, \"trace_framed_map\", (attribute, object) -> {\r\n            return NMSHandler.entityHelper.mapTrace(object.getLivingEntity());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.map_trace>\r\n        // @returns LocationTag\r\n        // @group location\r\n        // @deprecated use EntityTag.trace_framed_map\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.trace_framed_map>\r\n        // -->\r\n        registerSpawnedOnlyTag(LocationTag.class, \"map_trace\", (attribute, object) -> {\r\n            BukkitImplDeprecations.entityMapTraceTag.warn(attribute.context);\r\n            MapTag result = NMSHandler.entityHelper.mapTrace(object.getLivingEntity());\r\n            if (result == null) {\r\n                return null;\r\n            }\r\n            return new LocationTag(null, result.getElement(\"x\").asDouble(), result.getElement(\"y\").asDouble());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.can_see[<entity>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @group location\r\n        // @description\r\n        // Returns whether the entity can see the specified other entity (has an uninterrupted line-of-sight).\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"can_see\", (attribute, object) -> {\r\n            if (object.isLivingEntity() && attribute.hasParam() && EntityTag.matches(attribute.getParam())) {\r\n                EntityTag toEntity = attribute.paramAsType(EntityTag.class);\r\n                if (toEntity != null && toEntity.isSpawnedOrValidForTag()) {\r\n                    return new ElementTag(object.getLivingEntity().hasLineOfSight(toEntity.getBukkitEntity()));\r\n                }\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.eye_location>\r\n        // @returns LocationTag\r\n        // @group location\r\n        // @description\r\n        // Returns the location of the entity's eyes.\r\n        // -->\r\n        registerSpawnedOnlyTag(LocationTag.class, \"eye_location\", (attribute, object) -> {\r\n            return new LocationTag(object.getEyeLocation());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.eye_height>\r\n        // @returns ElementTag(Number)\r\n        // @group location\r\n        // @description\r\n        // Returns the height of the entity's eyes above its location.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"eye_height\", (attribute, object) -> {\r\n            if (object.isLivingEntity()) {\r\n                return new ElementTag(object.getLivingEntity().getEyeHeight());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.cursor_on_solid[(<range>)]>\r\n        // @returns LocationTag\r\n        // @group location\r\n        // @description\r\n        // Returns the location of the solid block the entity is looking at.\r\n        // Optionally, specify a maximum range to find the location from (defaults to 200).\r\n        // Note that this will return null if there is no solid block in range.\r\n        // This only uses solid blocks, ie it ignores passable blocks like tall-grass. Use <@link tag EntityTag.cursor_on> to include passable blocks.\r\n        // Equivalent to <EntityTag.eye_location.ray_trace[return=block]>\r\n        // -->\r\n        registerSpawnedOnlyTag(LocationTag.class, \"cursor_on_solid\", (attribute, object) -> {\r\n            double range = attribute.getDoubleParam();\r\n            if (range <= 0) {\r\n                range = 200;\r\n            }\r\n            RayTraceResult traced = object.getWorld().rayTraceBlocks(object.getEyeLocation(), object.getEyeLocation().getDirection(), range, FluidCollisionMode.NEVER, true);\r\n            if (traced != null && traced.getHitBlock() != null) {\r\n                return new LocationTag(traced.getHitBlock().getLocation());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.cursor_on[(<range>)]>\r\n        // @returns LocationTag\r\n        // @group location\r\n        // @description\r\n        // Returns the location of the block the entity is looking at.\r\n        // Optionally, specify a maximum range to find the location from (defaults to 200).\r\n        // This uses logic equivalent to <@link tag LocationTag.precise_cursor_on_block>.\r\n        // Note that this will return null if there is no block in range.\r\n        // This uses all blocks, ie it includes passable blocks like tall-grass and water. Use <@link tag EntityTag.cursor_on_solid> to exclude passable blocks.\r\n        // Equivalent to <EntityTag.eye_location.ray_trace[return=block;fluids=true;nonsolids=true]>\r\n        // -->\r\n        registerSpawnedOnlyTag(LocationTag.class, \"cursor_on\", (attribute, object) -> {\r\n            double range = attribute.getDoubleParam();\r\n            if (range <= 0) {\r\n                range = 200;\r\n            }\r\n            RayTraceResult traced = object.getWorld().rayTraceBlocks(object.getEyeLocation(), object.getEyeLocation().getDirection(), range, FluidCollisionMode.ALWAYS, false);\r\n            if (traced != null && traced.getHitBlock() != null) {\r\n                return new LocationTag(traced.getHitBlock().getLocation());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.location>\r\n        // @returns LocationTag\r\n        // @group location\r\n        // @description\r\n        // Returns the location of the entity.\r\n        // For living entities, this is at the center of their feet.\r\n        // For eye location, use <@link tag EntityTag.eye_location>\r\n        // Works with offline players.\r\n        // -->\r\n        registerSpawnedOnlyTag(LocationTag.class, \"location\", (attribute, object) -> {\r\n            return object.doLocationTag(attribute);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.standing_on>\r\n        // @returns LocationTag\r\n        // @group location\r\n        // @description\r\n        // Returns the location of the block the entity is standing on top of (if on the ground, returns null if in the air).\r\n        // -->\r\n        registerSpawnedOnlyTag(LocationTag.class, \"standing_on\", (attribute, object) -> {\r\n            if (!object.getBukkitEntity().isOnGround()) {\r\n                return null;\r\n            }\r\n            Location loc = object.getBukkitEntity().getLocation().clone().subtract(0, 0.05, 0);\r\n            return new LocationTag(loc.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.body_yaw>\r\n        // @returns ElementTag(Decimal)\r\n        // @group location\r\n        // @description\r\n        // Returns a living entity's body yaw (separate from head yaw).\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"body_yaw\", (attribute, object) -> {\r\n            return new ElementTag(NMSHandler.entityHelper.getBaseYaw(object.getLivingEntity()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.velocity>\r\n        // @returns LocationTag\r\n        // @group location\r\n        // @mechanism EntityTag.velocity\r\n        // @description\r\n        // Returns the movement velocity of the entity.\r\n        // Note: Does not accurately calculate player clientside movement velocity.\r\n        // -->\r\n        registerSpawnedOnlyTag(LocationTag.class, \"velocity\", (attribute, object) -> {\r\n            return new LocationTag(object.getBukkitEntity().getVelocity().toLocation(object.getBukkitEntity().getWorld()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.world>\r\n        // @returns WorldTag\r\n        // @group location\r\n        // @description\r\n        // Returns the world the entity is in. Works with offline players.\r\n        // -->\r\n        registerSpawnedOnlyTag(WorldTag.class, \"world\", (attribute, object) -> {\r\n            return new WorldTag(object.getBukkitEntity().getWorld());\r\n        });\r\n\r\n        /////////////////////\r\n        //   STATE ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.can_pickup_items>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.can_pickup_items\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity can pick up items.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"can_pickup_items\", (attribute, object) -> {\r\n            if (object.isLivingEntity()) {\r\n                return new ElementTag(object.getLivingEntity().getCanPickupItems());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fallingblock_material>\r\n        // @returns MaterialTag\r\n        // @mechanism EntityTag.fallingblock_type\r\n        // @group attributes\r\n        // @description\r\n        // Returns the material of a fallingblock-type entity.\r\n        // -->\r\n        registerSpawnedOnlyTag(MaterialTag.class, \"fallingblock_material\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof FallingBlock)) {\r\n                return null;\r\n            }\r\n            return new MaterialTag(((FallingBlock) object.getBukkitEntity()).getBlockData());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fall_distance>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism EntityTag.fall_distance\r\n        // @group attributes\r\n        // @description\r\n        // Returns how far the entity has fallen.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"fall_distance\", (attribute, object) -> {\r\n            return new ElementTag(object.getBukkitEntity().getFallDistance());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fire_time>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.fire_time\r\n        // @group attributes\r\n        // @description\r\n        // Returns the duration for which the entity will remain on fire\r\n        // -->\r\n        registerSpawnedOnlyTag(DurationTag.class, \"fire_time\", (attribute, object) -> {\r\n            return new DurationTag(object.getBukkitEntity().getFireTicks() / 20);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.on_fire>\r\n        // @returns ElementTag(Boolean)\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity is currently ablaze or not.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"on_fire\", (attribute, object) -> {\r\n            return new ElementTag(object.getBukkitEntity().getFireTicks() > 0);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.leash_holder>\r\n        // @returns EntityTag\r\n        // @mechanism EntityTag.leash_holder\r\n        // @group attributes\r\n        // @description\r\n        // Returns the leash holder of entity.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityFormObject.class, \"leash_holder\", (attribute, object) -> {\r\n            if (object.isLivingEntity() && object.getLivingEntity().isLeashed()) {\r\n                return new EntityTag(object.getLivingEntity().getLeashHolder()).getDenizenObject();\r\n            }\r\n            return null;\r\n        }, \"get_leash_holder\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.passengers>\r\n        // @returns ListTag(EntityTag)\r\n        // @mechanism EntityTag.passengers\r\n        // @group attributes\r\n        // @description\r\n        // Returns a list of the entity's passengers, if any.\r\n        // -->\r\n        registerSpawnedOnlyTag(ListTag.class, \"passengers\", (attribute, object) -> {\r\n            ArrayList<EntityTag> passengers = new ArrayList<>();\r\n            for (Entity ent : object.getBukkitEntity().getPassengers()) {\r\n                passengers.add(new EntityTag(ent));\r\n            }\r\n            return new ListTag(passengers);\r\n        }, \"get_passengers\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.passenger>\r\n        // @returns EntityTag\r\n        // @mechanism EntityTag.passenger\r\n        // @group attributes\r\n        // @description\r\n        // Returns the entity's passenger, if any.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityFormObject.class, \"passenger\", (attribute, object) -> {\r\n            if (!object.getBukkitEntity().isEmpty()) {\r\n                return new EntityTag(object.getBukkitEntity().getPassenger()).getDenizenObject();\r\n            }\r\n            return null;\r\n        }, \"get_passenger\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.shooter>\r\n        // @returns EntityTag\r\n        // @group attributes\r\n        // @mechanism EntityTag.shooter\r\n        // @synonyms EntityTag.arrow_firer,EntityTag.fishhook_shooter,EntityTag.snowball_thrower\r\n        // @description\r\n        // Returns the projectile's shooter or TNT's priming source, if any.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityFormObject.class, \"shooter\", (attribute, object) -> {\r\n            EntityTag shooter = object.getShooter();\r\n            if (shooter == null) {\r\n                return null;\r\n            }\r\n            return shooter.getDenizenObject();\r\n        }, \"get_shooter\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.left_shoulder>\r\n        // @returns EntityTag\r\n        // @mechanism EntityTag.left_shoulder\r\n        // @description\r\n        // Returns the entity on the entity's left shoulder.\r\n        // Only applies to player-typed entities.\r\n        // NOTE: The returned entity will not be spawned within the world,\r\n        // so most operations are invalid unless the entity is first spawned in.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityFormObject.class, \"left_shoulder\", (attribute, object) -> {\r\n            if (!(object.getLivingEntity() instanceof HumanEntity)) {\r\n                return null;\r\n            }\r\n            Entity e = ((HumanEntity) object.getLivingEntity()).getShoulderEntityLeft();\r\n            if (e == null) {\r\n                return null;\r\n            }\r\n            return new EntityTag(e).getDenizenObject();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.right_shoulder>\r\n        // @returns EntityTag\r\n        // @mechanism EntityTag.right_shoulder\r\n        // @description\r\n        // Returns the entity on the entity's right shoulder.\r\n        // Only applies to player-typed entities.\r\n        // NOTE: The returned entity will not be spawned within the world,\r\n        // so most operations are invalid unless the entity is first spawned in.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityFormObject.class, \"right_shoulder\", (attribute, object) -> {\r\n            if (!(object.getLivingEntity() instanceof HumanEntity)) {\r\n                return null;\r\n            }\r\n            Entity e = ((HumanEntity) object.getLivingEntity()).getShoulderEntityRight();\r\n            if (e == null) {\r\n                return null;\r\n            }\r\n            return new EntityTag(e).getDenizenObject();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.vehicle>\r\n        // @returns EntityTag\r\n        // @group attributes\r\n        // @description\r\n        // If the entity is in a vehicle, returns the vehicle as a EntityTag.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityFormObject.class, \"vehicle\", (attribute, object) -> {\r\n            if (object.getBukkitEntity().isInsideVehicle()) {\r\n                return new EntityTag(object.getBukkitEntity().getVehicle()).getDenizenObject();\r\n            }\r\n            return null;\r\n        }, \"get_vehicle\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.can_breed>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.can_breed\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the animal entity is capable of mating with another of its kind.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"can_breed\", (attribute, object) -> {\r\n            if (!(object.getLivingEntity() instanceof Breedable)) {\r\n                return new ElementTag(false);\r\n            }\r\n            return new ElementTag(((Breedable) object.getLivingEntity()).canBreed());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.breeding>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.breed\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the animal entity is trying to mate with another of its kind.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"breeding\", (attribute, object) -> {\r\n            if (!(object.getLivingEntity() instanceof Animals)) {\r\n                return null;\r\n            }\r\n            return new ElementTag(((Animals) object.getLivingEntity()).getLoveModeTicks() > 0);\r\n        }, \"is_breeding\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.has_passenger>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.passenger\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity has a passenger.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"has_passenger\", (attribute, object) -> {\r\n            return new ElementTag(!object.getBukkitEntity().isEmpty());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_empty>\r\n        // @returns ElementTag(Boolean)\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity does not have a passenger.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_empty\", (attribute, object) -> {\r\n            return new ElementTag(object.getBukkitEntity().isEmpty());\r\n        }, \"empty\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_inside_vehicle>\r\n        // @returns ElementTag(Boolean)\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity is inside a vehicle.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_inside_vehicle\", (attribute, object) -> {\r\n            return new ElementTag(object.getBukkitEntity().isInsideVehicle());\r\n        }, \"inside_vehicle\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_leashed>\r\n        // @returns ElementTag(Boolean)\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity is leashed.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_leashed\", (attribute, object) -> {\r\n            return new ElementTag(object.isLivingEntity() && object.getLivingEntity().isLeashed());\r\n        }, \"leashed\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_on_ground>\r\n        // @returns ElementTag(Boolean)\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity is supported by a block.\r\n        // This can be inaccurate for players.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_on_ground\", (attribute, object) -> {\r\n            return new ElementTag(object.getBukkitEntity().isOnGround());\r\n        }, \"on_ground\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_persistent>\r\n        // @returns ElementTag(Boolean)\r\n        // @group attributes\r\n        // @mechanism EntityTag.persistent\r\n        // @description\r\n        // Returns whether the mob-entity will not be removed completely when far away from players.\r\n        // This is Bukkit's \"getRemoveWhenFarAway\" which is Mojang's \"isPersistenceRequired\".\r\n        // In many cases, <@link tag EntityTag.force_no_persist> may be preferred.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_persistent\", (attribute, object) -> {\r\n            return new ElementTag(object.isLivingEntity() && !object.getLivingEntity().getRemoveWhenFarAway());\r\n        }, \"persistent\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.force_no_persist>\r\n        // @returns ElementTag(Boolean)\r\n        // @group attributes\r\n        // @mechanism EntityTag.force_no_persist\r\n        // @description\r\n        // Returns 'true' if the entity is forced to not save to file when chunks unload.\r\n        // Returns 'false' if not forced to not-save (ie is allowed to save). May return 'false' even for entities that don't save for other reasons.\r\n        // This is a custom value added in Bukkit to block saving, which is not the same as Mojang's similar option under <@link tag EntityTag.is_persistent>.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"force_no_persist\", (attribute, object) -> {\r\n            return new ElementTag(!object.getBukkitEntity().isPersistent());\r\n        });\r\n        registerSpawnedOnlyTag(ElementTag.class, \"forced_no_persist\", (attribute, object) -> {\r\n            BukkitImplDeprecations.forcedNoPersist.warn(attribute.context);\r\n            return new ElementTag(object.getBukkitEntity().isPersistent());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_collidable>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.collidable\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity is collidable.\r\n        // Returns the persistent collidable value for NPCs.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_collidable\", (attribute, object) -> {\r\n            if (object.isCitizensNPC()) {\r\n                return new ElementTag(object.getDenizenNPC().getCitizen().data().get(NPC.Metadata.COLLIDABLE, true));\r\n            }\r\n            return new ElementTag(object.getLivingEntity().isCollidable());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_sleeping>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.is_sleeping\r\n        // @description\r\n        // Returns whether a living entity is currently sleeping.\r\n        // If the entity is not a fox, player, or villager, this will always return 'false'.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_sleeping\", (attribute, object) -> {\r\n            if (object.getBukkitEntity() instanceof LivingEntity livingEntity) {\r\n                return new ElementTag(livingEntity.isSleeping());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.killer>\r\n        // @returns PlayerTag\r\n        // @group attributes\r\n        // @description\r\n        // Returns the player that last killed the entity.\r\n        // -->\r\n        registerSpawnedOnlyTag(PlayerTag.class, \"killer\", (attribute, object) -> {\r\n            return getPlayerFrom(object.getLivingEntity().getKiller());\r\n        });\r\n\r\n        registerSpawnedOnlyTag(ObjectTag.class, \"last_damage\", (attribute, object) -> {\r\n            // <--[tag]\r\n            // @attribute <EntityTag.last_damage.amount>\r\n            // @returns ElementTag(Decimal)\r\n            // @group attributes\r\n            // @description\r\n            // Returns the amount of the last damage taken by the entity.\r\n            // -->\r\n            if (attribute.startsWith(\"amount\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getLivingEntity().getLastDamage());\r\n            }\r\n            // <--[tag]\r\n            // @attribute <EntityTag.last_damage.cause>\r\n            // @returns ElementTag\r\n            // @group attributes\r\n            // @description\r\n            // Returns the cause of the last damage taken by the entity.\r\n            // -->\r\n            if (attribute.startsWith(\"cause\", 2)) {\r\n                attribute.fulfill(1);\r\n                if (object.getBukkitEntity().getLastDamageCause() == null) {\r\n                    return null;\r\n                }\r\n                return new ElementTag(object.getBukkitEntity().getLastDamageCause().getCause());\r\n            }\r\n            // <--[tag]\r\n            // @attribute <EntityTag.last_damage.duration>\r\n            // @returns DurationTag\r\n            // @mechanism EntityTag.no_damage_duration\r\n            // @group attributes\r\n            // @description\r\n            // Returns the duration of the last damage taken by the entity.\r\n            // -->\r\n            if (attribute.startsWith(\"duration\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new DurationTag((long) object.getLivingEntity().getNoDamageTicks());\r\n            }\r\n            // <--[tag]\r\n            // @attribute <EntityTag.last_damage.max_duration>\r\n            // @returns DurationTag\r\n            // @mechanism EntityTag.max_no_damage_duration\r\n            // @group attributes\r\n            // @description\r\n            // Returns the maximum duration of the last damage taken by the entity.\r\n            // -->\r\n            if (attribute.startsWith(\"max_duration\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new DurationTag((long) object.getLivingEntity().getMaximumNoDamageTicks());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.absorption_health>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism EntityTag.absorption_health\r\n        // @description\r\n        // Returns the living entity's absorption health.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"absorption_health\", (attribute, object) -> {\r\n            return new ElementTag(object.getLivingEntity().getAbsorptionAmount());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.max_oxygen>\r\n        // @returns DurationTag\r\n        // @group attributes\r\n        // @description\r\n        // Returns the maximum duration of oxygen the entity can have.\r\n        // Works with offline players.\r\n        // -->\r\n        registerSpawnedOnlyTag(DurationTag.class, \"max_oxygen\", (attribute, object) -> {\r\n            return new DurationTag((long) object.getLivingEntity().getMaximumAir());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.oxygen>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.oxygen\r\n        // @group attributes\r\n        // @description\r\n        // Returns the duration of oxygen the entity has left.\r\n        // Works with offline players.\r\n        // -->\r\n        registerSpawnedOnlyTag(DurationTag.class, \"oxygen\", (attribute, object) -> {\r\n            if (attribute.startsWith(\"max\", 2)) {\r\n                BukkitImplDeprecations.entityMaxOxygenTag.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                return new DurationTag((long) object.getLivingEntity().getMaximumAir());\r\n            }\r\n            return new DurationTag((long) object.getLivingEntity().getRemainingAir());\r\n        });\r\n\r\n        registerSpawnedOnlyTag(ElementTag.class, \"remove_when_far\", (attribute, object) -> {\r\n            BukkitImplDeprecations.entityRemoveWhenFar.warn(attribute.context);\r\n            return new ElementTag(object.getLivingEntity().getRemoveWhenFarAway());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.target>\r\n        // @returns EntityTag\r\n        // @group attributes\r\n        // @description\r\n        // Returns the target entity of the creature or shulker_bullet, if any.\r\n        // This is the entity that a hostile mob is currently trying to attack.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityFormObject.class, \"target\", (attribute, object) -> {\r\n            if (object.getBukkitEntity() instanceof Creature) {\r\n                Entity target = ((Creature) object.getLivingEntity()).getTarget();\r\n                if (target != null) {\r\n                    return new EntityTag(target).getDenizenObject();\r\n                }\r\n            }\r\n            else if (object.getBukkitEntity() instanceof ShulkerBullet) {\r\n                Entity target = ((ShulkerBullet) object.getLivingEntity()).getTarget();\r\n                if (target != null) {\r\n                    return new EntityTag(target).getDenizenObject();\r\n                }\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.precise_target[(<range>)]>\r\n        // @returns EntityTag\r\n        // @description\r\n        // Returns the entity this entity is looking at, using precise ray trace logic.\r\n        // Optionally, specify a maximum range to find the entity from (defaults to 200).\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityFormObject.class, \"precise_target\", (attribute, object) -> {\r\n            int range = attribute.getIntParam();\r\n            if (range < 1) {\r\n                range = 200;\r\n            }\r\n            Predicate<Entity> requirement;\r\n            // <--[tag]\r\n            // @attribute <EntityTag.precise_target[(<range>)].type[<matcher>]>\r\n            // @returns EntityTag\r\n            // @description\r\n            // Returns the entity this entity is looking at, using precise ray trace logic.\r\n            // Optionally, specify a maximum range to find the entity from (defaults to 200).\r\n            // Specify an entity type matcher to only count matches as possible ray trace hits (types not listed will be ignored).\r\n            // -->\r\n            if (attribute.startsWith(\"type\", 2) && attribute.hasContext(2)) {\r\n                attribute.fulfill(1);\r\n                String matcher = attribute.getParam();\r\n                requirement = (e) -> !e.equals(object.getBukkitEntity()) && new EntityTag(e).tryAdvancedMatcher(matcher, attribute.context);\r\n            }\r\n            else {\r\n                requirement = (e) -> !e.equals(object.getBukkitEntity());\r\n            }\r\n            RayTraceResult result = object.getWorld().rayTrace(object.getEyeLocation(), object.getEyeLocation().getDirection(), range, FluidCollisionMode.NEVER, true, 0, requirement);\r\n            if (result != null && result.getHitEntity() != null) {\r\n                return new EntityTag(result.getHitEntity()).getDenizenObject();\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.precise_target_position[(<range>)]>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns the location this entity is looking at, using precise ray trace (against entities) logic.\r\n        // Optionally, specify a maximum range to find the target from (defaults to 200).\r\n        // -->\r\n        registerSpawnedOnlyTag(LocationTag.class, \"precise_target_position\", (attribute, object) -> {\r\n            int range = attribute.getIntParam();\r\n            if (range < 1) {\r\n                range = 200;\r\n            }\r\n            Predicate<Entity> requirement;\r\n            // <--[tag]\r\n            // @attribute <EntityTag.precise_target_position[(<range>)].type[<matcher>]>\r\n            // @returns LocationTag\r\n            // @description\r\n            // Returns the location this entity is looking at, using precise ray trace (against entities) logic.\r\n            // Optionally, specify a maximum range to find the target from (defaults to 200).\r\n            // Specify an entity type matcher to only count matches as possible ray trace hits (types not listed will be ignored).\r\n            // -->\r\n            if (attribute.startsWith(\"type\", 2) && attribute.hasContext(2)) {\r\n                attribute.fulfill(1);\r\n                String matcher = attribute.getParam();\r\n                requirement = (e) -> !e.equals(object.getBukkitEntity()) && new EntityTag(e).tryAdvancedMatcher(matcher, attribute.context);\r\n            }\r\n            else {\r\n                requirement = (e) -> !e.equals(object.getBukkitEntity());\r\n            }\r\n            RayTraceResult result = object.getWorld().rayTrace(object.getEyeLocation(), object.getEyeLocation().getDirection(), range, FluidCollisionMode.NEVER, true, 0, requirement);\r\n            if (result != null) {\r\n                return new LocationTag(object.getWorld(), result.getHitPosition());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.time_lived>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.time_lived\r\n        // @group attributes\r\n        // @description\r\n        // Returns how long the entity has lived.\r\n        // -->\r\n        registerSpawnedOnlyTag(DurationTag.class, \"time_lived\", (attribute, object) -> {\r\n            return new DurationTag((long) object.getBukkitEntity().getTicksLived());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.pickup_delay>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.pickup_delay\r\n        // @group attributes\r\n        // @description\r\n        // Returns how long before the item-type entity can be picked up by a player.\r\n        // -->\r\n        registerSpawnedOnlyTag(DurationTag.class, \"pickup_delay\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof Item)) {\r\n                return null;\r\n            }\r\n            return new DurationTag(((Item) object.getBukkitEntity()).getPickupDelay() * 20);\r\n        }, \"pickupdelay\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_in_block>\r\n        // @returns ElementTag(Boolean)\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the arrow/trident entity is in a block.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_in_block\", (attribute, object) -> {\r\n            return object.getBukkitEntity() instanceof AbstractArrow abstractArrow ? new ElementTag(abstractArrow.isInBlock()) : null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attached_block>\r\n        // @returns LocationTag\r\n        // @group attributes\r\n        // @description\r\n        // Returns the location of the block that the arrow/trident or hanging entity is attached to.\r\n        // -->\r\n        registerSpawnedOnlyTag(LocationTag.class, \"attached_block\", (attribute, object) -> {\r\n            if (object.getBukkitEntity() instanceof AbstractArrow abstractArrow) {\r\n                Block attachedBlock = abstractArrow.getAttachedBlock();\r\n                if (attachedBlock != null) {\r\n                    return new LocationTag(attachedBlock.getLocation());\r\n                }\r\n            }\r\n            else if (object.getBukkitEntity() instanceof Hanging hanging) {\r\n                Vector dir = hanging.getAttachedFace().getDirection();\r\n                return new LocationTag(object.getLocation().clone().add(dir.multiply(0.5))).getBlockLocation();\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.gliding>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.gliding\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether this entity is gliding.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"gliding\", (attribute, object) -> {\r\n            return new ElementTag(object.getLivingEntity().isGliding());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.swimming>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.swimming\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether this entity is swimming.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"swimming\", (attribute, object) -> {\r\n            return new ElementTag(object.getLivingEntity().isSwimming());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.visual_pose>\r\n        // @returns ElementTag\r\n        // @mechanism EntityTag.visual_pose\r\n        // @group attributes\r\n        // @description\r\n        // Returns the name of the entity's current visual pose.\r\n        // See <@link url https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/Pose.html>\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"visual_pose\", (attribute, object) -> {\r\n            return new ElementTag(object.getBukkitEntity().getPose());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.glowing>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.glowing\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether this entity is glowing.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"glowing\", (attribute, object) -> {\r\n            return new ElementTag(object.getBukkitEntity().isGlowing());\r\n        });\r\n\r\n        /////////////////////\r\n        //   TYPE ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_living>\r\n        // @returns ElementTag(Boolean)\r\n        // @group data\r\n        // @description\r\n        // Returns whether the entity type is a living-type entity (eg a cow or a player or anything else that lives, as specifically opposed to non-living entities like paintings, etc).\r\n        // Not to be confused with the idea of being alive - see <@link tag EntityTag.is_spawned>.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_living\", (attribute, object) -> {\r\n            return new ElementTag(object.isLivingEntityType());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_monster>\r\n        // @returns ElementTag(Boolean)\r\n        // @group data\r\n        // @description\r\n        // Returns whether the entity type is a hostile monster.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_monster\", (attribute, object) -> {\r\n            return new ElementTag(object.isMonsterType());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_mob>\r\n        // @returns ElementTag(Boolean)\r\n        // @group data\r\n        // @description\r\n        // Returns whether the entity type is a mob (Not a player or NPC).\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_mob\", (attribute, object) -> {\r\n            return new ElementTag(object.isMobType());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_npc>\r\n        // @returns ElementTag(Boolean)\r\n        // @group data\r\n        // @description\r\n        // Returns whether the entity is a Citizens NPC.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_npc\", (attribute, object) -> {\r\n            return new ElementTag(object.isCitizensNPC());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_player>\r\n        // @returns ElementTag(Boolean)\r\n        // @group data\r\n        // @description\r\n        // Returns whether the entity is a player.\r\n        // Works with offline players.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_player\", (attribute, object) -> {\r\n            return new ElementTag(object.isPlayer());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_projectile>\r\n        // @returns ElementTag(Boolean)\r\n        // @group data\r\n        // @description\r\n        // Returns whether the entity type is a projectile.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_projectile\", (attribute, object) -> {\r\n            if (object.getBukkitEntity() == null && object.entity_type != null) {\r\n                return new ElementTag(Projectile.class.isAssignableFrom(object.entity_type.getBukkitEntityType().getEntityClass()));\r\n            }\r\n            return new ElementTag(object.isProjectile());\r\n        });\r\n\r\n        /////////////////////\r\n        //   PROPERTY ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.tameable>\r\n        // @returns ElementTag(Boolean)\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the entity is tameable.\r\n        // If this returns true, it will enable access to:\r\n        // <@link mechanism EntityTag.tame>, <@link mechanism EntityTag.owner>,\r\n        // <@link tag EntityTag.is_tamed>, and <@link tag EntityTag.owner>\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"tameable\", (attribute, object) -> {\r\n            return new ElementTag(EntityTame.describes(object));\r\n        }, \"is_tameable\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.ageable>\r\n        // @returns ElementTag(Boolean)\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the entity is ageable.\r\n        // If this returns true, it will enable access to:\r\n        // <@link mechanism EntityTag.age>, <@link mechanism EntityTag.age_lock>,\r\n        // <@link tag EntityTag.is_baby>, <@link tag EntityTag.age>,\r\n        // and <@link tag EntityTag.is_age_locked>\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"ageable\", (attribute, object) -> {\r\n            return new ElementTag(EntityAge.describes(object));\r\n        }, \"is_ageable\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.colorable>\r\n        // @returns ElementTag(Boolean)\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the entity can be colored.\r\n        // If this returns true, it will enable access to <@link property EntityTag.color>.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"colorable\", (attribute, object) -> {\r\n            return new ElementTag(EntityColor.describes(object));\r\n        }, \"is_colorable\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.experience>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism EntityTag.experience\r\n        // @group properties\r\n        // @description\r\n        // Returns the experience value of this experience orb entity.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"experience\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof ExperienceOrb)) {\r\n                return null;\r\n            }\r\n            return new ElementTag(((ExperienceOrb) object.getBukkitEntity()).getExperience());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fuse_ticks>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism EntityTag.fuse_ticks\r\n        // @group properties\r\n        // @description\r\n        // Returns the number of ticks until the explosion of the primed TNT.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"fuse_ticks\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof TNTPrimed)) {\r\n                return null;\r\n            }\r\n            return new ElementTag(((TNTPrimed) object.getBukkitEntity()).getFuseTicks());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.dragon_phase>\r\n        // @returns ElementTag\r\n        // @mechanism EntityTag.dragon_phase\r\n        // @group properties\r\n        // @description\r\n        // Returns the phase an EnderDragon is currently in.\r\n        // Valid phases: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/EnderDragon.Phase.html>\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"dragon_phase\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof EnderDragon)) {\r\n                return null;\r\n            }\r\n            return new ElementTag(((EnderDragon) object.getLivingEntity()).getPhase());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.weapon_damage[(<entity>)]>\r\n        // @returns ElementTag(Number)\r\n        // @group properties\r\n        // @description\r\n        // Returns the amount of damage the entity will do based on its held item.\r\n        // Optionally, specify a target entity to test how much damage will be done to that specific target (modified based on enchantments and that entity's armor/status/etc).\r\n        // Note that the result will not always be completely exact, as it doesn't take into account some specific factors (eg sweeping vs single-hit, etc).\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"weapon_damage\", (attribute, object) -> {\r\n            Entity target = null;\r\n            if (attribute.hasParam()) {\r\n                target = attribute.paramAsType(EntityTag.class).getBukkitEntity();\r\n            }\r\n            return new ElementTag(NMSHandler.entityHelper.getDamageTo(object.getLivingEntity(), target));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.skin_layers>\r\n        // @returns ListTag\r\n        // @mechanism EntityTag.skin_layers\r\n        // @description\r\n        // Returns the skin layers currently visible on a player-type entity.\r\n        // Output is a list of values from the set of:\r\n        // CAPE, HAT, JACKET, LEFT_PANTS, LEFT_SLEEVE, RIGHT_PANTS, or RIGHT_SLEEVE.\r\n        // -->\r\n        registerSpawnedOnlyTag(ListTag.class, \"skin_layers\", (attribute, object) -> {\r\n            byte flags = NMSHandler.playerHelper.getSkinLayers((Player) object.getBukkitEntity());\r\n            ListTag result = new ListTag();\r\n            for (PlayerHelper.SkinLayer layer : PlayerHelper.SkinLayer.values()) {\r\n                if ((flags & layer.flag) != 0) {\r\n                    result.add(layer.name());\r\n                }\r\n            }\r\n            return result;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_disguised[(<player>)]>\r\n        // @returns ElementTag(Boolean)\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the entity is currently disguised, either globally (if no context input given), or to the specified player.\r\n        // Relates to <@link command disguise>.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_disguised\", (attribute, object) -> {\r\n            HashMap<UUID, DisguiseCommand.TrackedDisguise> map = DisguiseCommand.disguises.get(object.getUUID());\r\n            if (map == null) {\r\n                return new ElementTag(false);\r\n            }\r\n            if (attribute.hasParam()) {\r\n                PlayerTag player = attribute.paramAsType(PlayerTag.class);\r\n                if (player == null) {\r\n                    attribute.echoError(\"Invalid player for is_disguised tag.\");\r\n                    return null;\r\n                }\r\n                return new ElementTag(map.containsKey(player.getUUID()) || map.containsKey(null));\r\n            }\r\n            else {\r\n                return new ElementTag(map.containsKey(null));\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.disguised_type[(<player>)]>\r\n        // @returns EntityTag\r\n        // @group properties\r\n        // @description\r\n        // Returns the entity type the entity is disguised as, either globally (if no context input given), or to the specified player.\r\n        // Relates to <@link command disguise>.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityTag.class, \"disguised_type\", (attribute, object) -> {\r\n            HashMap<UUID, DisguiseCommand.TrackedDisguise> map = DisguiseCommand.disguises.get(object.getUUID());\r\n            if (map == null) {\r\n                return null;\r\n            }\r\n            DisguiseCommand.TrackedDisguise disguise;\r\n            if (attribute.hasParam()) {\r\n                PlayerTag player = attribute.paramAsType(PlayerTag.class);\r\n                if (player == null) {\r\n                    attribute.echoError(\"Invalid player for is_disguised tag.\");\r\n                    return null;\r\n                }\r\n                disguise = map.get(player.getUUID());\r\n                if (disguise == null) {\r\n                    disguise = map.get(null);\r\n                }\r\n            }\r\n            else {\r\n                disguise = map.get(null);\r\n            }\r\n            if (disguise == null) {\r\n                return null;\r\n            }\r\n            return disguise.as.duplicate();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.disguise_to_others[(<player>)]>\r\n        // @returns EntityTag\r\n        // @group properties\r\n        // @description\r\n        // Returns the fake entity used to disguise the entity in other's views, either globally (if no context input given), or to the specified player.\r\n        // Relates to <@link command disguise>.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityTag.class, \"disguise_to_others\", (attribute, object) -> {\r\n            HashMap<UUID, DisguiseCommand.TrackedDisguise> map = DisguiseCommand.disguises.get(object.getUUID());\r\n            if (map == null) {\r\n                return null;\r\n            }\r\n            DisguiseCommand.TrackedDisguise disguise;\r\n            if (attribute.hasParam()) {\r\n                PlayerTag player = attribute.paramAsType(PlayerTag.class);\r\n                if (player == null) {\r\n                    attribute.echoError(\"Invalid player for is_disguised tag.\");\r\n                    return null;\r\n                }\r\n                disguise = map.get(player.getUUID());\r\n                if (disguise == null) {\r\n                    disguise = map.get(null);\r\n                }\r\n            }\r\n            else {\r\n                disguise = map.get(null);\r\n            }\r\n            if (disguise == null) {\r\n                return null;\r\n            }\r\n            if (disguise.toOthers == null) {\r\n                return null;\r\n            }\r\n            return disguise.toOthers.entity;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.describe>\r\n        // @returns EntityTag\r\n        // @group properties\r\n        // @description\r\n        // Returns the entity's full description, including all properties.\r\n        // -->\r\n        tagProcessor.registerTag(EntityTag.class, \"describe\", (attribute, object) -> {\r\n            return object.describe(attribute.context);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.has_equipped[<item-matcher>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @group element checking\r\n        // @description\r\n        // Returns whether the entity has any armor equipment item that matches the given item matcher, using the system behind <@link language Advanced Object Matching>.\r\n        // For example, has_equipped[diamond_*] will return true if the entity is wearing at least one piece of diamond armor.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"has_equipped\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            if (!object.isLivingEntity()) {\r\n                return null;\r\n            }\r\n            String matcher = attribute.getParam();\r\n            for (ItemStack item : object.getLivingEntity().getEquipment().getArmorContents()) {\r\n                if (new ItemTag(item).tryAdvancedMatcher(matcher, attribute.context)) {\r\n                    return new ElementTag(true);\r\n                }\r\n            }\r\n            return new ElementTag(false);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.loot_table_id>\r\n        // @returns ElementTag\r\n        // @mechanism EntityTag.loot_table_id\r\n        // @description\r\n        // Returns an element indicating the minecraft key for the loot-table for the entity (if any).\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"loot_table_id\", (attribute, object) -> {\r\n            if (object.getBukkitEntity() instanceof Lootable lootable) {\r\n                LootTable table = lootable.getLootTable();\r\n                if (table != null) {\r\n                    return new ElementTag(table.getKey().toString());\r\n                }\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fish_hook_state>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the current state of the fish hook, as any of: UNHOOKED, HOOKED_ENTITY, BOBBING (unhooked means the fishing hook is in the air or on ground).\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"fish_hook_state\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof FishHook fishHook)) {\r\n                attribute.echoError(\"EntityTag.fish_hook_state is only valid for fish hooks.\");\r\n                return null;\r\n            }\r\n            return new ElementTag(fishHook.getState());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fish_hook_lure_time>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.fish_hook_lure_time\r\n        // @description\r\n        // Returns the remaining time before this fish hook will lure a fish.\r\n        // -->\r\n        registerSpawnedOnlyTag(DurationTag.class, \"fish_hook_lure_time\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof FishHook fishHook)) {\r\n                attribute.echoError(\"EntityTag.fish_hook_lure_time is only valid for fish hooks.\");\r\n                return null;\r\n            }\r\n            return new DurationTag((long) NMSHandler.fishingHelper.getLureTime(fishHook));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fish_hook_min_lure_time>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.fish_hook_min_lure_time\r\n        // @description\r\n        // Returns the minimum possible time before this fish hook can lure a fish.\r\n        // -->\r\n        registerSpawnedOnlyTag(DurationTag.class, \"fish_hook_min_lure_time\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof FishHook fishHook)) {\r\n                attribute.echoError(\"EntityTag.fish_hook_min_lure_time is only valid for fish hooks.\");\r\n                return null;\r\n            }\r\n            return new DurationTag((long) fishHook.getMinWaitTime());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fish_hook_max_lure_time>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.fish_hook_max_lure_time\r\n        // @description\r\n        // Returns the maximum possible time before this fish hook will lure a fish.\r\n        // -->\r\n        registerSpawnedOnlyTag(DurationTag.class, \"fish_hook_max_lure_time\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof FishHook fishHook)) {\r\n                attribute.echoError(\"EntityTag.fish_hook_max_lure_time is only valid for fish hooks.\");\r\n                return null;\r\n            }\r\n            return new DurationTag((long) fishHook.getMaxWaitTime());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fish_hook_hooked_entity>\r\n        // @returns EntityTag\r\n        // @mechanism EntityTag.fish_hook_hooked_entity\r\n        // @description\r\n        // Returns the entity this fish hook is attached to.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityTag.class, \"fish_hook_hooked_entity\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof FishHook fishHook)) {\r\n                attribute.echoError(\"EntityTag.fish_hook_hooked_entity is only valid for fish hooks.\");\r\n                return null;\r\n            }\r\n            Entity entity = fishHook.getHookedEntity();\r\n            return entity != null ? new EntityTag(entity) : null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fish_hook_apply_lure>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.fish_hook_apply_lure\r\n        // @description\r\n        // Returns whether this fish hook should respect the lure enchantment.\r\n        // Every level of lure enchantment reduces lure time by 5 seconds.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"fish_hook_apply_lure\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof FishHook fishHook)) {\r\n                attribute.echoError(\"EntityTag.fish_hook_apply_lure is only valid for fish hooks.\");\r\n                return null;\r\n            }\r\n            return new ElementTag(fishHook.getApplyLure());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fish_hook_in_open_water>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether this fish hook is in open water. Fish hooks in open water can catch treasure.\r\n        // See <@link url https://minecraft.wiki/w/Fishing> for more info.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"fish_hook_in_open_water\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof FishHook fishHook)) {\r\n                attribute.echoError(\"EntityTag.fish_hook_in_open_water is only valid for fish hooks.\");\r\n                return null;\r\n            }\r\n            return new ElementTag(fishHook.isInOpenWater());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attached_entities[(<player>)]>\r\n        // @returns ListTag(EntityTag)\r\n        // @description\r\n        // Returns the entities attached to this entity by <@link command attach>.\r\n        // Optionally, specify a player. If specified, will return entities attached visible to that player. If not specified, returns entities globally attached.\r\n        // -->\r\n        registerSpawnedOnlyTag(ListTag.class, \"attached_entities\", (attribute, object) -> {\r\n            PlayerTag player = attribute.hasParam() ? attribute.paramAsType(PlayerTag.class) : null;\r\n            EntityAttachmentHelper.EntityAttachedToMap data = EntityAttachmentHelper.toEntityToData.get(object.getUUID());\r\n            ListTag result = new ListTag();\r\n            if (data == null) {\r\n                return result;\r\n            }\r\n            for (EntityAttachmentHelper.PlayerAttachMap map : data.attachedToMap.values()) {\r\n                if (player == null || map.getAttachment(player.getUUID()) != null) {\r\n                    result.addObject(map.attached);\r\n                }\r\n            }\r\n            return result;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attached_to[(<player>)]>\r\n        // @returns EntityTag\r\n        // @description\r\n        // Returns the entity that this entity was attached to by <@link command attach>.\r\n        // Optionally, specify a player. If specified, will return entity attachment visible to that player. If not specified, returns any entity global attachment.\r\n        // -->\r\n        registerSpawnedOnlyTag(EntityTag.class, \"attached_to\", (attribute, object) -> {\r\n            PlayerTag player = attribute.hasParam() ? attribute.paramAsType(PlayerTag.class) : null;\r\n            EntityAttachmentHelper.PlayerAttachMap data = EntityAttachmentHelper.attachedEntityToData.get(object.getUUID());\r\n            if (data == null) {\r\n                return null;\r\n            }\r\n            EntityAttachmentHelper.AttachmentData attached = data.getAttachment(player == null ? null : player.getUUID());\r\n            if (attached == null) {\r\n                return null;\r\n            }\r\n            return attached.to;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attached_offset[(<player>)]>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns the offset of an attachment for this entity to another that was attached by <@link command attach>.\r\n        // Optionally, specify a player. If specified, will return entity attachment visible to that player. If not specified, returns any entity global attachment.\r\n        // -->\r\n        registerSpawnedOnlyTag(LocationTag.class, \"attached_offset\", (attribute, object) -> {\r\n            PlayerTag player = attribute.hasParam() ? attribute.paramAsType(PlayerTag.class) : null;\r\n            EntityAttachmentHelper.PlayerAttachMap data = EntityAttachmentHelper.attachedEntityToData.get(object.getUUID());\r\n            if (data == null) {\r\n                return null;\r\n            }\r\n            EntityAttachmentHelper.AttachmentData attached = data.getAttachment(player == null ? null : player.getUUID());\r\n            if (attached == null) {\r\n                return null;\r\n            }\r\n            return attached.positionalOffset == null ? null : new LocationTag(attached.positionalOffset);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attack_cooldown_duration>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.attack_cooldown\r\n        // @description\r\n        // Returns the amount of time that passed since the start of the attack cooldown.\r\n        // -->\r\n        registerSpawnedOnlyTag(DurationTag.class, \"attack_cooldown_duration\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof Player player)) {\r\n                attribute.echoError(\"Only player-type entities can have attack_cooldowns!\");\r\n                return null;\r\n            }\r\n            return new DurationTag((long) NMSHandler.playerHelper.ticksPassedDuringCooldown(player));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attack_cooldown_max_duration>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.attack_cooldown\r\n        // @description\r\n        // Returns the maximum amount of time that can pass before the player's main hand has returned\r\n        // to its original place after the cooldown has ended.\r\n        // NOTE: This is slightly inaccurate and may not necessarily match with the actual attack\r\n        // cooldown progress.\r\n        // -->\r\n        registerSpawnedOnlyTag(DurationTag.class, \"attack_cooldown_max_duration\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof Player player)) {\r\n                attribute.echoError(\"Only player-type entities can have attack_cooldowns!\");\r\n                return null;\r\n            }\r\n            return new DurationTag((long) NMSHandler.playerHelper.getMaxAttackCooldownTicks(player));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attack_cooldown_percent>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism EntityTag.attack_cooldown_percent\r\n        // @description\r\n        // Returns the progress of the attack cooldown. 0 means that the attack cooldown has just\r\n        // started, while 100 means that the attack cooldown has finished.\r\n        // NOTE: This may not match exactly with the clientside attack cooldown indicator.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"attack_cooldown_percent\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof Player player)) {\r\n                attribute.echoError(\"Only player-type entities can have attack_cooldowns!\");\r\n                return null;\r\n            }\r\n            return new ElementTag(player.getAttackCooldown() * 100);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_hand_raised>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player's hand is currently raised. Valid for players and player-type NPCs.\r\n        // A player's hand is raised when they are blocking with a shield, aiming a crossbow, looking through a spyglass, etc.\r\n        // -->\r\n        registerSpawnedOnlyTag(ElementTag.class, \"is_hand_raised\", (attribute, object) -> {\r\n            if (!(object.getBukkitEntity() instanceof HumanEntity humanEntity)) {\r\n                attribute.echoError(\"Only player-type entities can have is_hand_raised!\");\r\n                return null;\r\n            }\r\n            return new ElementTag(humanEntity.isHandRaised());\r\n        });\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.last_attack>\r\n            // @returns MapTag\r\n            // @description\r\n            // Returns an interaction entity's last attack interaction, if any.\r\n            // The returned map contains:\r\n            // - 'player' (PlayerTag): the player who interacted\r\n            // - 'duration' (DurationTag): the amount of time since the interaction. Note that this is a delta time (same limitations as <@link event delta time>), and may become inaccurate if the interaction entity changes worlds.\r\n            // - 'raw_game_time' (ElementTag(Number)): the raw game time the interaction occurred at, used to calculate the time above.\r\n            // -->\r\n            registerSpawnedOnlyTag(MapTag.class, \"last_attack\", (attribute, object) -> {\r\n                if (!(object.getBukkitEntity() instanceof Interaction interaction)) {\r\n                    attribute.echoError(\"'EntityTag.last_attack' is only valid for interaction entities.\");\r\n                    return null;\r\n                }\r\n                return MultiVersionHelper1_19.interactionToMap(interaction.getLastAttack(), interaction.getWorld());\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.last_interaction>\r\n            // @returns MapTag\r\n            // @description\r\n            // Returns an interaction entity's last right click interaction, if any.\r\n            // The returned map contains:\r\n            // - 'player' (PlayerTag): the player who interacted\r\n            // - 'duration' (DurationTag): the amount of time since the interaction. Note that this is a delta time (same limitations as <@link event delta time>), and may become inaccurate if the interaction entity changes worlds.\r\n            // - 'raw_game_time' (ElementTag(Number)): the raw game time the interaction occurred at, used to calculate the time above.\r\n            // -->\r\n            registerSpawnedOnlyTag(MapTag.class, \"last_interaction\", (attribute, object) -> {\r\n                if (!(object.getBukkitEntity() instanceof Interaction interaction)) {\r\n                    attribute.echoError(\"'EntityTag.last_interaction' is only valid for interaction entities.\");\r\n                    return null;\r\n                }\r\n                return MultiVersionHelper1_19.interactionToMap(interaction.getLastInteraction(), interaction.getWorld());\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.highest_anger>\r\n            // @returns ElementTag(Number)\r\n            // @description\r\n            // Returns a warden's anger level at its current target, or its highest anger level.\r\n            // @example\r\n            // # Use to heal a warden if its highest anger level is above 80.\r\n            // - if <[warden].highest_anger> > 80:\r\n            //   - heal <[warden]>\r\n            // -->\r\n            tagProcessor.registerTag(ElementTag.class, \"highest_anger\", (attribute, object) -> {\r\n                if (!(object.getBukkitEntity() instanceof Warden warden)) {\r\n                    return null;\r\n                }\r\n                return new ElementTag(warden.getAnger());\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.anger_at[<entity>]>\r\n            // @returns ElementTag(Number)\r\n            // @mechanism EntityTag.anger_at\r\n            // @description\r\n            // Returns a warden's anger level at a specific entity.\r\n            // @example\r\n            // # Use to tell the linked player if a warden's anger at them is above 100.\r\n            // - if <[warden].anger_at[<player>]> > 100:\r\n            //   - narrate \"The warden is angry!\"\r\n            // -->\r\n            tagProcessor.registerTag(ElementTag.class, EntityTag.class, \"anger_at\", (attribute, object, param) -> {\r\n                if (!(object.getBukkitEntity() instanceof Warden warden)) {\r\n                    return null;\r\n                }\r\n                Entity entity = param.getBukkitEntity();\r\n                if (entity == null) {\r\n                    attribute.echoError(\"Invalid entity '\" + param.debuggable() + \"<W>' specified: must be spawned.\");\r\n                    return null;\r\n                }\r\n                return new ElementTag(warden.getAnger(entity));\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.anger_level>\r\n            // @returns ElementTag\r\n            // @description\r\n            // Returns a warden's current anger level, which will be any of <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Warden.AngerLevel.html>.\r\n            // @example\r\n            // # Use to get a warden's anger level and narrate it.\r\n            // - narrate \"The warden is <[warden].anger_level.to_lowercase>!\"\r\n            // -->\r\n            tagProcessor.registerTag(ElementTag.class, \"anger_level\", (attribute, object) -> {\r\n                if (!(object.getBukkitEntity() instanceof Warden warden)) {\r\n                    return null;\r\n                }\r\n                return MultiVersionHelper1_19.getWardenAngerLevel(warden);\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.angry_at>\r\n            // @returns EntityTag\r\n            // @description\r\n            // Returns the entity a warden is the most angry at, if any.\r\n            // @example\r\n            // # Use to kill the entity a warden is the most angry at.\r\n            // - kill <[warden].angry_at>\r\n            // -->\r\n            tagProcessor.registerTag(EntityFormObject.class, \"angry_at\", (attribute, object) -> {\r\n                if (!(object.getBukkitEntity() instanceof Warden warden)) {\r\n                    return null;\r\n                }\r\n                Entity angryAt = warden.getEntityAngryAt();\r\n                return angryAt != null ? new EntityTag(angryAt).getDenizenObject() : null;\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name clear_anger\r\n            // @input EntityTag\r\n            // @description\r\n            // Clears a warden's anger towards the input entity.\r\n            // @tags\r\n            // <EntityTag.anger_at[<entity>]>\r\n            // @example\r\n            // # Use to clear a warden's anger towards a specific entity.\r\n            // - adjust <[warden]> clear_anger:<[entity]>\r\n            // -->\r\n            tagProcessor.registerMechanism(\"clear_anger\", false, EntityTag.class, (object, mechanism, input) -> {\r\n                if (!(object.getBukkitEntity() instanceof Warden warden)) {\r\n                    mechanism.echoError(\"Cannot adjust '\" + object.debuggable() + \"<W>': must be a warden.\");\r\n                    return;\r\n                }\r\n                Entity entity = input.getBukkitEntity();\r\n                if (entity == null) {\r\n                    mechanism.echoError(\"Invalid entity '\" + input.debuggable() + \"<W>' specified: must be spawned.\");\r\n                    return;\r\n                }\r\n                warden.clearAnger(entity);\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name anger_at\r\n            // @input MapTag\r\n            // @description\r\n            // Sets a warden's anger towards a specific entity.\r\n            // The input map needs to have the following keys:\r\n            // - \"entity\", the entity to set anger for.\r\n            // - \"anger\", the amount of anger.\r\n            // See <@link mechanism EntityTag.clear_anger> for clearing anger.\r\n            // See <@link mechanism EntityTag.increase_anger> for increasing it.\r\n            // @tags\r\n            // <EntityTag.anger_at[<entity>]>\r\n            // @example\r\n            // # Use to set a warden's anger at a specific entity to 20.\r\n            // - adjust <[warden]> anger_at:[entity=<[entity]>;anger=20]\r\n            // -->\r\n            tagProcessor.registerMechanism(\"anger_at\", false, MapTag.class, (object, mechanism, input) -> {\r\n                if (!(object.getBukkitEntity() instanceof Warden warden)) {\r\n                    mechanism.echoError(\"Cannot adjust '\" + object.debuggable() + \"<W>': must be a warden.\");\r\n                    return;\r\n                }\r\n                ElementTag anger = input.getElement(\"anger\");\r\n                EntityTag inputEntity = input.getObjectAs(\"entity\", EntityTag.class, mechanism.context);\r\n                if (anger == null || inputEntity == null) {\r\n                    mechanism.echoError(\"Invalid map input '\" + input.debuggable() + \"<W>' specified: must have 'anger' and 'entity' keys.\");\r\n                    return;\r\n                }\r\n                if (!anger.isInt()) {\r\n                    mechanism.echoError(\"Invalid anger '\" + anger + \"' specified: must be a number.\");\r\n                    return;\r\n                }\r\n                Entity entity = inputEntity.getBukkitEntity();\r\n                if (entity == null) {\r\n                    mechanism.echoError(\"Invalid entity '\" + inputEntity.debuggable() + \"<W>' specified: must be spawned.\");\r\n                    return;\r\n                }\r\n                warden.setAnger(entity, anger.asInt());\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name increase_anger\r\n            // @input MapTag\r\n            // @description\r\n            // Increases a warden's anger towards a specific entity.\r\n            // The input map needs to have the following keys:\r\n            // - \"entity\", the entity to increase anger for.\r\n            // - \"anger\", the amount of anger to add.\r\n            // See <@link mechanism EntityTag.clear_anger> for clearing anger.\r\n            // See <@link mechanism EntityTag.anger_at> for setting it.\r\n            // @tags\r\n            // <EntityTag.anger_at[<entity>]>\r\n            // @example\r\n            // # Use to increase a warden's anger at a specific entity by 20.\r\n            // - adjust <[warden]> increase_anger:[entity=<[entity]>;anger=20]\r\n            // -->\r\n            tagProcessor.registerMechanism(\"increase_anger\", false, MapTag.class, (object, mechanism, input) -> {\r\n                if (!(object.getBukkitEntity() instanceof Warden warden)) {\r\n                    mechanism.echoError(\"Cannot adjust '\" + object.debuggable() + \"<W>': must be a warden.\");\r\n                    return;\r\n                }\r\n                ElementTag anger = input.getElement(\"anger\");\r\n                EntityTag inputEntity = input.getObjectAs(\"entity\", EntityTag.class, mechanism.context);\r\n                if (anger == null || inputEntity == null) {\r\n                    mechanism.echoError(\"Invalid map input '\" + input.debuggable() + \"<W>' specified: must have 'anger' and 'entity' keys.\");\r\n                    return;\r\n                }\r\n                if (!anger.isInt()) {\r\n                    mechanism.echoError(\"Invalid anger '\" + anger + \"' specified: must be a number.\");\r\n                    return;\r\n                }\r\n                Entity entity = inputEntity.getBukkitEntity();\r\n                if (entity == null) {\r\n                    mechanism.echoError(\"Invalid entity '\" + inputEntity.debuggable() + \"<W>' specified: must be spawned.\");\r\n                    return;\r\n                }\r\n                warden.increaseAnger(entity, anger.asInt());\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name sense_disturbance\r\n            // @input LocationTag\r\n            // @description\r\n            // Makes a warden sense a disturbance at the input location.\r\n            // @example\r\n            // # Use to make a warden sense a disturbance at the linked player's location.\r\n            // - adjust <[warden]> sense_disturbance:<player.location>\r\n            // -->\r\n            registerSpawnedOnlyMechanism(\"sense_disturbance\", false, LocationTag.class, (object, mechanism, input) -> {\r\n                if (!(object.getBukkitEntity() instanceof Warden warden)) {\r\n                    mechanism.echoError(\"Cannot adjust '\" + object.debuggable() + \"<W>': must be a warden.\");\r\n                    return;\r\n                }\r\n                warden.setDisturbanceLocation(input);\r\n            });\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name alter_uuid\r\n        // @input ElementTag\r\n        // @description\r\n        // Alters the entity's UUID, changing it to the new input UUID.\r\n        // This is very likely to break things and is almost never a good idea.\r\n        // This sorta-works with players, with significant side effects that will need to be compensated for.\r\n        // @tags\r\n        // <EntityTag.uuid>\r\n        // -->\r\n        registerSpawnedOnlyMechanism(\"alter_uuid\", false, ElementTag.class, (object, mechanism, new_id) -> {\r\n            try {\r\n                UUID id = UUID.fromString(new_id.asString());\r\n                NMSHandler.entityHelper.setUUID(object.getBukkitEntity(), id);\r\n            }\r\n            catch (IllegalArgumentException ex) {\r\n                mechanism.echoError(\"Cannot parse UUID input '\" + new_id + \"': \" + ex.getMessage());\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name visual_pose\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the entity's visual pose, must be one of <@link url https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/Pose.html>.\r\n        // Note that not all entities support all poses, some are only supported by specific entity types.\r\n        // @tags\r\n        // <EntityTag.visual_pose>\r\n        // -->\r\n        registerSpawnedOnlyMechanism(\"visual_pose\", false, ElementTag.class, (object, mechanism, input) -> {\r\n            if (mechanism.requireEnum(Pose.class)) {\r\n                NMSHandler.entityHelper.setPose(object.getBukkitEntity(), input.asEnum(Pose.class));\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name is_sleeping\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether a fox is sleeping.\r\n        // The entity may wake up immediately after setting this to true. If this is not desired, disable <@link mechanism EntityTag.has_ai>.\r\n        // @tags\r\n        // <EntityTag.is_sleeping>\r\n        // -->\r\n        tagProcessor.registerMechanism(\"is_sleeping\", false, ElementTag.class, (object, mechanism, input) -> {\r\n            if (!mechanism.requireBoolean()) {\r\n                return;\r\n            }\r\n            if (!(object.getBukkitEntity() instanceof Fox fox)) {\r\n                mechanism.echoError(\"'is_sleeping' mechanism is only valid for Fox entities.\");\r\n                return;\r\n            }\r\n            fox.setSleeping(input.asBoolean());\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name detonate\r\n        // @input None\r\n        // @description\r\n        // If the entity is a firework, creeper, or wind charge, detonates it.\r\n        // -->\r\n        tagProcessor.registerMechanism(\"detonate\", false, (object, mechanism) -> {\r\n            Entity entity = object.getBukkitEntity();\r\n            if (entity instanceof Firework firework) {\r\n                firework.detonate();\r\n            }\r\n            else if (entity instanceof Creeper creeper) {\r\n                creeper.explode();\r\n            }\r\n            else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && entity instanceof AbstractWindCharge windCharge) {\r\n                windCharge.explode();\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Cannot detonate entity of type '\" + object.getEntityType() + \"'.\");\r\n            }\r\n        });\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name start_using_hand\r\n            // @input ElementTag\r\n            // @description\r\n            // Forces an entity to start using one of its hands.\r\n            // Input is either HAND or OFF_HAND, defaults to HAND.\r\n            // -->\r\n            tagProcessor.registerMechanism(\"start_using_hand\", false, (object, mechanism) -> {\r\n                if (!object.isLivingEntity()) {\r\n                    mechanism.echoError(\"The 'start_using_hand' mechanism only works for living entities!\");\r\n                    return;\r\n                }\r\n                EquipmentSlot hand = mechanism.hasValue() ? mechanism.getValue().asEnum(EquipmentSlot.class) : EquipmentSlot.HAND;\r\n                if (hand != EquipmentSlot.HAND && hand != EquipmentSlot.OFF_HAND) {\r\n                    mechanism.echoError(\"Invalid equipment slot '\" + mechanism.getValue() + \"' specified: must be HAND or OFF_HAND.\");\r\n                    return;\r\n                }\r\n                NMSHandler.entityHelper.startUsingItem(object.getLivingEntity(), hand);\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name stop_using_hand\r\n            // @input None\r\n            // @description\r\n            // Forces an entity to stop using either hand.\r\n            // -->\r\n            tagProcessor.registerMechanism(\"stop_using_hand\", false, (object, mechanism) -> {\r\n                if (!object.isLivingEntity()) {\r\n                    mechanism.echoError(\"The 'stop_using_hand' mechanism only works for living entities!\");\r\n                    return;\r\n                }\r\n                NMSHandler.entityHelper.stopUsingItem(object.getLivingEntity());\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name play_hurt_animation\r\n            // @input ElementTag(Decimal)\r\n            // @description\r\n            // Plays a hurt animation that makes the living entity flash red. When the entity is a player, you can change the direction the camera rotates.\r\n            // Damage direction is relative to the player, where 0 is in front, 90 is to the right, 180 is behind, and 270 is to the left.\r\n            // For versions 1.19 or below, use <@link command animate>.\r\n            // @example\r\n            // # The player's camera will rotate as if the player took damage from the right and the player will flash red.\r\n            // - adjust <player> play_hurt_animation:90\r\n            // @example\r\n            // # This will flash the entity red as if it took damage.\r\n            // - adjust <[entity]> play_hurt_animation:0\r\n            // -->\r\n            registerSpawnedOnlyMechanism(\"play_hurt_animation\", false, ElementTag.class, (object, mechanism, value) -> {\r\n                if (mechanism.requireFloat()) {\r\n                    if (!object.isLivingEntity()) {\r\n                        mechanism.echoError(\"The 'play_hurt_animation' mechanism only works for living entities!\");\r\n                        return;\r\n                    }\r\n                    object.getLivingEntity().playHurtAnimation(value.asFloat());\r\n                }\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name internal_data\r\n            // @input MapTag\r\n            // @description\r\n            // Modifies an entity's internal entity data as a map of data name to value.\r\n            // You should almost always prefer using the appropriate mechanism/property instead of this, other than very specific special cases.\r\n            // See <@link language Internal Entity Data> for more information on the input.\r\n            // -->\r\n            tagProcessor.registerMechanism(\"internal_data\", false, MapTag.class, (object, mechanism, input) -> {\r\n                NMSHandler.entityHelper.modifyInternalEntityData(object.getBukkitEntity(), input);\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.bookshelf_slot>\r\n            // @returns ElementTag(Number)\r\n            // @description\r\n            // Returns the Chiseled Bookshelf slot that the entity is looking at, if any.\r\n            // See also <@link tag LocationTag.slot>\r\n            // -->\r\n            registerSpawnedOnlyTag(ElementTag.class, \"bookshelf_slot\", (attribute, object) -> {\r\n                RayTraceResult result = object.getLivingEntity().rayTraceBlocks(4.5);\r\n                if (result == null || result.getHitBlock().getType() != Material.CHISELED_BOOKSHELF) {\r\n                    attribute.echoError(\"'EntityTag.bookshelf_slot' requires the entity to look at a Chiseled Bookshelf block.\");\r\n                    return null;\r\n                }\r\n                ChiseledBookshelf bookshelfState = (ChiseledBookshelf) result.getHitBlock().getState();\r\n                Vector vector = result.getHitPosition().subtract(result.getHitBlock().getLocation().toVector());\r\n                return new ElementTag(bookshelfState.getSlot(vector) + 1);\r\n            });\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.all_raw_nbt>\r\n            // @returns MapTag\r\n            // @mechanism EntityTag.raw_nbt\r\n            // @description\r\n            // Returns the entity's entire raw NBT data as a MapTag.\r\n            // See <@link language Raw NBT Encoding> for more information.\r\n            // -->\r\n            tagProcessor.registerTag(MapTag.class, \"all_raw_nbt\", (attribute, object) -> {\r\n                CompoundBinaryTag tag = NMSHandler.entityHelper.getRawNBT(object.getBukkitEntity());\r\n                return (MapTag) ItemRawNBT.nbtTagToObject(tag);\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object EntityTag\r\n            // @name raw_nbt\r\n            // @input MapTag\r\n            // @description\r\n            // Modifies an entity's raw NBT data based on the input MapTag.\r\n            // The input MapTag must be in MapTag NBT format (<@link language Raw NBT Encoding>), and needs to be strictly perfect.\r\n            // This doesn't override all the entity's data, only the values specified in the input map are set.\r\n            // @tags\r\n            // <EntityTag.all_raw_nbt>\r\n            // -->\r\n            tagProcessor.registerMechanism(\"raw_nbt\", false, MapTag.class, (object, mechanism, input) -> {\r\n                CompoundBinaryTag tag = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(input, mechanism.context, \"(entity).\");\r\n                if (tag != null) {\r\n                    NMSHandler.entityHelper.modifyRawNBT(object.getBukkitEntity(), tag);\r\n                }\r\n            });\r\n        }\r\n    }\r\n\r\n    public EntityTag describe(TagContext context) {\r\n        ArrayList<Mechanism> waitingMechs;\r\n        if (isSpawnedOrValidForTag()) {\r\n            waitingMechs = new ArrayList<>();\r\n            for (Map.Entry<StringHolder, ObjectTag> property : PropertyParser.getPropertiesMap(this).entrySet()) {\r\n                waitingMechs.add(new Mechanism(property.getKey().str, property.getValue(), context));\r\n            }\r\n        }\r\n        else {\r\n            waitingMechs = new ArrayList<>(getWaitingMechanisms());\r\n        }\r\n        EntityTag entity = new EntityTag(entity_type, waitingMechs);\r\n        entity.entityScript = entityScript;\r\n        return entity;\r\n    }\r\n\r\n    public static ObjectTagProcessor<EntityTag> tagProcessor = new ObjectTagProcessor<>();\r\n\r\n    public static <R extends ObjectTag> void registerSpawnedOnlyTag(Class<R> returnType, String name, TagRunnable.ObjectInterface<EntityTag, R> runnable, String... variants) {\r\n        tagProcessor.registerTag(returnType, name, (attribute, object) -> {\r\n            if (!object.isSpawnedOrValidForTag()) {\r\n                if (!attribute.hasAlternative()) {\r\n                    attribute.echoError(\"Entity is not spawned, but tag '\" + name + \"' requires the entity be spawned, for entity: \" + object.debuggable());\r\n                }\r\n                return null;\r\n            }\r\n            return runnable.run(attribute, object);\r\n        }, variants);\r\n    }\r\n\r\n    public static void registerSpawnedOnlyMechanism(String name, boolean allowProperty, Mechanism.GenericMechRunnerInterface<EntityTag> runner) {\r\n        tagProcessor.registerMechanism(name, allowProperty, (entity, mechanism) -> {\r\n            if (!entity.isSpawned()) {\r\n                mechanism.echoError(\"Entity is not spawned, but mechanism '\" + name + \"' requires the entity be spawned, for entity: \" + entity.debuggable());\r\n                return;\r\n            }\r\n            runner.run(entity, mechanism);\r\n        });\r\n    }\r\n\r\n    public static <P extends ObjectTag> void registerSpawnedOnlyMechanism(String name, boolean allowProperty, Class<P> paramType, Mechanism.ObjectInputMechRunnerInterface<EntityTag, P> runner) {\r\n        tagProcessor.registerMechanism(name, allowProperty, paramType, (entity, mechanism, param) -> {\r\n            if (!entity.isSpawned()) {\r\n                mechanism.echoError(\"Entity is not spawned, but mechanism '\" + name + \"' requires the entity be spawned, for entity: \" + entity.debuggable());\r\n                return;\r\n            }\r\n            runner.run(entity, mechanism, param);\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return tagProcessor.getObjectAttribute(this, attribute);\r\n    }\r\n\r\n    public ArrayList<Mechanism> mechanisms = new ArrayList<>();\r\n\r\n    public ArrayList<Mechanism> getWaitingMechanisms() {\r\n        return mechanisms;\r\n    }\r\n\r\n    public void applyProperty(Mechanism mechanism) {\r\n        if (isGeneric()) {\r\n            mechanisms.add(mechanism);\r\n            mechanism.fulfill();\r\n        }\r\n        else {\r\n            mechanism.echoError(\"Cannot apply properties to an already-spawned entity!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n        if (isGeneric()) {\r\n            mechanisms.add(mechanism);\r\n            mechanism.fulfill();\r\n            return;\r\n        }\r\n        if (getBukkitEntity() == null) {\r\n            if (isCitizensNPC()) {\r\n                mechanism.echoError(\"Cannot adjust not-spawned NPC \" + getDenizenNPC());\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Cannot adjust entity \" + this);\r\n            }\r\n            return;\r\n        }\r\n\r\n        if (mechanism.matches(\"attach_to\")) {\r\n            BukkitImplDeprecations.attachToMech.warn(mechanism.context);\r\n            if (mechanism.hasValue()) {\r\n                ListTag list = mechanism.valueAsType(ListTag.class);\r\n                Vector offset = null;\r\n                boolean rotateWith = true;\r\n                if (list.size() > 1) {\r\n                    offset = LocationTag.valueOf(list.get(1), mechanism.context).toVector();\r\n                    if (list.size() > 2) {\r\n                        rotateWith = new ElementTag(list.get(2)).asBoolean();\r\n                    }\r\n                }\r\n                EntityAttachmentHelper.forceAttachMove(this, EntityTag.valueOf(list.get(0), mechanism.context), offset, rotateWith);\r\n            }\r\n            else {\r\n                EntityAttachmentHelper.forceAttachMove(this, null, null, false);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name shooter\r\n        // @input EntityTag\r\n        // @synonyms EntityTag.arrow_firer,EntityTag.fishhook_shooter,EntityTag.snowball_thrower\r\n        // @description\r\n        // Sets the projectile's shooter or TNT's priming source.\r\n        // @tags\r\n        // <EntityTag.shooter>\r\n        // -->\r\n        if (mechanism.matches(\"shooter\")) {\r\n            setShooter(mechanism.valueAsType(EntityTag.class));\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name can_pickup_items\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the entity can pick up items.\r\n        // The entity must be living.\r\n        // @tags\r\n        // <EntityTag.can_pickup_items>\r\n        // -->\r\n        if (mechanism.matches(\"can_pickup_items\") && mechanism.requireBoolean()) {\r\n            getLivingEntity().setCanPickupItems(mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fall_distance\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the fall distance.\r\n        // @tags\r\n        // <EntityTag.fall_distance>\r\n        // -->\r\n        if (mechanism.matches(\"fall_distance\") && mechanism.requireFloat()) {\r\n            entity.setFallDistance(mechanism.getValue().asFloat());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fallingblock_drop_item\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the falling block will drop an item if broken.\r\n        // -->\r\n        if (mechanism.matches(\"fallingblock_drop_item\") && mechanism.requireBoolean()\r\n                && entity instanceof FallingBlock) {\r\n            ((FallingBlock) entity).setDropItem(mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fallingblock_hurt_entities\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the falling block will hurt entities when it lands.\r\n        // -->\r\n        if (mechanism.matches(\"fallingblock_hurt_entities\") && mechanism.requireBoolean()\r\n                && entity instanceof FallingBlock) {\r\n            ((FallingBlock) entity).setHurtEntities(mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fire_time\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the entity's current fire time (time before the entity stops being on fire).\r\n        // @tags\r\n        // <EntityTag.fire_time>\r\n        // -->\r\n        if (mechanism.matches(\"fire_time\") && mechanism.requireObject(DurationTag.class)) {\r\n            entity.setFireTicks(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name leash_holder\r\n        // @input EntityTag\r\n        // @description\r\n        // Sets the entity holding this entity by leash.\r\n        // The entity must be living.\r\n        // @tags\r\n        // <EntityTag.is_leashed>\r\n        // <EntityTag.leash_holder>\r\n        // -->\r\n        if (mechanism.matches(\"leash_holder\") && mechanism.requireObject(EntityTag.class)) {\r\n            getLivingEntity().setLeashHolder(mechanism.valueAsType(EntityTag.class).getBukkitEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name can_breed\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the entity is capable of mating with another of its kind.\r\n        // The entity must be living and 'ageable'.\r\n        // @tags\r\n        // <EntityTag.can_breed>\r\n        // -->\r\n        if (mechanism.matches(\"can_breed\") && mechanism.requireBoolean()) {\r\n            ((Ageable) getLivingEntity()).setBreed(true);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name breed\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the entity is trying to mate with another of its kind.\r\n        // The entity must be living and an animal.\r\n        // @tags\r\n        // <EntityTag.breeding>\r\n        // -->\r\n        if (mechanism.matches(\"breed\") && mechanism.requireBoolean()) {\r\n            ((Animals) getLivingEntity()).setLoveModeTicks(mechanism.getValue().asBoolean() ? 600 : 0);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name passengers\r\n        // @input ListTag(EntityTag)\r\n        // @description\r\n        // Sets the passengers of this entity.\r\n        // @tags\r\n        // <EntityTag.passengers>\r\n        // <EntityTag.is_empty>\r\n        // -->\r\n        if (mechanism.matches(\"passengers\")) {\r\n            entity.eject();\r\n            for (EntityTag ent : mechanism.valueAsType(ListTag.class).filter(EntityTag.class, mechanism.context)) {\r\n                if (comparesTo(ent) == 1) {\r\n                    continue;\r\n                }\r\n                if (!ent.isSpawned()) {\r\n                    ent.spawnAt(getLocation());\r\n                }\r\n                if (ent.isSpawned()) {\r\n                    entity.addPassenger(ent.getBukkitEntity());\r\n                }\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name passenger\r\n        // @input EntityTag\r\n        // @description\r\n        // Sets the passenger of this entity.\r\n        // @tags\r\n        // <EntityTag.passenger>\r\n        // <EntityTag.is_empty>\r\n        // -->\r\n        if (mechanism.matches(\"passenger\") && mechanism.requireObject(EntityTag.class)) {\r\n            EntityTag ent = mechanism.valueAsType(EntityTag.class);\r\n            if (!ent.isSpawned()) {\r\n                ent.spawnAt(getLocation());\r\n            }\r\n            entity.eject();\r\n            if (ent.isSpawned()) {\r\n                entity.addPassenger(ent.getBukkitEntity());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name time_lived\r\n        // @input DurationTag\r\n        // @synonyms EntityTag.age_nbt,EntityTag.time_nbt\r\n        // @description\r\n        // Sets the amount of time this entity has lived for.\r\n        // For entities that automatically despawn such as dropped_items or falling_blocks, it can be useful to set this value to \"-2147483648t\" (the minimum valid number of ticks) to cause it to persist indefinitely.\r\n        // For falling_block usage, see also <@link mechanism EntityTag.auto_expire>\r\n        // @tags\r\n        // <EntityTag.time_lived>\r\n        // -->\r\n        if (mechanism.matches(\"time_lived\") && mechanism.requireObject(DurationTag.class)) {\r\n            NMSHandler.entityHelper.setTicksLived(entity, mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name absorption_health\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the living entity's absorption health.\r\n        // @tags\r\n        // <EntityTag.absorption_health>\r\n        // -->\r\n        if (mechanism.matches(\"absorption_health\") && mechanism.requireFloat()) {\r\n            getLivingEntity().setAbsorptionAmount(mechanism.getValue().asDouble());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name oxygen\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets how much air the entity has remaining before it drowns.\r\n        // The entity must be living.\r\n        // @tags\r\n        // <EntityTag.oxygen>\r\n        // <EntityTag.max_oxygen>\r\n        // -->\r\n        if (mechanism.matches(\"oxygen\") && mechanism.requireObject(DurationTag.class)) {\r\n            getLivingEntity().setRemainingAir(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name remove_effects\r\n        // @input None\r\n        // @description\r\n        // Removes all potion effects from the entity.\r\n        // The entity must be living.\r\n        // @tags\r\n        // <EntityTag.has_effect[<effect>]>\r\n        // -->\r\n        if (mechanism.matches(\"remove_effects\")) {\r\n            for (PotionEffect potionEffect : this.getLivingEntity().getActivePotionEffects()) {\r\n                getLivingEntity().removePotionEffect(potionEffect.getType());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name release_left_shoulder\r\n        // @input None\r\n        // @description\r\n        // Releases the player's left shoulder entity.\r\n        // Only applies to player-typed entities.\r\n        // @tags\r\n        // <EntityTag.left_shoulder>\r\n        // -->\r\n        if (mechanism.matches(\"release_left_shoulder\") && getLivingEntity() instanceof HumanEntity) {\r\n            Entity bukkitEnt = ((HumanEntity) getLivingEntity()).getShoulderEntityLeft();\r\n            if (bukkitEnt != null) {\r\n                EntityTag ent = new EntityTag(bukkitEnt);\r\n                String escript = ent.getEntityScript();\r\n                ent = EntityTag.valueOf(\"e@\" + (escript != null && escript.length() > 0 ? escript : ent.getEntityType().getLowercaseName())\r\n                        + PropertyParser.getPropertiesString(ent), mechanism.context);\r\n                ent.spawnAt(getEyeLocation());\r\n                ((HumanEntity) getLivingEntity()).setShoulderEntityLeft(null);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name release_right_shoulder\r\n        // @input None\r\n        // @description\r\n        // Releases the player's right shoulder entity.\r\n        // Only applies to player-typed entities.\r\n        // @tags\r\n        // <EntityTag.right_shoulder>\r\n        // -->\r\n        if (mechanism.matches(\"release_right_shoulder\") && getLivingEntity() instanceof HumanEntity) {\r\n            Entity bukkitEnt = ((HumanEntity) getLivingEntity()).getShoulderEntityRight();\r\n            if (bukkitEnt != null) {\r\n                EntityTag ent = new EntityTag(bukkitEnt);\r\n                String escript = ent.getEntityScript();\r\n                ent = EntityTag.valueOf(\"e@\" + (escript != null && escript.length() > 0 ? escript : ent.getEntityType().getLowercaseName())\r\n                        + PropertyParser.getPropertiesString(ent), mechanism.context);\r\n                ent.spawnAt(getEyeLocation());\r\n                ((HumanEntity) getLivingEntity()).setShoulderEntityRight(null);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name left_shoulder\r\n        // @input EntityTag\r\n        // @description\r\n        // Sets the entity's left shoulder entity.\r\n        // Only applies to player-typed entities.\r\n        // Provide no input to remove the shoulder entity.\r\n        // NOTE: This mechanism will remove the current shoulder entity from the world.\r\n        // Also note the client will currently only render parrot entities.\r\n        // @tags\r\n        // <EntityTag.left_shoulder>\r\n        // -->\r\n        if (mechanism.matches(\"left_shoulder\") && getLivingEntity() instanceof HumanEntity) {\r\n            if (mechanism.hasValue()) {\r\n                if (mechanism.requireObject(EntityTag.class)) {\r\n                    ((HumanEntity) getLivingEntity()).setShoulderEntityLeft(mechanism.valueAsType(EntityTag.class).getBukkitEntity());\r\n                }\r\n            }\r\n            else {\r\n                ((HumanEntity) getLivingEntity()).setShoulderEntityLeft(null);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name right_shoulder\r\n        // @input EntityTag\r\n        // @description\r\n        // Sets the entity's right shoulder entity.\r\n        // Only applies to player-typed entities.\r\n        // Provide no input to remove the shoulder entity.\r\n        // NOTE: This mechanism will remove the current shoulder entity from the world.\r\n        // Also note the client will currently only render parrot entities.\r\n        // @tags\r\n        // <EntityTag.right_shoulder>\r\n        // -->\r\n        if (mechanism.matches(\"right_shoulder\") && getLivingEntity() instanceof HumanEntity) {\r\n            if (mechanism.hasValue()) {\r\n                if (mechanism.requireObject(EntityTag.class)) {\r\n                    ((HumanEntity) getLivingEntity()).setShoulderEntityRight(mechanism.valueAsType(EntityTag.class).getBukkitEntity());\r\n                }\r\n            }\r\n            else {\r\n                ((HumanEntity) getLivingEntity()).setShoulderEntityRight(null);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name persistent\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the mob-entity will not be removed completely when far away from players.\r\n        // This is Bukkit's \"setRemoveWhenFarAway\" which is Mojang's \"isPersistenceRequired\".\r\n        // In many cases, <@link mechanism EntityTag.force_no_persist> may be preferred.\r\n        // The entity must be a mob-type entity.\r\n        // @tags\r\n        // <EntityTag.is_persistent>\r\n        // -->\r\n        if (mechanism.matches(\"persistent\") && mechanism.requireBoolean()) {\r\n            getLivingEntity().setRemoveWhenFarAway(!mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name force_no_persist\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Set 'true' to indicate the entity should be forced to not save to file when chunks unload.\r\n        // Set 'false' to not force to not-save. Entities will then either save or not save depending on separate conditions.\r\n        // This is a custom value added in Bukkit to block saving, which is not the same as Mojang's similar option under <@link mechanism EntityTag.persistent>.\r\n        // @tags\r\n        // <EntityTag.force_no_persist>\r\n        // -->\r\n        if (mechanism.matches(\"force_no_persist\") && mechanism.requireBoolean()) {\r\n            getBukkitEntity().setPersistent(!mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        if (mechanism.matches(\"remove_when_far_away\") && mechanism.requireBoolean()) {\r\n            BukkitImplDeprecations.entityRemoveWhenFar.warn(mechanism.context);\r\n            getLivingEntity().setRemoveWhenFarAway(mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name collidable\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the entity is collidable.\r\n        // For NPCs, Sets the persistent collidable value.\r\n        // NOTE: To disable collision between two entities, set this mechanism to false on both entities.\r\n        // NOTE: For players, to fully remove collision you need to use <@link command team> and set \"option:collision_rule status:never\"\r\n        // @tags\r\n        // <EntityTag.is_collidable>\r\n        // -->\r\n        if (mechanism.matches(\"collidable\") && mechanism.requireBoolean()) {\r\n            if (isCitizensNPC()) {\r\n                getDenizenNPC().getCitizen().data().setPersistent(NPC.Metadata.COLLIDABLE, mechanism.getValue().asBoolean());\r\n            }\r\n            else {\r\n                getLivingEntity().setCollidable(mechanism.getValue().asBoolean());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name no_damage_duration\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the duration in which the entity will take no damage.\r\n        // @tags\r\n        // <EntityTag.last_damage.duration>\r\n        // <EntityTag.last_damage.max_duration>\r\n        // -->\r\n        if (mechanism.matches(\"no_damage_duration\") && mechanism.requireObject(DurationTag.class)) {\r\n            getLivingEntity().setNoDamageTicks(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name max_no_damage_duration\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the maximum duration in which the entity will take no damage.\r\n        // @tags\r\n        // <EntityTag.last_damage.duration>\r\n        // <EntityTag.last_damage.max_duration>\r\n        // -->\r\n        if (mechanism.matches(\"max_no_damage_duration\") && mechanism.requireObject(DurationTag.class)) {\r\n            getLivingEntity().setMaximumNoDamageTicks(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name velocity\r\n        // @input LocationTag\r\n        // @description\r\n        // Sets the entity's movement velocity vector.\r\n        // @tags\r\n        // <EntityTag.velocity>\r\n        // -->\r\n        if (mechanism.matches(\"velocity\") && mechanism.requireObject(LocationTag.class)) {\r\n            setVelocity(mechanism.valueAsType(LocationTag.class).toVector());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name move\r\n        // @input LocationTag\r\n        // @description\r\n        // Forces an entity to move in the direction of the velocity vector specified.\r\n        // -->\r\n        if (mechanism.matches(\"move\") && mechanism.requireObject(LocationTag.class)) {\r\n            NMSHandler.entityHelper.move(getBukkitEntity(), mechanism.valueAsType(LocationTag.class).toVector());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fake_move\r\n        // @input LocationTag\r\n        // @description\r\n        // Causes an entity to broadcast a fake movement packet in the direction of the velocity vector specified.\r\n        // The vector value must be in the range [-8,8] on each of X, Y, and Z.\r\n        // -->\r\n        if (mechanism.matches(\"fake_move\") && mechanism.requireObject(LocationTag.class)) {\r\n            NMSHandler.entityHelper.fakeMove(getBukkitEntity(), mechanism.valueAsType(LocationTag.class).toVector());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fake_teleport\r\n        // @input LocationTag\r\n        // @description\r\n        // Causes an entity to broadcast a fake teleport packet to the location specified.\r\n        // -->\r\n        if (mechanism.matches(\"fake_teleport\") && mechanism.requireObject(LocationTag.class)) {\r\n            NMSHandler.entityHelper.fakeTeleport(getBukkitEntity(), mechanism.valueAsType(LocationTag.class));\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name reset_client_location\r\n        // @input None\r\n        // @description\r\n        // Causes an entity to broadcast a fake teleport packet to its own location, forcibly resetting its location for all players that can see it.\r\n        // -->\r\n        if (mechanism.matches(\"reset_client_location\")) {\r\n            NMSHandler.entityHelper.clientResetLoc(getBukkitEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name send_update_packets\r\n        // @input None\r\n        // @description\r\n        // Causes an entity to broadcast any pending entity update packets to all players that can see it.\r\n        // -->\r\n        if (mechanism.matches(\"send_update_packets\")) {\r\n            NMSHandler.entityHelper.sendAllUpdatePackets(getBukkitEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name interact_with\r\n        // @input LocationTag\r\n        // @description\r\n        // Makes a player-type entity interact with a block.\r\n        // -->\r\n        if (mechanism.matches(\"interact_with\") && mechanism.requireObject(LocationTag.class)) {\r\n            Player player = getPlayer();\r\n            if (player == null) {\r\n                mechanism.echoError(\"Only player-type entities can interact with blocks!\");\r\n                return;\r\n            }\r\n            LocationTag interactLocation = mechanism.valueAsType(LocationTag.class);\r\n            NMSHandler.entityHelper.forceInteraction(player, interactLocation);\r\n        }\r\n\r\n        if (mechanism.matches(\"play_death\")) {\r\n            BukkitImplDeprecations.entityPlayDeath.warn(mechanism.context);\r\n            getLivingEntity().playEffect(EntityEffect.DEATH);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name pickup_delay\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the pickup delay of this Item Entity.\r\n        // @tags\r\n        // <EntityTag.pickup_delay>\r\n        // -->\r\n        if ((mechanism.matches(\"pickup_delay\") || mechanism.matches(\"pickupdelay\")) &&\r\n                getBukkitEntity() instanceof Item && mechanism.requireObject(DurationTag.class)) {\r\n            ((Item) getBukkitEntity()).setPickupDelay(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name gliding\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether this entity is gliding.\r\n        // @tags\r\n        // <EntityTag.gliding>\r\n        // -->\r\n        if (mechanism.matches(\"gliding\") && mechanism.requireBoolean()) {\r\n            getLivingEntity().setGliding(mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name glowing\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether this entity is glowing.\r\n        // @tags\r\n        // <EntityTag.glowing>\r\n        // -->\r\n        if (mechanism.matches(\"glowing\") && mechanism.requireBoolean()) {\r\n            getBukkitEntity().setGlowing(mechanism.getValue().asBoolean());\r\n            if (Depends.citizens != null && CitizensAPI.getNPCRegistry().isNPC(getLivingEntity())) {\r\n                CitizensAPI.getNPCRegistry().getNPC(getLivingEntity()).data().setPersistent(NPC.Metadata.GLOWING, mechanism.getValue().asBoolean());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name dragon_phase\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets an EnderDragon's combat phase.\r\n        // @tags\r\n        // <EntityTag.dragon_phase>\r\n        // -->\r\n        if (mechanism.matches(\"dragon_phase\")) {\r\n            EnderDragon ed = (EnderDragon) getLivingEntity();\r\n            ed.setPhase(EnderDragon.Phase.valueOf(mechanism.getValue().asString().toUpperCase()));\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name experience\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the experience value of this experience orb entity.\r\n        // @tags\r\n        // <EntityTag.experience>\r\n        // -->\r\n        if (mechanism.matches(\"experience\") && getBukkitEntity() instanceof ExperienceOrb && mechanism.requireInteger()) {\r\n            ((ExperienceOrb) getBukkitEntity()).setExperience(mechanism.getValue().asInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fuse_ticks\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the number of ticks until the TNT blows up after being primed.\r\n        // @tags\r\n        // <EntityTag.fuse_ticks>\r\n        // -->\r\n        if (mechanism.matches(\"fuse_ticks\") && getBukkitEntity() instanceof TNTPrimed && mechanism.requireInteger()) {\r\n            ((TNTPrimed) getBukkitEntity()).setFuseTicks(mechanism.getValue().asInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name show_to_players\r\n        // @input None\r\n        // @description\r\n        // Marks the entity as visible to players by default (if it was hidden).\r\n        // See also <@link mechanism EntityTag.hide_from_players>.\r\n        // To show to only one player, see <@link mechanism PlayerTag.show_entity>.\r\n        // Works with offline players.\r\n        // -->\r\n        if (mechanism.matches(\"show_to_players\")) {\r\n            HideEntitiesHelper.unhideEntity(null, getBukkitEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name hide_from_players\r\n        // @input None\r\n        // @description\r\n        // Hides the entity from players by default.\r\n        // See also <@link mechanism EntityTag.show_to_players>.\r\n        // To hide for only one player, see <@link mechanism PlayerTag.hide_entity>.\r\n        // Works with offline players.\r\n        // -->\r\n        if (mechanism.matches(\"hide_from_players\")) {\r\n            HideEntitiesHelper.hideEntity(null, getBukkitEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name skin_layers\r\n        // @input ListTag\r\n        // @description\r\n        // Sets the visible skin layers on a player-type entity (PlayerTag or player-type NPCTag).\r\n        // Input is a list of values from the set of:\r\n        // CAPE, HAT, JACKET, LEFT_PANTS, LEFT_SLEEVE, RIGHT_PANTS, RIGHT_SLEEVE, or \"ALL\"\r\n        // @tags\r\n        // <EntityTag.skin_layers>\r\n        // -->\r\n        if (mechanism.matches(\"skin_layers\")) {\r\n            int flags = 0;\r\n            for (String str : mechanism.valueAsType(ListTag.class)) {\r\n                String upper = str.toUpperCase();\r\n                if (upper.equals(\"ALL\")) {\r\n                    flags = 0xFF;\r\n                }\r\n                else {\r\n                    PlayerHelper.SkinLayer layer = PlayerHelper.SkinLayer.valueOf(upper);\r\n                    flags |= layer.flag;\r\n                }\r\n            }\r\n            NMSHandler.playerHelper.setSkinLayers((Player) getBukkitEntity(), (byte) flags);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name mirror_player\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Makes the player-like entity have the same skin as the player looking at it.\r\n        // For NPCs, this will add the Mirror trait.\r\n        // -->\r\n        if (mechanism.matches(\"mirror_player\") && mechanism.requireBoolean()) {\r\n            if (isNPC()) {\r\n                MirrorTrait mirror = getDenizenNPC().getCitizen().getOrAddTrait(MirrorTrait.class);\r\n                if (mechanism.getValue().asBoolean()) {\r\n                    mirror.enableMirror();\r\n                }\r\n                else {\r\n                    mirror.disableMirror();\r\n                }\r\n            }\r\n            else {\r\n                if (mechanism.getValue().asBoolean()) {\r\n                    ProfileEditor.mirrorUUIDs.add(getUUID());\r\n                }\r\n                else {\r\n                    ProfileEditor.mirrorUUIDs.remove(getUUID());\r\n                }\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name swimming\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the entity is swimming.\r\n        // @tags\r\n        // <EntityTag.swimming>\r\n        // -->\r\n        if (mechanism.matches(\"swimming\") && mechanism.requireBoolean()) {\r\n            getLivingEntity().setSwimming(mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name ignite\r\n        // @input None\r\n        // @description\r\n        // If the entity is a creeper, ignites it.\r\n        // -->\r\n        if (mechanism.matches(\"ignite\")) {\r\n            if (getBukkitEntity() instanceof Creeper) {\r\n                ((Creeper) getBukkitEntity()).ignite();\r\n            }\r\n            else {\r\n                Debug.echoError(\"Cannot ignite entity of type '\" + getBukkitEntityType().name() + \"'.\");\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name head_angle\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the raw head angle of a living entity.\r\n        // This will not rotate the body at all. Most users should prefer <@link command look>.\r\n        // -->\r\n        if (mechanism.matches(\"head_angle\") && mechanism.requireFloat()) {\r\n            NMSHandler.entityHelper.setHeadAngle(getLivingEntity(), mechanism.getValue().asFloat());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name skeleton_arms_raised\r\n        // @input ElementTag(Boolean)\r\n        // @deprecated use 'EntityTag.aggressive'.\r\n        // @description\r\n        // Deprecated in favor of <@link mechanism EntityTag.aggressive>.\r\n        // -->\r\n        if (mechanism.matches(\"skeleton_arms_raised\") && mechanism.requireBoolean()) {\r\n            BukkitImplDeprecations.entitySkeletonArmsRaised.warn(mechanism.context);\r\n            if (getBukkitEntityType() == EntityType.SKELETON) {\r\n                NMSHandler.entityHelper.setAggressive((Mob) getBukkitEntity(), mechanism.getValue().asBoolean());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name polar_bear_standing\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the polar bear entity should stand up.\r\n        // -->\r\n        if (mechanism.matches(\"polar_bear_standing\") && mechanism.requireBoolean()) {\r\n            EntityAnimation entityAnimation = NMSHandler.animationHelper.getEntityAnimation(mechanism.getValue().asBoolean() ? \"POLAR_BEAR_START_STANDING\" : \"POLAR_BEAR_STOP_STANDING\");\r\n            entityAnimation.play(entity);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name ghast_attacking\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the ghast entity should show the attacking face.\r\n        // -->\r\n        if (mechanism.matches(\"ghast_attacking\") && mechanism.requireBoolean()) {\r\n            NMSHandler.entityHelper.setGhastAttacking((Ghast) getBukkitEntity(), mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name enderman_angry\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the enderman entity should be screaming angrily.\r\n        // -->\r\n        if (mechanism.matches(\"enderman_angry\") && mechanism.requireBoolean()) {\r\n            NMSHandler.entityHelper.setEndermanAngry((Enderman) getBukkitEntity(), mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name melee_attack\r\n        // @input EntityTag\r\n        // @description\r\n        // Causes this hostile-mob entity to immediately melee-attack the specified target entity once.\r\n        // Works for Hostile Mobs, and Players.\r\n        // Does not work with passive mobs, non-living entities, etc.\r\n        // -->\r\n        if (mechanism.matches(\"melee_attack\") && mechanism.requireObject(EntityTag.class)) {\r\n            getLivingEntity().attack(mechanism.valueAsType(EntityTag.class).getBukkitEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name last_hurt_by\r\n        // @input EntityTag\r\n        // @description\r\n        // Tells this mob entity that it was last hurt by the specified entity.\r\n        // Passive mobs will panic and run away when this is set.\r\n        // Angerable mobs will get angry.\r\n        // -->\r\n        if (mechanism.matches(\"last_hurt_by\") && mechanism.requireObject(EntityTag.class)) {\r\n            NMSHandler.entityHelper.setLastHurtBy(getLivingEntity(), mechanism.valueAsType(EntityTag.class).getLivingEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fish_hook_nibble_time\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the time until this fish hook is next nibbled. If this value is set zero, biting will be processed instead.\r\n        // if this value is set above zero, when it runs out, a nibble (failed bite) will occur.\r\n        // -->\r\n        if (mechanism.matches(\"fish_hook_nibble_time\") && mechanism.requireObject(DurationTag.class)) {\r\n            if (!(getBukkitEntity() instanceof FishHook)) {\r\n                mechanism.echoError(\"fish_hook_nibble_time is only valid for FishHook entities.\");\r\n                return;\r\n            }\r\n            NMSHandler.fishingHelper.setNibble((FishHook) getBukkitEntity(), mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fish_hook_bite_time\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the time until this fish hook is next bit. If this value and also nibble_time are set zero, luring will happen instead.\r\n        // if this value is set above zero, when it runs out, a bite will occur (and a player can reel to catch it, or fail and have nibble set).\r\n        // -->\r\n        if (mechanism.matches(\"fish_hook_bite_time\") && mechanism.requireObject(DurationTag.class)) {\r\n            if (!(getBukkitEntity() instanceof FishHook)) {\r\n                mechanism.echoError(\"fish_hook_hook_time is only valid for FishHook entities.\");\r\n                return;\r\n            }\r\n            NMSHandler.fishingHelper.setHookTime((FishHook) getBukkitEntity(), mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fish_hook_lure_time\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the time until this fish hook is next lured. If this value and also bite_time and nibble_time are set zero, the luring value will be reset to a random amount.\r\n        // if this value is set above zero, when it runs out, particles will spawn and bite_time will be set to a random amount.\r\n        // @tags\r\n        // <EntityTag.fish_hook_lure_time>\r\n        // -->\r\n        if (mechanism.matches(\"fish_hook_lure_time\") && mechanism.requireObject(DurationTag.class)) {\r\n            if (!(getBukkitEntity() instanceof FishHook)) {\r\n                mechanism.echoError(\"fish_hook_lure_time is only valid for FishHook entities.\");\r\n                return;\r\n            }\r\n            NMSHandler.fishingHelper.setLureTime((FishHook) getBukkitEntity(), mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fish_hook_pull\r\n        // @input None\r\n        // @description\r\n        // Pulls the entity this fish hook is attached to towards the caster.\r\n        // -->\r\n        if (mechanism.matches(\"fish_hook_pull\")) {\r\n            if (!(getBukkitEntity() instanceof FishHook)) {\r\n                mechanism.echoError(\"fish_hook_pull is only valid for FishHook entities.\");\r\n                return;\r\n            }\r\n            ((FishHook) getBukkitEntity()).pullHookedEntity();\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fish_hook_apply_lure\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether this fish hook should respect the lure enchantment.\r\n        // Every level of lure enchantment reduces lure time by 5 seconds.\r\n        // @tags\r\n        // <EntityTag.fish_hook_apply_lure>\r\n        // -->\r\n        if (mechanism.matches(\"fish_hook_apply_lure\") && mechanism.requireBoolean()) {\r\n            if (!(getBukkitEntity() instanceof FishHook)) {\r\n                mechanism.echoError(\"fish_hook_apply_lure is only valid for FishHook entities.\");\r\n                return;\r\n            }\r\n            ((FishHook) getBukkitEntity()).setApplyLure(mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fish_hook_hooked_entity\r\n        // @input EntityTag\r\n        // @description\r\n        // Sets the entity this fish hook is attached to.\r\n        // @tags\r\n        // <EntityTag.fish_hook_hooked_entity>\r\n        // -->\r\n        if (mechanism.matches(\"fish_hook_hooked_entity\") && mechanism.requireObject(EntityTag.class)) {\r\n            if (!(getBukkitEntity() instanceof FishHook)) {\r\n                mechanism.echoError(\"fish_hook_hooked_entity is only valid for FishHook entities.\");\r\n                return;\r\n            }\r\n            ((FishHook) getBukkitEntity()).setHookedEntity(mechanism.valueAsType(EntityTag.class).getBukkitEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fish_hook_min_lure_time\r\n        // @input DurationTag\r\n        // @description\r\n        // Returns the minimum possible time before this fish hook can lure a fish.\r\n        // @tags\r\n        // <EntityTag.fish_hook_min_lure_time>\r\n        // -->\r\n        if (mechanism.matches(\"fish_hook_min_lure_time\") && mechanism.requireObject(DurationTag.class)) {\r\n            if (!(getBukkitEntity() instanceof FishHook)) {\r\n                mechanism.echoError(\"fish_hook_min_lure_time is only valid for FishHook entities.\");\r\n                return;\r\n            }\r\n            ((FishHook) getBukkitEntity()).setMinWaitTime(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fish_hook_max_lure_time\r\n        // @input DurationTag\r\n        // @description\r\n        // Returns the maximum possible time before this fish hook will lure a fish.\r\n        // @tags\r\n        // <EntityTag.fish_hook_max_lure_time>\r\n        // -->\r\n        if (mechanism.matches(\"fish_hook_max_lure_time\") && mechanism.requireObject(DurationTag.class)) {\r\n            if (!(getBukkitEntity() instanceof FishHook)) {\r\n                mechanism.echoError(\"fish_hook_max_lure_time is only valid for FishHook entities.\");\r\n                return;\r\n            }\r\n            ((FishHook) getBukkitEntity()).setMaxWaitTime(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name redo_attack_cooldown\r\n        // @input None\r\n        // @description\r\n        // Forces the player to wait for the full attack cooldown duration for the item in their hand.\r\n        // NOTE: The clientside attack cooldown indicator will not reflect this change!\r\n        // @tags\r\n        // <EntityTag.attack_cooldown_duration>\r\n        // <EntityTag.attack_cooldown_max_duration>\r\n        // <EntityTag.attack_cooldown_percent>\r\n        // -->\r\n        if (mechanism.matches(\"redo_attack_cooldown\")) {\r\n            if (!(getLivingEntity() instanceof Player)) {\r\n                mechanism.echoError(\"Only player-type entities can have attack_cooldowns!\");\r\n                return;\r\n            }\r\n            NMSHandler.playerHelper.setAttackCooldown((Player) getLivingEntity(), 0);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name reset_attack_cooldown\r\n        // @input None\r\n        // @description\r\n        // Ends the player's attack cooldown.\r\n        // NOTE: This will do nothing if the player's attack speed attribute is set to 0.\r\n        // NOTE: The clientside attack cooldown indicator will not reflect this change!\r\n        // @tags\r\n        // <EntityTag.attack_cooldown_duration>\r\n        // <EntityTag.attack_cooldown_max_duration>\r\n        // <EntityTag.attack_cooldown_percent>\r\n        // -->\r\n        if (mechanism.matches(\"reset_attack_cooldown\")) {\r\n            if (!(getLivingEntity() instanceof Player)) {\r\n                mechanism.echoError(\"Only player-type entities can have attack_cooldowns!\");\r\n                return;\r\n            }\r\n            NMSHandler.playerHelper.setAttackCooldown((Player) getLivingEntity(), Math.round(NMSHandler.playerHelper.getMaxAttackCooldownTicks((Player) getLivingEntity())));\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name attack_cooldown_percent\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the progress of the player's attack cooldown. Takes a decimal from 0 to 1.\r\n        // 0 means the cooldown has just begun, while 1 means the cooldown has been completed.\r\n        // NOTE: The clientside attack cooldown indicator will not reflect this change!\r\n        // @tags\r\n        // <EntityTag.attack_cooldown_duration>\r\n        // <EntityTag.attack_cooldown_max_duration>\r\n        // <EntityTag.attack_cooldown_percent>\r\n        // -->\r\n        if (mechanism.matches(\"attack_cooldown_percent\") && mechanism.requireFloat()) {\r\n            if (!(getLivingEntity() instanceof Player)) {\r\n                mechanism.echoError(\"Only player-type entities can have attack_cooldowns!\");\r\n                return;\r\n            }\r\n            float percent = mechanism.getValue().asFloat();\r\n            if (percent >= 0 && percent <= 1) {\r\n                NMSHandler.playerHelper.setAttackCooldown((Player) getLivingEntity(), Math.round(NMSHandler.playerHelper.getMaxAttackCooldownTicks((Player) getLivingEntity()) * mechanism.getValue().asFloat()));\r\n            }\r\n            else {\r\n                Debug.echoError(\"Invalid percentage! \\\"\" + percent + \"\\\" is not between 0 and 1!\");\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name attack_cooldown\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the player's time since their last attack. If the time is greater than the max duration of their\r\n        // attack cooldown, then the cooldown is considered finished.\r\n        // NOTE: The clientside attack cooldown indicator will not reflect this change!\r\n        // @tags\r\n        // <EntityTag.attack_cooldown_duration>\r\n        // <EntityTag.attack_cooldown_max_duration>\r\n        // <EntityTag.attack_cooldown_percent>\r\n        // -->\r\n        if (mechanism.matches(\"attack_cooldown\") && mechanism.requireObject(DurationTag.class)) {\r\n            if (!(getLivingEntity() instanceof Player)) {\r\n                mechanism.echoError(\"Only player-type entities can have attack_cooldowns!\");\r\n                return;\r\n            }\r\n            NMSHandler.playerHelper.setAttackCooldown((Player) getLivingEntity(), mechanism.getValue().asType(DurationTag.class, mechanism.context).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fallingblock_type\r\n        // @input MaterialTag\r\n        // @description\r\n        // Sets the block type of a falling_block entity (only valid while spawning).\r\n        // @tags\r\n        // <EntityTag.fallingblock_material>\r\n        // -->\r\n        if (mechanism.matches(\"fallingblock_type\") && mechanism.requireObject(MaterialTag.class)) {\r\n            if (!(getBukkitEntity() instanceof FallingBlock)) {\r\n                mechanism.echoError(\"'fallingblock_type' is only valid for Falling Block entities.\");\r\n                return;\r\n            }\r\n            NMSHandler.entityHelper.setFallingBlockType((FallingBlock) getBukkitEntity(), mechanism.valueAsType(MaterialTag.class).getModernData());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name tracking_range\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the range (in blocks) that an entity can be seen at. This is equivalent to the \"entity-tracking-range\" value in \"Spigot.yml\".\r\n        // -->\r\n        if (mechanism.matches(\"tracking_range\") && mechanism.requireInteger()) {\r\n            NMSHandler.entityHelper.setTrackingRange(getBukkitEntity(), mechanism.getValue().asInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fake_pickup\r\n        // @input EntityTag\r\n        // @description\r\n        // Makes it look like this entity (usually a player) has picked up another entity (item, arrow, or XP orb).\r\n        // This technically also works with any entity type.\r\n        // Note that the original entity doesn't actually get picked up, it's still there, just invisible now.\r\n        // -->\r\n        if (mechanism.matches(\"fake_pickup\") && mechanism.requireObject(EntityTag.class)) {\r\n            Entity ent = mechanism.valueAsType(EntityTag.class).getBukkitEntity();\r\n            int amount = 1;\r\n            if (ent instanceof Item) {\r\n                amount = ((Item) ent).getItemStack().getAmount();\r\n            }\r\n            for (Player player : NMSHandler.entityHelper.getPlayersThatSee(getBukkitEntity())) {\r\n                NMSHandler.packetHelper.sendCollectItemEntity(player, getBukkitEntity(), ent, amount);\r\n            }\r\n            if (isPlayer()) {\r\n                NMSHandler.packetHelper.sendCollectItemEntity((Player) getBukkitEntity(), getBukkitEntity(), ent, amount);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name loot_table_id\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the loot table of a lootable entity.\r\n        // This is the namespaced path of the loot table, provided by a datapack or Minecraft's default data.\r\n        // @tags\r\n        // <EntityTag.loot_table_id>\r\n        // @Example\r\n        // # Sets the nearest zombie's loot table to a phantom's\r\n        // - adjust <player.location.find_entities[zombie].within[5].first> loot_table_id:entities/phantom\r\n        // -->\r\n        if (mechanism.matches(\"loot_table_id\")) {\r\n            if (!(getBukkitEntity() instanceof Lootable)) {\r\n                mechanism.echoError(\"'loot_table_id' is only valid for lootable entities.\");\r\n                return;\r\n            }\r\n            LootTable table = Bukkit.getLootTable(Utilities.parseNamespacedKey(mechanism.getValue().asString()));\r\n            if (table == null) {\r\n                mechanism.echoError(\"Invalid loot table ID.\");\r\n                return;\r\n            }\r\n            ((Lootable) getBukkitEntity()).setLootTable(table);\r\n        }\r\n\r\n        tagProcessor.processMechanism(this, mechanism);\r\n    }\r\n\r\n    public static HashSet<String> specialEntityMatchables = new HashSet<>(Arrays.asList(\"entity\", \"npc\", \"player\", \"living\", \"vehicle\", \"fish\", \"projectile\", \"hanging\", \"monster\", \"mob\", \"animal\"));\r\n\r\n    public final boolean trySpecialEntityMatcher(String text, boolean isNPC) {\r\n        if (isNPC) {\r\n            return text.equals(\"entity\") || text.equals(\"npc\");\r\n        }\r\n        switch (text) {\r\n            case \"entity\":\r\n                return true;\r\n            case \"npc\":\r\n                return isCitizensNPC();\r\n            case \"player\":\r\n                return isPlayer();\r\n            case \"living\":\r\n                return isLivingEntityType();\r\n            case \"vehicle\":\r\n                return getBukkitEntity() instanceof Vehicle;\r\n            case \"fish\":\r\n                return getBukkitEntity() instanceof Fish;\r\n            case \"projectile\":\r\n                return getBukkitEntity() instanceof Projectile;\r\n            case \"hanging\":\r\n                return getBukkitEntity() instanceof Hanging;\r\n            case \"monster\":\r\n                return isMonsterType();\r\n            case \"mob\":\r\n                return isMobType();\r\n            case \"animal\":\r\n                return isAnimalType();\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public final boolean tryExactMatcher(String text) {\r\n        if (specialEntityMatchables.contains(text)) {\r\n            return trySpecialEntityMatcher(text, isCitizensNPC());\r\n        }\r\n        if (text.startsWith(\"npc_\") && !text.startsWith(\"npc_flagged\")) {\r\n            String check = text.substring(\"npc_\".length());\r\n            if (specialEntityMatchables.contains(check)) {\r\n                if (check.equals(\"player\")) { // Special case\r\n                    return npc.getEntityType() == EntityType.PLAYER;\r\n                }\r\n                return trySpecialEntityMatcher(check, false);\r\n            }\r\n            return check.equals(CoreUtilities.toLowerCase(npc.getEntityType().name()));\r\n        }\r\n        if (text.contains(\":\")) {\r\n            if (text.startsWith(\"entity_flagged:\")) {\r\n                return ScriptEvent.coreFlaggedCheck(text.substring(\"entity_flagged:\".length()), getFlagTracker());\r\n            }\r\n            else if (text.startsWith(\"player_flagged:\")) {\r\n                return isPlayer() && ScriptEvent.coreFlaggedCheck(text.substring(\"player_flagged:\".length()), getFlagTracker());\r\n            }\r\n            else if (text.startsWith(\"npc_flagged:\")) {\r\n                return isCitizensNPC() && ScriptEvent.coreFlaggedCheck(text.substring(\"npc_flagged:\".length()), getFlagTracker());\r\n            }\r\n            else if (text.startsWith(\"vanilla_tagged:\")) {\r\n                String tagCheck = text.substring(\"vanilla_tagged:\".length());\r\n                HashSet<String> tags = VanillaTagHelper.tagsByEntity.get(getBukkitEntityType());\r\n                if (tags == null) {\r\n                    return false;\r\n                }\r\n                ScriptEvent.MatchHelper matcher = ScriptEvent.createMatcher(tagCheck);\r\n                for (String tag : tags) {\r\n                    if (matcher.doesMatch(tag)) {\r\n                        return true;\r\n                    }\r\n                }\r\n                return false;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean advancedMatches(String text, TagContext context) {\r\n        ScriptEvent.MatchHelper matcher = ScriptEvent.createMatcher(text);\r\n        if (isCitizensNPC()) {\r\n            return matcher.doesMatch(\"npc\", this::tryExactMatcher);\r\n        }\r\n        if (getEntityScript() != null && matcher.doesMatch(getEntityScript(), this::tryExactMatcher)) {\r\n            return true;\r\n        }\r\n        if (matcher.doesMatch(getEntityType().getLowercaseName(), this::tryExactMatcher)) {\r\n            return true;\r\n        }\r\n        if (Settings.cache_legacySpigotNamesSupport && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            String updatedType = NMSHandler.instance.updateLegacyName(EntityType.class, text);\r\n            if (!CoreUtilities.equalsIgnoreCase(text, updatedType)) {\r\n                BukkitImplDeprecations.oldSpigotNames.warn(context);\r\n                return getEntityType().getName().equals(updatedType);\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/InventoryTag.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.scripts.containers.core.InventoryScriptContainer;\r\nimport com.denizenscript.denizen.scripts.containers.core.InventoryScriptHelper;\r\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryTrackerSystem;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryViewUtil;\r\nimport com.denizenscript.denizen.utilities.inventory.RecipeHelper;\r\nimport com.denizenscript.denizen.utilities.inventory.SlotHelper;\r\nimport com.denizenscript.denizen.utilities.nbt.CustomNBT;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.flags.SavableMapFlagTracker;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.objects.notable.Notable;\r\nimport com.denizenscript.denizencore.objects.notable.Note;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Keyed;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.block.DoubleChest;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.inventory.InventoryType;\r\nimport org.bukkit.inventory.*;\r\nimport org.bukkit.inventory.meta.BookMeta;\r\nimport org.bukkit.inventory.meta.ItemMeta;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\nimport java.util.List;\r\n\r\npublic class InventoryTag implements ObjectTag, Notable, Adjustable, FlaggableObject {\r\n\r\n    // <--[ObjectType]\r\n    // @name InventoryTag\r\n    // @prefix in\r\n    // @base ElementTag\r\n    // @implements FlaggableObject, PropertyHolderObject\r\n    // @ExampleTagBase player.inventory\r\n    // @ExampleValues <player.inventory>\r\n    // @ExampleForReturns\r\n    // - note %VALUE% as:my_new_inventory\r\n    // @ExampleForReturns\r\n    // - inventory set o:%VALUE% d:stick slot:5\r\n    // @format\r\n    // The identity format for inventories is the classification type of inventory to use. All other data is specified through properties.\r\n    //\r\n    // @description\r\n    // An InventoryTag represents an inventory, generically or attached to some in-the-world object.\r\n    //\r\n    // Inventories can be generically designed using inventory script containers,\r\n    // and can be modified using the inventory command.\r\n    //\r\n    // Valid inventory type classifications:\r\n    // \"npc\", \"player\", \"crafting\", \"enderchest\", \"workbench\", \"entity\", \"location\", \"generic\"\r\n    //\r\n    // This object type can be noted.\r\n    //\r\n    // This object type is flaggable when it is noted.\r\n    // Flags on this object type will be stored in the notables.yml file.\r\n    //\r\n    // @Matchable\r\n    // InventoryTag matchers, sometimes identified as \"<inventory>\":\r\n    // \"inventory\" plaintext: always matches.\r\n    // \"note\" plaintext: matches if the inventory is noted.\r\n    // Inventory script name: matches if the inventory comes from an inventory script of the given name, using advanced matchers.\r\n    // Inventory note name: matches if the inventory is noted with the given name, using advanced matchers.\r\n    // Inventory type: matches if the inventory is of a given type, using advanced matchers.\r\n    // \"inventory_flagged:<flag>\": a Flag Matchable for InventoryTag flags.\r\n    // \"gui\" plaintext: matches if the inventory is a GUI (see <@link language inventory script containers>).\r\n    //\r\n    // -->\r\n\r\n    public static void trackTemporaryInventory(InventoryTag tagForm) {\r\n        if (tagForm == null) {\r\n            return;\r\n        }\r\n        InventoryTrackerSystem.trackTemporaryInventory(tagForm.inventory, tagForm);\r\n    }\r\n\r\n    public static void setupInventoryTracker() {\r\n        InventoryTrackerSystem.setup();\r\n    }\r\n\r\n    public static InventoryTag mirrorBukkitInventory(Inventory inventory) {\r\n        if (inventory == null) {\r\n            return null;\r\n        }\r\n        InventoryTag result = InventoryTrackerSystem.getTagFormFor(inventory);\r\n        if (result != null) {\r\n            return result;\r\n        }\r\n        // Use the map to get noted inventories\r\n        result = InventoryScriptHelper.notedInventories.get(inventory);\r\n        if (result != null) {\r\n            return result;\r\n        }\r\n        // Iterate through offline player inventories\r\n        for (ImprovedOfflinePlayer player : ImprovedOfflinePlayer.offlinePlayers.values()) {\r\n            if (player.inventory != null && player.inventory.equals(inventory)) {\r\n                return new InventoryTag(player);\r\n            }\r\n            if (player.enderchest != null && player.enderchest.equals(inventory)) {\r\n                return new InventoryTag(player, true);\r\n            }\r\n        }\r\n        return new InventoryTag(inventory);\r\n    }\r\n\r\n    /////////////////////\r\n    //   STATIC FIELDS\r\n    /////////////////\r\n\r\n    // The maximum number of slots a Bukkit inventory can have\r\n    public final static int maxSlots = 54;\r\n\r\n    // All of the inventory id types we use\r\n    public final static String[] idTypes = {\"npc\", \"player\", \"crafting\", \"enderchest\", \"workbench\", \"entity\", \"location\", \"generic\"};\r\n\r\n    /////////////////////\r\n    //   NOTABLE METHODS\r\n    /////////////////\r\n\r\n    @Override\r\n    public boolean isUnique() {\r\n        return noteName != null;\r\n    }\r\n\r\n    public boolean isSaving = false;\r\n\r\n    public AbstractFlagTracker flagTracker = null;\r\n\r\n    @Note(\"Inventories\")\r\n    public Object getSaveObject() {\r\n        isSaving = true;\r\n        try {\r\n            YamlConfiguration section = new YamlConfiguration();\r\n            section.set(\"object\", \"in@\" + idType + PropertyParser.getPropertiesString(this));\r\n            section.set(\"flags\", flagTracker.toString());\r\n            return section;\r\n        }\r\n        finally {\r\n            isSaving = false;\r\n        }\r\n    }\r\n\r\n    public void setInventory(Inventory inventory) {\r\n        if (isUnique()) {\r\n            InventoryScriptHelper.notedInventories.remove(this.inventory);\r\n            InventoryScriptHelper.notedInventories.put(inventory, this);\r\n        }\r\n        this.inventory = inventory;\r\n    }\r\n\r\n    public String noteName = null, priorNoteName = null;\r\n\r\n    public void makeUnique(String id) {\r\n        InventoryTag toNote = new InventoryTag(inventory, idType, idHolder);\r\n        toNote.uniquifier = null;\r\n        String title = PaperAPITools.instance.getTitle(inventory);\r\n        if (title == null || title.startsWith(\"container.\")) {\r\n            title = toNote.inventory.getType().getDefaultTitle();\r\n        }\r\n        ItemStack[] contents = toNote.inventory.getContents();\r\n        if (getInventoryType() == InventoryType.CHEST) {\r\n            toNote.inventory = PaperAPITools.instance.createInventory(null, toNote.inventory.getSize(), title);\r\n        }\r\n        else {\r\n            toNote.inventory = PaperAPITools.instance.createInventory(null, toNote.inventory.getType(), title);\r\n        }\r\n        toNote.inventory.setContents(contents);\r\n        InventoryScriptHelper.notedInventories.put(toNote.inventory, toNote);\r\n        if (!idType.equals(\"generic\") && !idType.equals(\"script\")) {\r\n            toNote.idType = \"generic\";\r\n            toNote.idHolder = new ElementTag(CoreUtilities.toLowerCase(getInventoryType().name()));\r\n        }\r\n        toNote.flagTracker = new SavableMapFlagTracker();\r\n        NoteManager.saveAs(toNote, id);\r\n        toNote.noteName = id;\r\n    }\r\n\r\n    @Override\r\n    public void forget() {\r\n        if (noteName == null) {\r\n            return;\r\n        }\r\n        priorNoteName = noteName;\r\n        NoteManager.remove(this);\r\n        InventoryScriptHelper.notedInventories.remove(inventory);\r\n        flagTracker = null;\r\n        noteName = null;\r\n    }\r\n\r\n    @Override\r\n    public InventoryTag refreshState() {\r\n        if (noteName == null && priorNoteName != null) {\r\n            Notable note = NoteManager.getSavedObject(priorNoteName);\r\n            if (note instanceof InventoryTag) {\r\n                return (InventoryTag) note;\r\n            }\r\n            priorNoteName = null;\r\n        }\r\n        trackTemporaryInventory(this);\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public AbstractFlagTracker getFlagTracker() {\r\n        return flagTracker;\r\n    }\r\n\r\n    @Override\r\n    public void reapplyTracker(AbstractFlagTracker tracker) {\r\n        if (noteName != null) {\r\n            this.flagTracker = tracker;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getReasonNotFlaggable() {\r\n        if (noteName == null) {\r\n            return \"the inventory is not noted - only noted inventories can hold flags\";\r\n        }\r\n        return \"unknown reason - something went wrong\";\r\n    }\r\n\r\n    public static InventoryTag valueOf(String string, PlayerTag player, NPCTag npc, boolean silent) {\r\n        return valueOf(string, new BukkitTagContext(player, npc, null, !silent, null));\r\n    }\r\n\r\n    public static InventoryTag valueOf(String string, PlayerTag player, NPCTag npc) {\r\n        return valueOf(string, player, npc, false);\r\n    }\r\n\r\n    public static InventoryTag internalGetInventoryFor(TagContext context, List<String> properties) {\r\n        String typeName = properties.get(0);\r\n        String holder = null;\r\n        int size = -1;\r\n        for (String property : properties) {\r\n            if (property.startsWith(\"holder=\")) {\r\n                holder = ObjectFetcher.unescapeProperty(property.substring(\"holder=\".length()));\r\n            }\r\n            else if (property.startsWith(\"script_name=\")) {\r\n                holder = ObjectFetcher.unescapeProperty(property.substring(\"script_name=\".length()));\r\n                typeName = \"script\";\r\n            }\r\n            else if (property.startsWith(\"uniquifier=\")) {\r\n                String idText = property.substring(\"uniquifier=\".length());\r\n                if (!ArgumentHelper.matchesInteger(idText)) {\r\n                    return null;\r\n                }\r\n                long id = Long.parseLong(idText);\r\n                InventoryTag fixedResult = InventoryTrackerSystem.idTrackedInventories.get(id);\r\n                if (fixedResult != null) {\r\n                    trackTemporaryInventory(fixedResult);\r\n                    return fixedResult;\r\n                }\r\n            }\r\n            else if (property.startsWith(\"size=\")) {\r\n                String sizeText = property.substring(\"size=\".length());\r\n                if (!ArgumentHelper.matchesInteger(sizeText)) {\r\n                    return null;\r\n                }\r\n                size = Integer.parseInt(sizeText);\r\n            }\r\n        }\r\n        if (holder != null) {\r\n            switch (typeName) {\r\n                case \"player\":\r\n                case \"enderchest\":\r\n                case \"workbench\":\r\n                case \"crafting\":\r\n                    PlayerTag player = PlayerTag.valueOf(holder, context);\r\n                    if (player == null) {\r\n                        if (context == null || context.showErrors()) {\r\n                            Debug.echoError(\"Invalid inventory player '\" + holder + \"'\");\r\n                        }\r\n                        return null;\r\n                    }\r\n                    switch (typeName) {\r\n                        case \"player\":\r\n                            return player.getInventory();\r\n                        case \"enderchest\":\r\n                            return player.getEnderChest();\r\n                        case \"workbench\":\r\n                            return player.getWorkbench();\r\n                        case \"crafting\":\r\n                            Inventory opened = InventoryViewUtil.getTopInventory(player.getPlayerEntity().getOpenInventory());\r\n                            if (opened instanceof CraftingInventory) {\r\n                                return new InventoryTag(opened, player.getPlayerEntity());\r\n                            }\r\n                            else {\r\n                                return player.getInventory();\r\n                            }\r\n                    }\r\n                    break;\r\n                case \"npc\":\r\n                    NPCTag npc = NPCTag.valueOf(holder, context);\r\n                    if (npc == null) {\r\n                        if (context == null || context.showErrors()) {\r\n                            Debug.echoError(\"Invalid inventory npc '\" + holder + \"'\");\r\n                        }\r\n                        return null;\r\n                    }\r\n                    return npc.getDenizenInventory();\r\n                case \"entity\":\r\n                    EntityTag entity = EntityTag.valueOf(holder, context);\r\n                    if (entity == null) {\r\n                        if (context == null || context.showErrors()) {\r\n                            Debug.echoError(\"Invalid inventory entity '\" + holder + \"'\");\r\n                        }\r\n                        return null;\r\n                    }\r\n                    return entity.getInventory();\r\n                case \"location\":\r\n                    LocationTag location = LocationTag.valueOf(holder, context);\r\n                    if (location == null) {\r\n                        if (context == null || context.showErrors()) {\r\n                            Debug.echoError(\"Invalid inventory location '\" + holder + \"'\");\r\n                        }\r\n                        return null;\r\n                    }\r\n                    return location.getInventory();\r\n            }\r\n        }\r\n        InventoryTag result = null;\r\n        if (typeName.equals(\"generic\")) {\r\n            if (holder != null && !new ElementTag(holder).matchesEnum(InventoryType.class)) {\r\n                if (context == null || context.showErrors()) {\r\n                    Debug.echoError(\"Unknown inventory type '\" + holder + \"'\");\r\n                }\r\n                return null;\r\n            }\r\n            InventoryType type = holder == null ? InventoryType.CHEST : InventoryType.valueOf(holder.toUpperCase());\r\n            if (size == -1 || type != InventoryType.CHEST) {\r\n                result = new InventoryTag(type);\r\n            }\r\n            else {\r\n                result = new InventoryTag(size);\r\n            }\r\n        }\r\n        else if (typeName.equals(\"script\") && holder != null) {\r\n            ScriptTag script = ScriptTag.valueOf(holder, context);\r\n            if (script == null || !(script.getContainer() instanceof InventoryScriptContainer)) {\r\n                if (context == null || context.showErrors()) {\r\n                    Debug.echoError(\"Unknown inventory script '\" + holder + \"'\");\r\n                }\r\n                return null;\r\n            }\r\n            result = ((InventoryScriptContainer) script.getContainer()).getInventoryFrom(context);\r\n            if (size != -1) {\r\n                result.setSize(size);\r\n            }\r\n        }\r\n        if (result == null && holder != null) {\r\n            ScriptTag script = ScriptTag.valueOf(holder, context);\r\n            if (script != null && (script.getContainer() instanceof InventoryScriptContainer)) {\r\n                result = ((InventoryScriptContainer) script.getContainer()).getInventoryFrom(context);\r\n            }\r\n        }\r\n        if (result == null && new ElementTag(typeName).matchesEnum(InventoryType.class)) {\r\n            InventoryType type = InventoryType.valueOf(typeName.toUpperCase());\r\n            if (size == -1 || type != InventoryType.CHEST) {\r\n                result = new InventoryTag(type);\r\n            }\r\n            else {\r\n                result = new InventoryTag(size);\r\n            }\r\n        }\r\n        if (result == null) {\r\n            if (context == null || context.showErrors()) {\r\n                Debug.echoError(\"Unknown inventory idType '\" + typeName + \"'\");\r\n            }\r\n            return null;\r\n        }\r\n        internalApplyPropertySet(result, context, properties);\r\n        return result;\r\n    }\r\n\r\n    public static void internalApplyPropertySet(InventoryTag result, TagContext context, List<String> properties) {\r\n        for (int i = 1; i < properties.size(); i++) {\r\n            List<String> data = CoreUtilities.split(properties.get(i), '=', 2);\r\n            if (data.size() != 2) {\r\n                if (context == null || context.showErrors()) {\r\n                    Debug.echoError(\"Invalid property string '\" + properties.get(i) + \"'!\");\r\n                }\r\n                continue;\r\n            }\r\n            String name = CoreUtilities.toLowerCase(data.get(0));\r\n            if (!name.equals(\"holder\") && !name.equals(\"uniquifier\") && !name.equals(\"size\") && !name.equals(\"script_name\")) {\r\n                String description = ObjectFetcher.unescapeProperty(data.get(1));\r\n                Mechanism mechanism = new Mechanism(name, ObjectFetcher.pickObjectFor(description, context), context);\r\n                result.safeAdjust(mechanism);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Fetchable(\"in\")\r\n    public static InventoryTag valueOf(String string, TagContext context) {\r\n        if (string == null) {\r\n            return null;\r\n        }\r\n        if (string.startsWith(\"in@\")) {\r\n            string = string.substring(\"in@\".length());\r\n        }\r\n        List<String> properties = ObjectFetcher.separateProperties(string);\r\n        if (properties != null && properties.size() > 1) {\r\n            InventoryTag result = internalGetInventoryFor(context, properties);\r\n            if (result == null) {\r\n                if (context == null || context.showErrors()) {\r\n                    Debug.echoError(\"Value of InventoryTag returning null. Invalid InventoryTag-with-properties specified: \" + string);\r\n                }\r\n                return null;\r\n            }\r\n            trackTemporaryInventory(result);\r\n            return result;\r\n        }\r\n        Notable noted = NoteManager.getSavedObject(string);\r\n        if (noted instanceof InventoryTag) {\r\n            return (InventoryTag) noted;\r\n        }\r\n        if (ScriptRegistry.containsScript(string, InventoryScriptContainer.class)) {\r\n            return ScriptRegistry.getScriptContainerAs(string, InventoryScriptContainer.class).getInventoryFrom(context);\r\n        }\r\n        if (new ElementTag(string).matchesEnum(InventoryType.class)) {\r\n            InventoryType type = InventoryType.valueOf(string.toUpperCase());\r\n            return new InventoryTag(type);\r\n        }\r\n        if (context == null || context.showErrors()) {\r\n            Debug.echoError(\"Value of InventoryTag returning null. Invalid InventoryTag specified: \" + string);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static boolean matches(String arg) {\r\n        if (CoreUtilities.toLowerCase(arg).startsWith(\"in@\")) {\r\n            return true;\r\n        }\r\n        String tid = arg;\r\n        if (arg.contains(\"[\")) {\r\n            tid = arg.substring(0, arg.indexOf('['));\r\n        }\r\n        if (new ElementTag(tid).matchesEnum(InventoryType.class)) {\r\n            return true;\r\n        }\r\n        if (ScriptRegistry.containsScript(tid, InventoryScriptContainer.class)) {\r\n            return true;\r\n        }\r\n        if (NoteManager.getSavedObject(tid) instanceof InventoryTag) {\r\n            return true;\r\n        }\r\n        for (String idType : idTypes) {\r\n            if (tid.equalsIgnoreCase(idType)) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean isGeneric() {\r\n        return idType.equals(\"generic\") || idType.equals(\"script\") && !isUnique();\r\n    }\r\n\r\n    public static final ElementTag defaultIdHolder = new ElementTag(\"unknown\");\r\n\r\n    public String idType = null;\r\n\r\n    public ObjectTag idHolder = defaultIdHolder;\r\n\r\n    public Long uniquifier = null;\r\n\r\n    public Inventory inventory = null;\r\n\r\n    public String customTitle = null;\r\n\r\n    public String prefix = \"Inventory\";\r\n\r\n    private InventoryTag(Inventory inventory) {\r\n        this.inventory = inventory;\r\n        loadIdentifiers(inventory.getHolder());\r\n    }\r\n\r\n    public InventoryTag(Inventory inventory, InventoryHolder holder) {\r\n        this.inventory = inventory;\r\n        loadIdentifiers(holder);\r\n    }\r\n\r\n    public InventoryTag(Inventory inventory, String type, ObjectTag holder) {\r\n        this.inventory = inventory;\r\n        this.idType = type;\r\n        this.idHolder = holder;\r\n    }\r\n\r\n    public InventoryTag(ItemStack[] items) {\r\n        inventory = Bukkit.getServer().createInventory(null, (int) Math.ceil(items.length / 9.0) * 9);\r\n        idType = \"generic\";\r\n        idHolder = new ElementTag(\"chest\");\r\n        setContents(items);\r\n    }\r\n\r\n    public InventoryTag(ImprovedOfflinePlayer offlinePlayer) {\r\n        this(offlinePlayer, false);\r\n    }\r\n\r\n    public InventoryTag(ImprovedOfflinePlayer offlinePlayer, boolean isEnderChest) {\r\n        inventory = isEnderChest ? offlinePlayer.getEnderChest() : offlinePlayer.getInventory();\r\n        idType = isEnderChest ? \"enderchest\" : \"player\";\r\n        idHolder = new PlayerTag(offlinePlayer.getUniqueId());\r\n    }\r\n\r\n    public InventoryTag(int size, String title) {\r\n        if (size <= 0 || size % 9 != 0) {\r\n            Debug.echoError(\"InventorySize must be multiple of 9, and greater than 0.\");\r\n            return;\r\n        }\r\n        inventory = PaperAPITools.instance.createInventory(null, size, title);\r\n        idType = \"generic\";\r\n        idHolder = new ElementTag(\"chest\");\r\n    }\r\n\r\n    public InventoryTag(InventoryType type) {\r\n        inventory = Bukkit.getServer().createInventory(null, type);\r\n        idType = \"generic\";\r\n        idHolder = new ElementTag(CoreUtilities.toLowerCase(type.name()));\r\n    }\r\n\r\n    public InventoryTag(InventoryType type, String title) {\r\n        inventory = PaperAPITools.instance.createInventory(null, type, title);\r\n        idType = \"generic\";\r\n        idHolder = new ElementTag(CoreUtilities.toLowerCase(type.name()));\r\n    }\r\n\r\n    public InventoryTag(int size) {\r\n        this(size, \"Chest\");\r\n    }\r\n\r\n    public Inventory getInventory() {\r\n        return inventory;\r\n    }\r\n\r\n    public boolean containsItem(ItemTag item, int amount) {\r\n        if (item == null) {\r\n            return false;\r\n        }\r\n        item = new ItemTag(item.getItemStack().clone());\r\n        item.setAmount(1);\r\n        String myItem = CoreUtilities.toLowerCase(item.identify());\r\n        for (int i = 0; i < inventory.getSize(); i++) {\r\n            ItemStack is = inventory.getItem(i);\r\n            if (is == null || item.getMaterial().getMaterial() != is.getType()) {\r\n                continue;\r\n            }\r\n            is = is.clone();\r\n            int count = is.getAmount();\r\n            is.setAmount(1);\r\n            String newItem = CoreUtilities.toLowerCase(new ItemTag(is).identify());\r\n            if (myItem.equals(newItem)) {\r\n                if (count <= amount) {\r\n                    amount -= count;\r\n                    if (amount == 0) {\r\n                        return true;\r\n                    }\r\n                }\r\n                else if (count > amount) {\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void setSize(int size) {\r\n        if (!getIdType().equals(\"generic\") && !getIdType().equals(\"script\")) {\r\n            return;\r\n        }\r\n        else if (size <= 0 || size % 9 != 0) {\r\n            Debug.echoError(\"InventorySize must be multiple of 9, and greater than 0.\");\r\n            return;\r\n        }\r\n        else if (inventory == null) {\r\n            uniquifier = null;\r\n            inventory = Bukkit.getServer().createInventory(null, size, \"Chest\");\r\n            trackTemporaryInventory(this);\r\n            return;\r\n        }\r\n        int oldSize = inventory.getSize();\r\n        if (oldSize == size) {\r\n            return;\r\n        }\r\n        uniquifier = null;\r\n        ItemStack[] oldContents = inventory.getContents();\r\n        ItemStack[] newContents = new ItemStack[size];\r\n        if (oldSize > size) {\r\n            // TODO: Why is this a manual copy?\r\n            System.arraycopy(oldContents, 0, newContents, 0, size);\r\n        }\r\n        else {\r\n            newContents = oldContents;\r\n        }\r\n        String title = PaperAPITools.instance.getTitle(inventory);\r\n        if (title == null) {\r\n            setInventory(Bukkit.getServer().createInventory(null, size));\r\n        }\r\n        else {\r\n            setInventory(PaperAPITools.instance.createInventory(null, size, title));\r\n        }\r\n        inventory.setContents(newContents);\r\n        trackTemporaryInventory(this);\r\n    }\r\n\r\n    private void loadIdentifiers(final InventoryHolder holder) {\r\n        if (inventory == null) {\r\n            return;\r\n        }\r\n        InventoryTag realInv = InventoryTrackerSystem.getTagFormFor(inventory);\r\n        if (realInv != null) {\r\n            Debug.echoError(\"Tried to load already-tracked inventory as new inventory?\");\r\n            return;\r\n        }\r\n        trackTemporaryInventory(this);\r\n        if (holder != null) {\r\n            if (holder instanceof NPCTag) {\r\n                idType = \"npc\";\r\n                idHolder = ((NPCTag) holder);\r\n                return;\r\n            }\r\n            else if (holder instanceof Player) {\r\n                if (Depends.citizens != null && CitizensAPI.getNPCRegistry().isNPC((Player) holder)) {\r\n                    idType = \"npc\";\r\n                    idHolder = (NPCTag.fromEntity((Player) holder));\r\n                    return;\r\n                }\r\n                if (inventory.getType() == InventoryType.CRAFTING) {\r\n                    idType = \"crafting\";\r\n                }\r\n                if (inventory.getType() == InventoryType.ENDER_CHEST) {\r\n                    idType = \"enderchest\";\r\n                }\r\n                else if (inventory.getType() == InventoryType.WORKBENCH) {\r\n                    idType = \"workbench\";\r\n                }\r\n                else {\r\n                    idType = \"player\";\r\n                }\r\n                idHolder = new PlayerTag((Player) holder);\r\n                return;\r\n            }\r\n            else if (holder instanceof Entity) {\r\n                idType = \"entity\";\r\n                idHolder = new EntityTag((Entity) holder);\r\n                return;\r\n            }\r\n            else {\r\n                idType = \"location\";\r\n                idHolder = getLocation(holder);\r\n                if (idHolder != null) {\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n        else if (inventory instanceof AnvilInventory && inventory.getLocation() != null) {\r\n            idType = \"location\";\r\n            idHolder = new LocationTag(inventory.getLocation());\r\n        }\r\n        else if (getIdType().equals(\"player\")) {\r\n            if (idHolder instanceof PlayerTag) {\r\n                return;\r\n            }\r\n            for (ImprovedOfflinePlayer player : ImprovedOfflinePlayer.offlinePlayers.values()) { // TODO: Less weird lookup?\r\n                if (player.inventory != null && player.inventory.equals(inventory)) {\r\n                    idHolder = new PlayerTag(player.player);\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n        else if (getIdType().equals(\"enderchest\")) {\r\n            if (idHolder instanceof PlayerTag) {\r\n                return;\r\n            }\r\n            // Iterate through offline player enderchests\r\n            for (ImprovedOfflinePlayer player : ImprovedOfflinePlayer.offlinePlayers.values()) { // TODO: Less weird lookup?\r\n                if (player.enderchest != null && player.enderchest.equals(inventory)) {\r\n                    idHolder = new PlayerTag(player.player);\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n        else if (getIdType().equals(\"script\")) {\r\n            if (idHolder instanceof ScriptTag) {\r\n                return;\r\n            }\r\n            InventoryTag tracked = InventoryTrackerSystem.retainedInventoryLinks.get(inventory);\r\n            if (tracked != null) {\r\n                idHolder = tracked.idHolder;\r\n                return;\r\n            }\r\n        }\r\n        idType = \"generic\";\r\n        idHolder = new ElementTag(CoreUtilities.toLowerCase(getInventory().getType().name()));\r\n    }\r\n\r\n    public String getIdType() {\r\n        return idType == null ? \"\" : idType;\r\n    }\r\n\r\n    public ObjectTag getIdHolder() {\r\n        return idHolder;\r\n    }\r\n\r\n    public LocationTag getLocation() {\r\n        return getLocation(inventory.getHolder());\r\n    }\r\n\r\n    public LocationTag getLocation(InventoryHolder holder) {\r\n        if (inventory != null && holder != null) {\r\n            if (holder instanceof BlockState) {\r\n                return new LocationTag(((BlockState) holder).getLocation());\r\n            }\r\n            else if (holder instanceof DoubleChest) {\r\n                return new LocationTag(((DoubleChest) holder).getLocation());\r\n            }\r\n            else if (holder instanceof Entity) {\r\n                return new LocationTag(((Entity) holder).getLocation());\r\n            }\r\n            else if (holder instanceof NPCTag) {\r\n                NPCTag npc = (NPCTag) holder;\r\n                if (npc.getLocation() == null) {\r\n                    return new LocationTag(((NPCTag) holder).getCitizen().getStoredLocation());\r\n                }\r\n                return npc.getLocation();\r\n            }\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    public ItemStack[] getContents() {\r\n        if (inventory != null) {\r\n            return inventory.getContents();\r\n        }\r\n        else {\r\n            return new ItemStack[0];\r\n        }\r\n    }\r\n\r\n    public ItemStack[] getStorageContents() {\r\n        if (inventory != null) {\r\n            return inventory.getStorageContents();\r\n        }\r\n        else {\r\n            return new ItemStack[0];\r\n        }\r\n    }\r\n\r\n    public static void addToMapIfNonAir(MapTag map, String name, ItemStack item) {\r\n        if (item == null || item.getType() == Material.AIR) {\r\n            return;\r\n        }\r\n        map.putObject(name, new ItemTag(item));\r\n    }\r\n\r\n    public MapTag getEquipmentMap() {\r\n        MapTag output = new MapTag();\r\n        if (inventory instanceof PlayerInventory) {\r\n            ItemStack[] equipment = ((PlayerInventory) inventory).getArmorContents();\r\n            addToMapIfNonAir(output, \"boots\", equipment[0]);\r\n            addToMapIfNonAir(output, \"leggings\", equipment[1]);\r\n            addToMapIfNonAir(output, \"chestplate\", equipment[2]);\r\n            addToMapIfNonAir(output, \"helmet\", equipment[3]);\r\n        }\r\n        else if (inventory instanceof HorseInventory) {\r\n            addToMapIfNonAir(output, \"saddle\", ((HorseInventory) inventory).getSaddle());\r\n            addToMapIfNonAir(output, \"armor\", ((HorseInventory) inventory).getArmor());\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n        return output;\r\n    }\r\n\r\n    public ListTag getEquipment() {\r\n        ItemStack[] equipment = null;\r\n        if (inventory instanceof PlayerInventory) {\r\n            equipment = ((PlayerInventory) inventory).getArmorContents();\r\n        }\r\n        else if (inventory instanceof HorseInventory) {\r\n            equipment = new ItemStack[] {((HorseInventory) inventory).getSaddle(), ((HorseInventory) inventory).getArmor()};\r\n        }\r\n        if (equipment == null) {\r\n            return null;\r\n        }\r\n        ListTag equipmentList = new ListTag();\r\n        for (ItemStack item : equipment) {\r\n            equipmentList.addObject(new ItemTag(item));\r\n        }\r\n        return equipmentList;\r\n    }\r\n\r\n    public InventoryType getInventoryType() {\r\n        return inventory.getType();\r\n    }\r\n\r\n    public int getSize() {\r\n        return inventory.getSize();\r\n    }\r\n\r\n    public void setContents(ItemStack[] contents) {\r\n        inventory.setContents(contents);\r\n    }\r\n\r\n    public void setContents(ListTag list, TagContext context) {\r\n        int size;\r\n        if (inventory == null) {\r\n            size = (int) Math.ceil(list.size() / 9.0) * 9;\r\n            if (size == 0) {\r\n                size = 9;\r\n            }\r\n            inventory = Bukkit.getServer().createInventory(null, size);\r\n            idType = \"generic\";\r\n            idHolder = new ElementTag(\"chest\");\r\n        }\r\n        else {\r\n            size = inventory.getSize();\r\n        }\r\n        ItemStack[] contents = new ItemStack[size];\r\n        int filled = 0;\r\n        for (ItemTag item : list.filter(ItemTag.class, context)) {\r\n            if (filled >= contents.length) {\r\n                Debug.echoError(\"Cannot set contents of inventory to \" + list.size() + \" items, as the inventory size is only \" + size + \"!\");\r\n                break;\r\n            }\r\n            contents[filled] = item.getItemStack();\r\n            filled++;\r\n        }\r\n        while (filled < size) {\r\n            contents[filled] = new ItemStack(Material.AIR);\r\n            filled++;\r\n        }\r\n        inventory.setContents(contents);\r\n        if (Depends.citizens != null && idHolder instanceof NPCTag) {\r\n            ((NPCTag) idHolder).getInventoryTrait().setContents(contents);\r\n        }\r\n    }\r\n\r\n    private boolean isSlotAllowed(String allowedSlots, int slot) {\r\n        if (allowedSlots == null) {\r\n            return true;\r\n        }\r\n        return SlotHelper.doesMatch(allowedSlots, idHolder instanceof EntityFormObject ? ((EntityFormObject) idHolder).getDenizenEntity().getBukkitEntity() : null, slot);\r\n    }\r\n\r\n    public int firstPartial(int startSlot, ItemStack item, String allowedSlots) {\r\n        ItemStack[] inventory = getContents();\r\n        if (item == null) {\r\n            return -1;\r\n        }\r\n        for (int i = startSlot; i < inventory.length; i++) {\r\n            ItemStack item1 = inventory[i];\r\n            if (item1 != null && item1.getAmount() < item.getMaxStackSize() && item1.isSimilar(item) && isSlotAllowed(allowedSlots, i)) {\r\n                return i;\r\n            }\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    public int firstEmpty(int startSlot, String allowedSlots) {\r\n        ItemStack[] inventory = getStorageContents();\r\n        for (int i = startSlot; i < inventory.length; i++) {\r\n            if (inventory[i] == null && isSlotAllowed(allowedSlots, i)) {\r\n                return i;\r\n            }\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    // Somewhat simplified version of CraftBukkit's code\r\n    public InventoryTag add(int slot, ItemStack... items) {\r\n        if (inventory == null || items == null) {\r\n            return this;\r\n        }\r\n        for (int i = 0; i < items.length; i++) {\r\n            ItemStack item = items[i];\r\n            if (item == null || item.getType().isAir()) {\r\n                continue;\r\n            }\r\n            int amount = item.getAmount();\r\n            int max = item.getMaxStackSize();\r\n            while (true) {\r\n                // Do we already have a stack of it?\r\n                int firstPartial = firstPartial(slot, item, null);\r\n                // Drat! no partial stack\r\n                if (firstPartial == -1) {\r\n                    // Find a free spot!\r\n                    int firstFree = firstEmpty(slot, null);\r\n                    if (firstFree == -1) {\r\n                        // No space at all!\r\n                        break;\r\n                    }\r\n                    else {\r\n                        // More than a single stack!\r\n                        if (amount > max) {\r\n                            ItemStack clone = item.clone();\r\n                            clone.setAmount(max);\r\n                            NMSHandler.itemHelper.setInventoryItem(inventory, clone, firstFree);\r\n                            item.setAmount(amount -= max);\r\n                        }\r\n                        else {\r\n                            // Just store it\r\n                            NMSHandler.itemHelper.setInventoryItem(inventory, item, firstFree);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    // So, apparently it might only partially fit, well lets do just that\r\n                    ItemStack partialItem = inventory.getItem(firstPartial);\r\n                    int partialAmount = partialItem.getAmount();\r\n                    int total = amount + partialAmount;\r\n                    // Check if it fully fits\r\n                    if (total <= max) {\r\n                        partialItem.setAmount(total);\r\n                        break;\r\n                    }\r\n                    // It fits partially\r\n                    partialItem.setAmount(max);\r\n                    item.setAmount(amount = total - max);\r\n                }\r\n            }\r\n        }\r\n        return this;\r\n    }\r\n\r\n    public List<ItemStack> addWithLeftovers(int slot, String allowedSlots, boolean keepMaxStackSize, ItemStack... items) {\r\n        if (inventory == null || items == null) {\r\n            return null;\r\n        }\r\n        List<ItemStack> leftovers = new ArrayList<>();\r\n        for (int i = 0; i < items.length; i++) {\r\n            ItemStack item = items[i];\r\n            if (item == null || item.getType().isAir()) {\r\n                continue;\r\n            }\r\n            int amount = item.getAmount();\r\n            int max;\r\n            if (keepMaxStackSize) {\r\n                max = item.getMaxStackSize();\r\n            }\r\n            else {\r\n                max = 64;\r\n            }\r\n            while (true) {\r\n                // Do we already have a stack of it?\r\n                int firstPartial = firstPartial(slot, item, allowedSlots);\r\n                // Drat! no partial stack\r\n                if (firstPartial == -1) {\r\n                    // Find a free spot!\r\n                    int firstFree = firstEmpty(slot, allowedSlots);\r\n                    if (firstFree == -1) {\r\n                        // No space at all!\r\n                        leftovers.add(item);\r\n                        break;\r\n                    }\r\n                    else {\r\n                        // More than a single stack!\r\n                        if (amount > max) {\r\n                            ItemStack clone = item.clone();\r\n                            clone.setAmount(max);\r\n                            NMSHandler.itemHelper.setInventoryItem(inventory, clone, firstFree);\r\n                            item.setAmount(amount -= max);\r\n                        }\r\n                        else {\r\n                            // Just store it\r\n                            NMSHandler.itemHelper.setInventoryItem(inventory, item, firstFree);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    // So, apparently it might only partially fit, well lets do just that\r\n                    ItemStack partialItem = inventory.getItem(firstPartial);\r\n                    int partialAmount = partialItem.getAmount();\r\n                    int total = amount + partialAmount;\r\n                    // Check if it fully fits\r\n                    if (total <= max) {\r\n                        partialItem.setAmount(total);\r\n                        break;\r\n                    }\r\n                    // It fits partially\r\n                    partialItem.setAmount(max);\r\n                    item.setAmount(amount = total - max);\r\n                }\r\n            }\r\n        }\r\n        if (Depends.citizens != null && idHolder instanceof NPCTag) {\r\n            ((NPCTag) idHolder).getInventoryTrait().setContents(inventory.getContents());\r\n        }\r\n        return leftovers;\r\n    }\r\n\r\n    public int countByMaterial(Material material) {\r\n        if (inventory == null) {\r\n            return 0;\r\n        }\r\n        int qty = 0;\r\n        for (ItemStack invStack : inventory) {\r\n            if (invStack != null) {\r\n                if (invStack.getType() == material && !(new ItemTag(invStack).isItemscript())) {\r\n                    qty += invStack.getAmount();\r\n                }\r\n            }\r\n        }\r\n        return qty;\r\n    }\r\n\r\n    public int countByFlag(String flag) {\r\n        if (inventory == null) {\r\n            return 0;\r\n        }\r\n        int qty = 0;\r\n        for (ItemStack invStack : inventory) {\r\n            if (invStack != null) {\r\n                ItemTag item = new ItemTag(invStack);\r\n                if (item.getFlagTracker().hasFlag(flag)) {\r\n                    qty += invStack.getAmount();\r\n                }\r\n            }\r\n        }\r\n        return qty;\r\n    }\r\n\r\n    public int countByScriptName(String scriptName) {\r\n        if (inventory == null) {\r\n            return 0;\r\n        }\r\n        int qty = 0;\r\n        for (ItemStack invStack : inventory) {\r\n            if (invStack != null) {\r\n                ItemTag item = new ItemTag(invStack);\r\n                if (item.isItemscript() && item.getScriptName().equalsIgnoreCase(scriptName)) {\r\n                    qty += invStack.getAmount();\r\n                }\r\n            }\r\n        }\r\n        return qty;\r\n    }\r\n\r\n    /**\r\n     * Count the number or quantities of stacks that\r\n     * match an item in an inventory.\r\n     *\r\n     * @param item   The item (can be null)\r\n     * @param stacks Whether stacks should be counted\r\n     *               instead of item quantities\r\n     * @return The number of stacks or quantity of items\r\n     */\r\n    public int count(ItemStack item, boolean stacks) {\r\n        if (inventory == null) {\r\n            return 0;\r\n        }\r\n        int qty = 0;\r\n        for (ItemStack invStack : inventory) {\r\n            // If ItemStacks are empty here, they are null\r\n            if (invStack != null) {\r\n                // If item is null, include all items in the inventory\r\n                if (item == null || invStack.isSimilar(item)) {\r\n                    // If stacks is true, only count the number of stacks\r\n                    // Otherwise, count the quantities of stacks\r\n                    qty += (stacks ? 1 : invStack.getAmount());\r\n                }\r\n            }\r\n        }\r\n        return qty;\r\n    }\r\n\r\n    public InventoryTag setSlots(int slot, ItemStack... items) {\r\n        return setSlots(slot, items, items.length);\r\n    }\r\n\r\n    /**\r\n     * Set items in an inventory, starting with a specified slot\r\n     *\r\n     * @param slot  The slot to start from\r\n     * @param items The items to add\r\n     * @return The resulting InventoryTag\r\n     */\r\n    public InventoryTag setSlots(int slot, ItemStack[] items, int c) {\r\n        if (inventory == null || items == null) {\r\n            return this;\r\n        }\r\n        for (int i = 0; i < c; i++) {\r\n            if (i >= items.length || items[i] == null) {\r\n                NMSHandler.itemHelper.setInventoryItem(inventory, new ItemStack(Material.AIR), slot + i);\r\n            }\r\n            ItemStack item = items[i];\r\n            if (slot + i < 0 || slot + i >= inventory.getSize()) {\r\n                break;\r\n            }\r\n            NMSHandler.itemHelper.setInventoryItem(inventory, item, slot + i);\r\n        }\r\n        if (Depends.citizens != null && idHolder instanceof NPCTag) {\r\n            ((NPCTag) idHolder).getInventoryTrait().setContents(inventory.getContents());\r\n        }\r\n        return this;\r\n    }\r\n\r\n    public void clear() {\r\n        if (inventory != null) {\r\n            inventory.clear();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPrefix() {\r\n        return prefix;\r\n    }\r\n\r\n    @Override\r\n    public InventoryTag setPrefix(String prefix) {\r\n        this.prefix = prefix;\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public String identify() {\r\n        if (isUnique()) {\r\n            return \"in@\" + NoteManager.getSavedId(this);\r\n        }\r\n        else {\r\n            trackTemporaryInventory(this);\r\n            return \"in@\" + idType + PropertyParser.getPropertiesString(this);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String debuggable() {\r\n        if (isUnique()) {\r\n            return \"<LG>in@<Y>\" + NoteManager.getSavedId(this);\r\n        }\r\n        else {\r\n            trackTemporaryInventory(this);\r\n            return \"<LG>in@<Y>\" + idType + PropertyParser.getPropertiesDebuggable(this);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String identifySimple() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public Object getJavaObject() {\r\n        return inventory;\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\r\n        PropertyParser.registerPropertyTagHandlers(InventoryTag.class, tagProcessor);\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.empty_slots>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the number of empty slots in an inventory.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"empty_slots\", (attribute, object) -> {\r\n            InventoryTag dummyInv;\r\n            if (object.inventory.getType() == InventoryType.PLAYER) {\r\n                ItemStack[] contents = object.getStorageContents();\r\n                dummyInv = new InventoryTag(contents.length);\r\n                if (contents.length != dummyInv.getSize()) {\r\n                    contents = Arrays.copyOf(contents, dummyInv.getSize());\r\n                }\r\n                dummyInv.setContents(contents);\r\n            }\r\n            else {\r\n                dummyInv = object;\r\n            }\r\n            int full = dummyInv.count(null, true);\r\n            return new ElementTag(dummyInv.getSize() - full);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.can_fit[<item>|...]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the inventory can fit an item, or list of items.\r\n        // When giving multiple item inputs, the tag will only return true if every item can be added to the inventory at once.\r\n        // If only some fit, the tag will return false.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"can_fit\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            List<ItemTag> items = attribute.paramAsType(ListTag.class).filter(ItemTag.class, attribute.context, !attribute.hasAlternative());\r\n            if (items == null || items.isEmpty()) {\r\n                return null;\r\n            }\r\n\r\n            InventoryType type = object.inventory.getType();\r\n            InventoryTag dummyInv = new InventoryTag(type == InventoryType.PLAYER ? InventoryType.CHEST : type, PaperAPITools.instance.getTitle(object.inventory));\r\n            ItemStack[] contents = object.getStorageContents();\r\n            if (dummyInv.getInventoryType() == InventoryType.CHEST) {\r\n                dummyInv.setSize(contents.length);\r\n            }\r\n            if (contents.length != dummyInv.getSize()) {\r\n                contents = Arrays.copyOf(contents, dummyInv.getSize());\r\n            }\r\n            dummyInv.setContents(contents);\r\n\r\n            // <--[tag]\r\n            // @attribute <InventoryTag.can_fit[<item>].count>\r\n            // @returns ElementTag(Number)\r\n            // @description\r\n            // Returns the total count of how many times an item can fit into an inventory.\r\n            // -->\r\n            if (attribute.startsWith(\"count\", 2)) {\r\n                ItemStack toAdd = items.get(0).getItemStack().clone();\r\n                int totalCount = 64 * 64 * 4; // Technically nothing stops us from ridiculous numbers in an ItemStack amount.\r\n                toAdd.setAmount(totalCount);\r\n                List<ItemStack> leftovers = dummyInv.addWithLeftovers(0, null, true, toAdd);\r\n                int result = 0;\r\n                if (leftovers.size() > 0) {\r\n                    result += leftovers.get(0).getAmount();\r\n                }\r\n                attribute.fulfill(1);\r\n                return new ElementTag(totalCount - result);\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <InventoryTag.can_fit[<item>].quantity[<#>]>\r\n            // @returns ElementTag(Boolean)\r\n            // @description\r\n            // Returns whether the inventory can fit a certain quantity of an item.\r\n            // -->\r\n            if ((attribute.startsWith(\"quantity\", 2) || attribute.startsWith(\"qty\", 2)) && attribute.hasContext(2)) {\r\n                if (attribute.startsWith(\"qty\", 2)) {\r\n                    BukkitImplDeprecations.qtyTags.warn(attribute.context);\r\n                }\r\n                int qty = attribute.getIntContext(2);\r\n                ItemTag itemZero = new ItemTag(items.get(0).getItemStack().clone());\r\n                itemZero.setAmount(qty);\r\n                items.set(0, itemZero);\r\n                attribute.fulfill(1);\r\n            }\r\n\r\n            // NOTE: Could just also convert items to an array and pass it all in at once...\r\n            for (ItemTag itm : items) {\r\n                List<ItemStack> leftovers = dummyInv.addWithLeftovers(0, null, true, itm.getItemStack().clone());\r\n                if (!leftovers.isEmpty()) {\r\n                    return new ElementTag(false);\r\n                }\r\n            }\r\n            return new ElementTag(true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.include[<item>|...]>\r\n        // @returns InventoryTag\r\n        // @description\r\n        // Returns a copy of the InventoryTag with items added.\r\n        // -->\r\n        tagProcessor.registerTag(InventoryTag.class, \"include\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            List<ItemTag> items = ListTag.getListFor(attribute.getParamObject(), attribute.context).filter(ItemTag.class, attribute.context);\r\n            InventoryTag dummyInv = new InventoryTag(object.inventory.getType(), PaperAPITools.instance.getTitle(object.inventory));\r\n            if (object.inventory.getType() == InventoryType.CHEST) {\r\n                dummyInv.setSize(object.inventory.getSize());\r\n            }\r\n            dummyInv.setContents(object.getContents());\r\n            if (object.idHolder instanceof ScriptTag) {\r\n                dummyInv.idType = \"script\";\r\n                dummyInv.idHolder = object.idHolder;\r\n            }\r\n            trackTemporaryInventory(dummyInv);\r\n\r\n            // <--[tag]\r\n            // @attribute <InventoryTag.include[<item>].quantity[<#>]>\r\n            // @returns InventoryTag\r\n            // @description\r\n            // Returns the InventoryTag with a certain quantity of an item added.\r\n            // -->\r\n            if ((attribute.startsWith(\"quantity\", 2) || attribute.startsWith(\"qty\", 2)) && attribute.hasContext(2)) {\r\n                if (attribute.startsWith(\"qty\", 2)) {\r\n                    BukkitImplDeprecations.qtyTags.warn(attribute.context);\r\n                }\r\n                int qty = attribute.getIntContext(2);\r\n                ItemTag itemZero = new ItemTag(items.get(0).getItemStack().clone());\r\n                itemZero.setAmount(qty);\r\n                items.set(0, itemZero);\r\n                attribute.fulfill(1);\r\n            }\r\n            for (ItemTag item: items) {\r\n                dummyInv.add(0, item.getItemStack().clone());\r\n            }\r\n            return dummyInv;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.exclude_item[<item_matcher>]>\r\n        // @returns InventoryTag\r\n        // @description\r\n        // Returns a copy of the InventoryTag with all matching items excluded.\r\n        // -->\r\n        tagProcessor.registerTag(InventoryTag.class, \"exclude_item\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            String matcher = attribute.getParam();\r\n            InventoryTag dummyInv = new InventoryTag(object.inventory.getType(), PaperAPITools.instance.getTitle(object.inventory));\r\n            if (object.inventory.getType() == InventoryType.CHEST) {\r\n                dummyInv.setSize(object.inventory.getSize());\r\n            }\r\n            dummyInv.setContents(object.getContents());\r\n            if (object.idHolder instanceof ScriptTag) {\r\n                dummyInv.idType = \"script\";\r\n                dummyInv.idHolder = object.idHolder;\r\n            }\r\n            trackTemporaryInventory(dummyInv);\r\n            int quantity = Integer.MAX_VALUE;\r\n\r\n            // <--[tag]\r\n            // @attribute <InventoryTag.exclude_item[<item_matcher>].quantity[<#>]>\r\n            // @returns InventoryTag\r\n            // @description\r\n            // Returns the InventoryTag with a certain quantity of matching items excluded.\r\n            // -->\r\n            if (attribute.startsWith(\"quantity\", 2) && attribute.hasContext(2)) {\r\n                quantity = attribute.getIntContext(2);\r\n                attribute.fulfill(1);\r\n            }\r\n            for (int slot = 0; slot < dummyInv.inventory.getSize(); slot++) {\r\n                ItemStack item = dummyInv.inventory.getItem(slot);\r\n                if (item != null && new ItemTag(item).tryAdvancedMatcher(matcher, attribute.context)) {\r\n                    quantity -= item.getAmount();\r\n                    if (quantity >= 0) {\r\n                        dummyInv.inventory.setItem(slot, null);\r\n                    }\r\n                    else {\r\n                        item = item.clone();\r\n                        item.setAmount(-quantity);\r\n                        dummyInv.inventory.setItem(slot, item);\r\n                    }\r\n                    if (quantity <= 0) {\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            return dummyInv;\r\n        });\r\n\r\n        tagProcessor.registerTag(InventoryTag.class, \"exclude\", (attribute, object) -> {\r\n            BukkitImplDeprecations.inventoryNonMatcherTags.warn(attribute.context);\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            List<ItemTag> items = ListTag.getListFor(attribute.getParamObject(), attribute.context).filter(ItemTag.class, attribute.context);\r\n            InventoryTag dummyInv = new InventoryTag(object.inventory.getType(), PaperAPITools.instance.getTitle(object.inventory));\r\n            if (object.inventory.getType() == InventoryType.CHEST) {\r\n                dummyInv.setSize(object.inventory.getSize());\r\n            }\r\n            dummyInv.setContents(object.getContents());\r\n            if (object.idHolder instanceof ScriptTag) {\r\n                dummyInv.idType = \"script\";\r\n                dummyInv.idHolder = object.idHolder;\r\n            }\r\n            trackTemporaryInventory(dummyInv);\r\n            if ((attribute.startsWith(\"quantity\", 2) || attribute.startsWith(\"qty\", 2)) && attribute.hasContext(2)) {\r\n                if (attribute.startsWith(\"qty\", 2)) {\r\n                    BukkitImplDeprecations.qtyTags.warn(attribute.context);\r\n                }\r\n                int qty = attribute.getIntContext(2);\r\n                ItemTag itemZero = new ItemTag(items.get(0).getItemStack().clone());\r\n                itemZero.setAmount(qty);\r\n                items.set(0, itemZero);\r\n                attribute.fulfill(1);\r\n            }\r\n            for (ItemTag item : items) {\r\n                dummyInv.inventory.removeItem(item.getItemStack().clone());\r\n            }\r\n            return dummyInv;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.is_empty>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the inventory is empty.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_empty\", (attribute, object) -> {\r\n            boolean empty = true;\r\n            for (ItemStack item : object.getStorageContents()) {\r\n                if (item != null && item.getType() != Material.AIR) {\r\n                    empty = false;\r\n                    break;\r\n                }\r\n            }\r\n            return new ElementTag(empty);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.is_full>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the inventory is completely full.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_full\", (attribute, object) -> {\r\n            boolean full = true;\r\n\r\n            for (ItemStack item : object.getStorageContents()) {\r\n                if ((item == null) ||\r\n                        (item.getType() == Material.AIR) ||\r\n                        (item.getAmount() < item.getMaxStackSize())) {\r\n                    full = false;\r\n                    break;\r\n                }\r\n            }\r\n            return new ElementTag(full);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.contains_item[<matcher>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the inventory contains any item that matches the specified item matcher.\r\n        // Uses the system behind <@link language Advanced Object Matching>.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"contains_item\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            int qty = 1;\r\n            String matcher = attribute.getParam();\r\n\r\n            // <--[tag]\r\n            // @attribute <InventoryTag.contains_item[<matcher>].quantity[<#>]>\r\n            // @returns ElementTag(Boolean)\r\n            // @description\r\n            // Returns whether the inventory contains a certain number of items that match the specified item matcher.\r\n            // Uses the system behind <@link language Advanced Object Matching>.\r\n            // -->\r\n            if (attribute.startsWith(\"quantity\", 2) && attribute.hasContext(2)) {\r\n                qty = attribute.getIntContext(2);\r\n                attribute.fulfill(1);\r\n            }\r\n            int found_items = 0;\r\n            for (ItemStack item : object.getContents()) {\r\n                if (item != null) {\r\n                    if (new ItemTag(item).tryAdvancedMatcher(matcher, attribute.context)) {\r\n                        found_items += item.getAmount();\r\n                        if (found_items >= qty) {\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            return new ElementTag(found_items >= qty);\r\n        });\r\n\r\n        tagProcessor.registerTag(ElementTag.class, \"contains\", (attribute, object) -> {\r\n            // <--[tag]\r\n            // @attribute <InventoryTag.contains.display[(strict:)<element>]>\r\n            // @returns ElementTag(Boolean)\r\n            // @description\r\n            // Returns whether the inventory contains an item with the specified display name.\r\n            // Use 'strict:' in front of the search element to ensure the display name is EXACTLY the search element,\r\n            // otherwise the searching will only check if the search element is contained in the display name.\r\n            // -->\r\n            if (attribute.startsWith(\"display\", 2)) {\r\n                if (!attribute.hasContext(2)) {\r\n                    return null;\r\n                }\r\n                String search_string = attribute.getContext(2);\r\n                boolean strict = false;\r\n                if (CoreUtilities.toLowerCase(search_string).startsWith(\"strict:\") && search_string.length() > 7) {\r\n                    strict = true;\r\n                    search_string = search_string.substring(7);\r\n                }\r\n                if (search_string.length() == 0) {\r\n                    return null;\r\n                }\r\n                int qty = 1;\r\n\r\n                // <--[tag]\r\n                // @attribute <InventoryTag.contains.display[(strict:)<element>].quantity[<#>]>\r\n                // @returns ElementTag(Boolean)\r\n                // @description\r\n                // Returns whether the inventory contains a certain quantity of an item with the specified display name.\r\n                // Use 'strict:' in front of the search element to ensure the display name is EXACTLY the search element,\r\n                // otherwise the searching will only check if the search element is contained in the display name.\r\n                // -->\r\n                if ((attribute.startsWith(\"quantity\", 3) || attribute.startsWith(\"qty\", 3)) && attribute.hasContext(3)) {\r\n                    if (attribute.startsWith(\"qty\", 3)) {\r\n                        BukkitImplDeprecations.qtyTags.warn(attribute.context);\r\n                    }\r\n                    qty = attribute.getIntContext(3);\r\n                    attribute.fulfill(1);\r\n                }\r\n                int found_items = 0;\r\n                if (strict) {\r\n                    for (ItemStack item : object.getContents()) {\r\n                        if (item == null || !item.hasItemMeta()) {\r\n                            continue;\r\n                        }\r\n                        ItemMeta meta = item.getItemMeta();\r\n                        if (item.getType() == Material.WRITTEN_BOOK && ((BookMeta) meta).getTitle().equalsIgnoreCase(search_string)) {\r\n                            found_items += item.getAmount();\r\n                            if (found_items >= qty) {\r\n                                break;\r\n                            }\r\n                        }\r\n                        else if (meta.hasDisplayName() && meta.getDisplayName().equalsIgnoreCase(search_string)) {\r\n                            found_items += item.getAmount();\r\n                            if (found_items >= qty) {\r\n                                break;\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    for (ItemStack item : object.getContents()) {\r\n                        if (item == null || !item.hasItemMeta()) {\r\n                            continue;\r\n                        }\r\n                        ItemMeta meta = item.getItemMeta();\r\n                        if (item.getType() == Material.WRITTEN_BOOK && CoreUtilities.toLowerCase(((BookMeta) meta).getTitle()).contains(CoreUtilities.toLowerCase(search_string))) {\r\n                            found_items += item.getAmount();\r\n                            if (found_items >= qty) {\r\n                                break;\r\n                            }\r\n                        }\r\n                        else if (meta.hasDisplayName() && CoreUtilities.toLowerCase(meta.getDisplayName()).contains(CoreUtilities.toLowerCase(search_string))) {\r\n                            found_items += item.getAmount();\r\n                            if (found_items >= qty) {\r\n                                break;\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                attribute.fulfill(1);\r\n                return new ElementTag(found_items >= qty);\r\n            }\r\n            // <--[tag]\r\n            // @attribute <InventoryTag.contains.lore[(strict:)<element>|...]>\r\n            // @returns ElementTag(Boolean)\r\n            // @description\r\n            // Returns whether the inventory contains an item with the specified lore.\r\n            // Use 'strict:' in front of the search elements to ensure all lore lines are EXACTLY the search elements,\r\n            // otherwise the searching will only check if the search elements are contained in the lore.\r\n            // -->\r\n            if (attribute.startsWith(\"lore\", 2)) {\r\n                if (!attribute.hasContext(2)) {\r\n                    return null;\r\n                }\r\n                String search_string = attribute.getContext(2);\r\n                boolean strict = false;\r\n                if (CoreUtilities.toLowerCase(search_string).startsWith(\"strict:\")) {\r\n                    strict = true;\r\n                    search_string = search_string.substring(\"strict:\".length());\r\n                }\r\n                if (search_string.length() == 0) {\r\n                    return null;\r\n                }\r\n                ListTag lore = ListTag.valueOf(search_string, attribute.context);\r\n                int qty = 1;\r\n\r\n                // <--[tag]\r\n                // @attribute <InventoryTag.contains.lore[(strict:)<element>|...].quantity[<#>]>\r\n                // @returns ElementTag(Boolean)\r\n                // @description\r\n                // Returns whether the inventory contains a certain quantity of an item with the specified lore.\r\n                // Use 'strict:' in front of the search elements to ensure all lore lines are EXACTLY the search elements,\r\n                // otherwise the searching will only check if the search elements are contained in the lore.\r\n                // -->\r\n                if ((attribute.startsWith(\"quantity\", 3) || attribute.startsWith(\"qty\", 3)) && attribute.hasContext(3)) {\r\n                    if (attribute.startsWith(\"qty\", 3)) {\r\n                        BukkitImplDeprecations.qtyTags.warn(attribute.context);\r\n                    }\r\n                    qty = attribute.getIntContext(3);\r\n                    attribute.fulfill(1);\r\n                }\r\n                int found_items = 0;\r\n                if (strict) {\r\n                    strict_items:\r\n                    for (ItemStack item : object.getContents()) {\r\n                        if (item == null || !item.hasItemMeta()) {\r\n                            continue;\r\n                        }\r\n                        ItemMeta meta = item.getItemMeta();\r\n                        if (meta.hasLore()) {\r\n                            List<String> item_lore = meta.getLore();\r\n                            if (lore.size() != item_lore.size()) {\r\n                                continue;\r\n                            }\r\n                            for (int i = 0; i < item_lore.size(); i++) {\r\n                                if (!lore.get(i).equalsIgnoreCase(item_lore.get(i))) {\r\n                                    continue strict_items;\r\n                                }\r\n                            }\r\n                            found_items += item.getAmount();\r\n                            if (found_items >= qty) {\r\n                                break;\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    for (ItemStack item : object.getContents()) {\r\n                        if (item == null || !item.hasItemMeta()) {\r\n                            continue;\r\n                        }\r\n                        ItemMeta meta = item.getItemMeta();\r\n                        if (meta.hasLore()) {\r\n                            List<String> item_lore = meta.getLore();\r\n                            int loreCount = 0;\r\n                            lines:\r\n                            for (String line : lore) {\r\n                                for (String item_line : item_lore) {\r\n                                    if (CoreUtilities.toLowerCase(item_line).contains(CoreUtilities.toLowerCase(line))) {\r\n                                        loreCount++;\r\n                                        continue lines;\r\n                                    }\r\n                                }\r\n                            }\r\n                            if (loreCount == lore.size()) {\r\n                                found_items += item.getAmount();\r\n                                if (found_items >= qty) {\r\n                                    break;\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                attribute.fulfill(1);\r\n                return new ElementTag(found_items >= qty);\r\n            }\r\n            if (attribute.startsWith(\"scriptname\", 2)) {\r\n                BukkitImplDeprecations.inventoryNonMatcherTags.warn(attribute.context);\r\n                if (!attribute.hasContext(2)) {\r\n                    return null;\r\n                }\r\n                ListTag scrNameList = attribute.contextAsType(2, ListTag.class);\r\n                HashSet<String> scrNames = new HashSet<>();\r\n                for (String name : scrNameList) {\r\n                    scrNames.add(CoreUtilities.toLowerCase(name));\r\n                }\r\n                int qty = 1;\r\n\r\n                if ((attribute.startsWith(\"quantity\", 3) || attribute.startsWith(\"qty\", 3)) && attribute.hasContext(3)) {\r\n                    if (attribute.startsWith(\"qty\", 3)) {\r\n                        BukkitImplDeprecations.qtyTags.warn(attribute.context);\r\n                    }\r\n                    qty = attribute.getIntContext(3);\r\n                    attribute.fulfill(1);\r\n                }\r\n                int found_items = 0;\r\n                for (ItemStack item : object.getContents()) {\r\n                    if (item != null) {\r\n                        String itemName = new ItemTag(item).getScriptName();\r\n                        if (itemName != null && scrNames.contains(CoreUtilities.toLowerCase(itemName))) {\r\n                            found_items += item.getAmount();\r\n                            if (found_items >= qty) {\r\n                                break;\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                attribute.fulfill(1);\r\n                return new ElementTag(found_items >= qty);\r\n            }\r\n            if (attribute.startsWith(\"flagged\", 2)) {\r\n                BukkitImplDeprecations.inventoryNonMatcherTags.warn(attribute.context);\r\n                if (!attribute.hasContext(2)) {\r\n                    return null;\r\n                }\r\n                ListTag scrNameList = attribute.contextAsType(2, ListTag.class);\r\n                String[] flags = scrNameList.toArray(new String[0]);\r\n                int qty = 1;\r\n\r\n                if (attribute.startsWith(\"quantity\", 3) && attribute.hasContext(3)) {\r\n                    qty = attribute.getIntContext(3);\r\n                    attribute.fulfill(1);\r\n                }\r\n                int found_items = 0;\r\n                for (ItemStack item : object.getContents()) {\r\n                    if (item != null) {\r\n                        ItemTag itemTag = new ItemTag(item);\r\n                        for (String flag : flags) {\r\n                            if (itemTag.getFlagTracker().hasFlag(flag)) {\r\n                                found_items += item.getAmount();\r\n                                break;\r\n                            }\r\n                        }\r\n                        if (found_items >= qty) {\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n                attribute.fulfill(1);\r\n                return new ElementTag(found_items >= qty);\r\n            }\r\n            if (attribute.startsWith(\"nbt\", 2)) {\r\n                BukkitImplDeprecations.itemNbt.warn(attribute.context);\r\n                if (!attribute.hasContext(2)) {\r\n                    return null;\r\n                }\r\n                String keyName = attribute.getContext(2);\r\n                int qty = 1;\r\n                if ((attribute.startsWith(\"quantity\", 3) || attribute.startsWith(\"qty\", 3)) && attribute.hasContext(3)) {\r\n                    if (attribute.startsWith(\"qty\", 3)) {\r\n                        BukkitImplDeprecations.qtyTags.warn(attribute.context);\r\n                    }\r\n                    qty = attribute.getIntContext(3);\r\n                    attribute.fulfill(1);\r\n                }\r\n                int found_items = 0;\r\n                for (ItemStack item : object.getContents()) {\r\n                    if (CustomNBT.hasCustomNBT(item, keyName, CustomNBT.KEY_DENIZEN)) {\r\n                        found_items += item.getAmount();\r\n                        if (found_items >= qty) {\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n                attribute.fulfill(1);\r\n                return new ElementTag(found_items >= qty);\r\n            }\r\n            if (attribute.startsWith(\"material\", 2)) {\r\n                BukkitImplDeprecations.inventoryNonMatcherTags.warn(attribute.context);\r\n                if (!attribute.hasContext(2)) {\r\n                    return null;\r\n                }\r\n                List<MaterialTag> materials = attribute.contextAsType(2, ListTag.class).filter(MaterialTag.class, attribute.context);\r\n                int qty = 1;\r\n\r\n                if ((attribute.startsWith(\"quantity\", 3) || attribute.startsWith(\"qty\", 3)) && attribute.hasContext(3)) {\r\n                    if (attribute.startsWith(\"qty\", 3)) {\r\n                        BukkitImplDeprecations.qtyTags.warn(attribute.context);\r\n                    }\r\n                    qty = attribute.getIntContext(3);\r\n                    attribute.fulfill(1);\r\n                }\r\n                int found_items = 0;\r\n                mainLoop:\r\n                for (ItemStack item : object.getContents()) {\r\n                    if (item == null) {\r\n                        continue;\r\n                    }\r\n                    for (MaterialTag material : materials) {\r\n                        if (item.getType() == material.getMaterial() && !(new ItemTag(item).isItemscript())) {\r\n                            found_items += item.getAmount();\r\n                            if (found_items >= qty) {\r\n                                break mainLoop;\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                attribute.fulfill(1);\r\n                return new ElementTag(found_items >= qty);\r\n            }\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            ListTag list = attribute.paramAsType(ListTag.class);\r\n            if (list.isEmpty()) {\r\n                return null;\r\n            }\r\n            int qty = 1;\r\n\r\n            BukkitImplDeprecations.inventoryNonMatcherTags.warn(attribute.context);\r\n            if ((attribute.startsWith(\"quantity\", 2) || attribute.startsWith(\"qty\", 2)) && attribute.hasContext(2)) {\r\n                if (attribute.startsWith(\"qty\", 2)) {\r\n                    BukkitImplDeprecations.qtyTags.warn(attribute.context);\r\n                }\r\n                qty = attribute.getIntContext(2);\r\n                attribute.fulfill(1);\r\n            }\r\n            List<ItemTag> contains = list.filter(ItemTag.class, attribute.context, !attribute.hasAlternative());\r\n            if (contains.size() == list.size()) {\r\n                for (ItemTag item : contains) {\r\n                    if (!object.containsItem(item, qty)) {\r\n                        return new ElementTag(false);\r\n                    }\r\n                }\r\n                return new ElementTag(true);\r\n            }\r\n            return new ElementTag(false);\r\n        });\r\n\r\n        tagProcessor.registerTag(ElementTag.class, \"contains_any\", (attribute, object) -> {\r\n            BukkitImplDeprecations.inventoryNonMatcherTags.warn(attribute.context);\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            ListTag list = attribute.paramAsType(ListTag.class);\r\n            if (list.isEmpty()) {\r\n                return null;\r\n            }\r\n            int qty = 1;\r\n\r\n            if ((attribute.startsWith(\"quantity\", 2) || attribute.startsWith(\"qty\", 2)) && attribute.hasContext(2)) {\r\n                if (attribute.startsWith(\"qty\", 2)) {\r\n                    BukkitImplDeprecations.qtyTags.warn(attribute.context);\r\n                }\r\n                qty = attribute.getIntContext(2);\r\n                attribute.fulfill(1);\r\n            }\r\n            List<ItemTag> contains = list.filter(ItemTag.class, attribute.context, !attribute.hasAlternative());\r\n            if (!contains.isEmpty()) {\r\n                for (ItemTag item : contains) {\r\n                    if (object.containsItem(item, qty)) {\r\n                        return new ElementTag(true);\r\n                    }\r\n                }\r\n            }\r\n            return new ElementTag(false);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.first_empty>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the location of the first empty slot.\r\n        // Returns -1 if the inventory is full.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"first_empty\", (attribute, object) -> {\r\n            int val = object.firstEmpty(0, null);\r\n            return new ElementTag(val >= 0 ? (val + 1) : -1);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.find_item[<matcher>]>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the location of the first slot that contains an item that matches the given item matcher.\r\n        // Returns -1 if there's no match.\r\n        // Uses the system behind <@link language Advanced Object Matching>.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"find_item\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            String matcher = attribute.getParam();\r\n            for (int i = 0; i < object.inventory.getSize(); i++) {\r\n                ItemStack item = object.inventory.getItem(i);\r\n                if (item != null) {\r\n                    if (new ItemTag(item).tryAdvancedMatcher(matcher, attribute.context)) {\r\n                        return new ElementTag(i + 1);\r\n                    }\r\n                }\r\n            }\r\n            return new ElementTag(-1);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.find_all_items[<matcher>]>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of the location of all slots that contains an item that matches the given item matcher.\r\n        // Returns an empty list if there's no match.\r\n        // Uses the system behind <@link language Advanced Object Matching>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"find_all_items\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            ListTag result = new ListTag();\r\n            String matcher = attribute.getParam();\r\n            for (int i = 0; i < object.inventory.getSize(); i++) {\r\n                ItemStack item = object.inventory.getItem(i);\r\n                if (item != null) {\r\n                    if (new ItemTag(item).tryAdvancedMatcher(matcher, attribute.context)) {\r\n                        result.addObject(new ElementTag(i + 1));\r\n                    }\r\n                }\r\n            }\r\n            return result;\r\n        });\r\n\r\n        tagProcessor.registerTag(ElementTag.class, \"find\", (attribute, object) -> {\r\n            BukkitImplDeprecations.inventoryNonMatcherTags.warn(attribute.context);\r\n            if (attribute.startsWith(\"material\", 2)) {\r\n                ListTag list = attribute.contextAsType(2, ListTag.class);\r\n                if (list == null) {\r\n                    return null;\r\n                }\r\n                HashSet<Material> materials = new HashSet<>();\r\n                for (ObjectTag obj : list.objectForms) {\r\n                    materials.add(obj.asType(MaterialTag.class, attribute.context).getMaterial());\r\n                }\r\n                int slot = -1;\r\n                for (int i = 0; i < object.inventory.getSize(); i++) {\r\n                    if (object.inventory.getItem(i) != null && materials.contains(object.inventory.getItem(i).getType())) {\r\n                        slot = i + 1;\r\n                        break;\r\n                    }\r\n                }\r\n                attribute.fulfill(1);\r\n                return new ElementTag(slot);\r\n            }\r\n\r\n            if (attribute.startsWith(\"scriptname\", 2)) {\r\n                String scrname = attribute.contextAsType(2, ItemTag.class).getScriptName();\r\n                if (scrname == null) {\r\n                    return null;\r\n                }\r\n                int slot = -1;\r\n                for (int i = 0; i < object.inventory.getSize(); i++) {\r\n                    if (object.inventory.getItem(i) != null\r\n                            && scrname.equalsIgnoreCase(new ItemTag(object.inventory.getItem(i)).getScriptName())) {\r\n                        slot = i + 1;\r\n                        break;\r\n                    }\r\n                }\r\n                attribute.fulfill(1);\r\n                return new ElementTag(slot);\r\n            }\r\n            if (!attribute.hasParam() || !ItemTag.matches(attribute.getParam())) {\r\n                return null;\r\n            }\r\n            ItemTag item = attribute.paramAsType(ItemTag.class);\r\n            item.setAmount(1);\r\n            int slot = -1;\r\n            for (int i = 0; i < object.inventory.getSize(); i++) {\r\n                if (object.inventory.getItem(i) != null) {\r\n                    ItemTag compare_to = new ItemTag(object.inventory.getItem(i).clone());\r\n                    compare_to.setAmount(1);\r\n                    if (item.identify().equalsIgnoreCase(compare_to.identify())) {\r\n                        slot = i + 1;\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            return new ElementTag(slot);\r\n        });\r\n\r\n        tagProcessor.registerTag(ElementTag.class, \"find_imperfect\", (attribute, object) -> {\r\n            BukkitImplDeprecations.inventoryNonMatcherTags.warn(attribute.context);\r\n            if (!attribute.hasParam() || !ItemTag.matches(attribute.getParam())) {\r\n                return null;\r\n            }\r\n            ItemTag item = attribute.paramAsType(ItemTag.class);\r\n            item.setAmount(1);\r\n            int slot = -1;\r\n            for (int i = 0; i < object.inventory.getSize(); i++) {\r\n                if (object.inventory.getItem(i) != null) {\r\n                    ItemTag compare_to = new ItemTag(object.inventory.getItem(i).clone());\r\n                    compare_to.setAmount(1);\r\n                    if (item.identify().equalsIgnoreCase(compare_to.identify())\r\n                            || item.getScriptName().equalsIgnoreCase(compare_to.getScriptName())) {\r\n                        slot = i + 1;\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            return new ElementTag(slot);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.id_type>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns Denizen's type ID for this inventory (player, location, etc.).\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"id_type\", (attribute, object) -> {\r\n            return new ElementTag(object.idType);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.note_name>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Gets the name of a noted InventoryTag. If the inventory isn't noted, this is null.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"note_name\", (attribute, object) -> {\r\n            String noteName = NoteManager.getSavedId(object);\r\n            if (noteName == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(noteName);\r\n        }, \"notable_name\");\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.location>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns the location of this inventory's holder.\r\n        // -->\r\n        tagProcessor.registerTag(LocationTag.class, \"location\", (attribute, object) -> {\r\n            return object.getLocation();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.quantity_item[(<matcher>)]>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the combined quantity of itemstacks that match an item matcher if one is specified,\r\n        // or the combined quantity of all itemstacks if one is not.\r\n        // Uses the system behind <@link language Advanced Object Matching>.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"quantity_item\", (attribute, object) -> {\r\n            String matcher = attribute.hasParam() ? attribute.getParam() : null;\r\n            int found_items = 0;\r\n            for (ItemStack item : object.getContents()) {\r\n                if (item != null) {\r\n                    if (matcher == null || new ItemTag(item).tryAdvancedMatcher(matcher, attribute.context)) {\r\n                        found_items += item.getAmount();\r\n                    }\r\n                }\r\n            }\r\n            return new ElementTag(found_items);\r\n        });\r\n\r\n        tagProcessor.registerTag(ElementTag.class, \"quantity\", (attribute, object) -> {\r\n            BukkitImplDeprecations.inventoryNonMatcherTags.warn(attribute.context);\r\n            if (attribute.startsWith(\"scriptname\", 2)) {\r\n                if (!attribute.hasContext(2)) {\r\n                    return null;\r\n                }\r\n                String scriptName = attribute.getContext(2);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.countByScriptName(scriptName));\r\n            }\r\n            if (attribute.startsWith(\"flagged\", 2)) {\r\n                if (!attribute.hasContext(2)) {\r\n                    return null;\r\n                }\r\n                String flag = attribute.getContext(2);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.countByFlag(flag));\r\n            }\r\n            if (attribute.startsWith(\"material\", 2)) {\r\n                if (!attribute.hasContext(2) || !MaterialTag.matches(attribute.getContext(2))) {\r\n                    return null;\r\n                }\r\n                MaterialTag material = attribute.contextAsType(2, MaterialTag.class);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.countByMaterial(material.getMaterial()));\r\n            }\r\n            if (attribute.hasParam() && ItemTag.matches(attribute.getParam())) {\r\n                return new ElementTag(object.count\r\n                        (attribute.paramAsType(ItemTag.class).getItemStack(), false));\r\n            }\r\n            else {\r\n                return new ElementTag(object.count(null, false));\r\n            }\r\n        }, \"qty\");\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.stacks[(<item>)]>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the number of itemstacks that match an item if one is specified, or the number of all itemstacks if one is not.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"stacks\", (attribute, object) -> {\r\n            if (attribute.hasParam() && ItemTag.matches(attribute.getParam())) {\r\n                return new ElementTag(object.count(attribute.paramAsType(ItemTag.class).getItemStack(), true));\r\n            }\r\n            else {\r\n                return new ElementTag(object.count(null, true));\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.slot[<#>|...]>\r\n        // @returns ObjectTag\r\n        // @description\r\n        // If just a single slot is specified, returns the ItemTag in the specified slot.\r\n        // If a list is specified, returns a ListTag(ItemTag) of the item in each given slot.\r\n        // -->\r\n        tagProcessor.registerTag(ObjectTag.class, \"slot\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            ListTag slots = ListTag.getListFor(attribute.getParamObject(), attribute.context);\r\n            if (slots.isEmpty()) {\r\n                if (!attribute.hasAlternative()) {\r\n                    Debug.echoError(\"Cannot get a list of zero slots.\");\r\n                }\r\n                return null;\r\n            }\r\n            else if (slots.size() == 1 && !attribute.getParamObject().shouldBeType(ListTag.class)) {\r\n                int slot = SlotHelper.nameToIndexFor(attribute.getParam(), object.getInventory().getHolder());\r\n                if (slot == -1) {\r\n                    attribute.echoError(\"Invalid slot index '\" + attribute.getParam() + \"'.\");\r\n                    return null;\r\n                }\r\n                if (slot < 0) {\r\n                    slot = 0;\r\n                }\r\n                else if (slot > object.getInventory().getSize() - 1) {\r\n                    slot = object.getInventory().getSize() - 1;\r\n                }\r\n                return new ItemTag(object.getInventory().getItem(slot));\r\n            }\r\n            else {\r\n                ListTag result = new ListTag();\r\n                for (String slotText : slots) {\r\n                    int slot = SlotHelper.nameToIndexFor(slotText, object.getInventory().getHolder());\r\n                    if (slot == -1) {\r\n                        attribute.echoError(\"Invalid slot index '\" + slotText + \"'.\");\r\n                        return null;\r\n                    }\r\n                    if (slot < 0) {\r\n                        slot = 0;\r\n                    }\r\n                    else if (slot > object.getInventory().getSize() - 1) {\r\n                        slot = object.getInventory().getSize() - 1;\r\n                    }\r\n                    result.addObject(new ItemTag(object.getInventory().getItem(slot)));\r\n                }\r\n                return result;\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.inventory_type>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the type of the inventory (e.g. \"PLAYER\", \"CRAFTING\", \"HORSE\").\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"inventory_type\", (attribute, object) -> {\r\n            return new ElementTag(object.inventory instanceof HorseInventory ? \"HORSE\" : object.getInventory().getType().name());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.equipment_map>\r\n        // @returns MapTag\r\n        // @description\r\n        // Returns a MapTag containing the inventory's equipment.\r\n        // Output keys for players are boots, leggings,  chestplate, helmet.\r\n        // Output keys for horses are saddle, armor.\r\n        // Air items will be left out of the map.\r\n        // -->\r\n        tagProcessor.registerTag(MapTag.class, \"equipment_map\", (attribute, object) -> {\r\n            return object.getEquipmentMap();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.equipment>\r\n        // @returns ListTag(ItemTag)\r\n        // @description\r\n        // Returns the equipment of an inventory as a list of items.\r\n        // For players, the order is boots|leggings|chestplate|helmet.\r\n        // For horses, the order is saddle|armor.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"equipment\", (attribute, object) -> {\r\n            return object.getEquipment();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.matrix>\r\n        // @returns ListTag(ItemTag)\r\n        // @mechanism InventoryTag.matrix\r\n        // @description\r\n        // Returns the items currently in a crafting inventory's matrix.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"matrix\", (attribute, object) -> {\r\n            if (!(object.inventory instanceof CraftingInventory)) {\r\n                return null;\r\n            }\r\n            ListTag recipeList = new ListTag();\r\n            for (ItemStack item : ((CraftingInventory) object.inventory).getMatrix()) {\r\n                if (item != null) {\r\n                    recipeList.addObject(new ItemTag(item));\r\n                }\r\n                else {\r\n                    recipeList.addObject(new ItemTag(Material.AIR));\r\n                }\r\n            }\r\n            return recipeList;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.recipe>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the recipe ID for the recipe currently formed in a crafting inventory.\r\n        // Returns a list in the Namespace:Key format, for example \"minecraft:stick\".\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"recipe\", (attribute, object) -> {\r\n            Recipe recipe;\r\n            if (object.inventory instanceof CraftingInventory) {\r\n                recipe = ((CraftingInventory) object.inventory).getRecipe();\r\n            }\r\n            else if (object.inventory instanceof SmithingInventory) {\r\n                recipe = ((SmithingInventory) object.inventory).getRecipe();\r\n            }\r\n            else {\r\n                return null;\r\n            }\r\n            if (recipe == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(((Keyed) recipe).getKey().toString());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.craftable_quantity>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the quantity of items that would be received if this crafting inventory were fully crafted (eg via a shift click).\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"craftable_quantity\", (attribute, object) -> {\r\n            Recipe recipe;\r\n            if ((object.inventory instanceof CraftingInventory)) {\r\n                recipe = ((CraftingInventory) object.inventory).getRecipe();\r\n            }\r\n            else {\r\n                return null;\r\n            }\r\n            if (recipe == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(RecipeHelper.getMaximumOutputQuantity(recipe, (CraftingInventory) object.inventory) * recipe.getResult().getAmount());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.result>\r\n        // @returns ItemTag\r\n        // @mechanism InventoryTag.result\r\n        // @description\r\n        // Returns the item currently in the result section of a crafting inventory or furnace inventory.\r\n        // -->\r\n        tagProcessor.registerTag(ItemTag.class, \"result\", (attribute, object) -> {\r\n            ItemStack result;\r\n            if ((object.inventory instanceof CraftingInventory)) {\r\n                result = ((CraftingInventory) object.inventory).getResult();\r\n            }\r\n            else if ((object.inventory instanceof FurnaceInventory)) {\r\n                result = ((FurnaceInventory) object.inventory).getResult();\r\n            }\r\n            else {\r\n                return null;\r\n            }\r\n            if (result == null) {\r\n                return null;\r\n            }\r\n            return new ItemTag(result);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.anvil_repair_cost>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism InventoryTag.anvil_repair_cost\r\n        // @description\r\n        // Returns the current repair cost on an anvil.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"anvil_repair_cost\", (attribute, object) -> {\r\n            if (!(object.inventory instanceof AnvilInventory)) {\r\n                return null;\r\n            }\r\n            return new ElementTag(((AnvilInventory) object.inventory).getRepairCost());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.anvil_max_repair_cost>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism InventoryTag.anvil_max_repair_cost\r\n        // @description\r\n        // Returns the maximum repair cost on an anvil.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"anvil_max_repair_cost\", (attribute, object) -> {\r\n            if (!(object.inventory instanceof AnvilInventory)) {\r\n                return null;\r\n            }\r\n            return new ElementTag(((AnvilInventory) object.inventory).getMaximumRepairCost());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.anvil_rename_text>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the current entered renaming text on an anvil.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"anvil_rename_text\", (attribute, object) -> {\r\n            if (!(object.inventory instanceof AnvilInventory)) {\r\n                return null;\r\n            }\r\n            return new ElementTag(((AnvilInventory) object.inventory).getRenameText(), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.fuel>\r\n        // @returns ItemTag\r\n        // @mechanism InventoryTag.fuel\r\n        // @description\r\n        // Returns the item currently in the fuel section of a furnace or brewing stand inventory.\r\n        // -->\r\n        tagProcessor.registerTag(ItemTag.class, \"fuel\", (attribute, object) -> {\r\n            if (object.getInventory() instanceof FurnaceInventory) {\r\n                return new ItemTag(((FurnaceInventory) object.getInventory()).getFuel());\r\n            }\r\n            if (object.getInventory() instanceof BrewerInventory) {\r\n                return new ItemTag(((BrewerInventory) object.getInventory()).getFuel());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.input>\r\n        // @returns ItemTag\r\n        // @mechanism InventoryTag.input\r\n        // @description\r\n        // Returns the item currently in the smelting slot of a furnace inventory, or the ingredient slot of a brewing stand inventory.\r\n        // -->\r\n        tagProcessor.registerTag(ItemTag.class, \"input\", (attribute, object) -> {\r\n            if (object.getInventory() instanceof FurnaceInventory) {\r\n                return new ItemTag(((FurnaceInventory) object.getInventory()).getSmelting());\r\n            }\r\n            if (object.getInventory() instanceof BrewerInventory) {\r\n                return new ItemTag(((BrewerInventory) object.getInventory()).getIngredient());\r\n            }\r\n            return null;\r\n        });\r\n        tagProcessor.registerFutureTagDeprecation(\"input\", \"smelting\");\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.viewers>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of players viewing the inventory.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"viewers\", (attribute, object) -> {\r\n            ListTag list = new ListTag();\r\n            for (HumanEntity viewer : object.getInventory().getViewers()) {\r\n                if (!EntityTag.isNPC(viewer) && viewer instanceof Player) {\r\n                    list.addObject(new PlayerTag((Player) viewer));\r\n                }\r\n            }\r\n            return list;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.find_empty_slots>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns the index of all the empty slots in an inventory.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"find_empty_slots\", (attribute, object) -> {\r\n            if (object == null) {\r\n                return new ListTag();\r\n            }\r\n            ListTag indexes = new ListTag(object.getContents().length);\r\n            int index = 1;\r\n            for (ItemStack itemStack : object.getContents()) {\r\n                if (itemStack == null || itemStack.getType() == Material.AIR) {\r\n                    indexes.addObject(new ElementTag(index));\r\n                }\r\n                index++;\r\n            }\r\n            return indexes;\r\n        });\r\n    }\r\n\r\n    public static ObjectTagProcessor<InventoryTag> tagProcessor = new ObjectTagProcessor<>();\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return tagProcessor.getObjectAttribute(this, attribute);\r\n    }\r\n\r\n    public void applyProperty(Mechanism mechanism) {\r\n        mechanism.echoError(\"Cannot apply properties to non-generic inventory!\");\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        tagProcessor.processMechanism(this, mechanism);\r\n\r\n        // <--[mechanism]\r\n        // @object InventoryTag\r\n        // @name matrix\r\n        // @input ListTag(ItemTag)\r\n        // @description\r\n        // Sets the items in the matrix slots of this crafting inventory.\r\n        // @tags\r\n        // <InventoryTag.matrix>\r\n        // -->\r\n        if (mechanism.matches(\"matrix\") && mechanism.requireObject(ListTag.class)) {\r\n            if (!(inventory instanceof CraftingInventory)) {\r\n                mechanism.echoError(\"Inventory is not a crafting inventory, cannot set matrix.\");\r\n                return;\r\n            }\r\n            CraftingInventory craftingInventory = (CraftingInventory) inventory;\r\n            List<ItemTag> items = mechanism.valueAsType(ListTag.class).filter(ItemTag.class, mechanism.context);\r\n            ItemStack[] itemStacks = new ItemStack[9];\r\n            for (int i = 0; i < 9 && i < items.size(); i++) {\r\n                itemStacks[i] = items.get(i).getItemStack();\r\n            }\r\n            craftingInventory.setMatrix(itemStacks);\r\n            ((Player) inventory.getHolder()).updateInventory();\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object InventoryTag\r\n        // @name result\r\n        // @input ItemTag\r\n        // @description\r\n        // Sets the item in the result slot of this crafting inventory or furnace inventory.\r\n        // @tags\r\n        // <InventoryTag.result>\r\n        // -->\r\n        if (mechanism.matches(\"result\") && mechanism.requireObject(ItemTag.class)) {\r\n            if (inventory instanceof CraftingInventory) {\r\n                CraftingInventory craftingInventory = (CraftingInventory) inventory;\r\n                craftingInventory.setResult(mechanism.valueAsType(ItemTag.class).getItemStack());\r\n                ((Player) inventory.getHolder()).updateInventory();\r\n            }\r\n            else if (inventory instanceof FurnaceInventory) {\r\n                FurnaceInventory furnaceInventory = (FurnaceInventory) inventory;\r\n                furnaceInventory.setResult(mechanism.valueAsType(ItemTag.class).getItemStack());\r\n            }\r\n            else {\r\n                Debug.echoError(\"Inventory is not a crafting inventory or furnace inventory, cannot set result.\");\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object InventoryTag\r\n        // @name fuel\r\n        // @input ItemTag\r\n        // @description\r\n        // Sets the item in the fuel slot of this furnace or brewing stand inventory.\r\n        // @tags\r\n        // <InventoryTag.fuel>\r\n        // -->\r\n        if (mechanism.matches(\"fuel\") && mechanism.requireObject(ItemTag.class)) {\r\n            if (inventory instanceof FurnaceInventory) {\r\n                ((FurnaceInventory) inventory).setFuel(mechanism.valueAsType(ItemTag.class).getItemStack());\r\n            }\r\n            else if (inventory instanceof BrewerInventory) {\r\n                ((BrewerInventory) inventory).setFuel(mechanism.valueAsType(ItemTag.class).getItemStack());\r\n            }\r\n            else {\r\n                Debug.echoError(\"Inventory is not a furnace or brewing stand inventory, cannot set fuel.\");\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object InventoryTag\r\n        // @name input\r\n        // @input ItemTag\r\n        // @description\r\n        // Sets the item in the smelting slot of a furnace inventory, or ingredient slot of a brewing stand inventory.\r\n        // @tags\r\n        // <InventoryTag.input>\r\n        // -->\r\n        if ((mechanism.matches(\"input\") || mechanism.matches(\"smelting\")) && mechanism.requireObject(ItemTag.class)) {\r\n            if (inventory instanceof FurnaceInventory) {\r\n                ((FurnaceInventory) inventory).setSmelting(mechanism.valueAsType(ItemTag.class).getItemStack());\r\n            }\r\n            else if (inventory instanceof BrewerInventory) {\r\n                ((BrewerInventory) inventory).setIngredient(mechanism.valueAsType(ItemTag.class).getItemStack());\r\n            }\r\n            else {\r\n                Debug.echoError(\"Inventory is not a furnace inventory, cannot set smelting.\");\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object InventoryTag\r\n        // @name anvil_max_repair_cost\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the maximum repair cost of an anvil.\r\n        // @tags\r\n        // <InventoryTag.anvil_max_repair_cost>\r\n        // -->\r\n        if (mechanism.matches(\"anvil_max_repair_cost\") && mechanism.requireInteger()) {\r\n            if (!(inventory instanceof AnvilInventory)) {\r\n                Debug.echoError(\"Inventory is not an anvil, cannot set max repair cost.\");\r\n                return;\r\n            }\r\n            ((AnvilInventory) inventory).setMaximumRepairCost(mechanism.getValue().asInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object InventoryTag\r\n        // @name anvil_repair_cost\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the current repair cost of an anvil.\r\n        // @tags\r\n        // <InventoryTag.anvil_repair_cost>\r\n        // -->\r\n        if (mechanism.matches(\"anvil_repair_cost\") && mechanism.requireInteger()) {\r\n            if (!(inventory instanceof AnvilInventory)) {\r\n                Debug.echoError(\"Inventory is not an anvil, cannot set repair cost.\");\r\n                return;\r\n            }\r\n            ((AnvilInventory) inventory).setRepairCost(mechanism.getValue().asInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object InventoryTag\r\n        // @name reformat\r\n        // @input ElementTag\r\n        // @description\r\n        // Reformats the contents of an inventory to ensure any items within will be stackable with new Denizen-produced items.\r\n        // This is a simple handy cleanup tool that may sometimes be useful with Denizen updates.\r\n        // This essentially just parses the item to Denizen text, back to an item, and replaces the slot.\r\n        // Input can be \"scripts\" to only change items spawned by item scripts, or \"all\" to change ALL items.\r\n        // Most users are recommended to only use \"scripts\".\r\n        // -->\r\n        if (mechanism.matches(\"reformat\")) {\r\n            ItemStack[] items = inventory.getContents();\r\n            boolean any = false;\r\n            boolean scriptsOnly = CoreUtilities.equalsIgnoreCase(mechanism.getValue().asString(), \"scripts\");\r\n            if (!scriptsOnly && !CoreUtilities.equalsIgnoreCase(mechanism.getValue().asString(), \"all\")) {\r\n                mechanism.echoError(\"Invalid input to 'reformat' mechanism.\");\r\n                return;\r\n            }\r\n            for (int i = 0; i < items.length; i++) {\r\n                ItemStack item = items[i];\r\n                if (item == null) {\r\n                    continue;\r\n                }\r\n                if (scriptsOnly && !ItemScriptHelper.isItemscript(item)) {\r\n                    continue;\r\n                }\r\n                any = true;\r\n                String format = new ItemTag(item).identify();\r\n                ItemTag result = ItemTag.valueOf(format, mechanism.context);\r\n                if (result != null) {\r\n                    items[i] = result.getItemStack();\r\n                }\r\n            }\r\n            if (any) {\r\n                inventory.setContents(items);\r\n            }\r\n        }\r\n    }\r\n\r\n    public boolean compareInventoryToMatch(ScriptEvent.MatchHelper matcher) {\r\n        if (matcher instanceof ScriptEvent.InverseMatchHelper) {\r\n            return !compareInventoryToMatch(((ScriptEvent.InverseMatchHelper) matcher).matcher);\r\n        }\r\n        if (matcher.doesMatch(getInventoryType().name())) {\r\n            return true;\r\n        }\r\n        if (matcher.doesMatch(getIdType())) {\r\n            return true;\r\n        }\r\n        if (matcher.doesMatch(getIdHolder().toString())) {\r\n            return true;\r\n        }\r\n        if (getIdHolder() instanceof ScriptTag && matcher.doesMatch(((ScriptTag) getIdHolder()).getName())) {\r\n            return true;\r\n        }\r\n        String notedId = NoteManager.getSavedId(this);\r\n        if (notedId != null && matcher.doesMatch(notedId)) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean advancedMatches(String comparedto, TagContext context) {\r\n        String matcherLow = CoreUtilities.toLowerCase(comparedto);\r\n        if (matcherLow.equals(\"inventory\")) {\r\n            return true;\r\n        }\r\n        if (matcherLow.equals(\"notable\") || matcherLow.equals(\"note\")) {\r\n            return NoteManager.isSaved(this);\r\n        }\r\n        if (matcherLow.startsWith(\"inventory_flagged:\")) {\r\n            return flagTracker != null && BukkitScriptEvent.coreFlaggedCheck(comparedto.substring(\"inventory_flagged:\".length()), flagTracker);\r\n        }\r\n        if (matcherLow.equals(\"gui\")) {\r\n            return InventoryScriptHelper.isGUI(getInventory());\r\n        }\r\n        ScriptEvent.MatchHelper matcher = BukkitScriptEvent.createMatcher(comparedto);\r\n        return compareInventoryToMatch(matcher);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/ItemTag.java",
    "content": "package com.denizenscript.denizen.objects;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.nms.interfaces.ItemHelper;\nimport com.denizenscript.denizen.objects.properties.item.*;\nimport com.denizenscript.denizen.scripts.containers.core.BookScriptContainer;\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptContainer;\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper;\nimport com.denizenscript.denizen.tags.BukkitTagContext;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizen.utilities.nbt.CustomNBT;\nimport com.denizenscript.denizencore.events.ScriptEvent;\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\nimport com.denizenscript.denizencore.flags.FlaggableObject;\nimport com.denizenscript.denizencore.flags.MapTagFlagTracker;\nimport com.denizenscript.denizencore.objects.*;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ImageTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.PropertyMatchHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizencore.utilities.debugging.Debuggable;\nimport net.kyori.adventure.nbt.StringBinaryTag;\nimport org.bukkit.Bukkit;\nimport org.bukkit.Keyed;\nimport org.bukkit.Material;\nimport org.bukkit.NamespacedKey;\nimport org.bukkit.block.data.BlockData;\nimport org.bukkit.configuration.file.YamlConfiguration;\nimport org.bukkit.enchantments.Enchantment;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.inventory.Recipe;\nimport org.bukkit.inventory.meta.BlockStateMeta;\nimport org.bukkit.inventory.meta.Damageable;\nimport org.bukkit.inventory.meta.ItemMeta;\nimport org.bukkit.inventory.meta.MapMeta;\nimport org.bukkit.map.MapPalette;\nimport org.bukkit.map.MapView;\n\nimport java.awt.image.BufferedImage;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\npublic class ItemTag implements ObjectTag, Adjustable, FlaggableObject {\n\n    // <--[ObjectType]\n    // @name ItemTag\n    // @prefix i\n    // @base ElementTag\n    // @implements FlaggableObject, PropertyHolderObject\n    // @ExampleTagBase player.item_in_hand\n    // @ExampleValues <player.item_in_hand>,stick,iron_sword\n    // @ExampleForReturns\n    // - give %VALUE%\n    // @format\n    // The identity format for items is the basic material type name, or an item script name. Other data is specified in properties.\n    // For example, 'i@stick'.\n    //\n    // @description\n    // An ItemTag represents a holdable item generically.\n    //\n    // ItemTags are temporary objects, to actually modify an item in an inventory you must add the item into that inventory.\n    //\n    // ItemTags do NOT remember where they came from. If you read an item from an inventory, changing it\n    // does not change the original item in the original inventory. You must set it back in.\n    //\n    // Find a list of valid materials at:\n    // <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html>\n    // Note that some materials on that list are exclusively for use with blocks, and cannot be held as items.\n    //\n    // This object type is flaggable.\n    // Flags on this object type will be stored in the item NBT.\n    //\n    // @Matchable\n    // ItemTag matchers, sometimes identified as \"<item>\", often seen as \"with:<item>\":\n    // \"potion\": plaintext: matches if the item is any form of potion item.\n    // \"script\": plaintext: matches if the item is any form of script item.\n    // \"item_flagged:<flag>\": A Flag Matcher for item flags.\n    // \"item_enchanted:<enchantment>\": matches if the item is enchanted with the given enchantment name (excluding enchantment books). Allows advanced matchers.\n    // \"raw_exact:<item>\": matches based on exact raw item data comparison (almost always a bad idea to use).\n    // Item property format: will validate that the item material matches and all directly specified properties also match. Any properties not specified won't be checked.\n    //                       for example \"stick[display=Hi]\" will match any 'stick' with a displayname of 'hi', regardless of whether that stick has lore or not, or has enchantments or not, or etc.\n    // Item script names: matches if the item is a script item with the given item script name, using advanced matchers.\n    // If none of the above are used, uses MaterialTag matchables. Refer to MaterialTag matchable list above.\n    // Note that \"item\" plaintext is always true.\n    //\n    // -->\n\n    //////////////////\n    //    OBJECT FETCHER\n    ////////////////\n\n    public static ItemTag valueOf(String string, PlayerTag player, NPCTag npc) {\n        return valueOf(string, new BukkitTagContext(player, npc, null));\n    }\n\n    public static ItemTag valueOf(String string, Debuggable debugMe) {\n        return valueOf(string, new BukkitTagContext(null, null, null, debugMe == null || debugMe.shouldDebug(), null));\n    }\n\n    public static ItemTag valueOf(String string, boolean debugMe) {\n        return valueOf(string, new BukkitTagContext(null, null, null, debugMe, null));\n    }\n\n    @Fetchable(\"i\")\n    public static ItemTag valueOf(String string, TagContext context) {\n        if (string == null || string.equals(\"\")) {\n            return null;\n        }\n        ItemTag stack = null;\n        if (ObjectFetcher.isObjectWithProperties(string)) {\n            return ObjectFetcher.getObjectFromWithProperties(ItemTag.class, string, context);\n        }\n        if (string.startsWith(\"i@\")) {\n            string = string.substring(\"i@\".length());\n        }\n        string = CoreUtilities.toLowerCase(string);\n        try {\n            if (ScriptRegistry.containsScript(string, ItemScriptContainer.class)) {\n                ItemScriptContainer isc = ScriptRegistry.getScriptContainer(string);\n                // TODO: If a script does not contain tags, get the clean reference here.\n                stack = isc.getItemFrom(context);\n                if (stack == null && (context == null || context.showErrors())) {\n                    Debug.echoError(\"Item script '\" + isc.getName() + \"' returned a null item.\");\n                }\n            }\n            else if (ScriptRegistry.containsScript(string, BookScriptContainer.class)) {\n                BookScriptContainer book = ScriptRegistry.getScriptContainer(string);\n                stack = book.getBookFrom(context);\n                if (stack == null && (context == null || context.showErrors())) {\n                    Debug.echoError(\"Book script '\" + book.getName() + \"' returned a null item.\");\n                }\n            }\n            if (stack != null) {\n                return stack;\n            }\n        }\n        catch (Exception ex) {\n            if (CoreConfiguration.debugVerbose) {\n                Debug.echoError(ex);\n            }\n        }\n        try {\n            MaterialTag mat = MaterialTag.valueOf(string, context);\n            if (mat != null) {\n                stack = new ItemTag(mat.getMaterial());\n            }\n            if (stack != null) {\n                return stack;\n            }\n        }\n        catch (Exception ex) {\n            if (!string.equalsIgnoreCase(\"none\") && (context == null || context.showErrors())) {\n                Debug.log(\"Does not match a valid item ID or material: \" + string);\n            }\n            if (CoreConfiguration.debugVerbose) {\n                Debug.echoError(ex);\n            }\n        }\n        if (context == null || context.showErrors()) {\n            Debug.log(\"valueOf ItemTag returning null: \" + string);\n        }\n        return null;\n    }\n\n    public static boolean matches(String arg) {\n        if (arg == null) {\n            return false;\n        }\n        if (CoreUtilities.toLowerCase(arg).startsWith(\"i@\")) {\n            return true;\n        }\n        if (ScriptRegistry.containsScript(arg, ItemScriptContainer.class)) {\n            return true;\n        }\n        else if (ScriptRegistry.containsScript(arg, BookScriptContainer.class)) {\n            return true;\n        }\n        if (valueOf(arg, CoreUtilities.noDebugContext) != null) {\n            return true;\n        }\n        return false;\n    }\n\n    public static ItemTag getItemFor(ObjectTag object, TagContext context) {\n        return object instanceof ItemTag ? (ItemTag) object : valueOf(object.toString(), context);\n    }\n\n    @Override\n    public ObjectTag duplicate() {\n        return new ItemTag(item.clone());\n    }\n\n    ///////////////\n    //   Constructors\n    /////////////\n\n    public ItemTag(Material material) {\n        this(new ItemStack(material));\n    }\n\n    public ItemTag(Material material, int qty) {\n        this(new ItemStack(material, qty));\n    }\n\n    public ItemTag(MaterialTag material, int qty) {\n        this.item = new ItemStack(material.getMaterial(), qty);\n    }\n\n    public ItemTag(ItemStack item) {\n        if (item == null || item.getType() == Material.AIR) {\n            this.item = new ItemStack(Material.AIR, 0);\n        }\n        else {\n            this.item = item.clone();\n        }\n    }\n\n    /////////////////////\n    //   INSTANCE FIELDS/METHODS\n    /////////////////\n\n    @Override\n    public AbstractFlagTracker getFlagTracker() {\n        if (flagTrackerCache == null) {\n            String value = CustomNBT.getCustomNBT(getItemStack(), \"flags\", \"Denizen\");\n            if (value == null) {\n                return new MapTagFlagTracker();\n            }\n            flagTrackerCache = new MapTagFlagTracker(value, CoreUtilities.noDebugContext);\n        }\n        return flagTrackerCache;\n    }\n\n    @Override\n    public void reapplyTracker(AbstractFlagTracker tracker) {\n        if (tracker instanceof MapTagFlagTracker && ((MapTagFlagTracker) tracker).map.isEmpty()) {\n            setItemStack(CustomNBT.removeCustomNBT(getItemStack(), \"flags\", \"Denizen\"));\n        }\n        else {\n            setItemStack(CustomNBT.addCustomNBT(getItemStack(), \"flags\", tracker.toString(), \"Denizen\"));\n        }\n        flagTrackerCache = tracker;\n    }\n\n    private ItemStack item;\n\n    public ItemMeta metaCache;\n\n    public AbstractFlagTracker flagTrackerCache;\n\n    public ItemStack getItemStack() {\n        return item;\n    }\n\n    public ItemMeta getItemMeta() {\n        if (metaCache == null) {\n            metaCache = item.getItemMeta();\n        }\n        return metaCache;\n    }\n\n    public void setItemMeta(ItemMeta meta) {\n        this.metaCache = meta;\n        item.setItemMeta(meta);\n    }\n\n    public void setItemStack(ItemStack item) {\n        this.item = item;\n        resetCache();\n    }\n\n    public void resetCache() {\n        metaCache = null;\n        flagTrackerCache = null;\n    }\n\n    // Compare item to item.\n    // -1 indicates it is not a match\n    //  0 indicates it is a perfect match\n    //  1 or higher indicates the item being matched against\n    //    was probably originally alike, but may have been\n    //    modified or enhanced.\n\n    public int comparesTo(ItemTag item) {\n        return comparesTo(item.getItemStack());\n    }\n\n    public int comparesTo(ItemStack compared_to) {\n        if (item == null) {\n            return -1;\n        }\n        int determination = 0;\n        ItemStack compared = getItemStack();\n        if (compared.getType() != compared_to.getType()) {\n            return -1;\n        }\n        if (compared_to.hasItemMeta()) {\n            if (!compared.hasItemMeta()) {\n                return -1;\n            }\n            ItemMeta thisMeta = getItemMeta();\n            ItemMeta comparedItemMeta = compared_to.getItemMeta();\n            if (comparedItemMeta.hasDisplayName()) {\n                if (!thisMeta.hasDisplayName()) {\n                    return -1;\n                }\n                if (CoreUtilities.toLowerCase(comparedItemMeta.getDisplayName()).startsWith(CoreUtilities.toLowerCase(thisMeta.getDisplayName()))) {\n                    if (thisMeta.getDisplayName().length() > comparedItemMeta.getDisplayName().length()) {\n                        determination++;\n                    }\n                }\n                else {\n                    return -1;\n                }\n            }\n            if (comparedItemMeta.hasLore()) {\n                if (!thisMeta.hasLore()) {\n                    return -1;\n                }\n                for (String lore : comparedItemMeta.getLore()) {\n                    if (!thisMeta.getLore().contains(lore)) {\n                        return -1;\n                    }\n                }\n                if (thisMeta.getLore().size() > comparedItemMeta.getLore().size()) {\n                    determination++;\n                }\n            }\n            if (!comparedItemMeta.getEnchants().isEmpty()) {\n                if (thisMeta.getEnchants().isEmpty()) {\n                    return -1;\n                }\n                for (Map.Entry<Enchantment, Integer> enchant : comparedItemMeta.getEnchants().entrySet()) {\n                    if (!thisMeta.getEnchants().containsKey(enchant.getKey()) || thisMeta.getEnchants().get(enchant.getKey()) < enchant.getValue()) {\n                        return -1;\n                    }\n                }\n                if (thisMeta.getEnchants().size() > comparedItemMeta.getEnchants().size()) {\n                    determination++;\n                }\n            }\n            if (isRepairable()) {\n                if (((Damageable) thisMeta).getDamage() < ((Damageable) comparedItemMeta).getDamage()) {\n                    determination++;\n                }\n            }\n        }\n        return determination;\n    }\n\n    public boolean isItemscript() {\n        return ItemScriptHelper.isItemscript(item);\n    }\n\n    public String getScriptName() {\n        return ItemScriptHelper.getItemScriptNameText(item);\n    }\n\n    public void setItemScriptName(String name) {\n        setItemStack(NMSHandler.itemHelper.addNbtData(getItemStack(), \"DenizenItemScript\", StringBinaryTag.stringBinaryTag(CoreUtilities.toLowerCase(name))));\n    }\n\n    public void setItemScript(ItemScriptContainer script) {\n        if (script.contains(\"NO_ID\", String.class) && Boolean.parseBoolean(script.getString(\"NO_ID\"))) {\n            return;\n        }\n        setItemScriptName(script.getName());\n    }\n\n    public Material getBukkitMaterial() {\n        return getItemStack().getType();\n    }\n\n    public MaterialTag getMaterial() {\n        return new MaterialTag(getBukkitMaterial());\n    }\n\n    public void setAmount(int value) {\n        if (item != null) {\n            item.setAmount(value);\n        }\n    }\n\n    public int getAmount() {\n        if (item != null) {\n            return item.getAmount();\n        }\n        else {\n            return 0;\n        }\n    }\n\n    public void setDurability(short value) {\n        ItemMeta meta = getItemMeta();\n        if (meta instanceof Damageable) {\n            ((Damageable) meta).setDamage(value);\n            setItemMeta(meta);\n        }\n    }\n\n    public boolean isRepairable() {\n        return getItemMeta() instanceof Damageable;\n    }\n\n    public boolean matchesRawExact(ItemTag item) {\n        ItemTag thisItem = this;\n        if (thisItem.getItemStack().getAmount() != 1) {\n            thisItem = new ItemTag(thisItem.getItemStack().clone());\n            thisItem.getItemStack().setAmount(1);\n        }\n        if (item.getItemStack().getAmount() != 1) {\n            item = new ItemTag(item.getItemStack().clone());\n            item.getItemStack().setAmount(1);\n        }\n        return thisItem.identify().equals(item.identify());\n    }\n\n    //////////////////////////////\n    //  DSCRIPT ARGUMENT METHODS\n    /////////////////////////\n\n    private String prefix = \"Item\";\n\n    @Override\n    public String getPrefix() {\n        return prefix;\n    }\n\n    @Override\n    public ItemTag setPrefix(String prefix) {\n        this.prefix = prefix;\n        return this;\n    }\n\n    @Override\n    public String identify() {\n        if (item == null || item.getType() == Material.AIR) {\n            return \"i@air\";\n        }\n        return \"i@\" + getMaterial().identifyNoPropertiesNoIdentifier() + PropertyParser.getPropertiesString(this);\n    }\n\n    @Override\n    public String debuggable() {\n        if (item == null || item.getType() == Material.AIR) {\n            return \"<LG>i@<Y>air\";\n        }\n        return \"<LG>i@<Y>\" + getMaterial().identifyNoPropertiesNoIdentifier() + PropertyParser.getPropertiesDebuggable(this);\n    }\n\n    @Override\n    public String identifySimple() {\n        if (item == null) {\n            return \"null\";\n        }\n        if (item.getType() != Material.AIR) {\n            if (isItemscript()) {\n                return \"i@\" + getScriptName();\n            }\n        }\n        return \"i@\" + identifyMaterial().replace(\"m@\", \"\");\n    }\n\n    public String identifyMaterial() {\n        return getMaterial().identifySimple();\n    }\n\n    @Override\n    public String toString() {\n        return identify();\n    }\n\n    @Override\n    public Object getJavaObject() {\n        return getItemStack();\n    }\n\n    @Override\n    public boolean isUnique() {\n        return false;\n    }\n\n    @Override\n    public boolean isTruthy() {\n        return !getBukkitMaterial().isAir();\n    }\n\n    public static void register() {\n\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\n        PropertyParser.registerPropertyTagHandlers(ItemTag.class, tagProcessor);\n\n        // <--[tag]\n        // @attribute <ItemTag.repairable>\n        // @returns ElementTag(Boolean)\n        // @group properties\n        // @description\n        // Returns whether the item can be repaired.\n        // If this returns true, it will enable access to:\n        // <@link mechanism ItemTag.durability>,\n        // <@link tag ItemTag.max_durability>, and <@link tag ItemTag.durability>.\n        // Note that due to odd design choices in Spigot, this is effectively true for all items, even though the durability value of most items is locked at zero.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"repairable\", (attribute, object) -> {\n            return new ElementTag(ItemDurability.describes(object));\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.is_book>\n        // @returns ElementTag(Boolean)\n        // @group properties\n        // @description\n        // Returns whether the item is considered an editable book.\n        // If this returns true, it will enable access to:\n        // <@link mechanism ItemTag.book>,\n        // <@link tag ItemTag.book_author>, <@link tag ItemTag.book_title>, and <@link tag ItemTag.book_pages>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_book\", (attribute, object) -> {\n            return new ElementTag(ItemBook.describes(object));\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.is_colorable>\n        // @returns ElementTag(Boolean)\n        // @group properties\n        // @description\n        // Returns whether the item can have a custom color.\n        // If this returns true, it will enable access to:\n        // <@link mechanism ItemTag.color>, and <@link tag ItemTag.color>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_colorable\", (attribute, object) -> {\n            return new ElementTag(ItemColor.describes(object));\n        });\n\n        tagProcessor.registerTag(ElementTag.class, \"is_dyeable\", (attribute, object) -> {\n            return new ElementTag(ItemColor.describes(object));\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.is_firework>\n        // @returns ElementTag(Boolean)\n        // @group properties\n        // @description\n        // Returns whether the item is a firework.\n        // If this returns true, it will enable access to:\n        // <@link mechanism ItemTag.firework>, and <@link tag ItemTag.firework>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_firework\", (attribute, object) -> {\n            return new ElementTag(ItemFirework.describes(object));\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.has_inventory>\n        // @returns ElementTag(Boolean)\n        // @group properties\n        // @description\n        // Returns whether the item has an inventory.\n        // If this returns true, it will enable access to:\n        // <@link mechanism ItemTag.inventory_contents>, and <@link tag ItemTag.inventory_contents>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"has_inventory\", (attribute, object) -> {\n            return new ElementTag(ItemInventoryContents.describes(object));\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.is_lockable>\n        // @returns ElementTag(Boolean)\n        // @group properties\n        // @description\n        // Returns whether the item is lockable.\n        // If this returns true, it will enable access to:\n        // <@link mechanism ItemTag.lock>, and <@link tag ItemTag.lock>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_lockable\", (attribute, object) -> {\n            return new ElementTag(ItemLock.describes(object));\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.material>\n        // @returns MaterialTag\n        // @mechanism ItemTag.material\n        // @group conversion\n        // @description\n        // Returns the MaterialTag that is the basis of the item.\n        // EG, a stone with lore and a display name, etc. will return only \"m@stone\".\n        // -->\n        tagProcessor.registerTag(ObjectTag.class, \"material\", (attribute, object) -> {\n            if (attribute.getAttribute(2).equals(\"formatted\")) {\n                return object;\n            }\n            if (object.getItemMeta() instanceof BlockStateMeta) {\n                if (object.getBukkitMaterial() == Material.SHIELD) {\n                    MaterialTag material = new MaterialTag(Material.SHIELD);\n                    material.setModernData(((BlockStateMeta) object.getItemMeta()).getBlockState().getBlockData());\n                    return material;\n                }\n                return new MaterialTag(((BlockStateMeta) object.getItemMeta()).getBlockState());\n            }\n            return object.getMaterial();\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.placed_material>\n        // @returns MaterialTag\n        // @group conversion\n        // @description\n        // Returns the MaterialTag that this item would place as a block, if it is a block-like item.\n        // For example, the \"redstone\" item will return a \"redstone_wire\" block.\n        // Returns null if the item doesn't place as a block.\n        // -->\n        tagProcessor.registerTag(ObjectTag.class, \"placed_material\", (attribute, object) -> {\n            BlockData data = NMSHandler.itemHelper.getPlacedBlock(object.getBukkitMaterial());\n            if (data == null) {\n                return null;\n            }\n            return new MaterialTag(data);\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.json>\n        // @returns ElementTag\n        // @group conversion\n        // @description\n        // Returns the item converted to a raw JSON object with one layer of escaping for network transmission.\n        // EG, via /tellraw.\n        // Generally, prefer tags like <@link tag ElementTag.on_hover.type> with type 'show_item'.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"json\", (attribute, object) -> {\n            return new ElementTag(NMSHandler.itemHelper.getJsonString(object.item));\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.meta_type>\n        // @returns ElementTag\n        // @group conversion\n        // @description\n        // Returns the name of the Bukkit item meta type that applies to this item.\n        // This is for debugging purposes.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"meta_type\", (attribute, object) -> {\n            return new ElementTag(object.getItemMeta().getClass().getName());\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.bukkit_serial>\n        // @returns ElementTag\n        // @group conversion\n        // @description\n        // Returns a YAML text section representing the Bukkit serialization of the item, under subkey \"item\".\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"bukkit_serial\", (attribute, object) -> {\n            YamlConfiguration config = new YamlConfiguration();\n            config.set(\"item\", object.getItemStack());\n            return new ElementTag(config.saveToString());\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.simple>\n        // @returns ElementTag\n        // @group conversion\n        // @description\n        // Returns a simple reusable item identification for this item, with minimal extra data.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"simple\", (attribute, object) -> {\n            return new ElementTag(object.identifySimple());\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.recipe_ids[(<type>)]>\n        // @returns ListTag\n        // @description\n        // If the item is a scripted item, returns a list of all recipe IDs created by the item script.\n        // Others, returns a list of all recipe IDs that the server lists as capable of crafting the item.\n        // Returns a list in the Namespace:Key format, for example \"minecraft:gold_nugget\".\n        // Optionally, specify a recipe type (CRAFTING, FURNACE, COOKING, BLASTING, SHAPED, SHAPELESS, SMOKING, STONECUTTING, BREWING)\n        // to limit to just recipes of that type.\n        // Brewing recipes are only supported on Paper, and only custom ones are available.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"recipe_ids\", (attribute, object) -> {\n            String type = attribute.hasParam() ? CoreUtilities.toLowerCase(attribute.getParam()) : null;\n            ItemScriptContainer container = ItemScriptHelper.getItemScriptContainer(object.getItemStack());\n            ListTag list = new ListTag();\n            Consumer<NamespacedKey> addRecipe = (recipe) -> {\n                if (CoreUtilities.equalsIgnoreCase(recipe.getNamespace(), \"denizen\")) {\n                    if (container != ItemScriptHelper.recipeIdToItemScript.get(recipe.toString())) {\n                        return;\n                    }\n                }\n                else if (container != null) {\n                    return;\n                }\n                list.add(recipe.toString());\n            };\n            if (type == null || !type.equals(\"brewing\")) {\n                for (Recipe recipe : Bukkit.getRecipesFor(object.getItemStack())) {\n                    if (recipe instanceof Keyed keyedRecipe && Utilities.isRecipeOfType(recipe, type)) {\n                        addRecipe.accept(keyedRecipe.getKey());\n                    }\n                }\n            }\n            if (Denizen.supportsPaper && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18) && (type == null || type.equals(\"brewing\"))) {\n                for (Map.Entry<NamespacedKey, ItemHelper.BrewingRecipe> entry : NMSHandler.itemHelper.getCustomBrewingRecipes().entrySet()) {\n                    ItemStack result = entry.getValue().result();\n                    if (object.getBukkitMaterial() == result.getType() && (object.getItemStack().getDurability() == -1 || object.getItemStack().getDurability() == result.getDurability())) {\n                        addRecipe.accept(entry.getKey());\n                    }\n                }\n            }\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.formatted>\n        // @returns ElementTag\n        // @group formatting\n        // @description\n        // Returns the formatted material name of the item to be used in a sentence.\n        // Correctly uses singular and plural forms of item names, among other things.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"formatted\", (attribute, object) -> {\n            return new ElementTag(object.formattedName());\n        });\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name material\n        // @input MaterialTag\n        // @description\n        // Changes the item's material to the given material.\n        // Only copies the base material type, not any advanced block-data material properties.\n        // Note that this may cause some properties of the item to be lost.\n        // @tags\n        // <ItemTag.material>\n        // -->\n        tagProcessor.registerMechanism(\"material\", true, MaterialTag.class, (object, mechanism, material) -> {\n            object.item.setType(material.getMaterial());\n        });\n\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\n\n            // <--[tag]\n            // @attribute <ItemTag.map_to_image[<player>]>\n            // @returns ImageTag\n            // @description\n            // Returns an image of a filled map item's contents.\n            // Must specify a player for the map to render for, as if that player is holding the map.\n            // Note that this does not include cursors, as their rendering is entirely client-side.\n            // -->\n            tagProcessor.registerTag(ImageTag.class, PlayerTag.class, \"map_to_image\", (attribute, object, input) -> {\n                if (!(object.getItemMeta() instanceof MapMeta mapMeta)) {\n                    return null;\n                }\n                MapView mapView = mapMeta.getMapView();\n                if (mapView == null) {\n                    attribute.echoError(\"Invalid map item: must have contents.\");\n                    return null;\n                }\n                byte[] data = NMSHandler.itemHelper.renderMap(mapView, input.getPlayerEntity());\n                BufferedImage image = new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB);\n                for (int x = 0; x < 128; x++) {\n                    for (int y = 0; y < 128; y++) {\n                        image.setRGB(x, y, MapPalette.getColor(data[y * 128 + x]).getRGB());\n                    }\n                }\n                return new ImageTag(image);\n            });\n        }\n    }\n\n    public String formattedName() {\n        String id = CoreUtilities.toLowerCase(getMaterial().name()).replace('_', ' ');\n        if (id.equals(\"air\")) {\n            return \"nothing\";\n        }\n        if (id.equals(\"ice\") || id.equals(\"dirt\") || id.endsWith(\"copper\") || id.endsWith(\"cream\")) {\n            return id;\n        }\n        if (getItemStack().getAmount() > 1) {\n            if (id.equals(\"cactus\")) {\n                return \"cacti\";\n            }\n            if (id.endsWith(\" off\")) {\n                id = id.substring(0, id.length() - 4);\n            }\n            if (id.endsWith(\" on\")) {\n                id = id.substring(0, id.length() - 3);\n            }\n            if (id.equals(\"rotten flesh\") || id.equals(\"cooked fish\")\n                    || id.equals(\"raw fish\") || id.endsWith(\"s\")) {\n                return id;\n            }\n            if (id.endsWith(\"y\")) {\n                return id.substring(0, id.length() - 1) + \"ies\";  // ex: lily -> lilies\n            }\n            if (id.endsWith(\"sh\") || id.endsWith(\"ch\")) {\n                return id + \"es\";\n            }\n            return id + \"s\"; // iron sword -> iron swords\n        }\n        else {\n            if (id.equals(\"cactus\")) {\n                return \"a cactus\";\n            }\n            if (id.endsWith(\"s\")) {\n                return id;\n            }\n            if (id.endsWith(\" off\")) {\n                return \"a \" + id.substring(0, id.length() - 4);\n            }\n            if (id.endsWith(\" on\")) {\n                return \"a \" + id.substring(0, id.length() - 3);\n            }\n            if (id.startsWith(\"a\") || id.startsWith(\"e\") || id.startsWith(\"i\")\n                    || id.startsWith(\"o\") || id.startsWith(\"u\")) {\n                return \"an \" + id; // ex: emerald -> an emerald\n            }\n            return \"a \" + id; // ex: diamond -> a diamond\n        }\n    }\n\n    public static ObjectTagProcessor<ItemTag> tagProcessor = new ObjectTagProcessor<>();\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n        return tagProcessor.getObjectAttribute(this, attribute);\n    }\n\n    public void applyProperty(Mechanism mechanism) {\n        adjust(mechanism);\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n        tagProcessor.processMechanism(this, mechanism);\n    }\n\n    @Override\n    public boolean advancedMatches(String matcher, TagContext context) {\n        String matcherLow = CoreUtilities.toLowerCase(matcher);\n        if (matcherLow.contains(\":\")) {\n            if (matcherLow.startsWith(\"item_flagged:\")) {\n                if (getBukkitMaterial().isAir()) {\n                    return false;\n                }\n                return BukkitScriptEvent.coreFlaggedCheck(matcher.substring(\"item_flagged:\".length()), getFlagTracker());\n            }\n            else if (matcherLow.startsWith(\"item_enchanted:\")) {\n                String enchMatcherStr = matcher.substring(\"item_enchanted:\".length());\n                if (getBukkitMaterial().isAir() || !getItemMeta().hasEnchants()) {\n                    return false;\n                }\n                ScriptEvent.MatchHelper enchMatcher = ScriptEvent.createMatcher(enchMatcherStr);\n                for (Enchantment enchant : getItemMeta().getEnchants().keySet()) {\n                    if (enchMatcher.doesMatch(enchant.getKey().getKey()) || enchMatcher.doesMatch(enchant.getKey().toString())) {\n                        return true;\n                    }\n                }\n                return false;\n            }\n            else if (matcherLow.startsWith(\"raw_exact:\")) {\n                ItemTag compareItem = ItemTag.valueOf(matcher.substring(\"raw_exact:\".length()), CoreUtilities.errorButNoDebugContext);\n                return compareItem != null && compareItem.matchesRawExact(this);\n            }\n        }\n        if (matcherLow.equals(\"potion\") && CoreUtilities.toLowerCase(getBukkitMaterial().name()).contains(\"potion\")) {\n            return true;\n        }\n        boolean isItemScript = isItemscript();\n        if (matcherLow.equals(\"script\") && isItemScript) {\n            return true;\n        }\n        if (matcher.contains(\"[\") && matcher.endsWith(\"]\")) {\n            PropertyMatchHelper<ItemTag> helper = PropertyMatchHelper.getPropertyMatchHelper(ItemTag.class, matcher, (actual, compare) -> {\n                return actual.getBukkitMaterial() == compare.getBukkitMaterial();\n            });\n            if (helper == null) {\n                return false;\n            }\n            return helper.doesMatch(this);\n        }\n        if (isItemScript) {\n            ScriptEvent.MatchHelper matchHelper = BukkitScriptEvent.createMatcher(matcher);\n            if (matchHelper.doesMatch(getScriptName())) {\n                return true;\n            }\n        }\n        return MaterialTag.advancedMatchesInternal(getBukkitMaterial(), matcher, !isItemScript);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/LocationTag.java",
    "content": "package com.denizenscript.denizen.objects;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\nimport com.denizenscript.denizen.objects.properties.material.MaterialAttachmentFace;\nimport com.denizenscript.denizen.objects.properties.material.MaterialDirectional;\nimport com.denizenscript.denizen.objects.properties.material.MaterialDistance;\nimport com.denizenscript.denizen.objects.properties.material.MaterialHalf;\nimport com.denizenscript.denizen.scripts.commands.world.SwitchCommand;\nimport com.denizenscript.denizen.utilities.*;\nimport com.denizenscript.denizen.utilities.blocks.SpawnableHelper;\nimport com.denizenscript.denizen.utilities.flags.DataPersistenceFlagTracker;\nimport com.denizenscript.denizen.utilities.flags.LocationFlagSearchHelper;\nimport com.denizenscript.denizen.utilities.world.PathFinder;\nimport com.denizenscript.denizen.utilities.world.WorldListChangeTracker;\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\nimport com.denizenscript.denizencore.flags.FlaggableObject;\nimport com.denizenscript.denizencore.objects.*;\nimport com.denizenscript.denizencore.objects.core.*;\nimport com.denizenscript.denizencore.objects.notable.Notable;\nimport com.denizenscript.denizencore.objects.notable.Note;\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.tags.TagManager;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.SimplexNoise;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\nimport net.citizensnpcs.api.CitizensAPI;\nimport net.citizensnpcs.api.npc.NPC;\nimport org.bukkit.*;\nimport org.bukkit.block.*;\nimport org.bukkit.block.banner.PatternType;\nimport org.bukkit.block.data.BlockData;\nimport org.bukkit.block.data.Directional;\nimport org.bukkit.block.structure.Mirror;\nimport org.bukkit.block.structure.StructureRotation;\nimport org.bukkit.block.structure.UsageMode;\nimport org.bukkit.entity.*;\nimport org.bukkit.event.inventory.InventoryType;\nimport org.bukkit.inventory.*;\nimport org.bukkit.loot.LootTable;\nimport org.bukkit.loot.Lootable;\nimport org.bukkit.material.Attachable;\nimport org.bukkit.material.MaterialData;\nimport org.bukkit.potion.PotionEffect;\nimport org.bukkit.potion.PotionEffectType;\nimport org.bukkit.util.Vector;\nimport org.bukkit.util.*;\n\nimport java.lang.ref.WeakReference;\nimport java.util.*;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\npublic class LocationTag extends org.bukkit.Location implements VectorObject, ObjectTag, Notable, Adjustable, FlaggableObject {\n\n    // <--[ObjectType]\n    // @name LocationTag\n    // @prefix l\n    // @base ElementTag\n    // @implements FlaggableObject, VectorObject\n    // @ExampleTagBase player.location\n    // @ExampleValues <player.location>,<npc.location>\n    // @ExampleForReturns\n    // - teleport <player> %VALUE%\n    // @format\n    // The identity format for locations is <x>,<y>,<z>,<pitch>,<yaw>,<world>\n    // Note that you can leave off the world, and/or pitch and yaw, and/or the z value.\n    // You cannot leave off both the z and the pitch+yaw at the same time.\n    // For example, 'l@1,2.15,3,45,90,space' or 'l@7.5,99,3.2'\n    //\n    // @description\n    // A LocationTag represents a point in the world.\n    //\n    // Note that 'l' prefix is a lowercase 'L', the first letter in 'location'.\n    //\n    // This object type is flaggable.\n    // Flags on this object type will be stored in the chunk file inside the world folder.\n    //\n    // @Matchable\n    // LocationTag matchers, sometimes identified as \"<location>\" or \"<block>\":\n    // \"location\" plaintext: always matches.\n    // \"block_flagged:<flag>\": a Flag Matchable for location flags at the given block location.\n    // \"location_in:<area>\": runs AreaObject checks, as defined below.\n    // If none of the above are used, and the location is at a real block, a MaterialTag matchable is used. Refer to <@link objecttype MaterialTag> matchable list.\n    //\n    // -->\n\n    /**\n     * The world name if a world reference is bad.\n     */\n    public String backupWorld;\n    public int trackedWorldChange;\n    public WeakReference<World> internalWorld;\n\n    public String getWorldName() {\n        if (backupWorld != null) {\n            return backupWorld;\n        }\n        World w = internalWorld == null ? null : internalWorld.get();\n        if (w != null) {\n            backupWorld = w.getName();\n        }\n        return backupWorld;\n    }\n\n    @Override\n    public World getWorld() {\n        World w = internalWorld == null ? null : internalWorld.get();\n        if (w != null) {\n            if (trackedWorldChange == WorldListChangeTracker.changes) {\n                return w;\n            }\n            if (backupWorld == null) {\n                backupWorld = w.getName();\n            }\n        }\n        if (backupWorld == null) {\n            return null;\n        }\n        trackedWorldChange = WorldListChangeTracker.changes;\n        w = Bukkit.getWorld(backupWorld);\n        internalWorld = new WeakReference<>(w);\n        super.setWorld(w);\n        return w;\n    }\n\n    @Override\n    public LocationTag clone() {\n        return (LocationTag) super.clone();\n    }\n\n    public void makeUnique(String id) {\n        NoteManager.saveAs(this, id);\n    }\n\n    @Note(\"Locations\")\n    public String getSaveObject() {\n        return getX() + \",\" + getY() + \",\" + getZ() + \",\" + getPitch() + \",\" + getYaw() + \",\" + getWorldName();\n    }\n\n    public static String getSaved(LocationTag location) {\n        return NoteManager.getSavedId(location);\n    }\n\n    @Override\n    public void forget() {\n        NoteManager.remove(this);\n    }\n\n    @Fetchable(\"l\")\n    public static LocationTag valueOf(String string, TagContext context) {\n        if (string == null) {\n            return null;\n        }\n        if (string.startsWith(\"l@\")) {\n            string = string.substring(2);\n        }\n        if (!TagManager.isStaticParsing) {\n            Notable noted = NoteManager.getSavedObject(string);\n            if (noted instanceof LocationTag) {\n                return (LocationTag) noted;\n            }\n        }\n        List<String> split = CoreUtilities.split(string, ',');\n        try {\n            int size = split.size();\n            if (size < 2 || size > 6) {\n                if ((context == null || context.showErrors()) && !TagManager.isStaticParsing) {\n                    Debug.log(\"Minor: valueOf LocationTag returning null, not formatted as a LocationTag: \" + string);\n                }\n                return null;\n            }\n            // If 2 values, worldless 2D location format: x,y\n            // If 3 values, worldless location format: x,y,z\n            // If 4 values, standard dScript location format: x,y,z,world\n            // If 5 values, worldless location with pitch/yaw: x,y,z,pitch,yaw\n            // If 6 values, location with pitch/yaw: x,y,z,pitch,yaw,world\n            double x = Double.parseDouble(split.get(0));\n            double y = Double.parseDouble(split.get(1));\n            double z = 0;\n            float yaw = 0, pitch = 0;\n            String world = null;\n            if (size > 2) {\n                z = Double.parseDouble(split.get(2));\n            }\n            if (size == 5 || size == 6) {\n                pitch = Float.parseFloat(split.get(3));\n                yaw = Float.parseFloat(split.get(4));\n            }\n            if (size == 4 || size == 6) {\n                world = split.get(size - 1);\n                if (world.startsWith(\"w@\")) {\n                    world = world.substring(\"w@\".length());\n                }\n            }\n            return new LocationTag(world, x, y, z, yaw, pitch);\n        }\n        catch (Exception e) {\n            if (context == null || context.showErrors()) {\n                Debug.log(\"Minor: valueOf LocationTag returning null: \" + string + \"(internal exception:\" + e.getMessage() + \")\");\n            }\n            return null;\n        }\n    }\n\n    public static boolean matches(String string) {\n        if (string == null || string.length() == 0) {\n            return false;\n        }\n        if (string.startsWith(\"l@\")) {\n            return true;\n        }\n        return LocationTag.valueOf(string, CoreUtilities.noDebugContext) != null;\n    }\n\n    @Override\n    public LocationTag duplicate() {\n        return clone();\n    }\n\n    /**\n     * Turns a Bukkit Location into a LocationTag, which has some helpful methods\n     * for working with dScript.\n     *\n     * @param location the Bukkit Location to reference\n     */\n    public LocationTag(Location location) {\n        this(location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());\n        if (location instanceof LocationTag loctag) {\n            backupWorld = loctag.backupWorld;\n            internalWorld = loctag.internalWorld;\n        }\n    }\n\n    public LocationTag(Vector vector) {\n        this((World) null, vector.getX(), vector.getY(), vector.getZ(), 0, 0);\n    }\n\n    public LocationTag(World world, Vector vector) {\n        this(world, vector.getX(), vector.getY(), vector.getZ(), 0, 0);\n    }\n\n    public LocationTag(World world, double x, double y) {\n        this(world, x, y, 0);\n    }\n\n    /**\n     * Turns a world and coordinates into a Location, which has some helpful methods\n     * for working with dScript.\n     *\n     * @param world the world in which the location resides\n     * @param x     x-coordinate of the location\n     * @param y     y-coordinate of the location\n     * @param z     z-coordinate of the location\n     */\n    public LocationTag(World world, double x, double y, double z) {\n        this(world, x, y, z, 0, 0);\n    }\n\n    public LocationTag(double x, double y, double z, String worldName) {\n        super(worldName == null ? null : Bukkit.getWorld(worldName), x, y, z);\n        backupWorld = worldName;\n    }\n\n    public LocationTag(World world, double x, double y, double z, float yaw, float pitch) {\n        super(world, x, y, z, EntityHelper.normalizeYaw(yaw), pitch);\n        if (world != null) {\n            backupWorld = world.getName();\n            internalWorld = new WeakReference<>(world);\n        }\n    }\n\n    public LocationTag(String worldName, double x, double y, double z, float yaw, float pitch) {\n        super(worldName == null ? null : Bukkit.getWorld(worldName), x, y, z, EntityHelper.normalizeYaw(yaw), pitch);\n        backupWorld = worldName;\n    }\n\n    public boolean isChunkLoaded() {\n        return getWorld() != null && getWorld().isChunkLoaded(getBlockX() >> 4, getBlockZ() >> 4);\n    }\n\n    public boolean isChunkLoadedSafe() {\n        try {\n            NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n            return isChunkLoaded();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    @Override\n    public Block getBlock() {\n        if (getWorld() == null) {\n            Debug.echoError(\"LocationTag trying to read block, but cannot because no world is specified.\");\n            return null;\n        }\n        return super.getBlock();\n    }\n\n    public Block getBlockForTag(Attribute attribute) {\n        NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n        try {\n            if (getWorld() == null) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because no world is specified.\");\n                return null;\n            }\n            if (!isChunkLoaded()) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because the chunk is unloaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                return null;\n            }\n            return super.getBlock();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    public BlockData getBlockDataForTag(Attribute attribute) {\n        NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n        try {\n            if (getWorld() == null) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because no world is specified.\");\n                return null;\n            }\n            if (!isChunkLoaded()) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because the chunk is unloaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                return null;\n            }\n            return super.getBlock().getBlockData();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    public Material getBlockTypeForTag(Attribute attribute) {\n        NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n        try {\n            if (getWorld() == null) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because no world is specified.\");\n                return null;\n            }\n            if (!isChunkLoaded()) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because the chunk is unloaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                return null;\n            }\n            return super.getBlock().getType();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    public static BlockState getBlockStateSafe(Block block) {\n        NMSHandler.chunkHelper.changeChunkServerThread(block.getWorld());\n        try {\n            return block.getState();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(block.getWorld());\n        }\n    }\n\n    public BiomeNMS getBiome() {\n        return NMSHandler.instance.getBiomeAt(super.getBlock());\n    }\n\n    public BiomeNMS getBiomeForTag(Attribute attribute) {\n        NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n        try {\n            if (getWorld() == null) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because no world is specified.\");\n                return null;\n            }\n            if (!isChunkLoaded()) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because the chunk is unloaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                return null;\n            }\n            return getBiome();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    public Location getHighestBlockForTag(Attribute attribute) {\n        NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n        try {\n            if (getWorld() == null) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because no world is specified.\");\n                return null;\n            }\n            if (!isChunkLoaded()) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because the chunk is unloaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                return null;\n            }\n            return getWorld().getHighestBlockAt(this).getLocation();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    public Collection<ItemStack> getDropsForTag(Attribute attribute, ItemStack item) {\n        NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n        try {\n            if (getWorld() == null) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because no world is specified.\");\n                return null;\n            }\n            if (!isChunkLoaded()) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because the chunk is unloaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                return null;\n            }\n            return item == null ? super.getBlock().getDrops() : super.getBlock().getDrops(item);\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    public int getExpDropForTag(Attribute attribute, ItemStack item) {\n        NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n        try {\n            if (getWorld() == null) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because no world is specified.\");\n                return 0;\n            }\n            if (!isChunkLoaded()) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because the chunk is unloaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                return 0;\n            }\n            return NMSHandler.blockHelper.getExpDrop(super.getBlock(), item);\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    public BlockState getBlockState() {\n        return getBlock().getState();\n    }\n\n    public BlockState getBlockStateForTag(Attribute attribute) {\n        Block block = getBlockForTag(attribute);\n        if (block == null) {\n            return null;\n        }\n        return getBlockStateSafe(block);\n    }\n\n    public static boolean isSameBlock(Location a, Location b) {\n        return a != null && b != null && a.getWorld() == b.getWorld() && a.getBlockX() == b.getBlockX()\n                && a.getBlockY() == b.getBlockY() && a.getBlockZ() == b.getBlockZ();\n    }\n\n    public LocationTag getBlockLocation() {\n        return new LocationTag(getWorld(), getBlockX(), getBlockY(), getBlockZ());\n    }\n\n    @Override\n    public AbstractFlagTracker getFlagTracker() {\n        if (getWorld() == null) {\n            return null;\n        }\n        return new DataPersistenceFlagTracker(getChunk(), \"flag_tracker_\" + getBlockX() + \"_\" + getBlockY() + \"_\" + getBlockZ() + \"_\");\n    }\n\n    @Override\n    public AbstractFlagTracker getFlagTrackerForTag() {\n        if (!isChunkLoadedSafe()) {\n            return null;\n        }\n        return getFlagTracker();\n    }\n\n    @Override\n    public void reapplyTracker(AbstractFlagTracker tracker) {\n        // Nothing to do.\n    }\n\n    @Override\n    public String getReasonNotFlaggable() {\n        if (getWorld() == null) {\n            return \"missing world\";\n        }\n        if (!isChunkLoadedSafe()) {\n            return \"chunk is not loaded\";\n        }\n        return \"unknown reason\";\n    }\n\n    @Override\n    public void setPitch(float pitch) {\n        super.setPitch(pitch);\n    }\n\n    // TODO: Why does this and the above exist?\n    @Override\n    public void setYaw(float yaw) {\n        super.setYaw(yaw);\n    }\n\n    @Override\n    public LocationTag add(double x, double y, double z) {\n        super.add(x, y, z);\n        return this;\n    }\n\n    @Override\n    public LocationTag add(Vector input) {\n        super.add(input);\n        return this;\n    }\n\n    @Override\n    public LocationTag add(Location input) {\n        super.add(input.getX(), input.getY(), input.getZ());\n        return this;\n    }\n\n    @Override\n    public LocationTag multiply(double input) {\n        super.multiply(input);\n        return this;\n    }\n\n    @Override\n    public void setWorld(World world) {\n        super.setWorld(world);\n        internalWorld = world == null ? null : new WeakReference<>(world);\n        backupWorld = world == null ? null : world.getName();\n    }\n\n    public double distanceSquaredNoWorld(Location loc2) {\n        return NumberConversions.square(getX() - loc2.getX()) + NumberConversions.square(getY() - loc2.getY()) + NumberConversions.square(getZ() - loc2.getZ());\n    }\n\n    public Inventory getBukkitInventory() {\n        BlockState state = getBlockState();\n        if (state instanceof InventoryHolder) {\n            return((InventoryHolder) state).getInventory();\n        }\n        return null;\n    }\n\n    public InventoryTag getInventory() {\n        Inventory inv = getBukkitInventory();\n        if (inv != null) {\n            return InventoryTag.mirrorBukkitInventory(inv);\n        }\n        Material type = getBlock().getType();\n        if (type == Material.ANVIL || type == Material.CHIPPED_ANVIL || type == Material.DAMAGED_ANVIL) {\n            return new InventoryTag(Bukkit.createInventory(null, InventoryType.ANVIL), \"location\", this.clone());\n        }\n        return null;\n    }\n\n    public BlockFace getSkullBlockFace(int rotation) {\n        switch (rotation) {\n            case 0:\n                return BlockFace.NORTH;\n            case 1:\n                return BlockFace.NORTH_NORTH_EAST;\n            case 2:\n                return BlockFace.NORTH_EAST;\n            case 3:\n                return BlockFace.EAST_NORTH_EAST;\n            case 4:\n                return BlockFace.EAST;\n            case 5:\n                return BlockFace.EAST_SOUTH_EAST;\n            case 6:\n                return BlockFace.SOUTH_EAST;\n            case 7:\n                return BlockFace.SOUTH_SOUTH_EAST;\n            case 8:\n                return BlockFace.SOUTH;\n            case 9:\n                return BlockFace.SOUTH_SOUTH_WEST;\n            case 10:\n                return BlockFace.SOUTH_WEST;\n            case 11:\n                return BlockFace.WEST_SOUTH_WEST;\n            case 12:\n                return BlockFace.WEST;\n            case 13:\n                return BlockFace.WEST_NORTH_WEST;\n            case 14:\n                return BlockFace.NORTH_WEST;\n            case 15:\n                return BlockFace.NORTH_NORTH_WEST;\n            default:\n                return null;\n        }\n    }\n\n    public byte getSkullRotation(BlockFace face) {\n        switch (face) {\n            case NORTH:\n                return 0;\n            case NORTH_NORTH_EAST:\n                return 1;\n            case NORTH_EAST:\n                return 2;\n            case EAST_NORTH_EAST:\n                return 3;\n            case EAST:\n                return 4;\n            case EAST_SOUTH_EAST:\n                return 5;\n            case SOUTH_EAST:\n                return 6;\n            case SOUTH_SOUTH_EAST:\n                return 7;\n            case SOUTH:\n                return 8;\n            case SOUTH_SOUTH_WEST:\n                return 9;\n            case SOUTH_WEST:\n                return 10;\n            case WEST_SOUTH_WEST:\n                return 11;\n            case WEST:\n                return 12;\n            case WEST_NORTH_WEST:\n                return 13;\n            case NORTH_WEST:\n                return 14;\n            case NORTH_NORTH_WEST:\n                return 15;\n        }\n        return -1;\n    }\n\n    public static double[] getRotatedAroundX(double angle, double y, double z) {\n        double cos = Math.cos(angle);\n        double sin = Math.sin(angle);\n        double newY = (y * cos) - (z * sin);\n        double newZ = (y * sin) + (z * cos);\n        return new double[] { newY, newZ };\n    }\n\n    public static double[] getRotatedAroundY(double angle, double x, double z) {\n        double cos = Math.cos(angle);\n        double sin = Math.sin(angle);\n        double newX = (x * cos) + (z * sin);\n        double newZ = (x * -sin) + (z * cos);\n        return new double[] { newX, newZ };\n    }\n\n    public static double[] getRotatedAroundZ(double angle, double x, double y) {\n        double cos = Math.cos(angle);\n        double sin = Math.sin(angle);\n        double newX = (x * cos) - (y * sin);\n        double newY = (x * sin) + (y * cos);\n        return new double[] { newX, newY };\n    }\n\n    public static double[] parsePointsAroundArgs(Attribute attribute) {\n        MapTag inputMap = attribute.inputParameterMap();\n        ElementTag radiusElement = inputMap.getRequiredObjectAs(\"radius\", ElementTag.class, attribute);\n        ElementTag amountElement = inputMap.getRequiredObjectAs(\"points\", ElementTag.class, attribute);\n        if (radiusElement == null || amountElement == null) {\n            return null;\n        }\n        double radius = radiusElement.asDouble();\n        int amount = amountElement.asInt();\n        if (amount < 1) {\n            attribute.echoError(\"Invalid amount of points! There must be at least 1 point.\");\n            return null;\n        }\n        return new double[] { radius, amount };\n    }\n\n    public static class FloodFiller {\n\n        public Set<LocationTag> result;\n\n        public int iterationLimit;\n\n        public AreaContainmentObject areaLimit;\n\n        public Material requiredMaterial;\n\n        public String matcher;\n\n        public TagContext context;\n\n        public void run(LocationTag start, AreaContainmentObject area, TagContext context) {\n            iterationLimit = Settings.blockTagsMaxBlocks();\n            areaLimit = area;\n            result = new LinkedHashSet<>();\n            this.context = context;\n            flood(start.getBlockLocation());\n        }\n\n        public void flood(LocationTag loc) {\n            if (iterationLimit-- <= 0 || result.contains(loc) || !areaLimit.doesContainLocation(loc)) {\n                return;\n            }\n            if (!loc.isChunkLoaded()) {\n                return;\n            }\n            if (matcher == null ? loc.getBlock().getType() != requiredMaterial : !loc.tryAdvancedMatcher(matcher, context)) {\n                return;\n            }\n            result.add(loc);\n            flood(loc.clone().add(-1, 0, 0));\n            flood(loc.clone().add(1, 0, 0));\n            flood(loc.clone().add(0, 0, -1));\n            flood(loc.clone().add(0, 0, 1));\n            flood(loc.clone().add(0, -1, 0));\n            flood(loc.clone().add(0, 1, 0));\n        }\n    }\n\n    public int compare(Location loc1, Location loc2) {\n        if (loc1 == null || loc2 == null || loc1.equals(loc2)) {\n            return 0;\n        }\n        else {\n            return Double.compare(distanceSquared(loc1), distanceSquared(loc2));\n        }\n    }\n\n    @Override\n    public int hashCode() {\n        return (int) (Math.floor(getX()) + Math.floor(getY()) + Math.floor(getZ()));\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o == null) {\n            return false;\n        }\n        if (!(o instanceof LocationTag)) {\n            return false;\n        }\n        LocationTag other = (LocationTag) o;\n        if ((other.getWorldName() == null && getWorldName() != null)\n                || (getWorldName() == null && other.getWorldName() != null)\n                || (getWorldName() != null && other.getWorldName() != null\n                && !CoreUtilities.equalsIgnoreCase(getWorldName(), other.getWorldName()))) {\n            return false;\n        }\n        return getX() == other.getX()\n                && getY() == other.getY()\n                && getZ() == other.getZ()\n                && getYaw() == other.getYaw()\n                && getPitch() == other.getPitch();\n    }\n\n    String prefix = \"Location\";\n\n    @Override\n    public String getPrefix() {\n        return prefix;\n    }\n\n    @Override\n    public LocationTag setPrefix(String prefix) {\n        this.prefix = prefix;\n        return this;\n    }\n\n    @Override\n    public String debuggable() {\n        String saved = getSaved(this);\n        if (saved != null) {\n            return \"<Y>\" + saved + \"<GR> (\" + identifyRaw().replace(\",\", \"<G>,<GR> \") + \"<GR>)\";\n        }\n        else {\n            return \"<Y>\" + identifyRaw().replace(\",\", \"<G>,<Y> \");\n        }\n    }\n\n    @Override\n    public boolean isUnique() {\n        return getSaved(this) != null;\n    }\n\n    @Override\n    public String identify() {\n        String saved = getSaved(this);\n        if (saved != null) {\n            return \"l@\" + saved;\n        }\n        return identifyRaw();\n    }\n\n    @Override\n    public String identifySimple() {\n        String saved = getSaved(this);\n        if (saved != null) {\n            return saved;\n        }\n        else if (getWorldName() == null) {\n            return \"l@\" + getBlockX() + \",\" + getBlockY() + \",\" + getBlockZ();\n        }\n        else {\n            return \"l@\" + getBlockX() + \",\" + getBlockY() + \",\" + getBlockZ() + \",\" + getWorldName();\n        }\n    }\n\n    public String identifyRaw() {\n        if (getYaw() != 0.0 || getPitch() != 0.0) {\n            return \"l@\" + CoreUtilities.doubleToString(getX()) + \",\" + CoreUtilities.doubleToString(getY())\n                    + \",\" + CoreUtilities.doubleToString(getZ()) + \",\" + CoreUtilities.floatToCleanString(getPitch())\n                    + \",\" + CoreUtilities.floatToCleanString(getYaw())\n                    + (getWorldName() != null ? \",\" + getWorldName() : \"\");\n        }\n        else {\n            return \"l@\" + CoreUtilities.doubleToString(getX()) + \",\" + CoreUtilities.doubleToString(getY())\n                    + \",\" + CoreUtilities.doubleToString(getZ())\n                    + (getWorldName() != null ? \",\" + getWorldName() : \"\");\n        }\n    }\n\n    @Override\n    public String toString() {\n        return identify();\n    }\n\n    @Override\n    public Object getJavaObject() {\n        return clone();\n    }\n\n    public static void register() {\n\n        VectorObject.register(LocationTag.class, tagProcessor);\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\n\n        // <--[tag]\n        // @attribute <LocationTag.block_facing>\n        // @returns LocationTag\n        // @mechanism LocationTag.block_facing\n        // @group world\n        // @description\n        // Returns the relative location vector of where this block is facing.\n        // Only works for block types that have directionality (such as signs, chests, stairs, etc.).\n        // This can return for example \"1,0,0\" to mean the block is facing towards the positive X axis.\n        // You can use <some_block_location.add[<some_block_location.block_facing>]> to get the block directly in front of this block (based on its facing direction).\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"block_facing\", (attribute, object) -> {\n            BlockData block = object.getBlockDataForTag(attribute);\n            MaterialTag material = new MaterialTag(block);\n            if (!MaterialDirectional.describes(material)) {\n                return null;\n            }\n            Vector vec = MaterialDirectional.getFrom(material).getDirectionVector();\n            if (vec == null) {\n                return null;\n            }\n            return new LocationTag(object.getWorld(), vec);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.with_facing_direction>\n        // @returns LocationTag\n        // @group world\n        // @description\n        // Returns the location with its direction set to the block's facing direction.\n        // Only works for block types that have directionality (such as signs, chests, stairs, etc.).\n        // You can use <some_block_location.with_facing_direction.forward[1]> to get the block directly in front of this block (based on its facing direction).\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"with_facing_direction\", (attribute, object) -> {\n            BlockData block = object.getBlockDataForTag(attribute);\n            MaterialTag material = new MaterialTag(block);\n            if (!MaterialDirectional.describes(material)) {\n                return null;\n            }\n            Vector facing = MaterialDirectional.getFrom(material).getDirectionVector();\n            if (facing == null) {\n                return null;\n            }\n            LocationTag result = object.clone();\n            result.setDirection(facing);\n            return result;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.above[(<#.#>)]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location above this location. Optionally specify a number of blocks to go up.\n        // This just moves straight along the Y axis, equivalent to <@link tag VectorObject.add> with input 0,1,0 (or the input value instead of '1').\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"above\", (attribute, object) -> {\n            return new LocationTag(object.clone().add(0, attribute.hasParam() ? attribute.getDoubleParam() : 1, 0));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.below[(<#.#>)]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location below this location. Optionally specify a number of blocks to go down.\n        // This just moves straight along the Y axis, equivalent to <@link tag VectorObject.sub> with input 0,1,0 (or the input value instead of '1').\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"below\", (attribute, object) -> {\n            return new LocationTag(object.clone().subtract(0, attribute.hasParam() ? attribute.getDoubleParam() : 1, 0));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.forward_flat[(<#.#>)]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location in front of this location based on yaw but not pitch. Optionally specify a number of blocks to go forward.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"forward_flat\", (attribute, object) -> {\n            Location loc = object.clone();\n            loc.setPitch(0);\n            Vector vector = loc.getDirection().multiply(attribute.hasParam() ? attribute.getDoubleParam() : 1);\n            return new LocationTag(object.clone().add(vector));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.backward_flat[(<#.#>)]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location behind this location based on yaw but not pitch. Optionally specify a number of blocks to go backward.\n        // This is equivalent to <@link tag LocationTag.forward_flat> in the opposite direction.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"backward_flat\", (attribute, object) -> {\n            Location loc = object.clone();\n            loc.setPitch(0);\n            Vector vector = loc.getDirection().multiply(attribute.hasParam() ? attribute.getDoubleParam() : 1);\n            return new LocationTag(object.clone().subtract(vector));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.forward[(<#.#>)]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location in front of this location based on pitch and yaw. Optionally specify a number of blocks to go forward.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"forward\", (attribute, object) -> {\n            Vector vector = object.getDirection().multiply(attribute.hasParam() ? attribute.getDoubleParam() : 1);\n            return new LocationTag(object.clone().add(vector));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.backward[(<#.#>)]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location behind this location based on pitch and yaw. Optionally specify a number of blocks to go backward.\n        // This is equivalent to <@link tag LocationTag.forward> in the opposite direction.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"backward\", (attribute, object) -> {\n            Vector vector = object.getDirection().multiply(attribute.hasParam() ? attribute.getDoubleParam() : 1);\n            return new LocationTag(object.clone().subtract(vector));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.left[(<#.#>)]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location to the left of this location based on pitch and yaw. Optionally specify a number of blocks to go left.\n        // This is equivalent to <@link tag LocationTag.forward> with a +90 degree rotation to the yaw and the pitch set to 0.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"left\", (attribute, object) -> {\n            Location loc = object.clone();\n            loc.setPitch(0);\n            Vector vector = loc.getDirection().rotateAroundY(Math.PI / 2).multiply(attribute.hasParam() ? attribute.getDoubleParam() : 1);\n            return new LocationTag(object.clone().add(vector));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.right[(<#.#>)]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location to the right of this location based on pitch and yaw. Optionally specify a number of blocks to go right.\n        // This is equivalent to <@link tag LocationTag.forward> with a -90 degree rotation to the yaw and the pitch set to 0.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"right\", (attribute, object) -> {\n            Location loc = object.clone();\n            loc.setPitch(0);\n            Vector vector = loc.getDirection().rotateAroundY(Math.PI / 2).multiply(attribute.hasParam() ? attribute.getDoubleParam() : 1);\n            return new LocationTag(object.clone().subtract(vector));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.up[(<#.#>)]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location above this location based on pitch and yaw. Optionally specify a number of blocks to go up.\n        // This is equivalent to <@link tag LocationTag.forward> with a +90 degree rotation to the pitch.\n        // To just get the location above this location, use <@link tag LocationTag.above> instead.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"up\", (attribute, object) -> {\n            Location loc = object.clone();\n            loc.setPitch(loc.getPitch() - 90);\n            Vector vector = loc.getDirection().multiply(attribute.hasParam() ? attribute.getDoubleParam() : 1);\n            return new LocationTag(object.clone().add(vector));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.down[(<#.#>)]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location below this location based on pitch and yaw. Optionally specify a number of blocks to go down.\n        // This is equivalent to <@link tag LocationTag.forward> with a -90 degree rotation to the pitch.\n        // To just get the location above this location, use <@link tag LocationTag.below> instead.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"down\", (attribute, object) -> {\n            Location loc = object.clone();\n            loc.setPitch(loc.getPitch() - 90);\n            Vector vector = loc.getDirection().multiply(attribute.hasParam() ? attribute.getDoubleParam() : 1);\n            return new LocationTag(object.clone().subtract(vector));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.relative[<location>]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location relative to this location. Input is a vector location of the form left,up,forward.\n        // For example, input -1,1,1 will return a location 1 block to the right, 1 block up, and 1 block forward.\n        // To just get the location relative to this without rotation math, use <@link tag VectorObject.add> instead.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"relative\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            LocationTag offsetLoc = attribute.paramAsType(LocationTag.class);\n            if (offsetLoc == null) {\n                return null;\n            }\n\n            Location loc = object.clone();\n            Vector offset = loc.getDirection().multiply(offsetLoc.getZ());\n            loc.setPitch(loc.getPitch() - 90);\n            offset = offset.add(loc.getDirection().multiply(offsetLoc.getY()));\n            loc.setPitch(0);\n            offset = offset.add(loc.getDirection().rotateAroundY(Math.PI / 2).multiply(offsetLoc.getX()));\n\n            return new LocationTag(object.clone().add(offset));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.block>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location of the block this location is on,\n        // i.e. returns a location without decimals or direction.\n        // Note that you almost never actually need this tag. This does not \"get the block\", this just rounds coordinates down.\n        // If you have this in a script, it is more likely to be a mistake than actually needed.\n        // Consider using <@link tag LocationTag.round_down> instead.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"block\", (attribute, object) -> {\n            return new LocationTag(object.getWorld(), object.getBlockX(), object.getBlockY(), object.getBlockZ());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.center>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location at the center of the block this location is on.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"center\", (attribute, object) -> {\n            return new LocationTag(object.getWorld(), object.getBlockX() + 0.5, object.getBlockY() + 0.5, object.getBlockZ() + 0.5);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.simplex_3d>\n        // @returns ElementTag(Decimal)\n        // @group math\n        // @description\n        // Returns the 3D simplex noise value (from -1 to 1) for this location's X/Y/Z.\n        // See also <@link tag util.random_simplex>\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"simplex_3d\", (attribute, object) -> {\n            return new ElementTag(SimplexNoise.noise(object.getX(), object.getY(), object.getZ()));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.random_offset[<limit>]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns a copy of this location, with the X/Y/Z offset by a random decimal value up to a given limit.\n        // The limit can either be an X,Y,Z location vector like [3,1,3] or a single value like [3] (which is equivalent to [3,3,3]).\n        // For example, for a location at 0,100,0, \".random_offset[1,2,3]\" can return any decimal location within the cuboid from -1,98,-3 to 1,102,3.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"random_offset\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"LocationTag.random_offset[...] must have an input.\");\n                return null;\n            }\n            Vector offsetLimit;\n            if (ArgumentHelper.matchesDouble(attribute.getParam())) {\n                double val = attribute.getDoubleParam();\n                offsetLimit = new Vector(val, val, val);\n            }\n            else {\n                LocationTag val = attribute.paramAsType(LocationTag.class);\n                if (val == null) {\n                    return null;\n                }\n                offsetLimit = val.toVector();\n            }\n            offsetLimit.setX(offsetLimit.getX() * (CoreUtilities.getRandom().nextDouble() * 2 - 1));\n            offsetLimit.setY(offsetLimit.getY() * (CoreUtilities.getRandom().nextDouble() * 2 - 1));\n            offsetLimit.setZ(offsetLimit.getZ() * (CoreUtilities.getRandom().nextDouble() * 2 - 1));\n            LocationTag output = object.clone();\n            output.add(offsetLimit);\n            return output;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.highest>\n        // @returns LocationTag\n        // @group world\n        // @description\n        // Returns the location of the highest solid block at the location.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"highest\", (attribute, object) -> {\n            Location result = object.getHighestBlockForTag(attribute);\n            return new LocationTag(result);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.has_inventory>\n        // @returns ElementTag(Boolean)\n        // @group world\n        // @description\n        // Returns whether the block at the location has an inventory.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"has_inventory\", (attribute, object) -> {\n            return new ElementTag(object.getBlockStateForTag(attribute) instanceof InventoryHolder);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.inventory>\n        // @returns InventoryTag\n        // @group world\n        // @description\n        // Returns the InventoryTag of the block at the location. If the\n        // block is not a container, returns null.\n        // -->\n        tagProcessor.registerTag(InventoryTag.class, \"inventory\", (attribute, object) -> {\n            if (!object.isChunkLoadedSafe()) {\n                return null;\n            }\n            return ElementTag.handleNull(object.identify() + \".inventory\", object.getInventory(), \"InventoryTag\", attribute.hasAlternative());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.material>\n        // @returns MaterialTag\n        // @group world\n        // @description\n        // Returns the material of the block at the location.\n        // -->\n        tagProcessor.registerTag(MaterialTag.class, \"material\", (attribute, object) -> {\n            BlockData block = object.getBlockDataForTag(attribute);\n            if (block == null) {\n                return null;\n            }\n            return new MaterialTag(block);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.is_passable>\n        // @returns ElementTag(Boolean)\n        // @group world\n        // @description\n        // Returns whether the block at this location is non-solid and can be walked through.\n        // Note that for example an open door is still solid, and thus will return false.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_passable\", (attribute, object) -> {\n            Block block = object.getBlockForTag(attribute);\n            if (block == null) {\n                return null;\n            }\n            return new ElementTag(block.isPassable());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.patterns>\n        // @returns ListTag\n        // @mechanism LocationTag.patterns\n        // @group world\n        // @description\n        // Lists the patterns of the banner at this location in the form \"COLOR/PATTERN|COLOR/PATTERN\" etc.\n        // For the list of possible colors, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/DyeColor.html>.\n        // For the list of possible patterns, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/banner/PatternType.html>.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"patterns\", (attribute, object) -> {\n            ListTag list = new ListTag();\n            for (org.bukkit.block.banner.Pattern pattern : ((Banner) object.getBlockStateForTag(attribute)).getPatterns()) {\n                list.add(pattern.getColor().name() + \"/\" + pattern.getPattern().name());\n            }\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.head_rotation>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.head_rotation\n        // @group world\n        // @description\n        // Gets the rotation of the head at this location. Can be 1-16.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"head_rotation\", (attribute, object) -> {\n            return new ElementTag(object.getSkullRotation(((Skull) object.getBlockStateForTag(attribute)).getRotation()) + 1);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.switched>\n        // @returns ElementTag(Boolean)\n        // @group world\n        // @description\n        // Returns whether the block at the location is considered to be switched on.\n        // (For buttons, levers, etc.)\n        // To change this, see <@link command Switch>\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"switched\", (attribute, object) -> {\n            return new ElementTag(SwitchCommand.switchState(object.getBlockForTag(attribute)));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.sign_contents>\n        // @returns ListTag\n        // @mechanism LocationTag.sign_contents\n        // @group world\n        // @description\n        // Returns a list of lines on a sign.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"sign_contents\", (attribute, object) -> {\n            if (object.getBlockStateForTag(attribute) instanceof Sign) {\n                return new ListTag(Arrays.asList(PaperAPITools.instance.getSignLines(((Sign) object.getBlockStateForTag(attribute)))));\n            }\n            else {\n                return null;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.spawner_type>\n        // @returns EntityTag\n        // @mechanism LocationTag.spawner_type\n        // @group world\n        // @description\n        // Returns the type of entity spawned by a mob spawner, if any.\n        // -->\n        tagProcessor.registerTag(EntityTag.class, \"spawner_type\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof CreatureSpawner spawner)) {\n                return null;\n            }\n            EntityType spawnedType = spawner.getSpawnedType();\n            return spawnedType != null ? new EntityTag(spawnedType) : null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.spawner_display_entity>\n        // @returns EntityTag\n        // @group world\n        // @description\n        // Returns the full \"display entity\" for the spawner. This can contain more data than just a type.\n        // -->\n        tagProcessor.registerTag(EntityTag.class, \"spawner_display_entity\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof CreatureSpawner)) {\n                return null;\n            }\n            EntityTag fakeEnt = NMSHandler.entityHelper.getMobSpawnerDisplayEntity(((CreatureSpawner) object.getBlockStateForTag(attribute)));\n            fakeEnt.isFake = true;\n            return fakeEnt.describe(attribute.context);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.spawner_spawn_delay>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.spawner_delay_data\n        // @group world\n        // @description\n        // Returns the current spawn delay for the spawner.\n        // This changes over time between <@link tag LocationTag.spawner_minimum_spawn_delay> and <@link tag LocationTag.spawner_maximum_spawn_delay>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"spawner_spawn_delay\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof CreatureSpawner)) {\n                return null;\n            }\n            return new ElementTag(((CreatureSpawner) object.getBlockStateForTag(attribute)).getDelay());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.spawner_minimum_spawn_delay>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.spawner_delay_data\n        // @group world\n        // @description\n        // Returns the minimum spawn delay for the mob spawner.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"spawner_minimum_spawn_delay\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof CreatureSpawner)) {\n                return null;\n            }\n            return new ElementTag(((CreatureSpawner) object.getBlockStateForTag(attribute)).getMinSpawnDelay());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.spawner_maximum_spawn_delay>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.spawner_delay_data\n        // @group world\n        // @description\n        // Returns the maximum spawn delay for the mob spawner.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"spawner_maximum_spawn_delay\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof  CreatureSpawner)) {\n                return null;\n            }\n            return new ElementTag(((CreatureSpawner) object.getBlockStateForTag(attribute)).getMaxSpawnDelay());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.spawner_player_range>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.spawner_player_range\n        // @group world\n        // @description\n        // Returns the maximum player range for the spawner (ie how close a player must be for this spawner to be active).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"spawner_player_range\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof CreatureSpawner)) {\n                return null;\n            }\n            return new ElementTag(((CreatureSpawner) object.getBlockStateForTag(attribute)).getRequiredPlayerRange());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.spawner_range>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.spawner_range\n        // @group world\n        // @description\n        // Returns the spawn range for the spawner (the radius mobs will spawn in).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"spawner_range\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof CreatureSpawner)) {\n                return null;\n            }\n            return new ElementTag(((CreatureSpawner) object.getBlockStateForTag(attribute)).getSpawnRange());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.spawner_max_nearby_entities>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.spawner_max_nearby_entities\n        // @group world\n        // @description\n        // \tReturns the maximum nearby entities for the spawner (the radius mobs will spawn in).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"spawner_max_nearby_entities\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof CreatureSpawner)) {\n                return null;\n            }\n            return new ElementTag(((CreatureSpawner) object.getBlockStateForTag(attribute)).getMaxNearbyEntities());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.spawner_count>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.spawner_count\n        // @group world\n        // @description\n        // Returns the spawn count for the spawner.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"spawner_count\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof CreatureSpawner)) {\n                return null;\n            }\n            return new ElementTag(((CreatureSpawner) object.getBlockStateForTag(attribute)).getSpawnCount());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.lock>\n        // @returns ElementTag\n        // @mechanism LocationTag.lock\n        // @group world\n        // @description\n        // Returns the password to a locked container.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"lock\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof Lockable)) {\n                return null;\n            }\n            Lockable lock = (Lockable) object.getBlockStateForTag(attribute);\n            return new ElementTag(lock.isLocked() ? lock.getLock() : null);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.is_locked>\n        // @returns ElementTag(Boolean)\n        // @mechanism LocationTag.lock\n        // @group world\n        // @description\n        // Returns whether the container is locked.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_locked\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof Lockable)) {\n                return null;\n            }\n            return new ElementTag(((Lockable) object.getBlockStateForTag(attribute)).isLocked());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.is_lockable>\n        // @returns ElementTag(Boolean)\n        // @mechanism LocationTag.lock\n        // @group world\n        // @description\n        // Returns whether the container is lockable.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_lockable\", (attribute, object) -> {\n            return new ElementTag(object.getBlockStateForTag(attribute) instanceof Lockable);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.drops[(<item>)]>\n        // @returns ListTag(ItemTag)\n        // @group world\n        // @description\n        // Returns what items the block at the location would drop if broken naturally.\n        // Optionally specifier a breaker item.\n        // Not guaranteed to contain exactly correct or contain all possible drops (for things like plants that drop only when grown, ores that drop random amounts, etc).\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"drops\", (attribute, object) -> {\n            ItemStack inputItem = null;\n            if (attribute.hasParam()) {\n                inputItem = attribute.paramAsType(ItemTag.class).getItemStack();\n            }\n            ListTag list = new ListTag();\n            for (ItemStack it : object.getDropsForTag(attribute, inputItem)) {\n                list.addObject(new ItemTag(it));\n            }\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.xp_drop[(<item>)]>\n        // @returns ElementTag(Number)\n        // @group world\n        // @description\n        // Returns how much experience, if any, the block at the location would drop if broken naturally.\n        // Returns 0 if a block wouldn't drop xp.\n        // Optionally specifier a breaker item.\n        // Not guaranteed to contain exactly the amount that actual drops if then broken later, as the value is usually randomized.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"xp_drop\", (attribute, object) -> {\n            ItemStack inputItem = new ItemStack(Material.AIR);\n            if (attribute.hasParam()) {\n                inputItem = attribute.paramAsType(ItemTag.class).getItemStack();\n            }\n            return new ElementTag(object.getExpDropForTag(attribute, inputItem));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.hive_bee_count>\n        // @returns ElementTag(Number)\n        // @group world\n        // @description\n        // Returns the number of bees inside a hive.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"hive_bee_count\", (attribute, object) -> {\n            return new ElementTag(((Beehive) object.getBlockStateForTag(attribute)).getEntityCount());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.hive_max_bees>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.hive_max_bees\n        // @group world\n        // @description\n        // Returns the maximum number of bees allowed inside a hive.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"hive_max_bees\", (attribute, object) -> {\n            return new ElementTag(((Beehive) object.getBlockStateForTag(attribute)).getMaxEntities());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.skull_type>\n        // @returns ElementTag\n        // @group world\n        // @description\n        // Returns the type of the skull.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"skull_type\", (attribute, object) -> {\n            BlockState blockState = object.getBlockStateForTag(attribute);\n            if (blockState instanceof Skull) {\n                String t = ((Skull) blockState).getSkullType().name();\n                return new ElementTag(t);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.skull_name>\n        // @returns ElementTag\n        // @mechanism LocationTag.skull_skin\n        // @group world\n        // @description\n        // Returns the name of the skin the skull is displaying.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"skull_name\", (attribute, object) -> {\n            BlockState blockState = object.getBlockStateForTag(attribute);\n            if (blockState instanceof Skull) {\n                PlayerProfile profile = NMSHandler.blockHelper.getPlayerProfile((Skull) blockState);\n                if (profile == null) {\n                    return null;\n                }\n                String n = profile.getName();\n                if (n == null) {\n                    n = ((Skull) blockState).getOwningPlayer().getName();\n                }\n                return new ElementTag(n);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.skull_skin>\n        // @returns ElementTag\n        // @mechanism LocationTag.skull_skin\n        // @group world\n        // @description\n        // Returns the skin the skull is displaying - just the name or UUID as text, not a player object.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"skull_skin\", (attribute, object) -> {\n            BlockState blockState = object.getBlockStateForTag(attribute);\n            if (blockState instanceof Skull) {\n                PlayerProfile profile = NMSHandler.blockHelper.getPlayerProfile((Skull) blockState);\n                if (profile == null) {\n                    return null;\n                }\n                String name = profile.getName();\n                UUID uuid = profile.getUniqueId();\n                String texture = profile.getTexture();\n\n                // <--[tag]\n                // @attribute <LocationTag.skull_skin.full>\n                // @returns ElementTag\n                // @mechanism LocationTag.skull_skin\n                // @group world\n                // @description\n                // Returns the skin the skull item is displaying - just the name or UUID as text, not a player object,\n                // along with the permanently cached texture property.\n                // In format \"uuid|texture\" - separated by a pipe, but not a ListTag.\n                // -->\n                if (attribute.startsWith(\"full\", 2)) {\n                    attribute.fulfill(1);\n                    return new ElementTag((uuid != null ? uuid : name)\n                            + (texture != null ? \"|\" + texture : \"\"));\n                }\n                return new ElementTag(uuid != null ? uuid.toString() : name);\n            }\n            else {\n                return null;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.round>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns a rounded version of the LocationTag's coordinates.\n        // That is, each component (X, Y, Z, Yaw, Pitch) is rounded\n        // (eg, 0.1 becomes 0.0, 0.5 becomes 1.0, 0.9 becomes 1.0).\n        // This is NOT equivalent to the block coordinates. For that, use <@link tag LocationTag.round_down>.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"round\", (attribute, object) -> {\n            LocationTag result = object.clone();\n            result.setX(Math.round(result.getX()));\n            result.setY(Math.round(result.getY()));\n            result.setZ(Math.round(result.getZ()));\n            result.setYaw(Math.round(result.getYaw()));\n            result.setPitch(Math.round(result.getPitch()));\n            return result;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.round_up>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns a rounded-upward version of the LocationTag's coordinates.\n        // That is, each component (X, Y, Z, Yaw, Pitch) is rounded upward\n        // (eg, 0.1 becomes 1.0, 0.5 becomes 1.0, 0.9 becomes 1.0).\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"round_up\", (attribute, object) -> {\n            LocationTag result = object.clone();\n            result.setX(Math.ceil(result.getX()));\n            result.setY(Math.ceil(result.getY()));\n            result.setZ(Math.ceil(result.getZ()));\n            result.setYaw((float) Math.ceil((result.getYaw())));\n            result.setPitch((float) Math.ceil(result.getPitch()));\n            return result;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.round_down>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns a rounded-downward version of the LocationTag's coordinates.\n        // That is, each component (X, Y, Z, Yaw, Pitch) is rounded downward\n        // (eg, 0.1 becomes 0.0, 0.5 becomes 0.0, 0.9 becomes 0.0).\n        // This is equivalent to the block coordinates of the location.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"round_down\", (attribute, object) -> {\n            LocationTag result = object.clone();\n            result.setX(Math.floor(result.getX()));\n            result.setY(Math.floor(result.getY()));\n            result.setZ(Math.floor(result.getZ()));\n            result.setYaw((float) Math.floor((result.getYaw())));\n            result.setPitch((float) Math.floor(result.getPitch()));\n            return result;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.round_to[<#>]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns a rounded-to-precision version of the LocationTag's coordinates.\n        // That is, each component (X, Y, Z, Yaw, Pitch) is rounded to the specified decimal place\n        // (eg, 0.12345 .round_to[3] returns \"0.123\").\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"round_to\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag LocationTag.round_to[...] must have a value.\");\n                return null;\n            }\n            LocationTag result = object.clone();\n            int ten = (int) Math.pow(10, attribute.getIntParam());\n            result.setX(((double) Math.round(result.getX() * ten)) / ten);\n            result.setY(((double) Math.round(result.getY() * ten)) / ten);\n            result.setZ(((double) Math.round(result.getZ() * ten)) / ten);\n            result.setYaw(((float) Math.round(result.getYaw() * ten)) / ten);\n            result.setPitch(((float) Math.round(result.getPitch() * ten)) / ten);\n            return result;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.round_to_precision[<#.#>]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns a rounded-to-precision version of the LocationTag's coordinates.\n        // That is, each component (X, Y, Z, Yaw, Pitch) is rounded to the specified precision value\n        // (0.12345 .round_to_precision[0.005] returns \"0.125\").\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"round_to_precision\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag LocationTag.round_to_precision[...] must have a value.\");\n                return null;\n            }\n            LocationTag result = object.clone();\n            float precision = 1f / (float) attribute.getDoubleParam();\n            result.setX(((double) Math.round(result.getX() * precision)) / precision);\n            result.setY(((double) Math.round(result.getY() * precision)) / precision);\n            result.setZ(((double) Math.round(result.getZ() * precision)) / precision);\n            result.setYaw(((float) Math.round(result.getYaw() * precision)) / precision);\n            result.setPitch(((float) Math.round(result.getPitch() * precision)) / precision);\n            return result;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.simple>\n        // @returns ElementTag\n        // @group identity\n        // @description\n        // Returns a simple version of the LocationTag's block coordinates.\n        // In the format: x,y,z,world\n        // For example: 1,2,3,world_nether\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"simple\", (attribute, object) -> {\n            // <--[tag]\n            // @attribute <LocationTag.simple.formatted>\n            // @returns ElementTag\n            // @group identity\n            // @description\n            // Returns the formatted simple version of the LocationTag's block coordinates.\n            // In the format: X 'x', Y 'y', Z 'z', in world 'world'\n            // For example, X '1', Y '2', Z '3', in world 'world_nether'\n            // -->\n            if (attribute.startsWith(\"formatted\", 2)) {\n                attribute.fulfill(1);\n                return new ElementTag(\"X '\" + object.getBlockX()\n                        + \"', Y '\" + object.getBlockY()\n                        + \"', Z '\" + object.getBlockZ()\n                        + \"', in world '\" + object.getWorldName() + \"'\");\n            }\n            if (object.getWorldName() == null) {\n                return new ElementTag(object.getBlockX() + \",\" + object.getBlockY() + \",\" + object.getBlockZ());\n            }\n            else {\n                return new ElementTag(object.getBlockX() + \",\" + object.getBlockY() + \",\" + object.getBlockZ()\n                        + \",\" + object.getWorldName());\n            }\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.ray_trace[(range=<#.#>/{200});(return=<{precise}/block/normal>);(default=<{null}/air>);(fluids=<true/{false}>);(nonsolids=<true/{false}>);(entities=<matcher>);(ignore=<entity>|...);(raysize=<#.#>/{0})]>\n        // @returns LocationTag\n        // @synonyms LocationTag.raycast, LocationTag.raytrace, LocationTag.ray_cast\n        // @group world\n        // @description\n        // Traces a line from this location towards the direction it's facing, returning the location of the first hit block or (optionally) entity.\n        // This tag has also been referred to as 'cursor_on' or 'precise_cursor_on' in the past.\n        // For ray tracing entities, see <@link tag LocationTag.ray_trace_target>.\n        // Using 'return=normal' instead replaces the old 'precise_impact_normal' tag.\n        // Optionally specify:\n        // range: (defaults to 200) a maximum distance (in blocks) to trace before giving up.\n        // return: (defaults to precise)\n        //     specify \"precise\" to return the exact location of the hit (if it hits a block, returns a location along the edge of the block -- but if it hits an entity, returns a location along the body of the entity)\n        //     \"normal\" to return the normal vector of the impact location,\n        //     or \"block\" to return the location of the block hit (if it hits an entity, returns equivalent to 'precise')\n        //     For \"precise\" and \"block\", the location's direction is set to the direction of the block face hit (or entity bounding box face), pointing exactly away from whatever was hit (the 'normal' direction).\n        // default: (defaults to \"null\")\n        //     specify \"null\" to return null when nothing is hit,\n        //     or \"air\" to return the location of the air at the end of the trace (NOTE: can potentially be in water or other ignored block type, not just literal air).\n        // fluids: (defaults to false) specify \"true\" to count fluids like water as solid, or \"false\" to ignore them.\n        // nonsolids: (defaults to false) specify \"true\" to count passable blocks (like tallgrass) as solid, or false to ignore them.\n        // entities: (defaults to none) specify an entity matcher for entities to count as blocking the trace, \"*\" for any entity counts, or leave off (or empty) to ignore entities.\n        // ignore: (defaults to none) optional list of EntityTags to ignore even if they match the matcher.\n        // raysize: (defaults to 0) sets the radius of the ray being used to trace entities (and NOT for blocks!).\n        //\n        // @example\n        // # Destroys whatever solid block the player is looking at.\n        // - define target <player.eye_location.ray_trace[return=block]||null>\n        // - if <[target]> != null:\n        //     - modifyblock <[target]> air\n        //\n        // @example\n        // # Spawns a heart wherever the player is looking, no more than 5 blocks away.\n        // - playeffect effect:heart offset:0 at:<player.eye_location.ray_trace[range=5;entities=*;ignore=<player>;fluids=true;nonsolids=true;default=air]>\n        //\n        // @example\n        // # Spawns a line of fire starting at the player's target location and spewing out in the direction of the blockface or entity hit, demonstrating the concept of a normal vector.\n        // - define hit <player.eye_location.ray_trace[entities=*;ignore=<player>;fluids=true;nonsolids=true]||null>\n        // - if <[hit]> != null:\n        //     - playeffect effect:flame offset:0 at:<[hit].points_between[<[hit].forward[2]>].distance[0.2]>\n        //\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"ray_trace\", (attribute, object) -> {\n            if (object.getWorld() == null) {\n                return null;\n            }\n            MapTag input = attribute.inputParameterMap();\n            double range = input.getElement(\"range\", \"200\").asDouble();\n            String returnMode = input.getElement(\"return\", \"precise\").asString();\n            String defaultMode = input.getElement(\"default\", \"null\").asString();\n            boolean fluids = input.getElement(\"fluids\", \"false\").asBoolean();\n            boolean nonsolids = input.getElement(\"nonsolids\", \"false\").asBoolean();\n            String entitiesMatcher = input.getElement(\"entities\", \"\").asString();\n            double raySize = input.getElement(\"raysize\", \"0\").asDouble();\n            List<EntityTag> ignore = input.getObjectAs(\"ignore\", ListTag.class, attribute.context, ListTag::new).filter(EntityTag.class, attribute.context);\n            HashSet<UUID> ignoreIds = ignore.stream().map(EntityTag::getUUID).collect(Collectors.toCollection(HashSet::new));\n            Vector direction = object.getDirection();\n            RayTraceResult traced;\n            if (entitiesMatcher.isEmpty()) {\n                traced = object.getWorld().rayTraceBlocks(object, direction, range, fluids ? FluidCollisionMode.ALWAYS : FluidCollisionMode.NEVER, !nonsolids);\n            }\n            else {\n                traced = object.getWorld().rayTrace(object, direction, range, fluids ? FluidCollisionMode.ALWAYS : FluidCollisionMode.NEVER, !nonsolids, raySize, (e) -> !ignoreIds.contains(e.getUniqueId()) && new EntityTag(e).tryAdvancedMatcher(entitiesMatcher, attribute.context));\n            }\n            if (traced != null) {\n                LocationTag result = null;\n                if (CoreUtilities.equalsIgnoreCase(returnMode, \"block\")) {\n                    if (traced.getHitBlock() != null) {\n                        result = new LocationTag(traced.getHitBlock().getLocation());\n                    }\n                    else if (CoreUtilities.equalsIgnoreCase(defaultMode, \"air\")) {\n                        result = new LocationTag(object.getWorld(), traced.getHitPosition());\n                    }\n                }\n                else if (CoreUtilities.equalsIgnoreCase(returnMode, \"normal\")) {\n                    if (traced.getHitBlockFace() != null) {\n                        return new LocationTag(traced.getHitBlockFace().getDirection());\n                    }\n                }\n                else {\n                    result = new LocationTag(object.getWorld(), traced.getHitPosition());\n                }\n                if (result != null) {\n                    if (traced.getHitBlockFace() != null) {\n                        result.setDirection(traced.getHitBlockFace().getDirection());\n                    }\n                    return result;\n                }\n            }\n            if (CoreUtilities.equalsIgnoreCase(defaultMode, \"air\")) {\n                return object.clone().add(direction.clone().multiply(range));\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.ray_trace_target[(range=<#.#>/{200});(blocks=<{true}/false>);(fluids=<true/{false}>);(nonsolids=<true/{false}>);(entities=<matcher>);(ignore=<entity>|...);(raysize=<#.#>/{0})]>\n        // @returns EntityTag\n        // @synonyms LocationTag.raycast_target, LocationTag.raytrace_target, LocationTag.ray_cast_target\n        // @group world\n        // @description\n        // Traces a line from this location towards the direction it's facing, returning the first hit entity (if any).\n        // This is similar to <@link tag LocationTag.precise_target> and <@link tag PlayerTag.target>, except offering more options for how the ray trace is performed.\n        // For ray tracing locations, see <@link tag LocationTag.ray_trace>.\n        // Optionally specify:\n        // range: (defaults to 200) a maximum distance (in blocks) to trace before giving up.\n        // blocks: (defaults to true) specify \"false\" to ignore all blocks, solid or nonsolid or fluid, between this location and the target entity.\n        // fluids: (defaults to false) specify \"true\" to count fluids like water as solid, or \"false\" to ignore them.\n        // nonsolids: (defaults to false) specify \"true\" to count passable blocks (like tall_grass) as solid, or \"false\" to ignore them.\n        // entities: (defaults to none) specify an entity matcher for entities to ray trace for. Any non-matching entities along the way are ignored. Leave blank to match any entity.\n        // ignore: (defaults to none) optional list of EntityTags to ignore even if they match the matcher.\n        // raysize: (defaults to 0) sets the radius of the ray being used to trace entities.\n        //\n        // @example\n        // # Removes the entity a player is looking at.\n        // - define target <player.eye_location.ray_trace_target[ignore=<player>]||null>\n        // - if <[target]> != null:\n        //     - remove <[target]>\n        //\n        // @example\n        // # Returns any player within the view of an NPC.\n        // - define target <npc.eye_location.ray_trace_target[entities=player;raysize=2]||null>\n        //\n        // @example\n        // # Highlights an entity through any number or types of blocks.\n        // - define target <player.eye_location.ray_trace_target[ignore=<player>;blocks=false]||null>\n        // - if <[target]> != null:\n        //     - adjust <[target]> glowing:true\n        //\n        // -->\n        tagProcessor.registerTag(EntityTag.class, \"ray_trace_target\", (attribute, object) -> {\n            if (object.getWorld() == null) {\n                return null;\n            }\n            MapTag input = attribute.inputParameterMap();\n            double range = input.getElement(\"range\", \"200\").asDouble();\n            boolean blocks = input.getElement(\"blocks\", \"true\").asBoolean();\n            boolean fluids = input.getElement(\"fluids\", \"false\").asBoolean();\n            boolean nonsolids = input.getElement(\"nonsolids\", \"false\").asBoolean();\n            String entitiesMatcher = input.getElement(\"entities\", \"\").asString();\n            double raySize = input.getElement(\"raysize\", \"0\").asDouble();\n            List<EntityTag> ignore = input.getObjectAs(\"ignore\", ListTag.class, attribute.context, ListTag::new).filter(EntityTag.class, attribute.context);\n            HashSet<UUID> ignoreIds = ignore.stream().map(EntityTag::getUUID).collect(Collectors.toCollection(HashSet::new));\n            Vector direction = object.getDirection();\n            RayTraceResult traced;\n            if (!blocks) {\n                traced = object.getWorld().rayTraceEntities(object, direction, range, raySize,\n                        (e) -> !ignoreIds.contains(e.getUniqueId()) && (entitiesMatcher.isEmpty() || new EntityTag(e).tryAdvancedMatcher(entitiesMatcher, attribute.context)));\n            }\n            else {\n                traced = object.getWorld().rayTrace(object, direction, range, fluids ? FluidCollisionMode.ALWAYS : FluidCollisionMode.NEVER, !nonsolids, raySize,\n                        (e) -> !ignoreIds.contains(e.getUniqueId()) && (entitiesMatcher.isEmpty() || new EntityTag(e).tryAdvancedMatcher(entitiesMatcher, attribute.context)));\n            }\n            if (traced != null && traced.getHitEntity() != null) {\n                return new EntityTag(traced.getHitEntity());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.precise_impact_normal[(<range>)]>\n        // @returns LocationTag\n        // @group world\n        // @deprecated use \"ray_trace[return=normal]\" instead.\n        // @description\n        // Deprecated in favor of <@link tag LocationTag.ray_trace> with input setting [return=normal].\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"precise_impact_normal\", (attribute, object) -> {\n            BukkitImplDeprecations.locationOldCursorOn.warn(attribute.context);\n            double range = attribute.getDoubleParam();\n            if (range <= 0) {\n                range = 200;\n            }\n            RayTraceResult traced = object.getWorld().rayTraceBlocks(object, object.getDirection(), range);\n            if (traced != null && traced.getHitBlockFace() != null) {\n                return new LocationTag(traced.getHitBlockFace().getDirection());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.precise_cursor_on_block[(<range>)]>\n        // @returns LocationTag\n        // @group world\n        // @deprecated use \"ray_trace[return=block]\" instead.\n        // @description\n        // Deprecated in favor of <@link tag LocationTag.ray_trace> with input setting [return=block].\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"precise_cursor_on_block\", (attribute, object) -> {\n            BukkitImplDeprecations.locationOldCursorOn.warn(attribute.context);\n            double range = attribute.getDoubleParam();\n            if (range <= 0) {\n                range = 200;\n            }\n            RayTraceResult traced = object.getWorld().rayTraceBlocks(object, object.getDirection(), range);\n            if (traced != null && traced.getHitBlock() != null) {\n                return new LocationTag(traced.getHitBlock().getLocation());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.precise_cursor_on[(<range>)]>\n        // @returns LocationTag\n        // @group world\n        // @deprecated use \"ray_trace\" instead.\n        // @description\n        // Deprecated in favor of <@link tag LocationTag.ray_trace> with all default settings (no input other than optionally the range).\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"precise_cursor_on\", (attribute, object) -> {\n            BukkitImplDeprecations.locationOldCursorOn.warn(attribute.context);\n            double range = attribute.getDoubleParam();\n            if (range <= 0) {\n                range = 200;\n            }\n            RayTraceResult traced = object.getWorld().rayTraceBlocks(object, object.getDirection(), range);\n            if (traced != null && traced.getHitBlock() != null) {\n                return new LocationTag(traced.getHitBlock().getWorld(), traced.getHitPosition());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.precise_target_list[<range>]>\n        // @returns ListTag(EntityTag)\n        // @group world\n        // @description\n        // Returns a list of all entities this location is pointing directly at (using precise ray trace logic), up to a given range limit.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"precise_target_list\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            double range = attribute.getDoubleParam();\n            HashSet<UUID> hitIDs = new HashSet<>();\n            ListTag result = new ListTag();\n            Vector direction = object.getDirection();\n            World world = object.getWorld();\n            while (true) {\n                RayTraceResult hit = world.rayTrace(object, direction, range, FluidCollisionMode.NEVER, true, 0, (e) -> !hitIDs.contains(e.getUniqueId()));\n                if (hit == null || hit.getHitEntity() == null) {\n                    return result;\n                }\n                hitIDs.add(hit.getHitEntity().getUniqueId());\n                result.addObject(new EntityTag(hit.getHitEntity()));\n            }\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.precise_target[(<range>)]>\n        // @returns EntityTag\n        // @group world\n        // @description\n        // Returns the entity this location is pointing at, using precise ray trace logic.\n        // Optionally, specify a maximum range to find the entity from (defaults to 100).\n        // -->\n        tagProcessor.registerTag(EntityFormObject.class, \"precise_target\", (attribute, object) -> {\n            double range = attribute.getDoubleParam();\n            if (range <= 0) {\n                range = 100;\n            }\n            RayTraceResult result;\n            // <--[tag]\n            // @attribute <LocationTag.precise_target[(<range>)].type[<entity_type>|...]>\n            // @returns EntityTag\n            // @group world\n            // @description\n            // Returns the entity this location is pointing at, using precise ray trace logic.\n            // Optionally, specify a maximum range to find the entity from (defaults to 100).\n            // Accepts a list of types to trace against (types not listed will be ignored).\n            // -->\n            if (attribute.startsWith(\"type\", 2) && attribute.hasContext(2)) {\n                attribute.fulfill(1);\n                Set<EntityType> types = new HashSet<>();\n                for (String str : attribute.paramAsType(ListTag.class)) {\n                    types.add(EntityTag.valueOf(str, attribute.context).getBukkitEntityType());\n                }\n                result = object.getWorld().rayTrace(object, object.getDirection(), range, FluidCollisionMode.NEVER, true, 0, (e) -> types.contains(e.getType()));\n            }\n            else {\n                result = object.getWorld().rayTrace(object, object.getDirection(), range, FluidCollisionMode.NEVER, true, 0, null);\n            }\n            if (result != null && result.getHitEntity() != null) {\n                return new EntityTag(result.getHitEntity()).getDenizenObject();\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.precise_target_position[(<range>)]>\n        // @returns LocationTag\n        // @group world\n        // @deprecated use \"ray_trace[entities=*]\" instead.\n        // @description\n        // Deprecated in favor of <@link tag LocationTag.ray_trace> with input of [entities=*]\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"precise_target_position\", (attribute, object) -> {\n            double range = attribute.getDoubleParam();\n            if (range <= 0) {\n                range = 100;\n            }\n            BukkitImplDeprecations.locationOldCursorOn.warn(attribute.context);\n            RayTraceResult result;\n            // <--[tag]\n            // @attribute <LocationTag.precise_target_position[(<range>)].type[<entity_type>|...]>\n            // @returns LocationTag\n            // @group world\n            // @deprecated use \"ray_trace[entities=(your_types)]\" instead.\n            // @description\n            // Deprecated in favor of <@link tag LocationTag.ray_trace> with \"entities=\" set to your input types as a matcher.\n            // -->\n            if (attribute.startsWith(\"type\", 2) && attribute.hasContext(2)) {\n                attribute.fulfill(1);\n                Set<EntityType> types = new HashSet<>();\n                for (String str : attribute.paramAsType(ListTag.class)) {\n                    types.add(EntityTag.valueOf(str, attribute.context).getBukkitEntityType());\n                }\n                result = object.getWorld().rayTrace(object, object.getDirection(), range, FluidCollisionMode.NEVER, true, 0, (e) -> types.contains(e.getType()));\n            }\n            else {\n                result = object.getWorld().rayTrace(object, object.getDirection(), range, FluidCollisionMode.NEVER, true, 0, null);\n            }\n            if (result != null) {\n                return new LocationTag(object.getWorld(), result.getHitPosition());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.points_between[<location>]>\n        // @returns ListTag(LocationTag)\n        // @group math\n        // @description\n        // Finds all locations between this location and another, separated by 1 block-width each.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"points_between\", (attribute, object) -> {\n            LocationTag target = attribute.paramAsType(LocationTag.class);\n            if (target == null) {\n                return null;\n            }\n\n            // <--[tag]\n            // @attribute <LocationTag.points_between[<location>].distance[<#.#>]>\n            // @returns ListTag(LocationTag)\n            // @group math\n            // @description\n            // Finds all locations between this location and another, separated by the specified distance each.\n            // -->\n            double rad = 1d;\n            if (attribute.startsWith(\"distance\", 2)) {\n                rad = attribute.getDoubleContext(2);\n                attribute.fulfill(1);\n                if (rad < 0.000001) {\n                    attribute.echoError(\"Distance value cannot be zero or negative.\");\n                    return null;\n                }\n            }\n            ListTag list = new ListTag();\n            org.bukkit.util.Vector rel = target.toVector().subtract(object.toVector());\n            double len = rel.length();\n            if (len < 0.000001) {\n                return list;\n            }\n            if (len / rad > Settings.cache_blockTagsMaxBlocks) {\n                len = rad * Settings.cache_blockTagsMaxBlocks;\n            }\n            rel = rel.multiply(1d / len);\n            for (double i = 0d; i <= len; i += rad) {\n                list.addObject(new LocationTag(object.clone().add(rel.clone().multiply(i))));\n            }\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.facing_blocks[(<#>)]>\n        // @returns ListTag(LocationTag)\n        // @group world\n        // @description\n        // Finds all block locations in the direction this location is facing,\n        // optionally with a custom range (default is 100).\n        // For example a location at 0,0,0 facing straight up\n        // will include 0,1,0 0,2,0 and so on.\n        // This is an imperfect block line tracer.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"facing_blocks\", (attribute, object) -> {\n            int range = attribute.getIntParam();\n            if (range < 1) {\n                range = 100;\n            }\n            ListTag list = new ListTag();\n            try {\n                NMSHandler.chunkHelper.changeChunkServerThread(object.getWorld());\n                BlockIterator iterator = new BlockIterator(object, 0, range);\n                while (iterator.hasNext()) {\n                    list.addObject(new LocationTag(iterator.next().getLocation()));\n                }\n            }\n            finally {\n                NMSHandler.chunkHelper.restoreServerThread(object.getWorld());\n            }\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.line_of_sight[<location>]>\n        // @returns ElementTag(Boolean)\n        // @group math\n        // @description\n        // Returns whether the specified location is within this location's line of sight.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"line_of_sight\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            LocationTag location = attribute.paramAsType(LocationTag.class);\n            if (location != null) {\n                try {\n                    NMSHandler.chunkHelper.changeChunkServerThread(object.getWorld());\n                    return new ElementTag(NMSHandler.entityHelper.canTrace(object.getWorld(), object.toVector(), location.toVector()));\n                }\n                finally {\n                    NMSHandler.chunkHelper.restoreServerThread(object.getWorld());\n                }\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.direction[(<location>)]>\n        // @returns ElementTag\n        // @group math\n        // @description\n        // Returns the compass direction between two locations.\n        // If no second location is specified, returns the direction of the location.\n        // Example returns include \"north\", \"southwest\", ...\n        // -->\n        tagProcessor.registerTag(ObjectTag.class, \"direction\", (attribute, object) -> {\n            // <--[tag]\n            // @attribute <LocationTag.direction.vector>\n            // @returns LocationTag\n            // @group math\n            // @description\n            // Returns the location's direction as a one-length vector.\n            // -->\n            if (attribute.startsWith(\"vector\", 2)) {\n                attribute.fulfill(1);\n                return new LocationTag(object.getWorld(), object.getDirection());\n            }\n            // Get the cardinal direction from this location to another\n            if (attribute.hasParam() && LocationTag.matches(attribute.getParam())) {\n                // Subtract this location's vector from the other location's vector,\n                // not the other way around\n                LocationTag target = attribute.paramAsType(LocationTag.class);\n\n                // <--[tag]\n                // @attribute <LocationTag.direction[<location>].yaw>\n                // @returns ElementTag(Decimal)\n                // @group math\n                // @description\n                // Returns the yaw direction between two locations.\n                // -->\n                if (attribute.startsWith(\"yaw\", 2)) {\n                    attribute.fulfill(1);\n                    return new ElementTag(EntityHelper.normalizeYaw(NMSHandler.entityHelper.getYaw\n                            (target.toVector().subtract(object.toVector())\n                                    .normalize())));\n                }\n                else {\n                    return new ElementTag(NMSHandler.entityHelper.getCardinal(NMSHandler.entityHelper.getYaw\n                            (target.toVector().subtract(object.toVector())\n                                    .normalize())));\n                }\n            }\n            // Get a cardinal direction from this location's yaw\n            else {\n                return new ElementTag(NMSHandler.entityHelper.getCardinal(object.getYaw()));\n            }\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.rotate_yaw[<#.#>]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location with the yaw rotated the specified amount (eg 180 to face the location backwards).\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"rotate_yaw\", (attribute, object) -> {\n            LocationTag loc = LocationTag.valueOf(object.identify(), attribute.context).clone();\n            loc.setYaw(loc.getYaw() + (float) attribute.getDoubleParam());\n            return loc;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.rotate_pitch[<#.#>]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location with the pitch rotated the specified amount. Note that this is capped to +/- 90.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"rotate_pitch\", (attribute, object) -> {\n            LocationTag loc = LocationTag.valueOf(object.identify(), attribute.context).clone();\n            loc.setPitch(Math.max(-90, Math.min(90, loc.getPitch() + (float) attribute.getDoubleParam())));\n            return loc;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.face[<location>]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns a location containing a yaw/pitch that point from the current location\n        // to the target location.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"face\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            Location two = attribute.paramAsType(LocationTag.class);\n            return new LocationTag(NMSHandler.entityHelper.faceLocation(object, two));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.facing[<entity>/<location>]>\n        // @returns ElementTag(Boolean)\n        // @group math\n        // @description\n        // Returns whether the location's yaw is facing another entity or location, within a limit of 45 degrees of yaw.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"facing\", (attribute, object) -> {\n            if (attribute.hasParam()) {\n\n                // The default number of degrees if there is no degrees attribute\n                int degrees = 45;\n                LocationTag facingLoc;\n                if (LocationTag.matches(attribute.getParam())) {\n                    facingLoc = attribute.paramAsType(LocationTag.class);\n                }\n                else if (EntityTag.matches(attribute.getParam())) {\n                    facingLoc = attribute.paramAsType(EntityTag.class).getLocation();\n                }\n                else {\n                    attribute.echoError(\"Tag location.facing[...] was given an invalid facing target.\");\n                    return null;\n                }\n\n                // <--[tag]\n                // @attribute <LocationTag.facing[<entity>/<location>].degrees[<#>(,<#>)]>\n                // @returns ElementTag(Boolean)\n                // @group math\n                // @description\n                // Returns whether the location's yaw is facing another\n                // entity or location, within a specified degree range.\n                // Optionally specify a pitch limit as well.\n                // -->\n                if (attribute.startsWith(\"degrees\", 2) && attribute.hasContext(2)) {\n                    String context = attribute.getContext(2);\n                    attribute.fulfill(1);\n                    if (context.contains(\",\")) {\n                        String yaw = context.substring(0, context.indexOf(','));\n                        String pitch = context.substring(context.indexOf(',') + 1);\n                        degrees = Integer.parseInt(yaw);\n                        int pitchDegrees = Integer.parseInt(pitch);\n                        return new ElementTag(NMSHandler.entityHelper.isFacingLocation(object, facingLoc, degrees, pitchDegrees));\n                    }\n                    else {\n                        degrees = Integer.parseInt(context);\n                    }\n                }\n\n                return new ElementTag(NMSHandler.entityHelper.isFacingLocation(object, facingLoc, degrees));\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.pitch>\n        // @returns ElementTag(Decimal)\n        // @group identity\n        // @description\n        // Returns the pitch of the object at the location.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"pitch\", (attribute, object) -> {\n            return new ElementTag(object.getPitch());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.with_pose[<entity>/<pitch>,<yaw>]>\n        // @returns LocationTag\n        // @group identity\n        // @description\n        // Returns the location with pitch and yaw.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"with_pose\", (attribute, object) -> {\n            String context = attribute.getParam();\n            float pitch = 0f;\n            float yaw = 0f;\n            if (EntityTag.matches(context)) {\n                EntityTag ent = EntityTag.valueOf(context, attribute.context);\n                if (ent.isSpawnedOrValidForTag()) {\n                    pitch = ent.getBukkitEntity().getLocation().getPitch();\n                    yaw = ent.getBukkitEntity().getLocation().getYaw();\n                }\n            }\n            else if (context.split(\",\").length == 2) {\n                String[] split = context.split(\",\");\n                pitch = Float.parseFloat(split[0]);\n                yaw = Float.parseFloat(split[1]);\n            }\n            LocationTag loc = object.clone();\n            loc.setPitch(pitch);\n            loc.setYaw(yaw);\n            return loc;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.yaw>\n        // @returns ElementTag(Decimal)\n        // @group identity\n        // @description\n        // Returns the normalized yaw of the object at the location.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"yaw\", (attribute, object) -> {\n            // <--[tag]\n            // @attribute <LocationTag.yaw.simple>\n            // @returns ElementTag\n            // @description\n            // Returns the yaw as 'North', 'South', 'East', or 'West'.\n            // -->\n            if (attribute.startsWith(\"simple\", 2)) {\n                attribute.fulfill(1);\n                float yaw = EntityHelper.normalizeYaw(object.getYaw());\n                if (yaw < 45) {\n                    return new ElementTag(\"South\");\n                }\n                else if (yaw < 135) {\n                    return new ElementTag(\"West\");\n                }\n                else if (yaw < 225) {\n                    return new ElementTag(\"North\");\n                }\n                else if (yaw < 315) {\n                    return new ElementTag(\"East\");\n                }\n                else {\n                    return new ElementTag(\"South\");\n                }\n            }\n\n            // <--[tag]\n            // @attribute <LocationTag.yaw.raw>\n            // @returns ElementTag(Decimal)\n            // @group identity\n            // @description\n            // Returns the raw (un-normalized) yaw of the object at the location.\n            // -->\n            if (attribute.startsWith(\"raw\", 2)) {\n                attribute.fulfill(1);\n                return new ElementTag(object.getYaw());\n            }\n            return new ElementTag(EntityHelper.normalizeYaw(object.getYaw()));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.rotate_around_x[<#.#>]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location-vector rotated around the x axis by a specified angle in radians.\n        // Generally used in a format like <player.location.add[<location[0,1,0].rotate_around_x[<[some_angle]>]>]>.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"rotate_around_x\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            double[] values = getRotatedAroundX(attribute.getDoubleParam(), object.getY(), object.getZ());\n            Location location = object.clone();\n            location.setY(values[0]);\n            location.setZ(values[1]);\n            return new LocationTag(location);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.rotate_around_y[<#.#>]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location-vector rotated around the y axis by a specified angle in radians.\n        // Generally used in a format like <player.location.add[<location[1,0,0].rotate_around_y[<[some_angle]>]>]>.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"rotate_around_y\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            double[] values = getRotatedAroundY(attribute.getDoubleParam(), object.getX(), object.getZ());\n            Location location = object.clone();\n            location.setX(values[0]);\n            location.setZ(values[1]);\n            return new LocationTag(location);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.rotate_around_z[<#.#>]>\n        // @returns LocationTag\n        // @group math\n        // @description\n        // Returns the location-vector rotated around the z axis by a specified angle in radians.\n        // Generally used in a format like <player.location.add[<location[1,0,0].rotate_around_z[<[some_angle]>]>]>.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"rotate_around_z\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            double[] values = getRotatedAroundZ(attribute.getDoubleParam(), object.getX(), object.getY());\n            Location location = object.clone();\n            location.setX(values[0]);\n            location.setY(values[1]);\n            return new LocationTag(location);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.points_around_x[radius=<#.#>;points=<#>]>\n        // @returns ListTag(LocationTag)\n        // @group math\n        // @description\n        // Returns a list of points in a circle around a location's x axis with the specified radius and number of points.\n        // For example: <player.location.points_around_x[radius=10;points=16]>\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"points_around_x\", (attribute, object) -> {\n            double[] values = parsePointsAroundArgs(attribute);\n            if (values == null) {\n                return null;\n            }\n            double angle = 2 * Math.PI / values[1];\n            ListTag points = new ListTag();\n            for (int i = 0; i < values[1]; i++) {\n                double[] result = getRotatedAroundX(angle * i, values[0], 0);\n                points.addObject(object.clone().add(0, result[0], result[1]));\n            }\n            return points;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.points_around_y[radius=<#.#>;points=<#>]>\n        // @returns ListTag(LocationTag)\n        // @group math\n        // @description\n        // Returns a list of points in a circle around a location's y axis with the specified radius and number of points.\n        // For example: <player.location.points_around_y[radius=10;points=16]>\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"points_around_y\", (attribute, object) -> {\n            double[] values = parsePointsAroundArgs(attribute);\n            if (values == null) {\n                return null;\n            }\n            double angle = 2 * Math.PI / values[1];\n            ListTag points = new ListTag();\n            for (int i = 0; i < values[1]; i++) {\n                double[] result = getRotatedAroundY(angle * i, values[0], 0);\n                points.addObject(object.clone().add(result[0], 0, result[1]));\n            }\n            return points;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.points_around_z[radius=<#.#>;points=<#>]>\n        // @returns ListTag(LocationTag)\n        // @group math\n        // @description\n        // Returns a list of points in a circle around a location's z axis with the specified radius and number of points.\n        // For example: <player.location.points_around_z[radius=10;points=16]>\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"points_around_z\", (attribute, object) -> {\n            double[] values = parsePointsAroundArgs(attribute);\n            if (values == null) {\n                return null;\n            }\n            double angle = 2 * Math.PI / values[1];\n            ListTag points = new ListTag();\n            for (int i = 0; i < values[1]; i++) {\n                double[] result = getRotatedAroundZ(angle * i, 0, values[0]);\n                points.addObject(object.clone().add(result[0], result[1], 0));\n            }\n            return points;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.flood_fill[<limit>]>\n        // @returns ListTag(LocationTag)\n        // @group world\n        // @description\n        // Returns the set of all blocks, starting at the given location,\n        // that can be directly reached in a way that only travels through blocks of the same type as the starting block.\n        // For example, if starting at an air block inside an enclosed building, this will return all air blocks inside the building (but none outside, and no non-air blocks).\n        // As another example, if starting on a block of iron_ore in the ground, this will find all other blocks of iron ore that are part of the same vein.\n        // This will not travel diagonally, only the 6 cardinal directions (N/E/S/W/Up/Down).\n        // As this is potentially infinite should there be any opening however small, a limit must be given.\n        // The limit value can be: a CuboidTag, an EllipsoidTag, or an ElementTag(Decimal) to use as a radius.\n        // Note that the returned list will not be in any particular order.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"flood_fill\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            AreaContainmentObject area = CuboidTag.valueOf(attribute.getParam(), CoreUtilities.noDebugContext);\n            if (area == null) {\n                area = EllipsoidTag.valueOf(attribute.getParam(), CoreUtilities.noDebugContext);\n            }\n            if (area == null) {\n                double radius = attribute.getDoubleParam();\n                if (radius <= 0) {\n                    return null;\n                }\n                area = new EllipsoidTag(object.clone(), new LocationTag(object.getWorld(), radius, radius, radius));\n            }\n            FloodFiller flooder = new FloodFiller();\n            NMSHandler.chunkHelper.changeChunkServerThread(object.getWorld());\n            try {\n                if (object.getWorld() == null) {\n                    attribute.echoError(\"LocationTag trying to read block, but cannot because no world is specified.\");\n                    return null;\n                }\n                if (!object.isChunkLoaded()) {\n                    attribute.echoError(\"LocationTag trying to read block, but cannot because the chunk is unloaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                    return null;\n                }\n\n                // <--[tag]\n                // @attribute <LocationTag.flood_fill[<limit>].types[<matcher>]>\n                // @returns ListTag(LocationTag)\n                // @group world\n                // @description\n                // Returns the set of all blocks, starting at the given location,\n                // that can be directly reached in a way that only travels through blocks that match the given LocationTag matcher.\n                // This will not travel diagonally, only the 6 cardinal directions (N/E/S/W/Up/Down).\n                // As this is potentially infinite for some block types (like air, stone, etc.) should there be any opening however small, a limit must be given.\n                // The limit value can be: a CuboidTag, an EllipsoidTag, or an ElementTag(Decimal) to use as a radius.\n                // Note that the returned list will not be in any particular order.\n                // The result will be an empty list if the block at the start location is not one of the input materials.\n                // -->\n                if (attribute.startsWith(\"types\", 2) && attribute.hasContext(2)) {\n                    flooder.matcher = attribute.getContext(2);\n                    attribute.fulfill(1);\n                }\n                else {\n                    flooder.requiredMaterial = object.getBlock().getType();\n                }\n                flooder.run(object, area, attribute.context);\n            }\n            finally {\n                NMSHandler.chunkHelper.restoreServerThread(object.getWorld());\n            }\n            return new ListTag((Collection<LocationTag>) flooder.result);\n        });\n\n\n        // <--[tag]\n        // @attribute <LocationTag.find_nearest_biome[<biome>]>\n        // @returns LocationTag\n        // @group finding\n        // @description\n        // Returns the location of the nearest block of the given biome type (or null).\n        // Warning: may be extremely slow to process. Use with caution.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"find_nearest_biome\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            BiomeTag biome = attribute.paramAsType(BiomeTag.class);\n            if (biome == null) {\n                attribute.echoError(\"Invalid biome input.\");\n                return null;\n            }\n            Location result = NMSHandler.worldHelper.getNearestBiomeLocation(object, biome);\n            if (result == null) {\n                return null;\n            }\n            return new LocationTag(result);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.find_blocks_flagged[<flag_name>].within[<#>]>\n        // @returns ListTag(LocationTag)\n        // @group finding\n        // @description\n        // Returns a list of blocks that have the specified flag within a radius.\n        // Note: current implementation measures the center of nearby block's distance from the exact given location.\n        // Result list is sorted by closeness (1 = closest, 2 = next closest, ... last = farthest).\n        // Searches the internal flag lists, rather than through all possible blocks.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"find_blocks_flagged\", (attribute, object) -> {\n            if (!attribute.hasParam() || !attribute.startsWith(\"within\", 2) || !attribute.hasContext(2)) {\n                attribute.echoError(\"find_blocks_flagged[...].within[...] tag malformed.\");\n                return null;\n            }\n            String flagName = CoreUtilities.toLowerCase(attribute.getParam());\n            attribute.fulfill(1);\n            double radius = attribute.getDoubleParam();\n            if (!object.isChunkLoadedSafe()) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because the chunk is unloaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                return null;\n            }\n            double minPossibleX = object.getX() - radius;\n            double minPossibleZ = object.getZ() - radius;\n            double maxPossibleX = object.getX() + radius;\n            double maxPossibleZ = object.getZ() + radius;\n            int minChunkX = (int) Math.floor(minPossibleX / 16);\n            int minChunkZ = (int) Math.floor(minPossibleZ / 16);\n            int maxChunkX = (int) Math.ceil(maxPossibleX / 16);\n            int maxChunkZ = (int) Math.ceil(maxPossibleZ / 16);\n            ChunkTag testChunk = new ChunkTag(object);\n            final ArrayList<LocationTag> found = new ArrayList<>();\n            for (int x = minChunkX; x <= maxChunkX; x++) {\n                testChunk.chunkX = x;\n                for (int z = minChunkZ; z <= maxChunkZ; z++) {\n                    testChunk.chunkZ = z;\n                    testChunk.cachedChunk = null;\n                    if (testChunk.isLoadedSafe()) {\n                        LocationFlagSearchHelper.getFlaggedLocations(testChunk.getChunkForTag(attribute), flagName, (loc) -> {\n                            loc.setX(loc.getX() + 0.5);\n                            loc.setY(loc.getY() + 0.5);\n                            loc.setZ(loc.getZ() + 0.5);\n                            if (Utilities.checkLocation(object, loc, radius)) {\n                                found.add(new LocationTag(loc));\n                            }\n                        });\n                    }\n                }\n            }\n            found.sort(object::compare);\n            return new ListTag(found);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.find_entities[(<matcher>)].within[<#.#>]>\n        // @returns ListTag(EntityTag)\n        // @group finding\n        // @description\n        // Returns a list of entities within a radius, with an optional search parameter for the entity type.\n        // Result list is sorted by closeness (1 = closest, 2 = next closest, ... last = farthest).\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"find_entities\", (attribute, object) -> {\n            String matcher = attribute.hasParam() ? attribute.getParam() : null;\n            if (!attribute.startsWith(\"within\", 2) || !attribute.hasContext(2)) {\n                return null;\n            }\n            double radius = attribute.getDoubleContext(2);\n            attribute.fulfill(1);\n            ListTag found = new ListTag();\n            BoundingBox box = BoundingBox.of(object, radius, radius, radius);\n            for (Entity entity : new WorldTag(object.getWorld()).getPossibleEntitiesForBoundaryForTag(box)) {\n                if (Utilities.checkLocationWithBoundingBox(object, entity, radius)) {\n                    EntityTag current = new EntityTag(entity);\n                    if (matcher == null || current.tryAdvancedMatcher(matcher, attribute.context)) {\n                        found.addObject(current.getDenizenObject());\n                    }\n                }\n            }\n            found.objectForms.sort((ent1, ent2) -> object.compare(((EntityFormObject) ent1).getLocation(), ((EntityFormObject) ent2).getLocation()));\n            return found;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.find_blocks[(<matcher>)].within[<#.#>]>\n        // @returns ListTag(LocationTag)\n        // @group finding\n        // @description\n        // Returns a list of blocks within a radius, with an optional search parameter for the block material.\n        // Note: current implementation measures the center of nearby block's distance from the exact given location.\n        // Result list is sorted by closeness (1 = closest, 2 = next closest, ... last = farthest).\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"find_blocks\", (attribute, object) -> {\n            String matcher = attribute.hasParam() ? attribute.getParam() : null;\n            if (!attribute.startsWith(\"within\", 2) || !attribute.hasContext(2)) {\n                return null;\n            }\n            double radius = attribute.getDoubleContext(2);\n            attribute.fulfill(1);\n            ListTag found = new ListTag();\n            int max = Settings.blockTagsMaxBlocks();\n            int index = 0;\n            Location tstart = object.getBlockLocation();\n            double tstartY = tstart.getY();\n            int radiusInt = (int) Math.ceil(radius);\n            fullloop:\n            for (int y = -radiusInt; y <= radiusInt; y++) {\n                double newY = y + tstartY;\n                if (!Utilities.isLocationYSafe(newY, object.getWorld())) {\n                    continue;\n                }\n                for (int x = -radiusInt; x <= radiusInt; x++) {\n                    for (int z = -radiusInt; z <= radiusInt; z++) {\n                        index++;\n                        if (index > max) {\n                            break fullloop;\n                        }\n                        if (Utilities.checkLocation(object, tstart.clone().add(x + 0.5, y + 0.5, z + 0.5), radius)) {\n                            if (matcher == null || new LocationTag(tstart.clone().add(x, y, z)).tryAdvancedMatcher(matcher, attribute.context)) {\n                                found.addObject(new LocationTag(tstart.clone().add(x, y, z)));\n                            }\n                        }\n                    }\n                }\n            }\n            found.objectForms.sort((loc1, loc2) -> object.compare((LocationTag) loc1, (LocationTag) loc2));\n            return found;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.find_tile_entities[(<matcher>)].within[<#.#>]>\n        // @returns ListTag(LocationTag)\n        // @group finding\n        // @description\n        // Returns a list of tile-entity blocks within a radius, with an optional search parameter for the block material.\n        // This can be more efficient that <@link tag LocationTag.find_blocks.within> when only tile-entity blocks are relevant.\n        // Note: current implementation measures the center of nearby block's distance from the exact given location.\n        // Result list is sorted by closeness (1 = closest, 2 = next closest, ... last = farthest).\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"find_tile_entities\", (attribute, object) -> {\n            String matcher = attribute.hasParam() ? attribute.getParam() : null;\n            if (!attribute.startsWith(\"within\", 2) || !attribute.hasContext(2)) {\n                return null;\n            }\n            attribute.fulfill(1);\n            double radius = attribute.getDoubleParam();\n            ListTag found = new ListTag();\n            int max = Settings.blockTagsMaxBlocks();\n            int index = 0;\n            if (!object.isChunkLoadedSafe()) {\n                attribute.echoError(\"LocationTag trying to read block, but cannot because the chunk is unloaded. Use the 'chunkload' command to ensure the chunk is loaded.\");\n                return null;\n            }\n            double minPossibleX = object.getX() - radius;\n            double minPossibleZ = object.getZ() - radius;\n            double maxPossibleX = object.getX() + radius;\n            double maxPossibleZ = object.getZ() + radius;\n            int minChunkX = (int) Math.floor(minPossibleX / 16);\n            int minChunkZ = (int) Math.floor(minPossibleZ / 16);\n            int maxChunkX = (int) Math.ceil(maxPossibleX / 16);\n            int maxChunkZ = (int) Math.ceil(maxPossibleZ / 16);\n            ChunkTag testChunk = new ChunkTag(object);\n            Location refLoc = object.clone();\n            fullLoop:\n            for (int x = minChunkX; x <= maxChunkX; x++) {\n                testChunk.chunkX = x;\n                for (int z = minChunkZ; z <= maxChunkZ; z++) {\n                    testChunk.chunkZ = z;\n                    testChunk.cachedChunk = null;\n                    if (testChunk.isLoadedSafe()) {\n                        for (BlockState block : testChunk.getChunkForTag(attribute).getTileEntities()) {\n                            if (index++ > max) {\n                                break fullLoop;\n                            }\n                            Location current = block.getLocation(refLoc).add(0.5, 0.5, 0.5);\n                            if (Utilities.checkLocation(object, current, radius)) {\n                                LocationTag actualLoc = new LocationTag(current);\n                                if (matcher == null || actualLoc.tryAdvancedMatcher(matcher, attribute.context)) {\n                                    found.addObject(actualLoc);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n            found.objectForms.sort((loc1, loc2) -> object.compare((LocationTag) loc1, (LocationTag) loc2));\n            return found;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.find_spawnable_blocks_within[<#.#>]>\n        // @returns ListTag(LocationTag)\n        // @group finding\n        // @description\n        // Returns a list of blocks within a radius, that are safe for spawning, with the same logic as <@link tag LocationTag.is_spawnable>.\n        // Note: current implementation measures the center of nearby block's distance from the exact given location.\n        // Result list is sorted by closeness (1 = closest, 2 = next closest, ... last = farthest).\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"find_spawnable_blocks_within\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            double radius = attribute.getDoubleParam();\n            ListTag found = new ListTag();\n            int max = Settings.blockTagsMaxBlocks();\n            int index = 0;\n            Location tstart = object.getBlockLocation();\n            double tstartY = tstart.getY();\n            int radiusInt = (int) Math.ceil(radius);\n            fullloop:\n            for (int y = -radiusInt; y <= radiusInt; y++) {\n                double newY = y + tstartY;\n                if (!Utilities.isLocationYSafe(newY, object.getWorld())) {\n                    continue;\n                }\n                for (int x = -radiusInt; x <= radiusInt; x++) {\n                    for (int z = -radiusInt; z <= radiusInt; z++) {\n                        index++;\n                        if (index > max) {\n                            break fullloop;\n                        }\n                        Location loc = tstart.clone().add(x + 0.5, y + 0.5, z + 0.5);\n                        if (Utilities.checkLocation(object, loc, radius) && SpawnableHelper.isSpawnable(loc)) {\n                            found.addObject(new LocationTag(loc.add(0, -0.5, 0)));\n                        }\n                    }\n                }\n            }\n            found.objectForms.sort((loc1, loc2) -> object.compare((LocationTag) loc1, (LocationTag) loc2));\n            return found;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.find_players_within[<#.#>]>\n        // @returns ListTag(PlayerTag)\n        // @group finding\n        // @description\n        // Returns a list of players within a radius.\n        // Result list is sorted by closeness (1 = closest, 2 = next closest, ... last = farthest).\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"find_players_within\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            double radius = attribute.getDoubleParam();\n            ArrayList<PlayerTag> found = new ArrayList<>();\n            for (Player player : Bukkit.getOnlinePlayers()) {\n                if (!player.isDead() && Utilities.checkLocationWithBoundingBox(object, player, radius)) {\n                    found.add(new PlayerTag(player));\n                }\n            }\n            found.sort((pl1, pl2) -> object.compare(pl1.getLocation(), pl2.getLocation()));\n            return new ListTag(found);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.find_npcs_within[<#.#>]>\n        // @returns ListTag(NPCTag)\n        // @group finding\n        // @description\n        // Returns a list of NPCs within a radius.\n        // Result list is sorted by closeness (1 = closest, 2 = next closest, ... last = farthest).\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"find_npcs_within\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            double radius = attribute.getDoubleParam();\n            ArrayList<NPCTag> found = new ArrayList<>();\n            for (NPC npc : CitizensAPI.getNPCRegistry()) {\n                if (npc.isSpawned() && Utilities.checkLocationWithBoundingBox(object, npc.getEntity(), radius)) {\n                    found.add(new NPCTag(npc));\n                }\n            }\n            found.sort((npc1, npc2) -> object.compare(npc1.getLocation(), npc2.getLocation()));\n            return new ListTag(found);\n        });\n\n        tagProcessor.registerTag(ObjectTag.class, \"find\", (attribute, object) -> {\n            if (!attribute.startsWith(\"within\", 3) || !attribute.hasContext(3)) {\n                return null;\n            }\n            double radius = attribute.getDoubleContext(3);\n\n            if (attribute.startsWith(\"blocks\", 2)) {\n                BukkitImplDeprecations.locationFindEntities.warn(attribute.context);\n                ArrayList<LocationTag> found = new ArrayList<>();\n                List<MaterialTag> materials = new ArrayList<>();\n                if (attribute.hasContext(2)) {\n                    materials = attribute.contextAsType(2, ListTag.class).filter(MaterialTag.class, attribute.context);\n                }\n                // Avoid NPE from invalid materials\n                if (materials == null) {\n                    return null;\n                }\n                int max = Settings.blockTagsMaxBlocks();\n                int index = 0;\n\n                attribute.fulfill(2);\n                Location tstart = object.getBlockLocation();\n                double tstartY = tstart.getY();\n                int radiusInt = (int) radius;\n\n                fullloop:\n                for (int x = -radiusInt; x <= radiusInt; x++) {\n                    for (int y = -radiusInt; y <= radiusInt; y++) {\n                        double newY = y + tstartY;\n                        if (!Utilities.isLocationYSafe(newY, object.getWorld())) {\n                            continue;\n                        }\n                        for (int z = -radiusInt; z <= radiusInt; z++) {\n                            index++;\n                            if (index > max) {\n                                break fullloop;\n                            }\n                            if (Utilities.checkLocation(object, tstart.clone().add(x + 0.5, y + 0.5, z + 0.5), radius)) {\n                                if (!materials.isEmpty()) {\n                                    for (MaterialTag material : materials) {\n                                        if (material.getMaterial() == new LocationTag(tstart.clone().add(x, y, z)).getBlockTypeForTag(attribute)) {\n                                            found.add(new LocationTag(tstart.clone().add(x, y, z)));\n                                        }\n                                    }\n                                }\n                                else {\n                                    found.add(new LocationTag(tstart.clone().add(x, y, z)));\n                                }\n                            }\n                        }\n                    }\n                }\n                found.sort(object::compare);\n                return new ListTag(found);\n            }\n\n            // <--[tag]\n            // @attribute <LocationTag.find.surface_blocks[(<material>|...)].within[<#.#>]>\n            // @returns ListTag(LocationTag)\n            // @group finding\n            // @description\n            // Returns a list of matching surface blocks within a radius.\n            // Result list is sorted by closeness (1 = closest, 2 = next closest, ... last = farthest).\n            // -->\n            else if (attribute.startsWith(\"surface_blocks\", 2)) {\n                ArrayList<LocationTag> found = new ArrayList<>();\n                List<MaterialTag> materials = new ArrayList<>();\n                if (attribute.hasContext(2)) {\n                    materials = attribute.contextAsType(2, ListTag.class).filter(MaterialTag.class, attribute.context);\n                }\n                // Avoid NPE from invalid materials\n                if (materials == null) {\n                    return null;\n                }\n                int max = Settings.blockTagsMaxBlocks();\n                int index = 0;\n                attribute.fulfill(2);\n                Location blockLoc = object.getBlockLocation();\n                Location loc = blockLoc.clone().add(0.5f, 0.5f, 0.5f);\n\n                fullloop:\n                for (double x = -(radius); x <= radius; x++) {\n                    for (double y = -(radius); y <= radius; y++) {\n                        for (double z = -(radius); z <= radius; z++) {\n                            index++;\n                            if (index > max) {\n                                break fullloop;\n                            }\n                            if (Utilities.checkLocation(loc, blockLoc.clone().add(x + 0.5, y + 0.5, z + 0.5), radius)) {\n                                LocationTag l = new LocationTag(blockLoc.clone().add(x, y, z));\n                                if (!materials.isEmpty()) {\n                                    for (MaterialTag material : materials) {\n                                        if (material.getMaterial() == l.getBlockTypeForTag(attribute)) {\n                                            if (new LocationTag(l.clone().add(0, 1, 0)).getBlockTypeForTag(attribute) == Material.AIR\n                                                    && new LocationTag(l.clone().add(0, 2, 0)).getBlockTypeForTag(attribute) == Material.AIR\n                                                    && l.getBlockTypeForTag(attribute) != Material.AIR) {\n                                                found.add(new LocationTag(blockLoc.clone().add(x + 0.5, y, z + 0.5)));\n                                            }\n                                        }\n                                    }\n                                }\n                                else {\n                                    if (new LocationTag(l.clone().add(0, 1, 0)).getBlockTypeForTag(attribute) == Material.AIR\n                                            && new LocationTag(l.clone().add(0, 2, 0)).getBlockTypeForTag(attribute) == Material.AIR\n                                            && l.getBlockTypeForTag(attribute) != Material.AIR) {\n                                        found.add(new LocationTag(blockLoc.clone().add(x + 0.5, y, z + 0.5)));\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n                found.sort(object::compare);\n                return new ListTag(found);\n            }\n\n            else if (attribute.startsWith(\"players\", 2)) {\n                BukkitImplDeprecations.locationFindEntities.warn(attribute.context);\n                ArrayList<PlayerTag> found = new ArrayList<>();\n                attribute.fulfill(2);\n                for (Player player : Bukkit.getOnlinePlayers()) {\n                    if (!player.isDead() && Utilities.checkLocationWithBoundingBox(object, player, radius)) {\n                        found.add(new PlayerTag(player));\n                    }\n                }\n                found.sort((pl1, pl2) -> object.compare(pl1.getLocation(), pl2.getLocation()));\n                return new ListTag(found);\n            }\n\n            else if (attribute.startsWith(\"npcs\", 2)) {\n                BukkitImplDeprecations.locationFindEntities.warn(attribute.context);\n                ArrayList<NPCTag> found = new ArrayList<>();\n                attribute.fulfill(2);\n                for (NPC npc : CitizensAPI.getNPCRegistry()) {\n                    if (npc.isSpawned() && Utilities.checkLocationWithBoundingBox(object, npc.getEntity(), radius)) {\n                        found.add(new NPCTag(npc));\n                    }\n                }\n                found.sort((npc1, npc2) -> object.compare(npc1.getLocation(), npc2.getLocation()));\n                return new ListTag(found);\n            }\n\n            else if (attribute.startsWith(\"entities\", 2)) {\n                BukkitImplDeprecations.locationFindEntities.warn(attribute.context);\n                ListTag ent_list = attribute.hasContext(2) ? attribute.contextAsType(2, ListTag.class) : null;\n                ListTag found = new ListTag();\n                attribute.fulfill(2);\n                for (Entity entity : new WorldTag(object.getWorld()).getEntitiesForTag()) {\n                    if (Utilities.checkLocationWithBoundingBox(object, entity, radius)) {\n                        EntityTag current = new EntityTag(entity);\n                        if (ent_list != null) {\n                            for (String ent : ent_list) {\n                                if (current.comparedTo(ent)) {\n                                    found.addObject(current.getDenizenObject());\n                                    break;\n                                }\n                            }\n                        }\n                        else {\n                            found.addObject(current.getDenizenObject());\n                        }\n                    }\n                }\n                found.objectForms.sort((ent1, ent2) -> object.compare(((EntityFormObject) ent1).getLocation(), ((EntityFormObject) ent2).getLocation()));\n                return new ListTag(found.objectForms);\n            }\n\n            // <--[tag]\n            // @attribute <LocationTag.find.living_entities.within[<#.#>]>\n            // @returns ListTag(EntityTag)\n            // @group finding\n            // @description\n            // Returns a list of living entities within a radius.\n            // This includes Players, mobs, NPCs, etc., but excludes dropped items, experience orbs, etc.\n            // Result list is sorted by closeness (1 = closest, 2 = next closest, ... last = farthest).\n            // -->\n            else if (attribute.startsWith(\"living_entities\", 2)) {\n                ListTag found = new ListTag();\n                attribute.fulfill(2);\n                BoundingBox box = BoundingBox.of(object, radius, radius, radius);\n                for (Entity entity : new WorldTag(object.getWorld()).getPossibleEntitiesForBoundaryForTag(box)) {\n                    if (entity instanceof LivingEntity\n                            && Utilities.checkLocationWithBoundingBox(object, entity, radius)) {\n                        found.addObject(new EntityTag(entity).getDenizenObject());\n                    }\n                }\n                found.objectForms.sort((ent1, ent2) -> object.compare(((EntityFormObject) ent1).getLocation(), ((EntityFormObject) ent2).getLocation()));\n                return new ListTag(found.objectForms);\n            }\n\n            // <--[tag]\n            // @attribute <LocationTag.find.structure[<type>].within[<#.#>]>\n            // @returns LocationTag\n            // @deprecated Use 'LocationTag.find_structure' on 1.19+.\n            // @description\n            // Deprecated in favor of <@link tag LocationTag.find_structure> on 1.19+.\n            // -->\n            else if (attribute.startsWith(\"structure\", 2) && attribute.hasContext(2)) {\n                if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\n                    BukkitImplDeprecations.findStructureTags.warn(attribute.context);\n                }\n                String typeName = attribute.getContext(2);\n                StructureType type = StructureType.getStructureTypes().get(typeName);\n                if (type == null) {\n                    attribute.echoError(\"Invalid structure type '\" + typeName + \"'.\");\n                    return null;\n                }\n                attribute.fulfill(2);\n                Location result = object.getWorld().locateNearestStructure(object, type, (int) radius, false);\n                if (result == null) {\n                    return null;\n                }\n                return new LocationTag(result);\n            }\n\n            // <--[tag]\n            // @attribute <LocationTag.find.unexplored_structure[<type>].within[<#.#>]>\n            // @returns LocationTag\n            // @deprecated Use 'LocationTag.find_structure' with 'unexplored=true' on 1.19+.\n            // @description\n            // Deprecated in favor of <@link tag LocationTag.find_structure> with 'unexplored=true' on 1.19+.\n            // -->\n            else if (attribute.startsWith(\"unexplored_structure\", 2) && attribute.hasContext(2)) {\n                if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\n                    BukkitImplDeprecations.findStructureTags.warn(attribute.context);\n                }\n                String typeName = attribute.getContext(2);\n                StructureType type = StructureType.getStructureTypes().get(typeName);\n                if (type == null) {\n                    attribute.echoError(\"Invalid structure type '\" + typeName + \"'.\");\n                    return null;\n                }\n                attribute.fulfill(2);\n                Location result = object.getWorld().locateNearestStructure(object, type, (int) radius, true);\n                if (result == null) {\n                    return null;\n                }\n                return new LocationTag(result);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.find_path[<location>]>\n        // @returns ListTag(LocationTag)\n        // @group finding\n        // @description\n        // Returns a full list of points along the path from this location to the given location.\n        // Uses a max range of 100 blocks from the start.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"find_path\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            LocationTag two = attribute.paramAsType(LocationTag.class);\n            if (two == null) {\n                return null;\n            }\n            List<LocationTag> locs = PathFinder.getPath(object, two);\n            ListTag list = new ListTag();\n            for (LocationTag loc : locs) {\n                list.addObject(loc);\n            }\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.format[<format>]>\n        // @returns ElementTag\n        // @group identity\n        // @description\n        // Formats a LocationTag according to a specified format input.\n        // You can request x, y, z, yaw, or pitch as full \"f\", short \"s\", or block \"b\" format.\n        // eg \"fx\" for \"full X coordinate\" (like 1.051234), \"syaw\" for \"short yaw\" (like 1.03), or \"bz\" for \"block Z coordinate\" (like 1).\n        // \"world\" is also available to get the world name.\n        // If \"$\" is in the format, only codes prefixed with \"$\" will be replaced, eg \"$bx but not bx\" will become \"5 but not bx\" (allowing the safe use of unpredictable text inside the format block).\n        // @example\n        // # This example does a simple format, like \"1, 2, 3\".\n        // - narrate \"You are at <player.location.format[bx, by, bz]>\"\n        // @example\n        // # This example adds colors, uses the \"$\" prefix to be safe from bugs, and formats like \"You are at y = 32 in Space\".\n        // - narrate \"<&[base]>You are at <player.location.format[y = <&[emphasis]>$by <&[base]>in <&[emphasis]>$world]>\"\n        // -->\n        tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"format\", (attribute, object, formatElem) -> {\n            String format = formatElem.asString();\n            String prefix = format.contains(\"$\") ? \"$\" : \"\";\n            format = format.replace(prefix + \"world\", object.getWorldName() == null ? \"\" : object.getWorldName());\n            format = object.formatHelper(format, prefix + \"b\", (d) -> String.valueOf((long) Math.floor(d)));\n            format = object.formatHelper(format, prefix + \"f\", CoreUtilities::doubleToString);\n            format = object.formatHelper(format, prefix + \"s\", (d) -> CoreUtilities.doubleToString(Math.round(d * 100) / 100.0));\n            return new ElementTag(format, true);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.formatted>\n        // @returns ElementTag\n        // @group identity\n        // @description\n        // Returns the formatted version of the LocationTag.\n        // In the format: X 'x.x', Y 'y.y', Z 'z.z', in world 'world'\n        // For example: X '1.0', Y '2.0', Z '3.0', in world 'world_nether'\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"formatted\", (attribute, object) -> {\n            // <--[tag]\n            // @attribute <LocationTag.formatted.citizens>\n            // @returns ElementTag\n            // @group identity\n            // @description\n            // Returns the location formatted for a Citizens command.\n            // In the format: x.x:y.y:z.z:world\n            // For example: 1.0:2.0:3.0:world_nether\n            // -->\n            if (attribute.startsWith(\"citizens\", 2)) {\n                attribute.fulfill(1);\n                return new ElementTag(object.getX() + \":\" + object.getY() + \":\" + object.getZ() + \":\" + object.getWorldName());\n            }\n            return new ElementTag(\"X '\" + object.getX()\n                    + \"', Y '\" + object.getY()\n                    + \"', Z '\" + object.getZ()\n                    + \"', in world '\" + object.getWorldName() + \"'\");\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.chunk>\n        // @returns ChunkTag\n        // @group identity\n        // @description\n        // Returns the chunk that this location belongs to.\n        // -->\n        tagProcessor.registerTag(ChunkTag.class, \"chunk\", (attribute, object) -> {\n            return new ChunkTag(object);\n        }, \"get_chunk\");\n\n        // <--[tag]\n        // @attribute <LocationTag.raw>\n        // @returns ElementTag\n        // @group identity\n        // @description\n        // Returns the raw representation of this location, without any note name.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"raw\", (attribute, object) -> {\n            return new ElementTag(object.identifyRaw());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.world>\n        // @returns WorldTag\n        // @group identity\n        // @description\n        // Returns the world that the location is in.\n        // -->\n        tagProcessor.registerTag(WorldTag.class, \"world\", (attribute, object) -> {\n            return object.getWorldName() == null ? null : new WorldTag(object.getWorldName());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.with_yaw[<number>]>\n        // @returns LocationTag\n        // @group identity\n        // @description\n        // Returns a copy of the location with a changed yaw value.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"with_yaw\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            LocationTag output = object.clone();\n            output.setYaw((float) attribute.getDoubleParam());\n            return output;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.with_pitch[<number>]>\n        // @returns LocationTag\n        // @group identity\n        // @description\n        // Returns a copy of the location with a changed pitch value.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"with_pitch\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            LocationTag output = object.clone();\n            output.setPitch((float) attribute.getDoubleParam());\n            return output;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.with_world[<world>]>\n        // @returns LocationTag\n        // @group identity\n        // @description\n        // Returns a copy of the location with a changed world value.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"with_world\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            LocationTag output = object.clone();\n            WorldTag world = attribute.paramAsType(WorldTag.class);\n            output.setWorld(world.getWorld());\n            return output;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.note_name>\n        // @returns ElementTag\n        // @group identity\n        // @description\n        // Gets the name of a noted LocationTag. If the location isn't noted, this is null.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"note_name\", (attribute, object) -> {\n            String noteName = NoteManager.getSavedId((object));\n            if (noteName == null) {\n                return null;\n            }\n            return new ElementTag(noteName);\n        }, \"notable_name\");\n\n        // <--[tag]\n        // @attribute <LocationTag.vector_to_face>\n        // @returns ElementTag\n        // @description\n        // Returns the name of the BlockFace represented by a normal vector.\n        // Result can be any of the following:\n        // NORTH, EAST, SOUTH, WEST, UP, DOWN, NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST,\n        // WEST_NORTH_WEST, NORTH_NORTH_WEST, NORTH_NORTH_EAST, EAST_NORTH_EAST, EAST_SOUTH_EAST,\n        // SOUTH_SOUTH_EAST, SOUTH_SOUTH_WEST, WEST_SOUTH_WEST, SELF\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"vector_to_face\", (attribute, object) -> {\n            BlockFace face = Utilities.faceFor(object.toVector());\n            if (face != null) {\n                return new ElementTag(face);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.distance_squared[<location>]>\n        // @returns ElementTag(Decimal)\n        // @group math\n        // @description\n        // Returns the distance between 2 locations, squared.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"distance_squared\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            if (LocationTag.matches(attribute.getParam())) {\n                LocationTag toLocation = attribute.paramAsType(LocationTag.class);\n                if (object.getWorldName() == null) {\n                    return new ElementTag(object.toVector().distanceSquared(toLocation.toVector()));\n                }\n                if (!object.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) {\n                    attribute.echoError(\"Can't measure distance between two different worlds!\");\n                    return null;\n                }\n                return new ElementTag(object.distanceSquared(toLocation));\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.distance[<location>]>\n        // @returns ElementTag(Decimal)\n        // @group math\n        // @description\n        // Returns the distance between 2 locations.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"distance\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            if (LocationTag.matches(attribute.getParam())) {\n                LocationTag toLocation = attribute.paramAsType(LocationTag.class);\n\n                // <--[tag]\n                // @attribute <LocationTag.distance[<location>].horizontal>\n                // @returns ElementTag(Decimal)\n                // @group math\n                // @description\n                // Returns the horizontal distance between 2 locations.\n                // -->\n                if (attribute.startsWith(\"horizontal\", 2)) {\n\n                    // <--[tag]\n                    // @attribute <LocationTag.distance[<location>].horizontal.multiworld>\n                    // @returns ElementTag(Decimal)\n                    // @group math\n                    // @description\n                    // Returns the horizontal distance between 2 multiworld locations.\n                    // -->\n                    if (attribute.startsWith(\"multiworld\", 3)) {\n                        attribute.fulfill(2);\n                        return new ElementTag(Math.sqrt(Math.pow(object.getX() - toLocation.getX(), 2) +\n                                Math.pow(object.getZ() - toLocation.getZ(), 2)));\n                    }\n                    attribute.fulfill(1);\n                    if (object.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) {\n                        return new ElementTag(Math.sqrt(Math.pow(object.getX() - toLocation.getX(), 2) +\n                                Math.pow(object.getZ() - toLocation.getZ(), 2)));\n                    }\n                }\n\n                // <--[tag]\n                // @attribute <LocationTag.distance[<location>].vertical>\n                // @returns ElementTag(Decimal)\n                // @group math\n                // @description\n                // Returns the vertical distance between 2 locations.\n                // -->\n                else if (attribute.startsWith(\"vertical\", 2)) {\n\n                    // <--[tag]\n                    // @attribute <LocationTag.distance[<location>].vertical.multiworld>\n                    // @returns ElementTag(Decimal)\n                    // @group math\n                    // @description\n                    // Returns the vertical distance between 2 multiworld locations.\n                    // -->\n                    if (attribute.startsWith(\"multiworld\", 3)) {\n                        attribute.fulfill(2);\n                        return new ElementTag(Math.abs(object.getY() - toLocation.getY()));\n                    }\n                    attribute.fulfill(1);\n                    if (object.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) {\n                        return new ElementTag(Math.abs(object.getY() - toLocation.getY()));\n                    }\n                }\n                if (object.getWorldName() == null) {\n                    return new ElementTag(object.toVector().distance(toLocation.toVector()));\n                }\n                if (!object.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) {\n                    attribute.echoError(\"Can't measure distance between two different worlds!\");\n                    return null;\n                }\n                return new ElementTag(object.distance(toLocation));\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.is_within_border>\n        // @returns ElementTag(Boolean)\n        // @group world\n        // @description\n        // Returns whether the location is within the world border.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_within_border\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getWorldBorder().isInside(object));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.is_within[<area>]>\n        // @returns ElementTag(Boolean)\n        // @group areas\n        // @description\n        // Returns whether the location is within the specified area (cuboid, ellipsoid, polygon, ...).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_within\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            if (EllipsoidTag.matches(attribute.getParam())) {\n                EllipsoidTag ellipsoid = attribute.paramAsType(EllipsoidTag.class);\n                if (ellipsoid != null) {\n                    return new ElementTag(ellipsoid.contains(object));\n                }\n            }\n            else if (PolygonTag.matches(attribute.getParam())) {\n                PolygonTag polygon = attribute.paramAsType(PolygonTag.class);\n                if (polygon != null) {\n                    return new ElementTag(polygon.doesContainLocation(object));\n                }\n            }\n            else {\n                CuboidTag cuboid = attribute.paramAsType(CuboidTag.class);\n                if (cuboid != null) {\n                    return new ElementTag(cuboid.isInsideCuboid(object));\n                }\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.to_ellipsoid[<size>]>\n        // @returns EllipsoidTag\n        // @group areas\n        // @description\n        // Returns an ellipsoid centered at this location with the specified size.\n        // Size input is a vector of x,y,z size.\n        // -->\n        tagProcessor.registerTag(EllipsoidTag.class, \"to_ellipsoid\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"to_ellipsoid[...] tag must have input.\");\n                return null;\n            }\n            return new EllipsoidTag(object.clone(), attribute.getParamObject().asType(LocationTag.class, attribute.context));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.to_cuboid[<location>]>\n        // @returns CuboidTag\n        // @group areas\n        // @description\n        // Returns a cuboid from this location to the specified location.\n        // -->\n        tagProcessor.registerTag(CuboidTag.class, \"to_cuboid\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"to_cuboid[...] tag must have input.\");\n                return null;\n            }\n            return new CuboidTag(object.clone(), attribute.getParamObject().asType(LocationTag.class, attribute.context));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.biome>\n        // @mechanism LocationTag.biome\n        // @returns BiomeTag\n        // @group world\n        // @description\n        // Returns the biome at the location.\n        // -->\n        tagProcessor.registerTag(ObjectTag.class, \"biome\", (attribute, object) -> {\n            return new BiomeTag(object.getBiomeForTag(attribute));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.areas[(<matcher>)]>\n        // @returns ListTag(AreaObject)\n        // @group areas\n        // @description\n        // Returns a ListTag of all noted areas that include this location.\n        // Optionally, specify a matcher to only include areas that match the given AreaObject matcher text.\n        // @example\n        // # This example shows all areas at the player's location.\n        // - narrate \"You are inside: <player.location.areas.parse[note_name].formatted>\"\n        // @example\n        // # This example finds which \"Town\" area the player is in.\n        // - narrate \"You are inside the town of <player.location.areas[town_*].first.flag[town_name].if_null[Nowhere!]>\"\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"areas\", (attribute, object) -> {\n            String matcher = attribute.getParam();\n            ListTag list = new ListTag();\n            NotedAreaTracker.forEachAreaThatContains(object, (area) -> {\n                if (matcher == null || area.tryAdvancedMatcher(matcher, attribute.context)) {\n                    list.addObject(area);\n                }\n            });\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.cuboids[(<matcher>)]>\n        // @returns ListTag(CuboidTag)\n        // @group areas\n        // @description\n        // Returns a ListTag of all noted CuboidTags that include this location.\n        // Optionally, specify a matcher to only include areas that match the given AreaObject matcher text.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"cuboids\", (attribute, object) -> {\n            String matcher = attribute.getParam();\n            ListTag list = new ListTag();\n            NotedAreaTracker.forEachAreaThatContains(object, (area) -> {\n                if (area instanceof CuboidTag) {\n                    if (matcher == null || area.tryAdvancedMatcher(matcher, attribute.context)) {\n                        list.addObject(area);\n                    }\n                }\n            });\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.ellipsoids[(<matcher>)]>\n        // @returns ListTag(EllipsoidTag)\n        // @group areas\n        // @description\n        // Returns a ListTag of all noted EllipsoidTags that include this location.\n        // Optionally, specify a matcher to only include areas that match the given AreaObject matcher text.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"ellipsoids\", (attribute, object) -> {\n            String matcher = attribute.getParam();\n            ListTag list = new ListTag();\n            NotedAreaTracker.forEachAreaThatContains(object, (area) -> {\n                if (area instanceof EllipsoidTag) {\n                    if (matcher == null || area.tryAdvancedMatcher(matcher, attribute.context)) {\n                        list.addObject(area);\n                    }\n                }\n            });\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.polygons[(<matcher>)]>\n        // @returns ListTag(PolygonTag)\n        // @group areas\n        // @description\n        // Returns a ListTag of all noted PolygonTags that include this location.\n        // Optionally, specify a matcher to only include areas that match the given AreaObject matcher text.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"polygons\", (attribute, object) -> {\n            String matcher = attribute.getParam();\n            ListTag list = new ListTag();\n            NotedAreaTracker.forEachAreaThatContains(object, (area) -> {\n                if (area instanceof PolygonTag) {\n                    if (matcher == null || area.tryAdvancedMatcher(matcher, attribute.context)) {\n                        list.addObject(area);\n                    }\n                }\n            });\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.is_liquid>\n        // @returns ElementTag(Boolean)\n        // @group world\n        // @description\n        // Returns whether the block at the location is a liquid.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_liquid\", (attribute, object) -> {\n            Block b = object.getBlockForTag(attribute);\n            if (b != null) {\n                try {\n                    NMSHandler.chunkHelper.changeChunkServerThread(object.getWorld());\n                    return new ElementTag(b.isLiquid());\n                }\n                finally {\n                    NMSHandler.chunkHelper.restoreServerThread(object.getWorld());\n                }\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.light>\n        // @returns ElementTag(Number)\n        // @group world\n        // @description\n        // Returns the total amount of light on the location.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"light\", (attribute, object) -> {\n            Block b = object.getBlockForTag(attribute);\n            if (b != null) {\n                try {\n                    NMSHandler.chunkHelper.changeChunkServerThread(object.getWorld());\n\n                    // <--[tag]\n                    // @attribute <LocationTag.light.blocks>\n                    // @returns ElementTag(Number)\n                    // @group world\n                    // @description\n                    // Returns the amount of light from light blocks that is\n                    // on the location.\n                    // -->\n                    if (attribute.startsWith(\"blocks\", 2)) {\n                        attribute.fulfill(1);\n                        return new ElementTag(object.getBlockForTag(attribute).getLightFromBlocks());\n                    }\n\n                    // <--[tag]\n                    // @attribute <LocationTag.light.sky>\n                    // @returns ElementTag(Number)\n                    // @group world\n                    // @description\n                    // Returns the amount of light from the sky that is\n                    // on the location.\n                    // -->\n                    if (attribute.startsWith(\"sky\", 2)) {\n                        attribute.fulfill(1);\n                        return new ElementTag(object.getBlockForTag(attribute).getLightFromSky());\n                    }\n                    return new ElementTag(object.getBlockForTag(attribute).getLightLevel());\n                }\n                finally {\n                    NMSHandler.chunkHelper.restoreServerThread(object.getWorld());\n                }\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.power>\n        // @returns ElementTag(Number)\n        // @group world\n        // @description\n        // Returns the current redstone power level of a block.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"power\", (attribute, object) -> {\n            Block b = object.getBlockForTag(attribute);\n            if (b != null) {\n                try {\n                    NMSHandler.chunkHelper.changeChunkServerThread(object.getWorld());\n                    return new ElementTag(object.getBlockForTag(attribute).getBlockPower());\n                }\n                finally {\n                    NMSHandler.chunkHelper.restoreServerThread(object.getWorld());\n                }\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.lectern_page>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.lectern_page\n        // @group world\n        // @deprecated Use 'LocationTag.page'\n        // @description\n        // Deprecated in favor of <@link tag LocationTag.page>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"lectern_page\", (attribute, object) -> {\n            BukkitImplDeprecations.lecternPage.warn(attribute.context);\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (state instanceof Lectern lectern) {\n                return new ElementTag(lectern.getPage());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.page>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.page\n        // @group world\n        // @description\n        // Returns the current page on display in the book on this Lectern block.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"page\", (attribute, object) -> {\n            if (object.getBlockState() instanceof Lectern lectern) {\n                return new ElementTag(lectern.getPage() + 1);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.has_loot_table>\n        // @returns ElementTag(Boolean)\n        // @mechanism LocationTag.clear_loot_table\n        // @group world\n        // @description\n        // Returns an element indicating whether the chest at this location has a loot-table set.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"has_loot_table\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (state instanceof Lootable) {\n                return new ElementTag(((Lootable) state).getLootTable() != null);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.loot_table_id>\n        // @returns ElementTag\n        // @mechanism LocationTag.loot_table_id\n        // @group world\n        // @description\n        // Returns an element indicating the minecraft key for the loot-table for the chest at this location (if any).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"loot_table_id\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (state instanceof Lootable) {\n                LootTable table = ((Lootable) state).getLootTable();\n                if (table != null) {\n                    return new ElementTag(table.getKey().toString());\n                }\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.tree_distance>\n        // @returns ElementTag(Number)\n        // @group world\n        // @deprecated Use MaterialTag.distance\n        // @description\n        // Deprecated in favor of <@link tag MaterialTag.distance>\n        // Used like <[location].material.distance>\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"tree_distance\", (attribute, object) -> {\n            BukkitImplDeprecations.locationDistanceTag.warn(attribute.context);\n            MaterialTag material = new MaterialTag(object.getBlockDataForTag(attribute));\n            if (MaterialDistance.describes(material)) {\n                return new ElementTag(MaterialDistance.getFrom(material).getDistance());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.command_block_name>\n        // @returns ElementTag\n        // @mechanism LocationTag.command_block_name\n        // @group world\n        // @description\n        // Returns the name a command block is set to.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"command_block_name\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof CommandBlock)) {\n                return null;\n            }\n            return new ElementTag(((CommandBlock) object.getBlockStateForTag(attribute)).getName());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.command_block>\n        // @returns ElementTag\n        // @mechanism LocationTag.command_block\n        // @group world\n        // @description\n        // Returns the command a command block is set to.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"command_block\", (attribute, object) -> {\n            if (!(object.getBlockStateForTag(attribute) instanceof CommandBlock)) {\n                return null;\n            }\n            return new ElementTag(((CommandBlock) object.getBlockStateForTag(attribute)).getCommand());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.brewing_time>\n        // @returns DurationTag\n        // @mechanism LocationTag.brewing_time\n        // @group world\n        // @description\n        // Returns the brewing time a brewing stand has left.\n        // -->\n        tagProcessor.registerTag(DurationTag.class, \"brewing_time\", (attribute, object) -> {\n            return new DurationTag((long) ((BrewingStand) object.getBlockStateForTag(attribute)).getBrewingTime());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.brewing_fuel_level>\n        // @returns ElementTag(Number)\n        // @mechanism LocationTag.brewing_fuel_level\n        // @group world\n        // @description\n        // Returns the level of fuel a brewing stand has. Each unit of fuel can power one brewing operation.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"brewing_fuel_level\", (attribute, object) -> {\n            return new ElementTag(((BrewingStand) object.getBlockStateForTag(attribute)).getFuelLevel());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.furnace_burn_duration>\n        // @returns DurationTag\n        // @mechanism LocationTag.furnace_burn_duration\n        // @group world\n        // @description\n        // Returns the burn time a furnace has left.\n        // -->\n        tagProcessor.registerTag(DurationTag.class, \"furnace_burn_duration\", (attribute, object) -> {\n            return new DurationTag((long) ((Furnace) object.getBlockStateForTag(attribute)).getBurnTime());\n        });\n        tagProcessor.registerTag(ElementTag.class, \"furnace_burn_time\", (attribute, object) -> {\n            BukkitImplDeprecations.furnaceTimeTags.warn(attribute.context);\n            return new ElementTag(((Furnace) object.getBlockStateForTag(attribute)).getBurnTime());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.furnace_cook_duration>\n        // @returns DurationTag\n        // @mechanism LocationTag.furnace_cook_duration\n        // @group world\n        // @description\n        // Returns the cook time a furnace has been cooking its current item for.\n        // -->\n        tagProcessor.registerTag(DurationTag.class, \"furnace_cook_duration\", (attribute, object) -> {\n            return new DurationTag((long) ((Furnace) object.getBlockStateForTag(attribute)).getCookTime());\n        });\n        tagProcessor.registerTag(ElementTag.class, \"furnace_cook_time\", (attribute, object) -> {\n            BukkitImplDeprecations.furnaceTimeTags.warn(attribute.context);\n            return new ElementTag(((Furnace) object.getBlockStateForTag(attribute)).getCookTime());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.furnace_cook_duration_total>\n        // @returns DurationTag\n        // @mechanism LocationTag.furnace_cook_duration_total\n        // @group world\n        // @description\n        // Returns the total cook time a furnace has left.\n        // -->\n        tagProcessor.registerTag(DurationTag.class, \"furnace_cook_duration_total\", (attribute, object) -> {\n            return new DurationTag((long) ((Furnace) object.getBlockStateForTag(attribute)).getCookTimeTotal());\n        });\n        tagProcessor.registerTag(ElementTag.class, \"furnace_cook_time_total\", (attribute, object) -> {\n            BukkitImplDeprecations.furnaceTimeTags.warn(attribute.context);\n            return new ElementTag(((Furnace) object.getBlockStateForTag(attribute)).getCookTimeTotal());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.beacon_tier>\n        // @returns ElementTag(Number)\n        // @group world\n        // @description\n        // Returns the tier level of a beacon pyramid (0-4).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"beacon_tier\", (attribute, object) -> {\n            return new ElementTag(((Beacon) object.getBlockStateForTag(attribute)).getTier());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.beacon_primary_effect>\n        // @returns ElementTag\n        // @mechanism LocationTag.beacon_primary_effect\n        // @group world\n        // @description\n        // Returns the primary effect of the beacon. The return is simply a potion effect type name.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"beacon_primary_effect\", (attribute, object) -> {\n            PotionEffect effect = ((Beacon) object.getBlockStateForTag(attribute)).getPrimaryEffect();\n            if (effect == null) {\n                return null;\n            }\n            return new ElementTag(effect.getType().getName());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.beacon_secondary_effect>\n        // @returns ElementTag\n        // @mechanism LocationTag.beacon_secondary_effect\n        // @group world\n        // @description\n        // Returns the secondary effect of the beacon. The return is simply a potion effect type name.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"beacon_secondary_effect\", (attribute, object) -> {\n            PotionEffect effect = ((Beacon) object.getBlockStateForTag(attribute)).getSecondaryEffect();\n            if (effect == null) {\n                return null;\n            }\n            return new ElementTag(effect.getType().getName());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.attached_to>\n        // @returns LocationTag\n        // @group world\n        // @description\n        // Returns the block this block is attached to.\n        // (For buttons, levers, signs, torches, etc).\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"attached_to\", (attribute, object) -> {\n            BlockFace face = BlockFace.SELF;\n            MaterialTag material = new MaterialTag(object.getBlockDataForTag(attribute));\n            if (material.getMaterial() == Material.TORCH || material.getMaterial() == Material.REDSTONE_TORCH || material.getMaterial() == Material.SOUL_TORCH) {\n                face = BlockFace.DOWN;\n            }\n            else if (material.getMaterial() == Material.WALL_TORCH || material.getMaterial() == Material.REDSTONE_WALL_TORCH || material.getMaterial() == Material.SOUL_WALL_TORCH) {\n                face = ((Directional) material.getModernData()).getFacing().getOppositeFace();\n            }\n            else if (MaterialAttachmentFace.describes(material)) {\n                face = new MaterialAttachmentFace(material).getAttachedTo();\n            }\n            else if (material.hasModernData() && material.getModernData() instanceof org.bukkit.block.data.type.WallSign) {\n                face = ((org.bukkit.block.data.type.WallSign) material.getModernData()).getFacing().getOppositeFace();\n            }\n            else {\n                MaterialData data = object.getBlockStateForTag(attribute).getData();\n                if (data instanceof Attachable) {\n                    face = ((Attachable) data).getAttachedFace();\n                }\n            }\n            if (face != BlockFace.SELF) {\n                return new LocationTag(object.getBlockForTag(attribute).getRelative(face).getLocation());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.other_block>\n        // @returns LocationTag\n        // @group world\n        // @description\n        // If the location is part of a double-block structure (double chests, double plants, doors, beds, etc),\n        // returns the location of the other block in the double-block structure.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"other_block\", (attribute, object) -> {\n            BlockData b = object.getBlockDataForTag(attribute);\n            MaterialTag material = new MaterialTag(b);\n            if (MaterialHalf.describes(material)) {\n                Vector vec = MaterialHalf.getFrom(material).getRelativeBlockVector();\n                if (vec != null) {\n                    return new LocationTag(object.clone().add(vec));\n                }\n            }\n            attribute.echoError(\"Block of type \" + object.getBlockTypeForTag(attribute).name() + \" isn't supported by other_block.\");\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.custom_name>\n        // @returns ElementTag\n        // @mechanism LocationTag.custom_name\n        // @group world\n        // @description\n        // Returns the custom name of this block.\n        // Only works for nameable blocks, such as chests and dispensers.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"custom_name\", (attribute, object) -> {\n            if (object.getBlockStateForTag(attribute) instanceof Nameable) {\n                return new ElementTag(PaperAPITools.instance.getCustomName((Nameable) object.getBlockStateForTag(attribute)));\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.local_difficulty>\n        // @returns ElementTag(Decimal)\n        // @group world\n        // @description\n        // Returns the local difficulty (damage scaler) at the location.\n        // This is based internally on multiple factors, including <@link tag ChunkTag.inhabited_time> and <@link tag WorldTag.difficulty>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"local_difficulty\", (attribute, object) -> {\n            return new ElementTag(NMSHandler.worldHelper.getLocalDifficulty(object));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.jukebox_record>\n        // @returns ItemTag\n        // @mechanism LocationTag.jukebox_record\n        // @group world\n        // @description\n        // Returns the record item currently inside the jukebox.\n        // If there's no record, will return air.\n        // -->\n        tagProcessor.registerTag(ItemTag.class, \"jukebox_record\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (!(state instanceof Jukebox)) {\n                attribute.echoError(\"'jukebox_record' tag is only valid for jukebox blocks.\");\n                return null;\n            }\n\n            return new ItemTag(((Jukebox) state).getRecord());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.jukebox_is_playing>\n        // @returns ElementTag(Boolean)\n        // @mechanism LocationTag.jukebox_play\n        // @group world\n        // @description\n        // Returns whether the jukebox is currently playing a song.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"jukebox_is_playing\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (!(state instanceof Jukebox)) {\n                attribute.echoError(\"'jukebox_is_playing' tag is only valid for jukebox blocks.\");\n                return null;\n            }\n\n            return new ElementTag(((Jukebox) state).isPlaying());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.age>\n        // @returns DurationTag\n        // @mechanism LocationTag.age\n        // @group world\n        // @description\n        // Returns the age of an end gateway.\n        // -->\n        tagProcessor.registerTag(DurationTag.class, \"age\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (!(state instanceof EndGateway)) {\n                attribute.echoError(\"'age' tag is only valid for end_gateway blocks.\");\n                return null;\n            }\n\n            return new DurationTag(((EndGateway) state).getAge());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.is_exact_teleport>\n        // @returns ElementTag(Boolean)\n        // @mechanism LocationTag.is_exact_teleport\n        // @group world\n        // @description\n        // Returns whether an end gateway is 'exact teleport' - if false, the destination will be randomly chosen *near* the destination.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_exact_teleport\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (!(state instanceof EndGateway)) {\n                attribute.echoError(\"'is_exact_teleport' tag is only valid for end_gateway blocks.\");\n                return null;\n            }\n\n            return new ElementTag(((EndGateway) state).isExactTeleport());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.exit_location>\n        // @returns LocationTag\n        // @mechanism LocationTag.exit_location\n        // @group world\n        // @description\n        // Returns the exit location of an end gateway block.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"exit_location\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (!(state instanceof EndGateway)) {\n                attribute.echoError(\"'exit_location' tag is only valid for end_gateway blocks.\");\n                return null;\n            }\n            Location loc = ((EndGateway) state).getExitLocation();\n            if (loc == null) {\n                return null;\n            }\n            return new LocationTag(loc);\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.is_in[<matcher>]>\n        // @returns ElementTag(Boolean)\n        // @group areas\n        // @description\n        // Returns whether the location is in an area, using the same logic as an event \"in\" switch.\n        // Invalid input may produce odd error messages, as this is passed through the event system as a fake event.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_in\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            return new ElementTag(BukkitScriptEvent.inCheckInternal(attribute.context, \"is_in tag\", object, attribute.getParam(), \"is_in tag\", \"is_in tag\"));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.campfire_items>\n        // @returns ListTag(ItemTag)\n        // @mechanism LocationTag.campfire_items\n        // @group world\n        // @description\n        // Returns a list of items currently in this campfire.\n        // This list has air items in empty slots, and is always sized exactly the same as the number of spaces a campfire has.\n        // (A standard campfire has exactly 4 slots).\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"campfire_items\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (!(state instanceof Campfire)) {\n                return null;\n            }\n            Campfire fire = (Campfire) state;\n            ListTag output = new ListTag();\n            for (int i = 0; i < fire.getSize(); i++) {\n                output.addObject(new ItemTag(fire.getItem(i)));\n            }\n            return output;\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.is_spawnable>\n        // @returns ElementTag(Boolean)\n        // @group world\n        // @description\n        // Returns whether the location is safe to spawn at, for a player or player-like entity.\n        // Specifically this verifies that:\n        // - The block above this location is air.\n        // - The block at this location is non-solid.\n        // - The block below this location is solid.\n        // - All relevant blocks are not dangerous (like fire, lava, etc.), or unstable/small/awkward (like fences, doors, etc.) or otherwise likely to go wrong (like pressure plates).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_spawnable\", (attribute, object) -> {\n            return new ElementTag(SpawnableHelper.isSpawnable(object));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.sign_glowing>\n        // @returns ElementTag(Boolean)\n        // @mechanism LocationTag.sign_glowing\n        // @group world\n        // @description\n        // Returns whether the location is a Sign block that is glowing.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"sign_glowing\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (!(state instanceof Sign)) {\n                attribute.echoError(\"Location is not a valid Sign block.\");\n                return null;\n            }\n            return new ElementTag(((Sign) state).isGlowingText());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.sign_glow_color>\n        // @returns ElementTag\n        // @mechanism LocationTag.sign_glow_color\n        // @group world\n        // @description\n        // Returns the name of the glow-color of the sign at the location.\n        // See also <@link tag LocationTag.sign_glowing>\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"sign_glow_color\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (!(state instanceof Sign)) {\n                attribute.echoError(\"Location is not a valid Sign block.\");\n                return null;\n            }\n            return new ElementTag(((Sign) state).getColor());\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.map_color>\n        // @returns ColorTag\n        // @group world\n        // @description\n        // Returns the color of the block at the location, as seen in a map.\n        // -->\n        tagProcessor.registerTag(ColorTag.class, \"map_color\", (attribute, object) -> {\n            Block block = object.getBlockForTag(attribute);\n            if (block == null) {\n                return null;\n            }\n            return BukkitColorExtensions.fromColor(NMSHandler.blockHelper.getMapColor(block));\n        });\n\n        // <--[tag]\n        // @attribute <LocationTag.structure_block_data>\n        // @returns MapTag\n        // @mechanism LocationTag.structure_block_data\n        // @group world\n        // @description\n        // Returns the structure block data of the structure block at the location as a map with the following keys:\n        // - author: ElementTag: The name of the structure's creator. set to \"?\" for most vanilla structures.\n        // - integrity: ElementTag(Decimal): The integrity of the structure (0-1). Lower integrity values will result in more blocks being removed when loading a structure.\n        // used with the seed to determine which blocks are randomly removed to mimic \"decay\".\n        // - metadata: ElementTag: Only applies in DATA mode, sets specific functions that can be applied to the structure,\n        // check the Minecraft wiki (<@link url https://minecraft.wiki/w/Structure_Block#Data>) for more information.\n        // - mirror: ElementTag: How the structure is mirrored; \"NONE\", \"LEFT_RIGHT\", or \"FRONT_BACK\".\n        // - box_position: LocationTag: The position of the structure's bounding box, relative to the position of the structure block. Maximum allowed distance is 48 blocks in any direction.\n        // - rotation: ElementTag: The rotation of the structure; \"NONE\", \"CLOCKWISE_90\", \"CLOCKWISE_180\", or \"COUNTERCLOCKWISE_90\".\n        // - seed: ElementTag(Number): The seed used to determine how many blocks are removed upon loading of this structure (see \"integrity\" for more information).\n        // - structure_name: ElementTag: The name of the structure.\n        // - size: LocationTag: The size of the structure's bounding box, The maximum structure size is 48,48,48.\n        // - mode: ElementTag: The structure block's mode; \"CORNER\", \"DATA\", \"LOAD\", or \"SAVE\". See also <@link mechanism MaterialTag.mode>.\n        // - box_visible: ElementTag(Boolean): Whether the structure's bounding box is visible, only applies in LOAD mode.\n        // - ignore_entities: ElementTag(Boolean): Whether entities in the structure are ignored, only applies in SAVE mode.\n        // - show_invisible: ElementTag(Boolean): Whether invisible blocks in the structure are shown.\n        // -->\n        tagProcessor.registerTag(MapTag.class, \"structure_block_data\", (attribute, object) -> {\n            BlockState state = object.getBlockStateForTag(attribute);\n            if (!(state instanceof Structure)) {\n                attribute.echoError(\"Location is not a valid Structure block.\");\n                return null;\n            }\n            Structure structure = (Structure) state;\n            MapTag output = new MapTag();\n            output.putObject(\"author\", new ElementTag(structure.getAuthor()));\n            output.putObject(\"integrity\", new ElementTag(structure.getIntegrity()));\n            output.putObject(\"metadata\", new ElementTag(structure.getMetadata()));\n            output.putObject(\"mirror\", new ElementTag(structure.getMirror()));\n            output.putObject(\"box_position\", new LocationTag(structure.getRelativePosition()));\n            output.putObject(\"rotation\", new ElementTag(structure.getRotation()));\n            output.putObject(\"seed\", new ElementTag(structure.getSeed()));\n            output.putObject(\"structure_name\", new ElementTag(structure.getStructureName()));\n            output.putObject(\"size\", new LocationTag(structure.getStructureSize()));\n            output.putObject(\"mode\", new ElementTag(structure.getUsageMode()));\n            output.putObject(\"box_visible\", new ElementTag(structure.isBoundingBoxVisible()));\n            output.putObject(\"ignore_entities\", new ElementTag(structure.isIgnoreEntities()));\n            output.putObject(\"show_invisible\", new ElementTag(structure.isShowAir()));\n            return output;\n        });\n\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\n\n            // <--[tag]\n            // @attribute <LocationTag.temperature>\n            // @returns ElementTag(Decimal)\n            // @description\n            // Returns a location's temperature, based on the biome it's in.\n            // If this is less than 0.15, snow will form on the ground when weather occurs in the world and a layer of ice will form over water.\n            // See also <@link tag BiomeTag.temperature_at>.\n            // @example\n            // # Gives the player water if they are standing in a warm location.\n            // - if <player.location.temperature> > 0.5:\n            //   - give water_bucket\n            // -->\n            tagProcessor.registerTag(ElementTag.class, \"temperature\", (attribute, object) -> {\n                BiomeNMS biome = object.getBiomeForTag(attribute);\n                return biome != null ? new ElementTag(biome.getTemperatureAt(object)) : null;\n            });\n\n            // <--[tag]\n            // @attribute <LocationTag.downfall_type>\n            // @returns ElementTag\n            // @description\n            // Returns a location's downfall type (for when a world has weather), based on the biome it's in.\n            // This can be RAIN, SNOW, or NONE.\n            // See also <@link tag BiomeTag.downfall_at>.\n            // @example\n            // # Tells the linked player what the downfall type at their location is.\n            // - narrate \"The downfall type at your location is: <player.location.downfall_type>!\"\n            // -->\n            tagProcessor.registerTag(ElementTag.class, \"downfall_type\", (attribute, object) -> {\n                BiomeNMS biome = object.getBiomeForTag(attribute);\n                return biome != null ? new ElementTag(biome.getDownfallTypeAt(object)) : null;\n            });\n\n            // <--[tag]\n            // @attribute <LocationTag.last_interacted_slot>\n            // @returns ElementTag(Number)\n            // @mechanism LocationTag.last_interacted_slot\n            // @description\n            // Returns the last interacted slot of a Chiseled Bookshelf inventory.\n            // -->\n            tagProcessor.registerTag(ElementTag.class, \"last_interacted_slot\", (attribute, object) -> {\n                if (!(object.getBlockStateForTag(attribute) instanceof ChiseledBookshelf chiseledBookshelf)) {\n                    return null;\n                }\n                return new ElementTag(chiseledBookshelf.getLastInteractedSlot() + 1);\n            });\n\n            // <--[language]\n            // @name Structure lookups\n            // @group Useful Lists\n            // @description\n            // Structures can be located using <@link tag LocationTag.find_structure>.\n            // It works similarly to the '/locate' command, and has several side effects/edge cases:\n            // - The radius is in chunks, but isn't always a set square radius around the origin; certain structures may modify the amounts of chunks checked. For example, woodland mansions can potentially check up to 20,000 blocks away (or more) regardless of the radius used.\n            // - Lookups can take a long amount of time (several seconds, over 10 in some cases), especially when looking for unexplored structures, which will cause the server to freeze while searching.\n            // - They will not load/generate chunks (but can search not-yet-generated chunks and return a location in them).\n            // - They can lead to situations where the server hangs and crashes when trying to find unexplored structures (if there aren't any/any nearby), as it keeps looking further and further out.\n            // - The returned location only contains the X and Z values, and will always have a Y value of 0. Tags like <@link tag LocationTag.highest> are available, but note that they require the chunk to be loaded.\n            // -->\n\n            // <--[tag]\n            // @attribute <LocationTag.find_structure[structure=<structure>;radius=<#>(;unexplored=<true/{false}>)]>\n            // @returns LocationTag\n            // @warning See <@link language Structure lookups> for potential issues/edge cases in structure lookups.\n            // @group finding\n            // @description\n            // Finds the closest structure of the given type within the specified chunk radius (if any), optionally only searching for unexplored ones.\n            // For a list of default structures, see <@link url https://minecraft.wiki/w/Structure#ID>.\n            // Alternatively, you can specify a custom structure from a datapack, plugin, etc. as a namespaced key.\n            // See also <@link tag server.structures> for all structures currently available on the server.\n            // @example\n            // # Use to find the desert temple closest to the player, and tell them what direction it's in.\n            // - define found <player.location.find_structure[structure=desert_pyramid;radius=200].if_null[null]>\n            // - if <[found]> != null:\n            //   - narrate \"The closet desert temple is <player.location.direction[<[found]>]> of you!\"\n            // - else:\n            //   - narrate \"No desert temple found.\"\n            // -->\n            tagProcessor.registerTag(LocationTag.class, MapTag.class, \"find_structure\", (attribute, object, input) -> {\n                ElementTag structureName = input.getRequiredObjectAs(\"structure\", ElementTag.class, attribute);\n                ElementTag radius = input.getRequiredObjectAs(\"radius\", ElementTag.class, attribute);\n                if (structureName == null || radius == null) {\n                    return null;\n                }\n                org.bukkit.generator.structure.Structure structure = Registry.STRUCTURE.get(Utilities.parseNamespacedKey(structureName.asString()));\n                if (structure == null) {\n                    attribute.echoError(\"Invalid structure specified: \" + structureName + '.');\n                    return null;\n                }\n                if (!radius.isInt()) {\n                    attribute.echoError(\"Invalid radius '\" + radius + \" specified': must be a number.\");\n                    return null;\n                }\n                ElementTag unexplored = input.getElement(\"unexplored\", \"false\");\n                if (!unexplored.isBoolean()) {\n                    attribute.echoError(\"Invalid 'unexplored' value '\" + unexplored + \"' specified: must be a boolean.\");\n                    return null;\n                }\n                StructureSearchResult searchResult = object.getWorld().locateNearestStructure(object, structure, radius.asInt(), unexplored.asBoolean());\n                return searchResult != null ? new LocationTag(searchResult.getLocation()) : null;\n            });\n        }\n\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\n\n            // <--[tag]\n            // @attribute <LocationTag.sherds>\n            // @returns MapTag\n            // @mechanism LocationTag.sherds\n            // @description\n            // Returns a decorated pot's sherds as a map of sides to <@link objecttype MaterialTag>s of the sherds.\n            // The map will always contain every side, with a brick being the default value for when a side has no sherd.\n            // Valid sides are: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/DecoratedPot.Side.html>.\n            // Valid sherd materials are either a brick or any pottery sherd.\n            // @example\n            // # Tells the player if they're looking at a pot that has any sherds.\n            // - if <player.cursor_on.sherds.values.contains_match[!brick].if_null[false]>:\n            //   - narrate \"That pot has sherds in it! You should check!\"\n            // - else:\n            //   - narrate \"Try looking somewhere else.\"\n            // -->\n            tagProcessor.registerTag(MapTag.class, \"sherds\", (attribute, object) -> {\n                if (!(object.getBlockStateForTag(attribute) instanceof DecoratedPot decoratedPot)) {\n                    return null;\n                }\n                MapTag sherdsMap = new MapTag();\n                for (Map.Entry<DecoratedPot.Side, Material> entry : decoratedPot.getSherds().entrySet()) {\n                    sherdsMap.putObject(entry.getKey().name(), new MaterialTag(entry.getValue()));\n                }\n                return sherdsMap;\n            });\n\n            // <--[mechanism]\n            // @object LocationTag\n            // @name sherds\n            // @input MapTag\n            // @description\n            // Sets a decorated pot's sherds, input is a map of sides to <@link objecttype MaterialTag>s of the sherds.\n            // You only need to specify the sides you want to set, and the default value (for removing a side's sherd) is a brick.\n            // Valid sides are: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/DecoratedPot.Side.html>.\n            // Valid materials are either a brick or any pottery sherd.\n            // @tags\n            // <LocationTag.sherds>\n            // @example\n            // # Sets a decorated pot's left side to a random sherd.\n            // - adjust <[potLocation]> sherds:[left=<server.vanilla_tagged_materials[decorated_pot_sherds].random>]\n            // @example\n            // # Removes a decorated pot's sherds.\n            // - adjust <[potLocation]> sherds:[left=brick;right=brick;front=brick;back=brick]\n            // -->\n            tagProcessor.registerMechanism(\"sherds\", false, MapTag.class, (object, mechanism, input) -> {\n                if (!(object.getBlockState() instanceof DecoratedPot decoratedPot)) {\n                    mechanism.echoError(\"Mechanism 'LocationTag.sherds' is only valid for decorated pots.\");\n                    return;\n                }\n                for (Map.Entry<StringHolder, ObjectTag> entry : input.entrySet()) {\n                    DecoratedPot.Side side = ElementTag.asEnum(DecoratedPot.Side.class, entry.getKey().low);\n                    if (side == null) {\n                        mechanism.echoError(\"Invalid decorated pot side specified: \" + entry.getKey());\n                        continue;\n                    }\n                    MaterialTag sherd = entry.getValue().asType(MaterialTag.class, mechanism.context);\n                    if (sherd == null || !Tag.ITEMS_DECORATED_POT_INGREDIENTS.isTagged(sherd.getMaterial())) {\n                        mechanism.echoError(\"Invalid sherd material specified: \" + entry.getValue());\n                        continue;\n                    }\n                    decoratedPot.setSherd(side, sherd.getMaterial());\n                }\n                decoratedPot.update();\n            });\n\n            // <--[tag]\n            // @attribute <LocationTag.buried_item>\n            // @returns ItemTag\n            // @mechanism LocationTag.buried_item\n            // @description\n            // Returns the item buried in a brushable block (also referred to as \"suspicious blocks\"). Returns air if there is no item buried.\n            // -->\n            tagProcessor.registerTag(ItemTag.class, \"buried_item\", (attribute, object) -> {\n                if (!(object.getBlockStateForTag(attribute) instanceof BrushableBlock brushableBlock)) {\n                    return null;\n                }\n                return new ItemTag(brushableBlock.getItem());\n            });\n\n            // <--[mechanism]\n            // @object LocationTag\n            // @name buried_item\n            // @input ItemTag\n            // @description\n            // Sets the buried item in a brushable block (also referred to as \"suspicious blocks\"). Set to air to have no item buried.\n            // @tags\n            // <LocationTag.buried_item>\n            // -->\n            tagProcessor.registerMechanism(\"buried_item\", false, ItemTag.class, (object, mechanism, item) -> {\n                if (!(object.getBlockState() instanceof BrushableBlock brushableBlock)) {\n                    mechanism.echoError(\"Mechanism 'LocationTag.buried_item' is only valid for brushable blocks.\");\n                    return;\n                }\n                brushableBlock.setItem(item.getItemStack());\n                brushableBlock.update();\n            });\n\n            // <--[tag]\n            // @attribute <LocationTag.waxed>\n            // @returns ElementTag(Boolean)\n            // @mechanism LocationTag.waxed\n            // @group world\n            // @description\n            // If the location is a sign block, returns whether it is waxed (locked to prevent players editing the text).\n            // -->\n            tagProcessor.registerTag(ElementTag.class, \"waxed\", (attribute, object) -> {\n                if (!(object.getBlockStateForTag(attribute) instanceof Sign sign)) {\n                    attribute.echoError(\"Location is not a valid Sign block.\");\n                    return null;\n                }\n                return new ElementTag(sign.isWaxed());\n            });\n\n            // <--[mechanism]\n            // @object LocationTag\n            // @name waxed\n            // @input ElementTag(Boolean)\n            // @description\n            // Sets whether the sign at the location is waxed (locked to prevent players editing the text).\n            // @tags\n            // <LocationTag.waxed>\n            // -->\n            tagProcessor.registerMechanism(\"waxed\", false, ElementTag.class, (object, mechanism, value) -> {\n                if (!mechanism.requireBoolean()) {\n                    return;\n                }\n                if (!(object.getBlockState() instanceof Sign sign)) {\n                    mechanism.echoError(\"'waxed' mechanism can only be called on Sign blocks.\");\n                    return;\n                }\n                sign.setWaxed(value.asBoolean());\n                sign.update();\n            });\n\n            // <--[tag]\n            // @attribute <LocationTag.slot[<vector>]>\n            // @returns ElementTag(Number)\n            // @description\n            // Returns the appropriate slot based on a given point along the bookshelves' surface relative to its position.\n            // It will return 0 if the given vector is not within the bounds of any slot.\n            // See also <@link tag EntityTag.bookshelf_slot>\n            // @Example\n            // # Obtain and narrate the slot that the player right-clicked.\n            // on player right clicks chiseled_bookshelf:\n            // - define slot <player.eye_location.ray_trace.sub[<context.location>]>\n            // - narrate <context.location.slot[<[slot]>]>\n            // -->\n            tagProcessor.registerTag(ElementTag.class, LocationTag.class, \"slot\", (attribute, object, input) -> {\n                if (!(object.getBlockStateForTag(attribute) instanceof ChiseledBookshelf chiseledBookshelf)) {\n                    return null;\n                }\n                return new ElementTag(chiseledBookshelf.getSlot(input.toVector()) + 1);\n            });\n\n            // <--[tag]\n            // @attribute <LocationTag.disabled_slots>\n            // @returns ListTag\n            // @mechanism LocationTag.disabled_slots\n            // @group world\n            // @description\n            // Returns which slots in a crafter are disabled.\n            // The slots are arranged from left to right, top to bottom.\n            // -->\n            tagProcessor.registerTag(ListTag.class, \"disabled_slots\", (attribute, object) -> {\n                if (!(object.getBlockStateForTag(attribute) instanceof Crafter crafter)) {\n                    attribute.echoError(\"The 'LocationTag.disabled_slots' tag can only be called on a crafter block.\");\n                    return null;\n                }\n                ListTag slots = new ListTag();\n                for (int i = 0; i <= 8; i++) {\n                    if (crafter.isSlotDisabled(i)) {\n                        slots.addObject(new ElementTag(i + 1));\n                    }\n                }\n                return slots;\n            });\n\n            // <--[mechanism]\n            // @object LocationTag\n            // @name disabled_slots\n            // @input ListTag\n            // @description\n            // Sets which slots in a crafter are disabled.\n            // The slots are arranged from left to right, top to bottom.\n            // Provide no input to enable all slots.\n            // @tags\n            // <LocationTag.disabled_slots>\n            // @example\n            // # Disables the slots in the top left and middle right\n            // - adjustblock <[location]> disabled_slots:1|6\n            // -->\n            tagProcessor.registerMechanism(\"disabled_slots\", false, ListTag.class, (object, mechanism, input) -> {\n                if (!(object.getBlockState() instanceof Crafter crafter)) {\n                    mechanism.echoError(\"The 'LocationTag.disabled_slots' mechanism can only be called on a crafter block.\");\n                    return;\n                }\n                for (int i = 0; i <= 8; i++) {\n                    crafter.setSlotDisabled(i, false);\n                }\n                for (String slot : input) {\n                    ElementTag element = new ElementTag(slot);\n                    if (!element.isInt()) {\n                        mechanism.echoError(\"Invalid slot '\" + slot + \"' specified: must be an integer.\");\n                        continue;\n                    }\n                    int value = element.asInt();\n                    if (value < 1 || value > 9) {\n                        mechanism.echoError(\"Invalid slot '\" + slot + \"' specified: must between 1 and 9.\");\n                        continue;\n                    }\n                    crafter.setSlotDisabled(value - 1, true);\n                }\n                crafter.update();\n            });\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name spawner_type\n        // @input EntityTag\n        // @description\n        // Sets the entity that a mob spawner will spawn.\n        // Provide no input to unset (only on 1.20 and above).\n        // @tags\n        // <LocationTag.spawner_type>\n        // -->\n        tagProcessor.registerMechanism(\"spawner_type\", false, (object, mechanism) -> {\n            if (!(object.getBlockState() instanceof CreatureSpawner spawner)) {\n                mechanism.echoError(\"Mechanism 'LocationTag.spawner_type' is only valid for spawners.\");\n                return;\n            }\n            if (!mechanism.hasValue() && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\n                spawner.setSpawnedType(null);\n                spawner.update();\n            }\n            else if (mechanism.requireObject(EntityTag.class)) {\n                NMSHandler.blockHelper.setSpawnerSpawnedType(spawner, mechanism.valueAsType(EntityTag.class));\n                spawner.update();\n            }\n        });\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name last_interacted_slot\n        // @input ElementTag(Number)\n        // @description\n        // Sets the last interacted slot of a Chiseled Bookshelf inventory.\n        // @tags\n        // <LocationTag.last_interacted_slot>\n        // -->\n        tagProcessor.registerMechanism(\"last_interacted_slot\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (!(object.getBlockState() instanceof ChiseledBookshelf chiseledBookshelf)) {\n                mechanism.echoError(\"Mechanism 'LocationTag.last_interacted_slot' is only valid for Chiseled Bookshelves.\");\n                return;\n            }\n            if (mechanism.requireInteger()) {\n                chiseledBookshelf.setLastInteractedSlot(input.asInt() - 1);\n            }\n        });\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name page\n        // @input ElementTag(Number)\n        // @description\n        // Sets the page currently displayed on the book in a lectern block.\n        // @tags\n        // <LocationTag.page>\n        // -->\n        tagProcessor.registerMechanism(\"page\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (!mechanism.requireInteger()) {\n                return;\n            }\n            if (object.getBlockState() instanceof Lectern lectern) {\n                lectern.setPage(input.asInt() - 1);\n                lectern.update();\n            }\n            else {\n                mechanism.echoError(\"The 'LocationTag.page' mechanism can only be called on a lectern block.\");\n            }\n        });\n    }\n\n    public static ObjectTagProcessor<LocationTag> tagProcessor = new ObjectTagProcessor<>();\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n        return tagProcessor.getObjectAttribute(this, attribute);\n    }\n\n    public void applyProperty(Mechanism mechanism) {\n        mechanism.echoError(\"Cannot apply properties to a location!\");\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name block_facing\n        // @input LocationTag\n        // @description\n        // Sets the facing direction of the block, as a vector.\n        // @tags\n        // <LocationTag.block_facing>\n        // -->\n        if (mechanism.matches(\"block_facing\") && mechanism.requireObject(LocationTag.class)) {\n            LocationTag faceVec = mechanism.valueAsType(LocationTag.class);\n            Block block = getBlock();\n            MaterialTag material = new MaterialTag(block);\n            if (!MaterialDirectional.describes(material)) {\n                mechanism.echoError(\"LocationTag.block_facing mechanism failed: block is not directional.\");\n                return;\n            }\n            MaterialDirectional.getFrom(material).setFacing(Utilities.faceFor(faceVec.toVector()));\n            block.setBlockData(material.getModernData());\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name block_type\n        // @input MaterialTag\n        // @description\n        // Sets the type of the block.\n        // @tags\n        // <LocationTag.material>\n        // -->\n        if (mechanism.matches(\"block_type\") && mechanism.requireObject(MaterialTag.class)) {\n            MaterialTag mat = mechanism.valueAsType(MaterialTag.class);\n            getBlock().setBlockData(mat.getModernData(), false);\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name biome\n        // @input BiomeTag\n        // @description\n        // Sets the biome of the block.\n        // @tags\n        // <LocationTag.biome>\n        // -->\n        if (mechanism.matches(\"biome\") && mechanism.requireObject(BiomeTag.class)) {\n            mechanism.valueAsType(BiomeTag.class).getBiome().setTo(getBlock());\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name spawner_custom_rules\n        // @input MapTag\n        // @description\n        // Sets the custom spawner rules for this spawner. Input is a map, like: [sky_min=0;sky_max=15;block_min=0;block_max=15]\n        // -->\n        if (mechanism.matches(\"spawner_custom_rules\") && mechanism.requireObject(MapTag.class) && getBlockState() instanceof CreatureSpawner) {\n            CreatureSpawner spawner = ((CreatureSpawner) getBlockState());\n            MapTag map = mechanism.valueAsType(MapTag.class);\n            ElementTag skyMin = map.getElement(\"sky_min\"), skyMax = map.getElement(\"sky_max\"), blockMin = map.getElement(\"block_min\"), blockMax = map.getElement(\"block_max\");\n            if (skyMin == null || skyMax == null || blockMin == null || blockMax == null) {\n                mechanism.echoError(\"Invalid spawner_custom_rules input, missing map keys.\");\n                return;\n            }\n            NMSHandler.blockHelper.setSpawnerCustomRules(spawner, skyMin.asInt(), skyMax.asInt(), blockMin.asInt(), blockMax.asInt());\n            spawner.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name spawner_delay_data\n        // @input ListTag\n        // @description\n        // Sets the current spawn delay, minimum spawn delay, and maximum spawn delay of the mob spawner.\n        // For example, -1|200|800\n        // @tags\n        // <LocationTag.spawner_spawn_delay>\n        // <LocationTag.spawner_minimum_spawn_delay>\n        // <LocationTag.spawner_maximum_spawn_delay>\n        // -->\n        if (mechanism.matches(\"spawner_delay_data\") && getBlockState() instanceof CreatureSpawner) {\n            ListTag list = mechanism.valueAsType(ListTag.class);\n            if (list.size() < 3) {\n                return;\n            }\n            CreatureSpawner spawner = ((CreatureSpawner) getBlockState());\n            spawner.setDelay(Integer.parseInt(list.get(0)));\n            int minDelay = Integer.parseInt(list.get(1));\n            int maxDelay = Integer.parseInt(list.get(2));\n            // Minecraft won't set the limits if the new max would be lower than the current min\n            // or new min would be higher than the current max\n            if (minDelay > spawner.getMaxSpawnDelay()) {\n                spawner.setMaxSpawnDelay(maxDelay);\n                spawner.setMinSpawnDelay(minDelay);\n            } else {\n                spawner.setMinSpawnDelay(minDelay);\n                spawner.setMaxSpawnDelay(maxDelay);\n            }\n            spawner.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name spawner_max_nearby_entities\n        // @input ElementTag(Number)\n        // @description\n        // Sets the maximum nearby entities of the spawner.\n        // @tags\n        // <LocationTag.spawner_max_nearby_entities>\n        // -->\n        if (mechanism.matches(\"spawner_max_nearby_entities\") && mechanism.requireInteger() && getBlockState() instanceof CreatureSpawner) {\n            CreatureSpawner spawner = ((CreatureSpawner) getBlockState());\n            spawner.setMaxNearbyEntities(mechanism.getValue().asInt());\n            spawner.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name spawner_player_range\n        // @input ElementTag(Number)\n        // @description\n        // Sets the maximum player range of the spawner.\n        // @tags\n        // <LocationTag.spawner_player_range>\n        // -->\n        if (mechanism.matches(\"spawner_player_range\") && mechanism.requireInteger() && getBlockState() instanceof CreatureSpawner) {\n            CreatureSpawner spawner = ((CreatureSpawner) getBlockState());\n            spawner.setRequiredPlayerRange(mechanism.getValue().asInt());\n            spawner.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name spawner_range\n        // @input ElementTag(Number)\n        // @description\n        // Sets the spawn range of the spawner (the radius mobs will spawn in).\n        // @tags\n        // <LocationTag.spawner_range>\n        // -->\n        if (mechanism.matches(\"spawner_range\") && mechanism.requireInteger() && getBlockState() instanceof CreatureSpawner) {\n            CreatureSpawner spawner = ((CreatureSpawner) getBlockState());\n            spawner.setSpawnRange(mechanism.getValue().asInt());\n            spawner.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name spawner_count\n        // @input ElementTag(Number)\n        // @description\n        // Sets the spawn count of the spawner.\n        // @tags\n        // <LocationTag.spawner_count>\n        // -->\n        if (mechanism.matches(\"spawner_count\") && mechanism.requireInteger() && getBlockState() instanceof CreatureSpawner) {\n            CreatureSpawner spawner = ((CreatureSpawner) getBlockState());\n            spawner.setSpawnCount(mechanism.getValue().asInt());\n            spawner.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name lock\n        // @input ElementTag\n        // @description\n        // Sets the container's lock password.\n        // Locked containers can only be opened while holding an item with the name of the lock.\n        // Leave blank to remove a container's lock.\n        // @tags\n        // <LocationTag.lock>\n        // <LocationTag.is_locked>\n        // <LocationTag.is_lockable>\n        // -->\n        if (mechanism.matches(\"lock\") && getBlockState() instanceof Lockable) {\n            BlockState state = getBlockState();\n            ((Lockable) state).setLock(mechanism.hasValue() ? mechanism.getValue().asString() : null);\n            state.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name sign_contents\n        // @input ListTag\n        // @description\n        // Sets the contents of a sign block.\n        // @tags\n        // <LocationTag.sign_contents>\n        // -->\n        if (mechanism.matches(\"sign_contents\") && getBlockState() instanceof Sign) {\n            Sign state = (Sign) getBlockState();\n            for (int i = 0; i < 4; i++) {\n                PaperAPITools.instance.setSignLine(state, i, \"\");\n            }\n            ListTag list = mechanism.valueAsType(ListTag.class);\n            CoreUtilities.fixNewLinesToListSeparation(list);\n            if (list.size() > 4) {\n                mechanism.echoError(\"Sign can only hold four lines!\");\n            }\n            else {\n                for (int i = 0; i < list.size(); i++) {\n                    PaperAPITools.instance.setSignLine(state, i, list.get(i));\n                }\n            }\n            state.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name skull_skin\n        // @input ElementTag(|ElementTag(|ElementTag))\n        // @description\n        // Sets the skin of a skull block.\n        // The first ElementTag is a UUID.\n        // Optionally, use the second ElementTag for the skin texture cache.\n        // Optionally, use the third ElementTag for a player name.\n        // @tags\n        // <LocationTag.skull_skin>\n        // -->\n        if (mechanism.matches(\"skull_skin\")) {\n            final BlockState blockState = getBlockState();\n            Material material = getBlock().getType();\n            if (blockState instanceof Skull) {\n                ListTag list = mechanism.valueAsType(ListTag.class);\n                String idString = list.get(0);\n                String texture = null;\n                if (list.size() > 1) {\n                    texture = list.get(1);\n                }\n                PlayerProfile profile;\n                if (idString.contains(\"-\")) {\n                    UUID uuid = UUID.fromString(idString);\n                    String name = null;\n                    if (list.size() > 2) {\n                        name = list.get(2);\n                    }\n                    profile = new PlayerProfile(name, uuid, texture);\n                }\n                else {\n                    profile = new PlayerProfile(idString, null, texture);\n                }\n                if (texture == null || profile.getUniqueId() == null) { // Load if needed\n                    profile = NMSHandler.instance.fillPlayerProfile(profile);\n                }\n                if (texture != null) {\n                    profile.setTexture(texture);\n                }\n                NMSHandler.blockHelper.setPlayerProfile((Skull) blockState, profile);\n            }\n            else {\n                mechanism.echoError(\"Unable to set skull_skin on block of type \" + material.name() + \" with state \" + blockState.getClass().getCanonicalName());\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name hive_max_bees\n        // @input ElementTag(Number)\n        // @description\n        // Sets the maximum allowed number of bees in a beehive.\n        // @tags\n        // <LocationTag.hive_max_bees>\n        // -->\n        if (mechanism.matches(\"hive_max_bees\") && mechanism.requireInteger()) {\n            Beehive hive = (Beehive) getBlockState();\n            hive.setMaxEntities(mechanism.getValue().asInt());\n            hive.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name release_bees\n        // @input None\n        // @description\n        // Causes a beehive to release all its bees.\n        // Will do nothing if the hive is empty.\n        // @tags\n        // <LocationTag.hive_bee_count>\n        // -->\n        if (mechanism.matches(\"release_bees\")) {\n            Beehive hive = (Beehive) getBlockState();\n            hive.releaseEntities();\n            hive.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name add_bee\n        // @input EntityTag\n        // @description\n        // Adds a bee into a beehive.\n        // Will do nothing if there's no room left in the hive.\n        // @tags\n        // <LocationTag.hive_bee_count>\n        // -->\n        if (mechanism.matches(\"add_bee\") && mechanism.requireObject(EntityTag.class)) {\n            Beehive hive = (Beehive) getBlockState();\n            hive.addEntity((Bee) mechanism.valueAsType(EntityTag.class).getBukkitEntity());\n            hive.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name command_block_name\n        // @input ElementTag\n        // @description\n        // Sets the name of a command block.\n        // @tags\n        // <LocationTag.command_block_name>\n        // -->\n        if (mechanism.matches(\"command_block_name\")) {\n            if (getBlock().getState() instanceof CommandBlock) {\n                CommandBlock block = ((CommandBlock) getBlockState());\n                block.setName(mechanism.getValue().asString());\n                block.update();\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name command_block\n        // @input ElementTag\n        // @description\n        // Sets the command of a command block.\n        // @tags\n        // <LocationTag.command_block>\n        // -->\n        if (mechanism.matches(\"command_block\")) {\n            if (getBlock().getState() instanceof CommandBlock) {\n                CommandBlock block = ((CommandBlock) getBlockState());\n                block.setCommand(mechanism.getValue().asString());\n                block.update();\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name custom_name\n        // @input ElementTag\n        // @description\n        // Sets the custom name of the block.\n        // Use no value to reset the block's name.\n        // @tags\n        // <LocationTag.custom_name>\n        // -->\n        if (mechanism.matches(\"custom_name\")) {\n            if (getBlockState() instanceof Nameable) {\n                String title = null;\n                if (mechanism.hasValue()) {\n                    title = mechanism.getValue().asString();\n                }\n                BlockState state = getBlockState();\n                PaperAPITools.instance.setCustomName((Nameable) state, title);\n                state.update(true);\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name brewing_time\n        // @input DurationTag\n        // @description\n        // Sets the brewing time a brewing stand has left.\n        // @tags\n        // <LocationTag.brewing_time>\n        // -->\n        if (mechanism.matches(\"brewing_time\")) {\n            if (getBlockState() instanceof BrewingStand) {\n                BrewingStand stand = (BrewingStand) getBlockState();\n                stand.setBrewingTime(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\n                stand.update();\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name brewing_fuel_level\n        // @input ElementTag(Number)\n        // @description\n        // Sets the brewing fuel level a brewing stand has.\n        // @tags\n        // <LocationTag.brewing_fuel_level>\n        // -->\n        if (mechanism.matches(\"brewing_fuel_level\")) {\n            if (getBlockState() instanceof BrewingStand) {\n                BrewingStand stand = (BrewingStand) getBlockState();\n                stand.setFuelLevel(mechanism.getValue().asInt());\n                stand.update();\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name furnace_burn_duration\n        // @input DurationTag\n        // @description\n        // Sets the burn time for a furnace in ticks. Maximum is 32767 ticks.\n        // @tags\n        // <LocationTag.furnace_burn_duration>\n        // -->\n        if (mechanism.matches(\"furnace_burn_duration\") && mechanism.requireObject(DurationTag.class)) {\n            if (getBlockState() instanceof Furnace) {\n                Furnace furnace = (Furnace) getBlockState();\n                furnace.setBurnTime((short) mechanism.valueAsType(DurationTag.class).getTicks());\n                furnace.update();\n            }\n        }\n        if (mechanism.matches(\"furnace_burn_time\")) {\n            BukkitImplDeprecations.furnaceTimeTags.warn(mechanism.context);\n            if (getBlockState() instanceof Furnace) {\n                Furnace furnace = (Furnace) getBlockState();\n                furnace.setBurnTime((short) mechanism.getValue().asInt());\n                furnace.update();\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name furnace_cook_duration\n        // @input DurationTag\n        // @description\n        // Sets the current cook time for a furnace in ticks. Maximum is 32767 ticks.\n        // @tags\n        // <LocationTag.furnace_cook_duration>\n        // -->\n        if (mechanism.matches(\"furnace_cook_duration\")) {\n            if (getBlockState() instanceof Furnace) {\n                Furnace furnace = (Furnace) getBlockState();\n                furnace.setCookTime((short) mechanism.valueAsType(DurationTag.class).getTicks());\n                furnace.update();\n            }\n        }\n        if (mechanism.matches(\"furnace_cook_time\")) {\n            BukkitImplDeprecations.furnaceTimeTags.warn(mechanism.context);\n            if (getBlockState() instanceof Furnace) {\n                Furnace furnace = (Furnace) getBlockState();\n                furnace.setCookTime((short) mechanism.getValue().asInt());\n                furnace.update();\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name furnace_cook_duration_total\n        // @input DurationTag\n        // @description\n        // Sets the total cook time for a furnace in ticks. Maximum is 32767 ticks.\n        // @tags\n        // <LocationTag.furnace_cook_duration_total>\n        // -->\n        if (mechanism.matches(\"furnace_cook_duration_total\")) {\n            if (getBlockState() instanceof Furnace) {\n                Furnace furnace = (Furnace) getBlockState();\n                furnace.setCookTimeTotal((short) mechanism.valueAsType(DurationTag.class).getTicks());\n                furnace.update();\n            }\n        }\n        if (mechanism.matches(\"furnace_cook_time_total\")) {\n            BukkitImplDeprecations.furnaceTimeTags.warn(mechanism.context);\n            if (getBlockState() instanceof Furnace) {\n                Furnace furnace = (Furnace) getBlockState();\n                furnace.setCookTimeTotal((short) mechanism.getValue().asInt());\n                furnace.update();\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name patterns\n        // @input ListTag\n        // @description\n        // Changes the patterns of the banner at this location. Input must be in the form \"COLOR/PATTERN|COLOR/PATTERN\" etc.\n        // For the list of possible colors, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/DyeColor.html>.\n        // For the list of possible patterns, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/banner/PatternType.html>.\n        // @tags\n        // <LocationTag.patterns>\n        // <server.pattern_types>\n        // -->\n        if (mechanism.matches(\"patterns\")) {\n            List<org.bukkit.block.banner.Pattern> patterns = new ArrayList<>();\n            ListTag list = mechanism.valueAsType(ListTag.class);\n            List<String> split;\n            for (String string : list) {\n                try {\n                    split = CoreUtilities.split(string, '/', 2);\n                    patterns.add(new org.bukkit.block.banner.Pattern(DyeColor.valueOf(split.get(0).toUpperCase()),\n                            PatternType.valueOf(split.get(1).toUpperCase())));\n                }\n                catch (Exception e) {\n                    mechanism.echoError(\"Could not apply pattern to banner: \" + string);\n                }\n            }\n            Banner banner = (Banner) getBlockState();\n            banner.setPatterns(patterns);\n            banner.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name head_rotation\n        // @input ElementTag(Number)\n        // @description\n        // Sets the rotation of the head at this location. Must be an integer 1 to 16.\n        // @tags\n        // <LocationTag.head_rotation>\n        // -->\n        if (mechanism.matches(\"head_rotation\") && mechanism.requireInteger()) {\n            Skull sk = (Skull) getBlockState();\n            sk.setRotation(getSkullBlockFace(mechanism.getValue().asInt() - 1));\n            sk.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name generate_tree\n        // @input ElementTag\n        // @description\n        // Generates a tree at this location if possible.\n        // For a list of valid tree types, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/TreeType.html>\n        // @tags\n        // <server.tree_types>\n        // -->\n        if (mechanism.matches(\"generate_tree\") && mechanism.requireEnum(TreeType.class)) {\n            boolean generated = getWorld().generateTree(this, TreeType.valueOf(mechanism.getValue().asString().toUpperCase()));\n            if (!generated) {\n                mechanism.echoError(\"Could not generate tree at \" + identifySimple() + \". Make sure this location can naturally generate a tree!\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name beacon_primary_effect\n        // @input ElementTag\n        // @description\n        // Sets the primary effect of a beacon, with input as just an effect type name.\n        // @tags\n        // <LocationTag.beacon_primary_effect>\n        // -->\n        if (mechanism.matches(\"beacon_primary_effect\")) {\n            Beacon beacon = (Beacon) getBlockState();\n            beacon.setPrimaryEffect(PotionEffectType.getByName(mechanism.getValue().asString().toUpperCase()));\n            beacon.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name beacon_secondary_effect\n        // @input ElementTag\n        // @description\n        // Sets the secondary effect of a beacon, with input as just an effect type name.\n        // @tags\n        // <LocationTag.beacon_secondary_effect>\n        // -->\n        if (mechanism.matches(\"beacon_secondary_effect\")) {\n            Beacon beacon = (Beacon) getBlockState();\n            beacon.setSecondaryEffect(PotionEffectType.getByName(mechanism.getValue().asString().toUpperCase()));\n            beacon.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name activate\n        // @input None\n        // @description\n        // Activates the block at the location if possible.\n        // Works for blocks like dispensers, which have explicit 'activation' methods.\n        // -->\n        if (mechanism.matches(\"activate\")) {\n            BlockState state = getBlockState();\n            if (state instanceof Dispenser) {\n                ((Dispenser) state).dispense();\n            }\n            else if (state instanceof Dropper) {\n                ((Dropper) state).drop();\n            }\n            else {\n                mechanism.echoError(\"'activate' mechanism does not work for blocks of type: \" + state.getType().name());\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name lectern_page\n        // @input ElementTag(Number)\n        // @deprecated Use LocationTag.page\n        // @description\n        // Deprecated in favor of <@link tag LocationTag.page>.\n        // @tags\n        // <LocationTag.lectern_page>\n        // -->\n        if (mechanism.matches(\"lectern_page\") && mechanism.requireInteger()) {\n            BukkitImplDeprecations.lecternPage.warn(mechanism.context);\n            BlockState state = getBlockState();\n            if (state instanceof Lectern lectern) {\n                lectern.setPage(mechanism.getValue().asInt());\n                state.update();\n            }\n            else {\n                mechanism.echoError(\"'lectern_page' mechanism can only be called on a lectern block.\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name clear_loot_table\n        // @input None\n        // @description\n        // Removes the loot table from the chest at this location.\n        // @tags\n        // <LocationTag.has_loot_table>\n        // -->\n        if (mechanism.matches(\"clear_loot_table\")) {\n            BlockState state = getBlockState();\n            if (state instanceof Lootable) {\n                ((Lootable) state).setLootTable(null);\n                state.update();\n            }\n            else {\n                mechanism.echoError(\"'clear_loot_table' mechanism can only be called on a lootable block (like a chest).\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name loot_table_id\n        // @input ElementTag\n        // @description\n        // Sets the loot table of a lootable container at this location.\n        // This is the namespaced path of the loot table, provided by a datapack or Minecraft's default data.\n        // @tags\n        // <LocationTag.loot_table_id>\n        // <LocationTag.has_loot_table>\n        // @Example\n        // # Sets the chest's loot table to a bonus chest\n        // - adjust <[location]> loot_table_id:chests/ancient_city\n        // -->\n        if (mechanism.matches(\"loot_table_id\")) {\n            BlockState state = getBlockState();\n            if (state instanceof Lootable) {\n                LootTable table = Bukkit.getLootTable(Utilities.parseNamespacedKey(mechanism.getValue().asString()));\n                if (table == null) {\n                    mechanism.echoError(\"Invalid loot table ID.\");\n                    return;\n                }\n                ((Lootable) state).setLootTable(table);\n                state.update();\n            }\n            else {\n                mechanism.echoError(\"'loot_table_id' mechanism can only be called on a lootable block (like a chest).\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name jukebox_record\n        // @input ItemTag\n        // @description\n        // Sets the record item played by a jukebox. Give no input to set the jukebox to empty.\n        // See also <@link mechanism LocationTag.jukebox_play>.\n        // @tags\n        // <LocationTag.jukebox_record>\n        // -->\n        if (mechanism.matches(\"jukebox_record\")) {\n            if (!(getBlockState() instanceof Jukebox jukebox)) {\n                mechanism.echoError(\"'jukebox_record' mechanism can only be called on a jukebox block.\");\n                return;\n            }\n            if (mechanism.hasValue()) {\n                if (!mechanism.requireObject(ItemTag.class)) {\n                    return;\n                }\n                jukebox.setRecord(mechanism.valueAsType(ItemTag.class).getItemStack());\n            }\n            else {\n                NMSHandler.blockHelper.makeBlockStateRaw(jukebox);\n                jukebox.setRecord(null);\n            }\n            jukebox.update();\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name jukebox_play\n        // @input ElementTag(Boolean)\n        // @description\n        // If 'true', starts playing the record inside. If 'false', stops playing any song.\n        // See also <@link mechanism LocationTag.jukebox_record>.\n        // @tags\n        // <LocationTag.jukebox_is_playing>\n        // -->\n        if (mechanism.matches(\"jukebox_play\") && mechanism.requireBoolean()) {\n            BlockState state = getBlockState();\n            if (state instanceof Jukebox) {\n                if (mechanism.getValue().asBoolean()) {\n                    Material mat = ((Jukebox) state).getRecord().getType();\n                    if (mat == Material.AIR) {\n                        mechanism.echoError(\"'jukebox_play' cannot play nothing.\");\n                        return;\n                    }\n                    ((Jukebox) state).setPlaying(mat);\n                }\n                else {\n                    ((Jukebox) state).stopPlaying();\n                }\n                state.update();\n            }\n            else {\n                mechanism.echoError(\"'jukebox_play' mechanism can only be called on a jukebox block.\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name age\n        // @input DurationTag\n        // @description\n        // Sets the age of an end gateway.\n        // @tags\n        // <LocationTag.age>\n        // -->\n        if (mechanism.matches(\"age\") && mechanism.requireObject(DurationTag.class)) {\n            BlockState state = getBlockState();\n            if (state instanceof EndGateway) {\n                ((EndGateway) state).setAge(mechanism.valueAsType(DurationTag.class).getTicks());\n                state.update();\n            }\n            else {\n                mechanism.echoError(\"'age' mechanism can only be called on end gateway blocks.\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name is_exact_teleport\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether an end gateway is 'exact teleport' - if false, the destination will be randomly chosen *near* the destination.\n        // @tags\n        // <LocationTag.is_exact_teleport>\n        // -->\n        if (mechanism.matches(\"is_exact_teleport\") && mechanism.requireBoolean()) {\n            BlockState state = getBlockState();\n            if (state instanceof EndGateway) {\n                ((EndGateway) state).setExactTeleport(mechanism.getValue().asBoolean());\n                state.update();\n            }\n            else {\n                mechanism.echoError(\"'is_exact_teleport' mechanism can only be called on end gateway blocks.\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name exit_location\n        // @input LocationTag\n        // @description\n        // Sets the exit location of an end gateway block.\n        // See also <@link mechanism LocationTag.is_exact_teleport>.\n        // @tags\n        // <LocationTag.exit_location>\n        // -->\n        if (mechanism.matches(\"exit_location\") && mechanism.requireObject(LocationTag.class)) {\n            BlockState state = getBlockState();\n            if (state instanceof EndGateway) {\n                ((EndGateway) state).setExitLocation(mechanism.valueAsType(LocationTag.class));\n                state.update();\n            }\n            else {\n                mechanism.echoError(\"'exit_location' mechanism can only be called on end gateway blocks.\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name vanilla_tick\n        // @input None\n        // @description\n        // Causes an immediate vanilla tick at a block location (normally processed at random according to the randomTickSpeed gamerule).\n        // -->\n        if (mechanism.matches(\"vanilla_tick\")) {\n            NMSHandler.blockHelper.doRandomTick(this);\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name apply_bonemeal\n        // @input ElementTag\n        // @description\n        // Applies bonemeal to the block, on the given block face. Input is NORTH, EAST, SOUTH, WEST, UP, or DOWN.\n        // For example: - adjust <player.location.below> apply_bonemeal:up\n        // -->\n        if (mechanism.matches(\"apply_bonemeal\") && mechanism.requireEnum(BlockFace.class)) {\n            getBlock().applyBoneMeal(BlockFace.valueOf(mechanism.getValue().asString().toUpperCase()));\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name campfire_items\n        // @input ListTag(ItemTag)\n        // @description\n        // Sets the items in this campfire, as a list of items, where the index in the list directly corresponds to index in the campfire slots.\n        // @tags\n        // <LocationTag.campfire_items>\n        // -->\n        if (mechanism.matches(\"campfire_items\") && mechanism.requireObject(ListTag.class)) {\n            BlockState state = getBlockState();\n            if (!(state instanceof Campfire)) {\n                mechanism.echoError(\"'campfire_items' mechanism can only be called on campfire blocks.\");\n            }\n            else {\n                Campfire fire = (Campfire) state;\n                List<ItemTag> list = mechanism.valueAsType(ListTag.class).filter(ItemTag.class, mechanism.context);\n                for (int i = 0; i < list.size(); i++) {\n                    if (i >= fire.getSize()) {\n                        mechanism.echoError(\"Cannot add item for index \" + (i + 1) + \" as the campfire can only hold \" + fire.getSize() + \" items.\");\n                        break;\n                    }\n                    ItemStack item = list.get(i).getItemStack();\n                    fire.setCookTime(i, 0);\n                    fire.setCookTimeTotal(i, 0);\n                    Iterator<Recipe> recipeIterator = Bukkit.recipeIterator();\n                    while (recipeIterator.hasNext()) {\n                        Recipe recipe = recipeIterator.next();\n                        if (recipe instanceof CampfireRecipe) {\n                            if (((CampfireRecipe) recipe).getInputChoice().test(item)) {\n                                fire.setCookTimeTotal(i, ((CampfireRecipe) recipe).getCookingTime());\n                                break;\n                            }\n                        }\n                    }\n                    fire.setItem(i, item);\n                }\n                fire.update();\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name ring_bell\n        // @input None\n        // @description\n        // Causes the bell to ring.\n        // -->\n        if (mechanism.matches(\"ring_bell\")) {\n            BlockState state = getBlockState();\n            if (!(state instanceof Bell)) {\n                mechanism.echoError(\"'ring_bell' mechanism can only be called on Bell blocks.\");\n            }\n            else {\n                NMSHandler.blockHelper.ringBell((Bell) state);\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name sign_glowing\n        // @input ElementTag(Boolean)\n        // @description\n        // Changes whether the sign at the location is glowing.\n        // @tags\n        // <LocationTag.sign_glow_color>\n        // <LocationTag.sign_glowing>\n        // -->\n        if (mechanism.matches(\"sign_glowing\") && mechanism.requireBoolean()) {\n            BlockState state = getBlockState();\n            if (!(state instanceof Sign)) {\n                mechanism.echoError(\"'sign_glowing' mechanism can only be called on Sign blocks.\");\n            }\n            else {\n                Sign sign = (Sign) state;\n                sign.setGlowingText(mechanism.getValue().asBoolean());\n                sign.update();\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name sign_glow_color\n        // @input ElementTag\n        // @description\n        // Changes the glow color of a sign.\n        // For the list of possible colors, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/DyeColor.html>.\n        // If a sign is not glowing, this is equivalent to applying a chat color to the sign.\n        // Use <@link mechanism LocationTag.sign_glowing> to toggle whether the sign is glowing.\n        // @tags\n        // <LocationTag.sign_glow_color>\n        // <LocationTag.sign_glowing>\n        // -->\n        if (mechanism.matches(\"sign_glow_color\") && mechanism.requireEnum(DyeColor.class)) {\n            BlockState state = getBlockState();\n            if (!(state instanceof Sign)) {\n                mechanism.echoError(\"'sign_glow_color' mechanism can only be called on Sign blocks.\");\n            }\n            else {\n                Sign sign = (Sign) state;\n                sign.setColor(mechanism.getValue().asEnum(DyeColor.class));\n                sign.update();\n            }\n        }\n\n        // <--[mechanism]\n        // @object LocationTag\n        // @name structure_block_data\n        // @input MapTag\n        // @description\n        // Sets the structure block data of the structure block at the location. Input is a map with the following keys (all keys are optional):\n        // - author: EntityTag: The Structure's author, can also input an ElementTag to set the name directly (set to \"?\" for most vanilla structures).\n        // - integrity: ElementTag(Decimal): The integrity of the structure (0-1). Lower integrity values will result in more blocks being removed when loading a structure.\n        // used with the seed to determine which blocks are randomly removed to mimic \"decay\".\n        // - metadata: ElementTag: Can only be set while in DATA mode. sets specific functions that can be applied to the structure,\n        // check the Minecraft wiki (<@link url https://minecraft.wiki/w/Structure_Block#Data>) for more information.\n        // - mirror: ElementTag: How the structure is mirrored; \"NONE\", \"LEFT_RIGHT\", or \"FRONT_BACK\".\n        // - box_position: LocationTag: The position of the structure's bounding box, relative to the position of the structure block. Maximum allowed distance is 48 blocks in any direction.\n        // - rotation: ElementTag: The rotation of the structure; \"NONE\", \"CLOCKWISE_90\", \"CLOCKWISE_180\", or \"COUNTERCLOCKWISE_90\".\n        // - seed: ElementTag(Number): The seed used to determine how many blocks are removed upon loading of this structure (see \"integrity\" for more information).\n        // - structure_name: ElementTag: The name of the structure.\n        // - size: LocationTag: The size of the structure's bounding box, The maximum structure size is 48,48,48.\n        // - mode: ElementTag: The structure block's mode; \"CORNER\", \"DATA\", \"LOAD\", or \"SAVE\". See also <@link mechanism MaterialTag.mode>.\n        // - box_visible: ElementTag(Boolean): Whether the structure's bounding box is visible, only applies in LOAD mode.\n        // - ignore_entities: ElementTag(Boolean): Whether entities in the structure are ignored, only applies in SAVE mode.\n        // - show_invisible: ElementTag(Boolean): Whether invisible blocks in the structure are shown.\n        // @tags\n        // <LocationTag.structure_block_data>\n        // -->\n        if (mechanism.matches(\"structure_block_data\") && mechanism.requireObject(MapTag.class)) {\n            BlockState state = getBlockState();\n            if (!(state instanceof Structure)) {\n                mechanism.echoError(\"'structure_block_data' mechanism can only be called on Structure blocks.\");\n                return;\n            }\n            Structure structure = (Structure) state;\n            MapTag input = mechanism.valueAsType(MapTag.class);\n            ObjectTag author = input.getObject(\"author\");\n            if (author != null) {\n                if (author.shouldBeType(EntityTag.class)) {\n                    EntityTag entity = author.asType(EntityTag.class, mechanism.context);\n                    if (!entity.isLivingEntity()) {\n                        mechanism.echoError(\"Invalid author entity input '\" + author + \"': entity must be living.\");\n                        return;\n                    }\n                    structure.setAuthor(entity.getLivingEntity());\n                }\n                else {\n                    structure.setAuthor(author.toString());\n                }\n            }\n            ElementTag integrity = input.getElement(\"integrity\");\n            if (integrity != null) {\n                float integrityFloat = integrity.isFloat() ? integrity.asFloat() : -1;\n                if (integrityFloat < 0 || integrityFloat > 1) {\n                    mechanism.echoError(\"Invalid integrity input '\" + integrity + \"': must be a decimal between 0 and 1.\");\n                    return;\n                }\n                structure.setIntegrity(integrityFloat);\n            }\n            ElementTag metadata = input.getElement(\"metadata\");\n            if (metadata != null) {\n                if (structure.getUsageMode() != UsageMode.DATA) {\n                    mechanism.echoError(\"metadata can only be set while in DATA mode.\");\n                    return;\n                }\n                structure.setMetadata(metadata.toString());\n            }\n            ElementTag mirror = input.getElement(\"mirror\");\n            if (mirror != null) {\n                Mirror mirrorEnum = mirror.asEnum(Mirror.class);\n                if (mirrorEnum == null) {\n                    mechanism.echoError(\"Invalid mirror input '\" + mirror + \"': check meta docs for more information.\");\n                    return;\n                }\n                structure.setMirror(mirrorEnum);\n            }\n            LocationTag boxPositionLoc = input.getObjectAs(\"box_position\", LocationTag.class, mechanism.context);\n            if (boxPositionLoc != null) {\n                int x = boxPositionLoc.getBlockX();\n                int y = boxPositionLoc.getBlockY();\n                int z = boxPositionLoc.getBlockZ();\n                if (x < -48 || x > 48 || y < -48 || y > 48 || z < -48 || z > 48) {\n                    mechanism.echoError(\"Invalid box_position input '\" + boxPositionLoc + \"': must be within 48 blocks of the structure block.\");\n                    return;\n                }\n                structure.setRelativePosition(new BlockVector(boxPositionLoc.toVector()));\n            }\n            ElementTag rotation = input.getElement(\"rotation\");\n            if (rotation != null) {\n                StructureRotation rotationEnum = rotation.asEnum(StructureRotation.class);\n                if (rotationEnum == null) {\n                    mechanism.echoError(\"Invalid rotation input '\" + rotation + \"': check meta docs for more information.\");\n                    return;\n                }\n                structure.setRotation(rotationEnum);\n            }\n            ElementTag seed = input.getElement(\"seed\");\n            if (seed != null) {\n                if (!seed.isInt()) {\n                    mechanism.echoError(\"Invalid seed input '\" + seed + \"': must be an integer.\");\n                    return;\n                }\n                structure.setSeed(seed.asLong());\n            }\n            ElementTag structureName = input.getElement(\"structure_name\");\n            if (structureName != null) {\n                structure.setStructureName(structureName.toString());\n            }\n            LocationTag sizeLoc = input.getObjectAs(\"size\", LocationTag.class, mechanism.context);\n            if (sizeLoc != null) {\n                int x = sizeLoc.getBlockX();\n                int y = sizeLoc.getBlockY();\n                int z = sizeLoc.getBlockZ();\n                if (x < 0 || x > 48 || y < 0 || y > 48 || z < 0 || z > 48) {\n                    mechanism.echoError(\"Invalid size input '\" + sizeLoc + \"': cannot be larger than 48,48,48 or smaller than 0,0,0.\");\n                    return;\n                }\n                structure.setStructureSize(new BlockVector(sizeLoc.toVector()));\n            }\n            ElementTag mode = input.getElement(\"mode\");\n            if (mode != null) {\n                UsageMode usageMode = mode.asEnum(UsageMode.class);\n                if (usageMode == null) {\n                    mechanism.echoError(\"Invalid mode input '\" + mode + \"': check meta docs for more information.\");\n                    return;\n                }\n                structure.setUsageMode(usageMode);\n            }\n            ElementTag boxVisible = input.getElement(\"box_visible\");\n            if (boxVisible != null) {\n                if (!boxVisible.isBoolean()) {\n                    mechanism.echoError(\"Invalid box_visible input '\" + boxVisible + \"': must be a boolean.\");\n                    return;\n                }\n                structure.setBoundingBoxVisible(boxVisible.asBoolean());\n            }\n            ElementTag ignoreEntities = input.getElement(\"ignore_entities\");\n            if (ignoreEntities != null) {\n                if (!ignoreEntities.isBoolean()) {\n                    mechanism.echoError(\"Invalid ignore_entities input '\" + ignoreEntities + \"': must be a boolean.\");\n                    return;\n                }\n                structure.setIgnoreEntities(ignoreEntities.asBoolean());\n            }\n            ElementTag showInvisible = input.getElement(\"show_invisible\");\n            if (showInvisible != null) {\n                if (!showInvisible.isBoolean()) {\n                    mechanism.echoError(\"Invalid show_invisible input '\" + showInvisible + \"': must be a boolean.\");\n                    return;\n                }\n                structure.setShowAir(showInvisible.asBoolean());\n            }\n            structure.update();\n        }\n\n        tagProcessor.processMechanism(this, mechanism);\n    }\n\n    public String formatHelper(String format, String prefix, Function<Double, String> formatNum) {\n        if (!format.contains(prefix)) {\n            return format;\n        }\n        return format.replace(prefix + \"yaw\", formatNum.apply((double) getYaw()))\n                .replace(prefix + \"pitch\", formatNum.apply((double) getPitch()))\n                .replace(prefix + \"x\", formatNum.apply(getX()))\n                .replace(prefix + \"y\", formatNum.apply(getY()))\n                .replace(prefix + \"z\", formatNum.apply(getZ()));\n    }\n\n    @Override\n    public boolean advancedMatches(String matcher, TagContext context) {\n        String matcherLow = CoreUtilities.toLowerCase(matcher);\n        if (matcherLow.equals(\"location\")) {\n            return true;\n        }\n        if (matcherLow.contains(\":\")) {\n            if (matcherLow.startsWith(\"block_flagged:\")) {\n                return BukkitScriptEvent.coreFlaggedCheck(matcher.substring(\"block_flagged:\".length()), getFlagTracker());\n            }\n            if (matcherLow.startsWith(\"location_in:\")) {\n                return BukkitScriptEvent.inCheckInternal(CoreUtilities.noDebugContext, \"tryLocation\", this, matcher.substring(\"location_in:\".length()), \"tryLocation\", \"tryLocation\");\n            }\n        }\n        if (getWorld() == null) {\n            return false;\n        }\n        if (getY() < getWorld().getMinHeight() || getY() >= getWorld().getMaxHeight()) {\n            return false;\n        }\n        return new MaterialTag(getBlock()).advancedMatches(matcher, context);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/MaterialTag.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.interfaces.BlockHelper;\r\nimport com.denizenscript.denizen.objects.properties.material.*;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.VanillaTagHelper;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.flags.RedirectionFlagTracker;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.PropertyMatchHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.block.data.BlockData;\r\n\r\nimport java.util.HashSet;\r\nimport java.util.Set;\r\n\r\npublic class MaterialTag implements ObjectTag, Adjustable, FlaggableObject {\r\n\r\n    // <--[ObjectType]\r\n    // @name MaterialTag\r\n    // @prefix m\r\n    // @base ElementTag\r\n    // @implements FlaggableObject, PropertyHolderObject\r\n    // @ExampleTagBase material[stone]\r\n    // @ExampleValues stone,dirt,stick,iron_sword\r\n    // @ExampleForReturns\r\n    // - foreach <player.location.find_blocks[%VALUE%].within[5]> as:loc:\r\n    //     - modifyblock <[loc]> air\r\n    // @ExampleForReturns\r\n    // - modifyblock <player.location.below> %VALUE%\r\n    // @format\r\n    // The identity format for materials is the material type name.\r\n    // For example, 'm@stick'.\r\n    //\r\n    // @description\r\n    // A MaterialTag represents a material (a type of block or item).\r\n    //\r\n    // Block materials may sometimes also contain property data,\r\n    // for specific values on the block material such as the growth stage of a plant or the orientation of a stair block.\r\n    //\r\n    // Material types: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html>.\r\n    //\r\n    // This object type is flaggable.\r\n    // Flags on this object type will be stored in the server saves file, under special sub-key \"__materials\"\r\n    //\r\n    // @Matchable\r\n    // MaterialTag matchers, sometimes identified as \"<material>\", associated with \"<block>\":\r\n    // \"material\" plaintext: always matches.\r\n    // \"block\" plaintext: matches if the material is a block-type material.\r\n    // \"item\" plaintext: matches if the material is an item-type material.\r\n    // \"material_flagged:<flag>\": a Flag Matchable for MaterialTag flags.\r\n    // \"vanilla_tagged:<tag_name>\": matches if the given vanilla tag applies to the material. Allows advanced matchers, for example: \"vanilla_tagged:mineable*\".\r\n    // If none of the above are used, uses an advanced matcher for the material name, like \"stick\".\r\n    //\r\n    // -->\r\n\r\n    /**\r\n     * Gets a Material Object from a string form.\r\n     *\r\n     * @param string the string\r\n     * @return a Material, or null if incorrectly formatted\r\n     */\r\n    @Fetchable(\"m\")\r\n    public static MaterialTag valueOf(String string, TagContext context) {\r\n        if (ObjectFetcher.isObjectWithProperties(string)) {\r\n            return ObjectFetcher.getObjectFromWithProperties(MaterialTag.class, string, context);\r\n        }\r\n        string = CoreUtilities.toUpperCase(string);\r\n        if (string.startsWith(\"M@\")) {\r\n            string = string.substring(\"M@\".length());\r\n        }\r\n        if (string.equals(\"RANDOM\")) {\r\n            return new MaterialTag(Material.values()[CoreUtilities.getRandom().nextInt(Material.values().length)]);\r\n        }\r\n        Material m = Material.getMaterial(string);\r\n        if (m != null) {\r\n            return new MaterialTag(m);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    /**\r\n     * Determine whether a string is a valid material.\r\n     *\r\n     * @param arg the string\r\n     * @return true if matched, otherwise false\r\n     */\r\n    public static boolean matches(String arg) {\r\n        if (valueOf(arg, CoreUtilities.noDebugContext) != null) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag duplicate() {\r\n        if (hasModernData()) {\r\n            return new MaterialTag(getModernData().clone());\r\n        }\r\n        else {\r\n            return new MaterialTag(getMaterial());\r\n        }\r\n    }\r\n\r\n    /**\r\n     * @param object object-fetchable String of a valid MaterialTag, or a MaterialTag object\r\n     * @return true if the MaterialTags are the same.\r\n     */\r\n    @Override\r\n    public boolean equals(Object object) {\r\n        if (object instanceof MaterialTag) {\r\n            return getMaterial() == ((MaterialTag) object).getMaterial();\r\n        }\r\n        else {\r\n            MaterialTag parsed = valueOf(object.toString(), CoreUtilities.noDebugContext);\r\n            return equals(parsed);\r\n        }\r\n    }\r\n\r\n    public MaterialTag(Material material) {\r\n        this.material = material;\r\n        if (material.isBlock()) {\r\n            modernData = material.createBlockData();\r\n        }\r\n    }\r\n\r\n    public MaterialTag(BlockState state) {\r\n        this.material = state.getType();\r\n        this.modernData = state.getBlockData();\r\n    }\r\n\r\n    public MaterialTag(Block block) {\r\n        this.modernData = block.getBlockData();\r\n        this.material = modernData.getMaterial();\r\n    }\r\n\r\n    public MaterialTag(BlockData data) {\r\n        this.modernData = data;\r\n        this.material = data.getMaterial();\r\n    }\r\n\r\n    private Material material;\r\n    private BlockData modernData;\r\n\r\n    public boolean hasModernData() {\r\n        return modernData != null;\r\n    }\r\n\r\n    public BlockData getModernData() {\r\n        return modernData;\r\n    }\r\n\r\n    public void setModernData(BlockData data) {\r\n        modernData = data;\r\n    }\r\n\r\n    public Material getMaterial() {\r\n        return material;\r\n    }\r\n\r\n    public String name() {\r\n        return material.name();\r\n    }\r\n\r\n    public boolean isStructure() {\r\n        if (material == Material.CHORUS_PLANT || material == Material.RED_MUSHROOM_BLOCK || material == Material.BROWN_MUSHROOM_BLOCK) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    String prefix = \"material\";\r\n\r\n    @Override\r\n    public String getPrefix() {\r\n        return prefix;\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnique() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public String identify() {\r\n        return \"m@\" + identifyNoIdentifier();\r\n    }\r\n\r\n    @Override\r\n    public String debuggable() {\r\n        return \"<LG>m@<Y>\" + CoreUtilities.toLowerCase(material.name()) + PropertyParser.getPropertiesDebuggable(this);\r\n    }\r\n\r\n    @Override\r\n    public String identifySimple() {\r\n        return \"m@\" + identifySimpleNoIdentifier();\r\n    }\r\n\r\n    public String identifyNoPropertiesNoIdentifier() {\r\n        return CoreUtilities.toLowerCase(material.name());\r\n    }\r\n\r\n    public String identifyNoIdentifier() {\r\n        return CoreUtilities.toLowerCase(material.name()) + PropertyParser.getPropertiesString(this);\r\n    }\r\n\r\n    public String identifySimpleNoIdentifier() {\r\n        return CoreUtilities.toLowerCase(material.name());\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public Object getJavaObject() {\r\n        return modernData == null ? material : modernData;\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag setPrefix(String prefix) {\r\n        if (prefix != null) {\r\n            this.prefix = prefix;\r\n        }\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public boolean isTruthy() {\r\n        return !getMaterial().isAir();\r\n    }\r\n\r\n    @Override\r\n    public AbstractFlagTracker getFlagTracker() {\r\n        return new RedirectionFlagTracker(DenizenCore.serverFlagMap, \"__materials.\" + material.name().replace(\".\", \"&dot\"));\r\n    }\r\n\r\n    @Override\r\n    public void reapplyTracker(AbstractFlagTracker tracker) {\r\n        // Nothing to do.\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\r\n        PropertyParser.registerPropertyTagHandlers(MaterialTag.class, tagProcessor);\r\n\r\n        tagProcessor.registerTag(ElementTag.class, \"is_ageable\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialAge.describes(object));\r\n        }, \"is_plant\");\r\n        tagProcessor.registerTag(ElementTag.class, \"is_campfire\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialCampfire.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"is_directional\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialDirectional.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"has_multiple_faces\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialFaces.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"can_drag\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialDrags.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"is_bisected\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialHalf.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"has_leaf_size\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialLeafSize.describes(object));\r\n        }, \"is_bamboo\");\r\n        tagProcessor.registerTag(ElementTag.class, \"is_levelable\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialLevel.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"is_lightable\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialLightable.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"is_leaves\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialPersistent.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"has_count\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialCount.describes(object));\r\n        }, \"is_pickle\");\r\n        tagProcessor.registerTag(ElementTag.class, \"has_type\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialBlockType.describes(object));\r\n        }, \"is_slab\");\r\n        tagProcessor.registerTag(ElementTag.class, \"is_snowable\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialSnowable.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"is_switch\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialAttachmentFace.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"is_waterloggable\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialWaterlogged.describes(object));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, \"is_switchable\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialPropertyTags.warn(attribute.context);\r\n            return new ElementTag(MaterialSwitchable.describes(object));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.has_gravity>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is affected by gravity.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"has_gravity\", (attribute, object) -> {\r\n            return new ElementTag(object.material.hasGravity());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_block>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is a placeable block.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_block\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isBlock());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_item>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is a holdable item.\r\n        // Note that most blocks are valid items as well.\r\n        // This only returns \"false\" for certain non-holdable \"special\" blocks, like Fire.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_item\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isItem());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_interactable>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material can be interacted with.\r\n        // Some blocks such as piston heads and stairs are considered interactable.\r\n        // Note that this will return true if at least one state of a material has interaction handling.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_interactable\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isInteractable());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_burnable>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is a block that can burn away.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_burnable\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isBurnable());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_edible>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is edible.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_edible\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isEdible());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_flammable>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is a block that can catch fire.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_flammable\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isFlammable());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_fuel>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is a block that can be burned in a furnace as fuel.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_fuel\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isFuel());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.fuel_burn_time>\r\n        // @returns DurationTag\r\n        // @description\r\n        // Returns the duration that a burnable fuel block will burn in a furnace for.\r\n        // -->\r\n        tagProcessor.registerTag(DurationTag.class, \"fuel_burn_time\", (attribute, object) -> {\r\n            Integer ticks = NMSHandler.itemHelper.burnTime(object.getMaterial());\r\n            if (ticks != null) {\r\n                return new DurationTag(ticks.longValue());\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_occluding>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is a block that completely blocks vision.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_occluding\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isOccluding());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_record>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is a playable music disc.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_record\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isRecord());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_solid>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is a block that is solid (can be built upon).\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_solid\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isSolid());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_transparent>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the material is a block that does not block any light.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_transparent\", (attribute, object) -> {\r\n            return new ElementTag(object.material.isTransparent());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.max_durability>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the maximum durability of this material.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"max_durability\", (attribute, object) -> {\r\n            return new ElementTag(object.material.getMaxDurability());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.block_resistance>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism MaterialTag.block_resistance\r\n        // @description\r\n        // Returns the explosion resistance for all blocks of this material type.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"block_resistance\", (attribute, object) -> {\r\n            if (!object.getMaterial().isBlock()) {\r\n                Debug.echoError(\"Provided material does not have a placeable block.\");\r\n                return null;\r\n            }\r\n            return new ElementTag(NMSHandler.blockHelper.getBlockResistance(object.getMaterial()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.hardness>\r\n        // @returns ElementTag(Decimal)\r\n        // @description\r\n        // Returns the value representing how hard a material, used as a basis for calculating the time it takes to break.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"hardness\", (attribute, object) -> {\r\n            if (!object.getMaterial().isBlock()) {\r\n                Debug.echoError(\"Provided material does not have a placeable block.\");\r\n                return null;\r\n            }\r\n            return new ElementTag(object.getMaterial().getHardness());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.max_stack_size>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.max_stack_size\r\n        // @description\r\n        // Returns the maximum amount of this material that can be held in a stack.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"max_stack_size\", (attribute, object) -> {\r\n            return new ElementTag(object.material.getMaxStackSize());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.translated_name>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the localized name of the material.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"translated_name\", (attribute, object) -> {\r\n            String key = object.material.getKey().getKey();\r\n            key = key.replace(\"wall_banner\", \"banner\");\r\n            String type = object.material.isBlock() ? \"block\" : \"item\";\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[translate=\" + type + \".minecraft.\" + key + \"]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.name>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the name of the material.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"name\", (attribute, object) -> {\r\n            return new ElementTag(CoreUtilities.toLowerCase(object.material.name()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.item>\r\n        // @returns ItemTag\r\n        // @description\r\n        // Returns an item of the material. Not all materials can be items.\r\n        // -->\r\n        tagProcessor.registerTag(ItemTag.class, \"item\", (attribute, object) -> {\r\n            return object.material.isItem() ? new ItemTag(object, 1) : null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.piston_reaction>\r\n        // @returns ElementTag\r\n        // @mechanism MaterialTag.piston_reaction\r\n        // @description\r\n        // Returns the material's piston reaction. (Only for block materials).\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"piston_reaction\", (attribute, object) -> {\r\n            return new ElementTag(NMSHandler.blockHelper.getPushReaction(object.material));\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name piston_reaction\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the piston reaction for all blocks of this material type.\r\n        // Input may be: NORMAL (push and pull allowed), DESTROY (break when pushed), BLOCK (prevent a push or pull), IGNORE (don't use this), or PUSH_ONLY (push allowed but not pull)\r\n        // @tags\r\n        // <MaterialTag.piston_reaction>\r\n        // -->\r\n        tagProcessor.registerMechanism(\"piston_reaction\", false, ElementTag.class, (object, mechanism, input) -> {\r\n            if (!mechanism.requireEnum(BlockHelper.PistonPushReaction.class)) {\r\n                return;\r\n            }\r\n            if (!object.getMaterial().isBlock()) {\r\n                mechanism.echoError(\"'piston_reaction' mechanism is only valid for block types.\");\r\n            }\r\n            NMSHandler.blockHelper.setPushReaction(object.getMaterial(), input.asEnum(BlockHelper.PistonPushReaction.class));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.block_strength>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism MaterialTag.block_strength\r\n        // @description\r\n        // Returns the material's strength level. (Only for block materials).\r\n        // This is a representation of how much time mining is needed to break a block.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"block_strength\", (attribute, object) -> {\r\n            return new ElementTag(NMSHandler.blockHelper.getBlockStrength(object.material));\r\n        });\r\n\r\n        tagProcessor.registerTag(ElementTag.class, \"has_vanilla_data_tag\", (attribute, object) -> {\r\n            BukkitImplDeprecations.materialHasDataPackTag.warn(attribute.context);\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"MaterialTag.has_vanilla_data_tag[...] tag must have an input value.\");\r\n                return null;\r\n            }\r\n            NamespacedKey key = NamespacedKey.minecraft(CoreUtilities.toLowerCase(attribute.getParam()));\r\n            Tag<Material> tagBlock = Bukkit.getTag(\"blocks\", key, Material.class);\r\n            Tag<Material> tagItem = Bukkit.getTag(\"items\", key, Material.class);\r\n            return new ElementTag((tagBlock != null && tagBlock.isTagged(object.getMaterial()) || (tagItem != null && tagItem.isTagged(object.getMaterial()))));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.vanilla_tags>\r\n        // @returns ListTag\r\n        // @mechanism MaterialTag.vanilla_tags\r\n        // @description\r\n        // Returns a list of vanilla tags that apply to this material. See also <@link url https://minecraft.wiki/w/Tag>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"vanilla_tags\", (attribute, object) -> {\r\n            HashSet<String> tags = VanillaTagHelper.tagsByMaterial.get(object.getMaterial());\r\n            if (tags == null) {\r\n                return new ListTag();\r\n            }\r\n            return new ListTag(tags);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.produced_instrument>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the name of the instrument that would be used by a note block placed above or below (depending on the material type) a block of this material.\r\n        // See list at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Instrument.html>.\r\n        // For the current instrument of a note block material refer to <@link tag MaterialTag.instrument>.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"produced_instrument\", (attribute, object) -> {\r\n            return new ElementTag(NMSHandler.blockHelper.getInstrumentFor(object.getMaterial()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.block_sound_data>\r\n        // @returns MapTag\r\n        // @description\r\n        // If the material is a block, returns the sound data for that block.\r\n        // The returned map has the following keys:\r\n        // volume, pitch: a decimal number\r\n        // break_sound, step_sound, place_sound, hit_sound, fall_sound: Bukkit name of the sound effect\r\n        // -->\r\n        tagProcessor.registerTag(MapTag.class, \"block_sound_data\", (attribute, object) -> {\r\n            if (!object.hasModernData()) {\r\n                attribute.echoError(\"Not a valid block.\");\r\n                return null;\r\n            }\r\n            SoundGroup group = object.getModernData().getSoundGroup();\r\n            MapTag result = new MapTag();\r\n            result.putObject(\"volume\", new ElementTag(group.getVolume()));\r\n            result.putObject(\"pitch\", new ElementTag(group.getPitch()));\r\n            result.putObject(\"break_sound\", new ElementTag(group.getBreakSound().name()));\r\n            result.putObject(\"step_sound\", new ElementTag(group.getStepSound().name()));\r\n            result.putObject(\"place_sound\", new ElementTag(group.getPlaceSound().name()));\r\n            result.putObject(\"hit_sound\", new ElementTag(group.getHitSound().name()));\r\n            result.putObject(\"fall_sound\", new ElementTag(group.getFallSound().name()));\r\n            return result;\r\n        });\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <MaterialTag.food_points>\r\n            // @returns ElementTag(Number)\r\n            // @description\r\n            // If the material is an item, returns the amount of hunger it restores when eaten.\r\n            // See <@link url https://minecraft.wiki/w/Food> for more information on food mechanics.\r\n            // -->\r\n            tagProcessor.registerTag(ElementTag.class, \"food_points\", (attribute, object) -> {\r\n                Material itemType = object.getMaterial();\r\n                return itemType.isEdible() ? new ElementTag(NMSHandler.itemHelper.getFoodPoints(itemType)) : null;\r\n            });\r\n        }\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <MaterialTag.is_enabled[<world>]>\r\n            // @returns ElementTag(Boolean)\r\n            // @description\r\n            // Returns whether the material is enabled in the specified world.\r\n            // If experimental features are disabled in the given world, and the MaterialTag is an item or block that is only enabled by experimental features, this will return false.\r\n            // -->\r\n            tagProcessor.registerTag(ElementTag.class, WorldTag.class, \"is_enabled\", (attribute, object, world) -> {\r\n                return new ElementTag(object.getMaterial().isEnabledByFeature(world.getWorld()));\r\n            });\r\n        }\r\n    }\r\n\r\n    public static ObjectTagProcessor<MaterialTag> tagProcessor = new ObjectTagProcessor<>();\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return tagProcessor.getObjectAttribute(this, attribute);\r\n    }\r\n\r\n    @Override\r\n    public void applyProperty(Mechanism mechanism) {\r\n        adjust(mechanism);\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name vanilla_tags\r\n        // @input ListTag\r\n        // @description\r\n        // Sets a material's vanilla tags.\r\n        // Any tag name will be accepted - meaning, any tag name input will be added to the material, regardless of whether it previously existed or not.\r\n        // This will work at any time, but applying your changes once during <@link event server prestart> is recommended, as usages require a server resources reload.\r\n        // @tags\r\n        // <MaterialTag.vanilla_tags>\r\n        // @example\r\n        // # Adds the guarded_by_piglins tag to Netherrack, without removing its other tags.\r\n        // - adjust <material[netherrack]> vanilla_tags:<material[netherrack].vanilla_tags.include[guarded_by_piglins]>\r\n        // @example\r\n        // # Removes the dead_bush_may_place_on tag from <[material]>, while keeping its other tags.\r\n        // - adjust <[material]> vanilla_tags:<[material].vanilla_tags.exclude[dead_bush_may_place_on]>\r\n        // @example\r\n        // # Removes all vanilla tags from <[material]>, leaving it with only the wither_summon_base_blocks tag.\r\n        // - adjust <[material]> vanilla_tags:wither_summon_base_blocks\r\n        // -->\r\n        if (!mechanism.isProperty && mechanism.matches(\"vanilla_tags\") && mechanism.requireObject(ListTag.class)) {\r\n            ListTag input = mechanism.valueAsType(ListTag.class);\r\n            Set<NamespacedKey> tags = new HashSet<>();\r\n            for (String tag : input) {\r\n                NamespacedKey tagKey = NamespacedKey.fromString(tag);\r\n                if (tagKey == null) {\r\n                    mechanism.echoError(\"Invalid tag name '\" + tag + \"' inputted.\");\r\n                    continue;\r\n                }\r\n                tags.add(tagKey);\r\n            }\r\n            PaperAPITools.instance.setMaterialTags(material, tags);\r\n        }\r\n\r\n        // TODO: 1.20.6: need an ItemTag variant providing the proper functionality, and then deprecate this\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name max_stack_size\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the maximum stack size for all items this material type.\r\n        // Note that altering this will probably require a script performing \"- inventory update\" in the event \"after player clicks in inventory:\" to maintain sync.\r\n        // The maximum the client will interact with is stacks of 64, however you can set the max up to 127 and the client will render it, but refuse to move stacks properly.\r\n        // @tags\r\n        // <MaterialTag.max_stack_size>\r\n        // -->\r\n        if (!mechanism.isProperty && mechanism.matches(\"max_stack_size\") && mechanism.requireInteger()) {\r\n            NMSHandler.itemHelper.setMaxStackSize(material, mechanism.getValue().asInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name block_resistance\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the explosion resistance for all blocks of this material type.\r\n        // @tags\r\n        // <MaterialTag.block_resistance>\r\n        // -->\r\n        if (!mechanism.isProperty && mechanism.matches(\"block_resistance\") && mechanism.requireFloat()) {\r\n            if (!NMSHandler.blockHelper.setBlockResistance(material, mechanism.getValue().asFloat())) {\r\n                Debug.echoError(\"Provided material does not have a placeable block.\");\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name block_strength\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the strength for all blocks of this material type.\r\n        // This does not work for specifically obsidian (as it is a hardcoded special case in the Minecraft internals).\r\n        // @tags\r\n        // <MaterialTag.block_strength>\r\n        // -->\r\n        if (!mechanism.isProperty && mechanism.matches(\"block_strength\") && mechanism.requireFloat()) {\r\n            if (!material.isBlock()) {\r\n                Debug.echoError(\"'block_strength' mechanism is only valid for block types.\");\r\n            }\r\n            NMSHandler.blockHelper.setBlockStrength(material, mechanism.getValue().asFloat());\r\n        }\r\n\r\n        tagProcessor.processMechanism(this, mechanism);\r\n    }\r\n\r\n    public static boolean advancedMatchesInternal(Material mat, String comparedto, boolean allowByMaterialName) {\r\n        if (comparedto == null || comparedto.isEmpty() || mat == null) {\r\n            return false;\r\n        }\r\n        String matcherLow = CoreUtilities.toLowerCase(comparedto);\r\n        if (matcherLow.equals(\"material\")) {\r\n            return true;\r\n        }\r\n        if (matcherLow.equals(\"block\")) {\r\n            return mat.isBlock();\r\n        }\r\n        if (matcherLow.equals(\"item\")) {\r\n            return mat.isItem();\r\n        }\r\n        if (matcherLow.contains(\":\")) {\r\n            if (matcherLow.startsWith(\"vanilla_tagged:\")) {\r\n                String tagCheck = comparedto.substring(\"vanilla_tagged:\".length());\r\n                HashSet<String> tags = VanillaTagHelper.tagsByMaterial.get(mat);\r\n                if (tags == null) {\r\n                    return false;\r\n                }\r\n                ScriptEvent.MatchHelper matcher = ScriptEvent.createMatcher(tagCheck);\r\n                for (String tag : tags) {\r\n                    if (matcher.doesMatch(tag)) {\r\n                        return true;\r\n                    }\r\n                }\r\n                return false;\r\n            }\r\n            else if (matcherLow.startsWith(\"material_flagged:\")) {\r\n                return ScriptEvent.coreFlaggedCheck(comparedto.substring(\"material_flagged:\".length()), new MaterialTag(mat).getFlagTracker());\r\n            }\r\n        }\r\n        if (allowByMaterialName) {\r\n            Material quickOf = Material.getMaterial(CoreUtilities.toUpperCase(comparedto));\r\n            if (quickOf != null) {\r\n                return quickOf == mat;\r\n            }\r\n            ScriptEvent.MatchHelper matcher = ScriptEvent.createMatcher(comparedto);\r\n            if (matcher.doesMatch(mat.name())) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean advancedMatches(String matcher, TagContext context) {\r\n        if (advancedMatchesInternal(getMaterial(), matcher, true)) {\r\n            return true;\r\n        }\r\n        if (matcher.contains(\"[\") && matcher.endsWith(\"]\")) {\r\n            PropertyMatchHelper<MaterialTag> helper = PropertyMatchHelper.getPropertyMatchHelper(MaterialTag.class, matcher, (actual, compare) -> {\r\n                return actual.getMaterial() == compare.getMaterial();\r\n            });\r\n            if (helper == null) {\r\n                return false;\r\n            }\r\n            return helper.doesMatch(this);\r\n        }\r\n        return false;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/NPCTag.java",
    "content": "package com.denizenscript.denizen.objects;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.npc.DenizenNPCHelper;\nimport com.denizenscript.denizen.npc.traits.*;\nimport com.denizenscript.denizen.scripts.commands.npc.EngageCommand;\nimport com.denizenscript.denizen.scripts.containers.core.AssignmentScriptContainer;\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptContainer;\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptHelper;\nimport com.denizenscript.denizen.scripts.triggers.AbstractTrigger;\nimport com.denizenscript.denizen.tags.core.NPCTagBase;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\nimport com.denizenscript.denizencore.flags.FlaggableObject;\nimport com.denizenscript.denizencore.objects.*;\nimport com.denizenscript.denizencore.objects.core.*;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.citizensnpcs.api.CitizensAPI;\nimport net.citizensnpcs.api.ai.Navigator;\nimport net.citizensnpcs.api.ai.TeleportStuckAction;\nimport net.citizensnpcs.api.event.DespawnReason;\nimport net.citizensnpcs.api.npc.NPC;\nimport net.citizensnpcs.api.npc.NPCRegistry;\nimport net.citizensnpcs.api.trait.Trait;\nimport net.citizensnpcs.api.trait.trait.Equipment;\nimport net.citizensnpcs.api.trait.trait.Owner;\nimport net.citizensnpcs.api.util.DataKey;\nimport net.citizensnpcs.api.util.MemoryDataKey;\nimport net.citizensnpcs.npc.ai.NPCHolder;\nimport net.citizensnpcs.npc.skin.SkinnableEntity;\nimport net.citizensnpcs.trait.*;\nimport net.citizensnpcs.trait.waypoint.*;\nimport net.citizensnpcs.util.Anchor;\nimport net.citizensnpcs.util.Pose;\nimport org.bukkit.*;\nimport org.bukkit.entity.*;\nimport org.bukkit.inventory.Inventory;\nimport org.bukkit.inventory.InventoryHolder;\n\nimport java.util.*;\n\npublic class NPCTag implements ObjectTag, Adjustable, InventoryHolder, EntityFormObject, FlaggableObject {\n\n    // <--[ObjectType]\n    // @name NPCTag\n    // @prefix n\n    // @base EntityTag\n    // @implements FlaggableObject\n    // @ExampleTagBase npc\n    // @ExampleValues <npc>\n    // @ExampleForReturns\n    // - kill %VALUE%\n    // @ExampleForReturns\n    // - heal %VALUE%\n    // @ExampleForReturns\n    // - walk %VALUE% <player.location>\n    // @format\n    // The identity format for NPCs is the NPC's id number.\n    // For example, 'n@5'.\n    // Or, an NPC's id number, followed by a comma, followed by a custom registry name.\n    // For example 'n@12,specialnpcs'\n    //\n    // @description\n    // An NPCTag represents an NPC configured through Citizens.\n    //\n    // This object type is flaggable.\n    // Flags on this object type will be stored in the Citizens saves.yml file, under the 'denizen_flags' trait.\n    //\n    // -->\n\n    public static NPCRegistry getRegistryByName(String name) {\n        NPCRegistry registry = CitizensAPI.getNamedNPCRegistry(name);\n        if (registry != null) {\n            return registry;\n        }\n        for (NPCRegistry possible : CitizensAPI.getNPCRegistries()) {\n            if (possible.getName().equals(name)) {\n                return possible;\n            }\n        }\n        return null;\n    }\n\n    public static NPCTag fromEntity(Entity entity) {\n        return new NPCTag(((NPCHolder) entity).getNPC());\n    }\n\n    @Fetchable(\"n\")\n    public static NPCTag valueOf(String string, TagContext context) {\n        if (string == null) {\n            return null;\n        }\n        if (string.startsWith(\"n@\")) {\n            string = string.substring(\"n@\".length());\n        }\n        NPCRegistry registry;\n        int commaIndex = string.indexOf(',');\n        String idText = string;\n        if (commaIndex == -1) {\n            registry = CitizensAPI.getNPCRegistry();\n        }\n        else {\n            registry = getRegistryByName(string.substring(commaIndex + 1));\n            if (registry == null) {\n                if (context == null || context.showErrors()) {\n                    Debug.echoError(\"Unknown NPC registry for '\" + string + \"'.\");\n                }\n                return null;\n            }\n            idText = string.substring(0, commaIndex);\n        }\n        if (ArgumentHelper.matchesInteger(idText)) {\n            int id = Integer.parseInt(idText);\n            NPC npc = registry.getById(id);\n            if (npc != null) {\n                return new NPCTag(npc);\n            }\n            else if (context == null || context.showErrors()) {\n                Debug.echoError(\"NPC '\" + id + \"' does not exist in \" + registry.getName() + \".\");\n            }\n        }\n        return null;\n    }\n\n    public static boolean matches(String string) {\n        if (CoreUtilities.toLowerCase(string).startsWith(\"n@\")) {\n            return true;\n        }\n        if (valueOf(string, CoreUtilities.noDebugContext) != null) {\n            return true;\n        }\n        return false;\n    }\n\n    public boolean isValid() {\n        return npc != null && npc.getOwningRegistry().getById(npc.getId()) != null;\n    }\n\n    @Override\n    public AbstractFlagTracker getFlagTracker() {\n        return npc.getOrAddTrait(DenizenFlagsTrait.class).fullFlagData;\n    }\n\n    public boolean hasFlag(String flag) {\n        DenizenFlagsTrait flagTrait = npc.getTraitNullable(DenizenFlagsTrait.class);\n        if (flagTrait == null) {\n            return false;\n        }\n        return flagTrait.fullFlagData.hasFlag(flag);\n    }\n\n    @Override\n    public void reapplyTracker(AbstractFlagTracker tracker) {\n        // Nothing to do.\n    }\n\n    public NPC npc;\n\n    public NPCTag(NPC citizensNPC) {\n        this.npc = citizensNPC;\n    }\n\n    public NPC getCitizen() {\n        return npc;\n    }\n\n    public Entity getEntity() {\n        try {\n            return getCitizen().getEntity();\n        }\n        catch (NullPointerException ex) {\n            Debug.echoError(\"Uh oh! Denizen has encountered a NPE while trying to fetch an NPC entity. \" +\n                    \"Has this NPC been removed?\");\n            if (CoreConfiguration.debugVerbose) {\n                Debug.echoError(ex);\n            }\n            return null;\n        }\n    }\n\n    public LivingEntity getLivingEntity() {\n        try {\n            if (getCitizen().getEntity() instanceof LivingEntity) {\n                return (LivingEntity) getCitizen().getEntity();\n            }\n            else {\n                Debug.log(\"Uh oh! Tried to get the living entity of a non-living NPC!\");\n                return null;\n            }\n        }\n        catch (NullPointerException ex) {\n            Debug.echoError(\"Uh oh! Denizen has encountered a NPE while trying to fetch an NPC livingEntity. \" +\n                    \"Has this NPC been removed?\");\n            if (CoreConfiguration.debugVerbose) {\n                Debug.echoError(ex);\n            }\n            return null;\n        }\n    }\n\n    @Override\n    public EntityTag getDenizenEntity() {\n        try {\n            return new EntityTag(getCitizen().getEntity());\n        }\n        catch (NullPointerException ex) {\n            Debug.echoError(\"Uh oh! Denizen has encountered a NPE while trying to fetch an NPC EntityTag. \" +\n                    \"Has this NPC been removed?\");\n            if (CoreConfiguration.debugVerbose) {\n                Debug.echoError(ex);\n            }\n            return null;\n        }\n    }\n\n    @Override\n    public Inventory getInventory() {\n        return DenizenNPCHelper.getInventory(getCitizen());\n    }\n\n    public InventoryTag getDenizenInventory() {\n        return new InventoryTag(getInventory(), this);\n    }\n\n    public EntityType getEntityType() {\n        return getCitizen().getEntity().getType();\n    }\n\n    public Navigator getNavigator() {\n        return getCitizen().getNavigator();\n    }\n\n    public int getId() {\n        return npc.getId();\n    }\n\n    public String getName() {\n        return getCitizen().getName();\n    }\n\n    public List<InteractScriptContainer> getInteractScripts() {\n        return InteractScriptHelper.getInteractScripts(this);\n    }\n\n    public List<InteractScriptContainer> getInteractScripts(PlayerTag player, Class<? extends AbstractTrigger> triggerType) {\n        return InteractScriptHelper.getInteractScripts(this, player, true, triggerType);\n    }\n\n    public List<InteractScriptContainer> getInteractScriptsQuietly(PlayerTag player, Class<? extends AbstractTrigger> triggerType) {\n        return InteractScriptHelper.getInteractScripts(this, player, false, triggerType);\n    }\n\n    public void destroy() {\n        getCitizen().destroy();\n    }\n\n    @Override\n    public LocationTag getLocation() {\n        if (isSpawned()) {\n            return new LocationTag(getEntity().getLocation());\n        }\n        else {\n            return new LocationTag(getCitizen().getStoredLocation());\n        }\n    }\n\n    public LocationTag getEyeLocation() {\n        if (isSpawned() && getCitizen().getEntity() instanceof LivingEntity) {\n            return new LocationTag(((LivingEntity) getCitizen().getEntity()).getEyeLocation());\n        }\n        else if (isSpawned()) {\n            return new LocationTag(getEntity().getLocation());\n        }\n        else {\n            return new LocationTag(getCitizen().getStoredLocation());\n        }\n    }\n\n    public World getWorld() {\n        if (isSpawned()) {\n            return getEntity().getWorld();\n        }\n        else {\n            return getCitizen().getStoredLocation().getWorld();\n        }\n    }\n\n    @Override\n    public String toString() {\n        return identify();\n    }\n\n    @Override\n    public Object getJavaObject() {\n        return getCitizen();\n    }\n\n    public boolean isSpawned() {\n        return npc.isSpawned();\n    }\n\n    public UUID getOwner() {\n        return getCitizen().getOrAddTrait(Owner.class).getOwnerId();\n    }\n\n    public Equipment getEquipmentTrait() {\n        return getCitizen().getOrAddTrait(Equipment.class);\n    }\n\n    public NicknameTrait getNicknameTrait() {\n        return getCitizen().getOrAddTrait(NicknameTrait.class);\n    }\n\n    public FishingTrait getFishingTrait() {\n        return getCitizen().getOrAddTrait(FishingTrait.class);\n    }\n\n    public net.citizensnpcs.api.trait.trait.Inventory getInventoryTrait() {\n        return getCitizen().getOrAddTrait(net.citizensnpcs.api.trait.trait.Inventory.class);\n    }\n\n    public PushableTrait getPushableTrait() {\n        return getCitizen().getOrAddTrait(PushableTrait.class);\n    }\n\n    public LookClose getLookCloseTrait() {\n        return getCitizen().getOrAddTrait(LookClose.class);\n    }\n\n    public TriggerTrait getTriggerTrait() {\n        return getCitizen().getOrAddTrait(TriggerTrait.class);\n    }\n\n    public ListTag action(String actionName, PlayerTag player, Map<String, ObjectTag> context) {\n        ListTag result = new ListTag();\n        if (getCitizen() != null) {\n            if (getCitizen().hasTrait(AssignmentTrait.class)) {\n                for (AssignmentScriptContainer container : getCitizen().getOrAddTrait(AssignmentTrait.class).containerCache) {\n                    if (container != null && container.shouldEnable()) {\n                        ListTag singleResult = Denizen.getInstance().npcHelper.getActionHandler().doAction(actionName, this, player, container, context);\n                        if (singleResult != null) {\n                            result.addAll(singleResult);\n                        }\n                    }\n                }\n            }\n        }\n        return result;\n    }\n\n    public ListTag action(String actionName, PlayerTag player) {\n        return action(actionName, player, null);\n    }\n\n    private String prefix = \"npc\";\n\n    @Override\n    public String getPrefix() {\n        return prefix;\n    }\n\n    @Override\n    public String debuggable() {\n        if (npc.getOwningRegistry() == CitizensAPI.getNPCRegistry()) {\n            return \"<LG>n@<Y>\" + npc.getId() + \"<GR> (\" + getName() + \"<GR>)\";\n        }\n        else {\n            return \"<LG>n@<Y>\" + npc.getId() + \"<LG>,\" + npc.getOwningRegistry().getName() + \"<GR> (\" + getName() + \"<GR>)\";\n        }\n    }\n\n    @Override\n    public boolean isUnique() {\n        return true;\n    }\n\n    @Override\n    public String identify() {\n        if (npc.getOwningRegistry() == CitizensAPI.getNPCRegistry()) {\n            return \"n@\" + npc.getId();\n        }\n        else {\n            return \"n@\" + npc.getId() + \",\" + npc.getOwningRegistry().getName();\n        }\n    }\n\n    @Override\n    public String identifySimple() {\n        return identify();\n    }\n\n    @Override\n    public NPCTag setPrefix(String prefix) {\n        this.prefix = prefix;\n        return this;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o == null) {\n            return false;\n        }\n        if (!(o instanceof NPCTag)) {\n            return false;\n        }\n        return getId() == ((NPCTag) o).getId();\n    }\n\n    @Override\n    public int hashCode() {\n        return getId();\n    }\n\n    @Override\n    public boolean isTruthy() {\n        return isSpawned();\n    }\n\n    public static void register() {\n\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\n\n        // Defined in EntityTag\n        tagProcessor.registerTag(ElementTag.class, \"is_npc\", (attribute, object) -> {\n            return new ElementTag(true);\n        });\n\n        // Defined in EntityTag\n        tagProcessor.registerTag(ObjectTag.class, \"location\", (attribute, object) -> {\n            if (attribute.startsWith(\"previous_location\", 2)) {\n                attribute.fulfill(1);\n                BukkitImplDeprecations.npcPreviousLocationTag.warn(attribute.context);\n                return NPCTagBase.previousLocations.get(object.getId());\n            }\n            if (object.isSpawned()) {\n                return new EntityTag(object).doLocationTag(attribute);\n            }\n            return object.getLocation();\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.previous_location>\n        // @returns LocationTag\n        // @description\n        // Returns the NPC's previous navigated location.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"previous_location\", (attribute, object) -> {\n            return NPCTagBase.previousLocations.get(object.getId());\n        });\n\n        // Defined in EntityTag\n        tagProcessor.registerTag(LocationTag.class, \"eye_location\", (attribute, object) -> {\n            return object.getEyeLocation();\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.has_nickname>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns true if the NPC has a nickname.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"has_nickname\", (attribute, object) -> {\n            NPC citizen = object.getCitizen();\n            return new ElementTag(citizen.hasTrait(NicknameTrait.class) && citizen.getOrAddTrait(NicknameTrait.class).hasNickname());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.is_sitting>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns true if the NPC is sitting. Relates to <@link command sit>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_sitting\", (attribute, object) -> {\n            NPC citizen = object.getCitizen();\n            return new ElementTag(citizen.hasTrait(SittingTrait.class) && citizen.getOrAddTrait(SittingTrait.class).isSitting());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.is_sleeping>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns true if the NPC is sleeping. Relates to <@link command sleep>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_sleeping\", (attribute, object) -> {\n            NPC citizen = object.getCitizen();\n            return new ElementTag(citizen.hasTrait(SleepingTrait.class) && citizen.getOrAddTrait(SleepingTrait.class).isSleeping());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.nickname>\n        // @returns ElementTag\n        // @description\n        // Returns the NPC's display name, as set by the Nickname trait (or the default NPC name).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"nickname\", (attribute, object) -> {\n            return new ElementTag(object.getCitizen().hasTrait(NicknameTrait.class) ? object.getCitizen().getOrAddTrait(NicknameTrait.class)\n                    .getNickname() : object.getName());\n        });\n\n        // Documented in EntityTag\n        tagProcessor.registerTag(ElementTag.class, \"name\", (attribute, object) -> {\n            if (attribute.startsWith(\"nickname\", 2)) {\n                BukkitImplDeprecations.npcNicknameTag.warn(attribute.context);\n                attribute.fulfill(1);\n                return new ElementTag(object.getCitizen().hasTrait(NicknameTrait.class) ? object.getCitizen().getOrAddTrait(NicknameTrait.class)\n                        .getNickname() : object.getName());\n            }\n            return new ElementTag(object.getName());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.traits>\n        // @returns ListTag\n        // @description\n        // Returns a list of all of the NPC's traits.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"traits\", (attribute, object) -> {\n            List<String> list = new ArrayList<>();\n            for (Trait trait : object.getCitizen().getTraits()) {\n                list.add(trait.getName());\n            }\n            return new ListTag(list);\n        }, \"list_traits\");\n\n        // <--[tag]\n        // @attribute <NPCTag.has_trait[<trait>]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC has a specified trait.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"has_trait\", (attribute, object) -> {\n            if (attribute.hasParam()) {\n                Class<? extends Trait> trait = CitizensAPI.getTraitFactory().getTraitClass(attribute.getParam());\n                if (trait != null) {\n                    return new ElementTag(object.getCitizen().hasTrait(trait));\n                }\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.pushable>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC is pushable.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"pushable\", (attribute, object) -> {\n            return new ElementTag(object.getPushableTrait().isPushable());\n        }, \"is_pushable\");\n\n        // <--[tag]\n        // @attribute <NPCTag.has_trigger[<trigger>]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC has a specified trigger.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"has_trigger\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            if (!object.getCitizen().hasTrait(TriggerTrait.class)) {\n                return new ElementTag(false);\n            }\n            TriggerTrait trait = object.getCitizen().getOrAddTrait(TriggerTrait.class);\n            return new ElementTag(trait.hasTrigger(attribute.getParam()));\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.has_anchors>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC has anchors assigned.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"has_anchors\", (attribute, object) -> {\n            return (new ElementTag(object.getCitizen().getOrAddTrait(Anchors.class).getAnchors().size() > 0));\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.list_anchors>\n        // @returns ListTag\n        // @description\n        // Returns a list of anchor names currently assigned to the NPC.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"list_anchors\", (attribute, object) -> {\n            ListTag list = new ListTag();\n            for (Anchor anchor : object.getCitizen().getOrAddTrait(Anchors.class).getAnchors()) {\n                list.add(anchor.getName());\n            }\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.anchor[<name>]>\n        // @returns LocationTag\n        // @description\n        // Returns the location associated with the specified anchor, or null if it doesn't exist.\n        // -->\n        tagProcessor.registerTag(ObjectTag.class, \"anchor\", (attribute, object) -> {\n            Anchors trait = object.getCitizen().getOrAddTrait(Anchors.class);\n            if (attribute.hasParam()) {\n                Anchor anchor = trait.getAnchor(attribute.getParam());\n                    if (anchor != null) {\n                        return new LocationTag(anchor.getLocation());\n                    }\n                    else {\n                        attribute.echoError(\"NPC Anchor '\" + attribute.getParam() + \"' is not defined.\");\n                        return null;\n                    }\n            }\n            else if (attribute.startsWith(\"list\", 2)) {\n                attribute.fulfill(1);\n                BukkitImplDeprecations.npcAnchorListTag.warn(attribute.context);\n                ListTag list = new ListTag();\n                for (Anchor anchor : trait.getAnchors()) {\n                    list.add(anchor.getName());\n                }\n                return list;\n            }\n            else {\n                attribute.echoError(\"npc.anchor[...] tag must have an input.\");\n            }\n            return null;\n        }, \"anchors\");\n\n        // <--[tag]\n        // @attribute <NPCTag.constant[<constant_name>]>\n        // @returns ElementTag\n        // @description\n        // Returns the specified constant from the NPC.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"constant\", (attribute, object) -> {\n            if (attribute.hasParam()) {\n                if (object.getCitizen().hasTrait(ConstantsTrait.class)\n                        && object.getCitizen().getOrAddTrait(ConstantsTrait.class).getConstant(attribute.getParam()) != null) {\n                    return new ElementTag(object.getCitizen().getOrAddTrait(ConstantsTrait.class)\n                            .getConstant(attribute.getParam()));\n                }\n                else {\n                    return null;\n                }\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.has_pose[<name>]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns true if the NPC has the specified pose, otherwise returns false.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"has_pose\", (attribute, object) -> {\n            if (attribute.hasParam()) {\n                return new ElementTag(object.getCitizen().getOrAddTrait(Poses.class).hasPose(attribute.getParam()));\n            }\n            else {\n                return null;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.pose[<name>]>\n        // @returns LocationTag\n        // @description\n        // Returns the pose as a LocationTag with x, y, and z set to 0, and the world set to the first\n        // possible available world Bukkit knows about.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"pose\", (attribute, object) -> {\n            if (attribute.hasParam()) {\n                Pose pose = object.getCitizen().getOrAddTrait(Poses.class).getPose(attribute.getParam());\n                return new LocationTag(org.bukkit.Bukkit.getWorlds().get(0), 0, 0, 0, pose.getYaw(), pose.getPitch());\n            }\n            else {\n                return null;\n            }\n        }, \"get_pose\");\n\n        // <--[tag]\n        // @attribute <NPCTag.name_hologram_npc>\n        // @returns NPCTag\n        // @description\n        // Returns the NPCTag of a hologram attached to this NPC as its nameplate (if any).\n        // Note that this can regenerate at any time.\n        // -->\n        tagProcessor.registerTag(ObjectTag.class, \"name_hologram_npc\", (attribute, object) -> {\n            if (!object.getCitizen().hasTrait(HologramTrait.class)) {\n                return null;\n            }\n            HologramTrait hologram = object.getCitizen().getTraitNullable(HologramTrait.class);\n            Entity entity = hologram.getNameEntity();\n            if (entity == null) {\n                return null;\n            }\n            return new EntityTag(entity).getDenizenObject();\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.hologram_npcs>\n        // @returns ListTag(NPCTag)\n        // @description\n        // Returns the list of hologram NPCs attached to an NPC (if any).\n        // Note that these can regenerate at any time.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"hologram_npcs\", (attribute, object) -> {\n            if (!object.getCitizen().hasTrait(HologramTrait.class)) {\n                return null;\n            }\n            HologramTrait hologram = object.getCitizen().getTraitNullable(HologramTrait.class);\n            Collection<Entity> stands = hologram.getHologramEntities();\n            if (stands == null || stands.isEmpty()) {\n                return null;\n            }\n            ListTag output = new ListTag();\n            for (Entity stand : stands) {\n                output.addObject(new EntityTag(stand).getDenizenObject());\n            }\n            return output;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.hologram_lines>\n        // @returns ListTag\n        // @mechanism NPCTag.hologram_lines\n        // @description\n        // Returns the list of hologram lines attached to an NPC.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"hologram_lines\", (attribute, object) -> {\n            if (!object.getCitizen().hasTrait(HologramTrait.class)) {\n                return null;\n            }\n            HologramTrait hologram = object.getCitizen().getTraitNullable(HologramTrait.class);\n            return new ListTag(hologram.getLines());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.hologram_direction>\n        // @returns ElementTag\n        // @mechanism NPCTag.hologram_direction\n        // @deprecated This was removed from Citizens.\n        // @description\n        // Returns the direction of an NPC's hologram as \"BOTTOM_UP\" or \"TOP_DOWN\".\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"hologram_direction\", (attribute, object) -> {\n            BukkitImplDeprecations.npcHologramDirection.warn(attribute.context);\n            if (!object.getCitizen().hasTrait(HologramTrait.class)) {\n                return null;\n            }\n            //HologramTrait hologram = object.getCitizen().getTraitNullable(HologramTrait.class);\n            //return new ElementTag(hologram.getDirection());\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.hologram_line_height>\n        // @returns ElementTag(Decimal)\n        // @mechanism NPCTag.hologram_line_height\n        // @description\n        // Returns the line height for an NPC's hologram. Can be -1, indicating a default value should be used.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"hologram_line_height\", (attribute, object) -> {\n            if (!object.getCitizen().hasTrait(HologramTrait.class)) {\n                return null;\n            }\n            HologramTrait hologram = object.getCitizen().getTraitNullable(HologramTrait.class);\n            return new ElementTag(hologram.getLineHeight());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.is_sneaking>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC is currently sneaking. Only works for player-type NPCs.\n        // -->\n        if (!Denizen.supportsPaper || NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) {\n            tagProcessor.registerTag(ElementTag.class, \"is_sneaking\", (attribute, object) -> {\n                if (!object.isSpawned() && object.getEntity() instanceof Player) {\n                    return null;\n                }\n                return new ElementTag(((Player) object.getEntity()).isSneaking());\n            });\n        }\n\n        // <--[tag]\n        // @attribute <NPCTag.engaged[(<player>)]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC is currently engaged.\n        // See <@link command engage>\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"engaged\", (attribute, object) -> {\n            return new ElementTag(EngageCommand.getEngaged(object.getCitizen(), attribute.hasParam() ? attribute.paramAsType(PlayerTag.class) : null));\n        }, \"is_engaged\");\n\n        // <--[tag]\n        // @attribute <NPCTag.invulnerable>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC is currently invulnerable.\n        // See <@link command vulnerable>\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"invulnerable\", (attribute, object) -> {\n            return new ElementTag(object.getCitizen().data().get(NPC.Metadata.DEFAULT_PROTECTED, true));\n        }, \"vulnerable\");\n\n        // <--[tag]\n        // @attribute <NPCTag.id>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the NPC's ID number.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"id\", (attribute, object) -> {\n            return new ElementTag(object.getId());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.owner>\n        // @returns PlayerTag\n        // @mechanism NPCTag.owner\n        // @description\n        // Returns the owner of the NPC as a PlayerTag, if any.\n        // -->\n        tagProcessor.registerTag(ObjectTag.class, \"owner\", (attribute, object) -> {\n            UUID owner = object.getOwner();\n            if (owner == null) {\n                return null;\n            }\n            OfflinePlayer player = Bukkit.getOfflinePlayer(owner);\n            if (player.isOnline() || player.hasPlayedBefore()) {\n                return new PlayerTag(player);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.has_skin>\n        // @returns ElementTag(Boolean)\n        // @mechanism NPCTag.skin\n        // @description\n        // Returns whether the NPC has a custom skin.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"has_skin\", (attribute, object) -> {\n            return new ElementTag(object.getCitizen().hasTrait(SkinTrait.class) && object.getCitizen().getOrAddTrait(SkinTrait.class).getSkinName() != null);\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.skin_blob>\n        // @returns ElementTag\n        // @mechanism NPCTag.skin_blob\n        // @description\n        // Returns the NPC's custom skin blob, if any.\n        // In the format: \"texture;signature\" (two values separated by a semicolon).\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"skin_blob\", (attribute, object) -> {\n            if (object.getCitizen().hasTrait(SkinTrait.class)) {\n                SkinTrait skin = object.getCitizen().getOrAddTrait(SkinTrait.class);\n                String tex = skin.getTexture();\n                String sign = \"\";\n                if (skin.getSignature() != null) {\n                    sign = \";\" + skin.getSignature();\n                }\n                return new ElementTag(tex + sign);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.skull_skin>\n        // @returns ElementTag\n        // @description\n        // Returns the NPC's current skin blob, formatted for input to a Player Skull item.\n        // In the format: \"UUID|Texture\" (two values separated by pipes).\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"skull_skin\", (attribute, object) -> {\n            if (!object.getCitizen().hasTrait(SkinTrait.class)) {\n                return null;\n            }\n            SkinTrait skin = object.getCitizen().getOrAddTrait(SkinTrait.class);\n            return new ElementTag(skin.getSkinName() + \"|\" + skin.getTexture());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.skin>\n        // @returns ElementTag\n        // @mechanism NPCTag.skin\n        // @description\n        // Returns the NPC's custom skin, if any.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"skin\", (attribute, object) -> {\n            if (object.getCitizen().hasTrait(SkinTrait.class)) {\n                return new ElementTag(object.getCitizen().getOrAddTrait(SkinTrait.class).getSkinName());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.auto_update_skin>\n        // @returns ElementTag(Boolean)\n        // @mechanism NPCTag.auto_update_skin\n        // @description\n        // Returns whether the NPC is set to automatically update skins from name.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"auto_update_skin\", (attribute, object) -> {\n            if (object.getCitizen().hasTrait(SkinTrait.class)) {\n                return new ElementTag(object.getCitizen().getOrAddTrait(SkinTrait.class).shouldUpdateSkins());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.inventory>\n        // @returns InventoryTag\n        // @description\n        // Returns the InventoryTag of the NPC.\n        // -->\n        tagProcessor.registerTag(InventoryTag.class, \"inventory\", (attribute, object) -> {\n            return object.getDenizenInventory();\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.is_spawned>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC is spawned.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_spawned\", (attribute, object) -> {\n            return new ElementTag(object.isSpawned());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.is_protected>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC is protected.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_protected\", (attribute, object) -> {\n            return new ElementTag(object.getCitizen().isProtected());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.lookclose>\n        // @returns ElementTag(Boolean)\n        // @mechanism NPCTag.lookclose\n        // @description\n        // Returns whether the NPC has lookclose enabled.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"lookclose\", (attribute, object) -> {\n            NPC citizen = object.getCitizen();\n            if (citizen.hasTrait(LookClose.class)) {\n                // There is no method to check if the NPC has LookClose enabled...\n                // LookClose.toString() returns \"LookClose{\" + enabled + \"}\"\n                String lookclose = citizen.getOrAddTrait(LookClose.class).toString();\n                lookclose = lookclose.substring(10, lookclose.length() - 1);\n                return new ElementTag(Boolean.parseBoolean(lookclose));\n            }\n            return new ElementTag(false);\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.controllable>\n        // @returns ElementTag(Boolean)\n        // @mechanism NPCTag.controllable\n        // @description\n        // Returns whether the NPC has controllable enabled.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"controllable\", (attribute, object) -> {\n            if (object.getCitizen().hasTrait(Controllable.class)) {\n                return new ElementTag(object.getCitizen().getOrAddTrait(Controllable.class).isEnabled());\n            }\n            return new ElementTag(false);\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.targetable>\n        // @returns ElementTag(Boolean)\n        // @mechanism NPCTag.targetable\n        // @description\n        // Returns whether the NPC is targetable.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"targetable\", (attribute, object) -> {\n            TargetableTrait trait = object.getCitizen().getTraitNullable(TargetableTrait.class);\n            if (trait != null) {\n                return new ElementTag(trait.isTargetable());\n            }\n            return new ElementTag(object.getCitizen().data().get(NPC.Metadata.DEFAULT_PROTECTED, true));\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.teleport_on_stuck>\n        // @returns ElementTag(Boolean)\n        // @mechanism NPCTag.teleport_on_stuck\n        // @description\n        // Returns whether the NPC teleports when it is stuck.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"teleport_on_stuck\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getDefaultParameters().stuckAction() == TeleportStuckAction.INSTANCE);\n        });\n\n        tagProcessor.registerTag(ElementTag.class, \"has_script\", (attribute, object) -> {\n            BukkitImplDeprecations.hasScriptTags.warn(attribute.context);\n            NPC citizen = object.getCitizen();\n            return new ElementTag(citizen.hasTrait(AssignmentTrait.class));\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.script>\n        // @returns ScriptTag\n        // @deprecated Use 'NPCTag.scripts' (plural) instead.\n        // @description\n        // Deprecated variant of <@link tag NPCTag.scripts>.\n        // -->\n        tagProcessor.registerTag(ScriptTag.class, \"script\", (attribute, object) -> {\n            BukkitImplDeprecations.npcScriptSingle.warn(attribute.context);\n            NPC citizen = object.getCitizen();\n            if (!citizen.hasTrait(AssignmentTrait.class)) {\n                return null;\n            }\n            else {\n                for (AssignmentScriptContainer container : citizen.getOrAddTrait(AssignmentTrait.class).containerCache) {\n                    if (container != null) {\n                        return new ScriptTag(container);\n                    }\n                }\n                return null;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.scripts>\n        // @returns ListTag(ScriptTag)\n        // @description\n        // Returns a list of all assignment scripts on the NPC. Returns null if none.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"scripts\", (attribute, object) -> {\n            NPC citizen = object.getCitizen();\n            if (!citizen.hasTrait(AssignmentTrait.class)) {\n                return null;\n            }\n            else {\n                ListTag result = new ListTag();\n                for (AssignmentScriptContainer container : citizen.getOrAddTrait(AssignmentTrait.class).containerCache) {\n                    if (container != null) {\n                       result.addObject(new ScriptTag(container));\n                    }\n                }\n                return result;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.distance_margin>\n        // @returns ElementTag(Decimal)\n        // @mechanism NPCTag.distance_margin\n        // @description\n        // Returns the NPC's current pathfinding distance margin. That is, how close it needs to get to its destination (in block-lengths).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"distance_margin\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getDefaultParameters().distanceMargin());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.path_distance_margin>\n        // @returns ElementTag(Decimal)\n        // @mechanism NPCTag.path_distance_margin\n        // @description\n        // Returns the NPC's current pathfinding distance margin. That is, how close it needs to get to individual points along its path.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"path_distance_margin\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getDefaultParameters().pathDistanceMargin());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.use_new_finder>\n        // @returns ElementTag(Boolean)\n        // @mechanism NPCTag.use_new_finder\n        // @description\n        // If output is 'true', the NPC uses the 'new' Citizens A-Star pathfinder.\n        // if 'false', the NPC uses the 'old' minecraft vanilla mob pathfinder.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"use_new_finder\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getDefaultParameters().useNewPathfinder());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.is_navigating>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC is currently navigating.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_navigating\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().isNavigating());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.speed>\n        // @returns ElementTag(Decimal)\n        // @mechanism NPCTag.speed\n        // @description\n        // Returns the current speed of the NPC.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"speed\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getLocalParameters().speed());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.range>\n        // @returns ElementTag(Decimal)\n        // @mechanism NPCTag.range\n        // @description\n        // Returns the NPC's current maximum pathfinding range.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"range\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getLocalParameters().range());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.attack_range>\n        // @returns ElementTag(Decimal)\n        // @mechanism NPCTag.attack_range\n        // @description\n        // Returns the NPC's current navigator attack range limit.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"attack_range\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getLocalParameters().attackRange());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.attack_strategy>\n        // @returns ElementTag\n        // @description\n        // Returns the NPC's current navigator attack strategy.\n        // Not related to Sentinel combat.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"attack_strategy\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getLocalParameters().attackStrategy().toString());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.speed_modifier>\n        // @returns ElementTag(Decimal)\n        // @description\n        // Returns the NPC's current movement speed modifier (a multiplier applied over their base speed).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"speed_modifier\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getLocalParameters().speedModifier());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.base_speed>\n        // @returns ElementTag(Decimal)\n        // @description\n        // Returns the NPC's base navigation speed.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"base_speed\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getLocalParameters().baseSpeed());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.avoid_water>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC will avoid water.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"avoid_water\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getLocalParameters().avoidWater());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.target_location>\n        // @returns LocationTag\n        // @description\n        // Returns the location the NPC is currently navigating towards (if any).\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"target_location\", (attribute, object) -> {\n            if (object.getNavigator().getTargetAsLocation() == null) {\n                return null;\n            }\n            return new LocationTag(object.getNavigator().getTargetAsLocation());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.navigator_look_at>\n        // @returns LocationTag\n        // @mechanism NPCTag.navigator_look_at\n        // @description\n        // Returns the location the NPC will currently look at while moving, if any.\n        // -->\n        tagProcessor.registerTag(LocationTag.class, \"navigator_look_at\", (attribute, object) -> {\n            if (object.getNavigator().getLocalParameters().lookAtFunction() == null) {\n                return null;\n            }\n            Location res = object.getNavigator().getLocalParameters().lookAtFunction().apply(object.getNavigator());\n            if (res == null) {\n                return null;\n            }\n            return new LocationTag(res);\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.is_fighting>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the NPC is currently targeting an entity for the Citizens internal punching pathfinder.\n        // Not compatible with Sentinel.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"is_fighting\", (attribute, object) -> {\n            return new ElementTag(object.getNavigator().getEntityTarget() != null && object.getNavigator().getEntityTarget().isAggressive());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.target_type>\n        // @returns ElementTag\n        // @description\n        // Returns the entity type of the NPC's current navigation target (if any).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"target_type\", (attribute, object) -> {\n            if (object.getNavigator().getTargetType() == null) {\n                return null;\n            }\n            return new ElementTag(object.getNavigator().getTargetType().toString());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.target_entity>\n        // @returns EntityTag\n        // @description\n        // Returns the entity being targeted by the NPC's current navigation (if any).\n        // -->\n        tagProcessor.registerTag(EntityFormObject.class, \"target_entity\", (attribute, object) -> {\n            if (object.getNavigator().getEntityTarget() == null || object.getNavigator().getEntityTarget().getTarget() == null) {\n                return null;\n            }\n            return new EntityTag(object.getNavigator().getEntityTarget().getTarget()).getDenizenObject();\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.registry_name>\n        // @returns ElementTag\n        // @description\n        // Returns the name of the registry this NPC came from.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"registry_name\", (attribute, object) -> {\n            return new ElementTag(object.getCitizen().getOwningRegistry().getName());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.citizens_data[<key>]>\n        // @returns ElementTag\n        // @description\n        // Returns the value of a Citizens NPC metadata key.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"citizens_data\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                return null;\n            }\n            Object val = object.getCitizen().data().get(attribute.getParam());\n            if (val == null) {\n                return null;\n            }\n            return new ElementTag(val.toString());\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.citizens_data_keys>\n        // @returns ListTag\n        // @description\n        // Returns a list of Citizens NPC metadata keys.\n        // -->\n        tagProcessor.registerTag(ListTag.class, \"citizens_data_keys\", (attribute, object) -> {\n            DataKey holder = new MemoryDataKey();\n            object.getCitizen().data().saveTo(holder);\n            ListTag result = new ListTag();\n            for (DataKey key : holder.getSubKeys()) {\n                result.addObject(new ElementTag(key.name(), true));\n            }\n            return result;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.waypoint_provider>\n        // @returns ElementTag\n        // @mechanism NPCTag.waypoint_provider\n        // @description\n        // Returns the NPC's current Waypoint Provider type (default types: linear, wander, guided).\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"waypoint_provider\", (attribute, object) -> {\n            Waypoints wp = object.getCitizen().getTraitNullable(Waypoints.class);\n            if (wp != null) {\n                return new ElementTag(wp.getCurrentProviderName(), true);\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.wander_delay>\n        // @returns DurationTag\n        // @mechanism NPCTag.wander_delay\n        // @description\n        // Returns the delay for the NPC's wander Waypoint Provider, if that provider is in use.\n        // -->\n        tagProcessor.registerTag(DurationTag.class, \"wander_delay\", (attribute, object) -> {\n            Waypoints wp = object.getCitizen().getTraitNullable(Waypoints.class);\n            if (wp != null && wp.getCurrentProvider() instanceof WanderWaypointProvider wanderWaypointProvider) {\n                return new DurationTag((long) wanderWaypointProvider.getDelay());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.wander_xrange>\n        // @returns ElementTag(Number)\n        // @mechanism NPCTag.wander_xrange\n        // @description\n        // Returns the xrange for the NPC's wander Waypoint Provider, if that provider is in use.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"wander_xrange\", (attribute, object) -> {\n            Waypoints wp = object.getCitizen().getTraitNullable(Waypoints.class);\n            if (wp != null && wp.getCurrentProvider() instanceof WanderWaypointProvider wanderWaypointProvider) {\n                return new ElementTag(wanderWaypointProvider.getXRange());\n            }\n            return null;\n        });\n\n        // <--[tag]\n        // @attribute <NPCTag.wander_yrange>\n        // @returns ElementTag(Number)\n        // @mechanism NPCTag.wander_yrange\n        // @description\n        // Returns the yrange for the NPC's wander Waypoint Provider, if that provider is in use.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"wander_yrange\", (attribute, object) -> {\n            Waypoints wp = object.getCitizen().getTraitNullable(Waypoints.class);\n            if (wp != null && wp.getCurrentProvider() instanceof WanderWaypointProvider wanderWaypointProvider) {\n                return new ElementTag(wanderWaypointProvider.getYRange());\n            }\n            return null;\n        });\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name hologram_lines\n        // @input ListTag\n        // @description\n        // Sets the NPC's hologram lines.\n        // Each item in the list can be either:\n        // - An ElementTag for a permanent hologram line\n        // - A map with \"text\" (ElementTag) and \"duration\" (DurationTag) keys for a temporary hologram line that will disappear after the specified duration\n        // @tags\n        // <NPCTag.hologram_lines>\n        // -->\n        tagProcessor.registerMechanism(\"hologram_lines\", false, ObjectTag.class, (object, mechanism, input) -> {\n            HologramTrait hologram = object.getCitizen().getOrAddTrait(HologramTrait.class);\n            hologram.clear();\n            for (ObjectTag value : CoreUtilities.objectToList(input, mechanism.context)) {\n                if (!value.canBeType(MapTag.class)) {\n                    hologram.addLine(value.toString());\n                    continue;\n                }\n                MapTag map = value.asType(MapTag.class, mechanism.context);\n                ElementTag text = map.getElement(\"text\");\n                DurationTag duration = map.getObjectAs(\"duration\", DurationTag.class, mechanism.context);\n                if (text == null || duration == null) {\n                    mechanism.echoError(\"Invalid temporary hologram line map '\" + map + \"': 'text' and/or 'duration' keys missing or have invalid values.\");\n                    continue;\n                }\n                hologram.addTemporaryLine(text.asString(), duration.getTicksAsInt());\n            }\n        });\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name waypoint_provider\n        // @input ElementTag\n        // @description\n        // Sets the NPC's waypoint provider (default options: linear, wander, guided).\n        // @tags\n        // <NPCTag.waypoint_provider>\n        // -->\n        tagProcessor.registerMechanism(\"waypoint_provider\", false, ElementTag.class, (object, mechanism, input) -> {\n            Waypoints wp = object.getCitizen().getOrAddTrait(Waypoints.class);\n            wp.setWaypointProvider(input.asString());\n        });\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name wander_delay\n        // @input DurationTag\n        // @description\n        // Sets the delay for an NPC's wander Waypoint Provider.\n        // <@link mechanism NPCTag.waypoint_provider> must be set to 'wander' before setting wander_delay.\n        // Set to 0 to disable.\n        // @tags\n        // <NPCTag.wander_delay>\n        // -->\n        tagProcessor.registerMechanism(\"wander_delay\", false, DurationTag.class, (object, mechanism, input) -> {\n            Waypoints wp = object.getCitizen().getOrAddTrait(Waypoints.class);\n            if (wp.getCurrentProvider() instanceof WanderWaypointProvider wanderWaypointProvider) {\n                wanderWaypointProvider.setDelay(input.getTicksAsInt());\n            }\n            else {\n                mechanism.echoError(\"Must set waypoint_provider to 'wander' before setting wander_delay!\");\n            }\n        });\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name wander_xrange\n        // @input ElementTag(Number)\n        // @description\n        // Sets the xrange for an NPC's wander path.\n        // <@link mechanism NPCTag.waypoint_provider> must be set to 'wander' before setting wander_xrange.\n        // @tags\n        // <NPCTag.wander_xrange>\n        // -->\n        tagProcessor.registerMechanism(\"wander_xrange\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (!mechanism.requireInteger()) {\n                return;\n            }\n            Waypoints wp = object.getCitizen().getOrAddTrait(Waypoints.class);\n            if (wp.getCurrentProvider() instanceof WanderWaypointProvider wanderWaypointProvider) {\n                wanderWaypointProvider.setXYRange(input.asInt(), wanderWaypointProvider.getYRange());\n            }\n            else {\n                mechanism.echoError(\"Must set waypoint_provider to 'wander' before setting wander_xrange!\");\n            }\n        });\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name wander_yrange\n        // @input ElementTag(Number)\n        // @description\n        // Sets the yrange for an NPC's wander path.\n        // <@link mechanism NPCTag.waypoint_provider> must be set to 'wander' before setting wander_yrange.\n        // @tags\n        // <NPCTag.wander_yrange>\n        // -->\n        tagProcessor.registerMechanism(\"wander_yrange\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (!mechanism.requireInteger()) {\n                return;\n            }\n            Waypoints wp = object.getCitizen().getOrAddTrait(Waypoints.class);\n            if (wp.getCurrentProvider() instanceof WanderWaypointProvider wanderWaypointProvider) {\n                wanderWaypointProvider.setXYRange(wanderWaypointProvider.getXRange(), input.asInt());\n            }\n            else {\n                mechanism.echoError(\"Must set waypoint_provider to 'wander' before setting wander_yrange!\");\n            }\n        });\n    }\n\n    public static ObjectTagProcessor<NPCTag> tagProcessor = new ObjectTagProcessor<>();\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n        return tagProcessor.getObjectAttribute(this, attribute);\n    }\n\n    @Override\n    public ObjectTag getNextObjectTypeDown() {\n        if (getEntity() != null) {\n            return new EntityTag(this);\n        }\n        return new ElementTag(identify());\n    }\n\n    public void applyProperty(Mechanism mechanism) {\n        mechanism.echoError(\"Cannot apply properties to an NPC!\");\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // TODO: For all the mechanism tags, add the @Mechanism link!\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name set_assignment\n        // @input ScriptTag\n        // @description\n        // Sets the NPC's assignment script. Equivalent to 'clear_assignments' + 'add_assignment'.\n        // @tags\n        // <NPCTag.script>\n        // -->\n        if (mechanism.matches(\"set_assignment\") && mechanism.requireObject(ScriptTag.class)) {\n            AssignmentTrait trait = getCitizen().getOrAddTrait(AssignmentTrait.class);\n            trait.clearAssignments(null);\n            trait.addAssignmentScript((AssignmentScriptContainer) mechanism.valueAsType(ScriptTag.class).getContainer(), null);\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name add_assignment\n        // @input ScriptTag\n        // @description\n        // Adds an assignment script to the NPC.\n        // @tags\n        // <NPCTag.script>\n        // -->\n        if (mechanism.matches(\"add_assignment\") && mechanism.requireObject(ScriptTag.class)) {\n            getCitizen().getOrAddTrait(AssignmentTrait.class).addAssignmentScript((AssignmentScriptContainer) mechanism.valueAsType(ScriptTag.class).getContainer(), null);\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name remove_assignment\n        // @input ScriptTag\n        // @description\n        // Removes an assignment script from the NPC.\n        // @tags\n        // <NPCTag.script>\n        // -->\n        if (mechanism.matches(\"remove_assignment\")) {\n            if (npc.hasTrait(AssignmentTrait.class)) {\n                if (mechanism.hasValue()) {\n                    AssignmentTrait trait = getCitizen().getOrAddTrait(AssignmentTrait.class);\n                    trait.removeAssignmentScript(mechanism.getValue().asString(), null);\n                    trait.checkAutoRemove();\n                }\n                else {\n                    BukkitImplDeprecations.assignmentRemove.warn(mechanism.context);\n                    getCitizen().getOrAddTrait(AssignmentTrait.class).clearAssignments(null);\n                    npc.removeTrait(AssignmentTrait.class);\n                }\n            }\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name clear_assignments\n        // @input None\n        // @description\n        // Removes all the NPC's assignment scripts.\n        // @tags\n        // <NPCTag.script>\n        // -->\n        if (mechanism.matches(\"clear_assignments\")) {\n            if (npc.hasTrait(AssignmentTrait.class)) {\n                getCitizen().getOrAddTrait(AssignmentTrait.class).clearAssignments(null);\n                npc.removeTrait(AssignmentTrait.class);\n            }\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name hologram_direction\n        // @input ElementTag\n        // @deprecated This was removed from Citizens.\n        // @description\n        // Sets the NPC's hologram direction, as either BOTTOM_UP or TOP_DOWN.\n        // @tags\n        // <NPCTag.hologram_direction>\n        // -->\n        if (mechanism.matches(\"hologram_direction\")) { //  && mechanism.requireEnum(HologramTrait.HologramDirection.class)\n            BukkitImplDeprecations.npcHologramDirection.warn(mechanism.context);\n            //HologramTrait hologram = getCitizen().getOrAddTrait(HologramTrait.class);\n            //hologram.setDirection(HologramTrait.HologramDirection.valueOf(mechanism.getValue().asString().toUpperCase()));\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name hologram_line_height\n        // @input ElementTag(Decimal)\n        // @description\n        // Sets the NPC's hologram line height. Can be -1 to indicate a default value.\n        // @tags\n        // <NPCTag.hologram_line_height>\n        // -->\n        if (mechanism.matches(\"hologram_line_height\") && mechanism.requireDouble()) {\n            HologramTrait hologram = getCitizen().getOrAddTrait(HologramTrait.class);\n            hologram.setLineHeight(mechanism.getValue().asDouble());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name set_nickname\n        // @input ElementTag\n        // @description\n        // Sets the NPC's nickname.\n        // @tags\n        // <NPCTag.nickname>\n        // -->\n        if (mechanism.matches(\"set_nickname\")) {\n            getNicknameTrait().setNickname(mechanism.getValue().asString());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name remove_nickname\n        // @input None\n        // @description\n        // Removes the NPC's nickname.\n        // @tags\n        // <NPCTag.has_nickname>\n        // -->\n        if (mechanism.matches(\"remove_nickname\")) {\n            getNicknameTrait().removeNickname();\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name set_entity_type\n        // @input EntityTag\n        // @description\n        // Sets the NPC's entity type.\n        // @tags\n        // <NPCTag.entity_type>\n        // -->\n        if (mechanism.matches(\"set_entity_type\") && mechanism.requireObject(EntityTag.class)) {\n            getCitizen().setBukkitEntityType(mechanism.valueAsType(EntityTag.class).getBukkitEntityType());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name name\n        // @input ElementTag\n        // @description\n        // Sets the name of the NPC.\n        // @tags\n        // <NPCTag.name>\n        // -->\n        if (mechanism.matches(\"name\") || mechanism.matches(\"set_name\")) {\n            getCitizen().setName(mechanism.getValue().asString().length() > 256 ? mechanism.getValue().asString().substring(0, 256) : mechanism.getValue().asString());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name owner\n        // @input PlayerTag\n        // @description\n        // Sets the owner of the NPC.\n        // @tags\n        // <NPCTag.owner>\n        // -->\n        if (mechanism.matches(\"owner\")) {\n            if (PlayerTag.matches(mechanism.getValue().asString())) {\n                getCitizen().getOrAddTrait(Owner.class).setOwner(mechanism.valueAsType(PlayerTag.class).getPlayerEntity());\n            }\n            else {\n                getCitizen().getOrAddTrait(Owner.class).setOwner(mechanism.getValue().asString());\n            }\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name skin_blob\n        // @input ElementTag\n        // @description\n        // Sets the skin blob of an NPC, in the form of \"texture;signature;name\".\n        // Call with no value to clear the custom skin value.\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\n        // @tags\n        // <NPCTag.skin>\n        // -->\n        if (mechanism.matches(\"skin_blob\")) {\n            if (!mechanism.hasValue()) {\n                if (getCitizen().hasTrait(SkinTrait.class)) {\n                    getCitizen().getOrAddTrait(SkinTrait.class).clearTexture();\n                    if (getCitizen().isSpawned()) {\n                        getCitizen().despawn(DespawnReason.PENDING_RESPAWN);\n                        getCitizen().spawn(getCitizen().getStoredLocation());\n                    }\n                }\n            }\n            else {\n                SkinTrait skinTrait = getCitizen().getOrAddTrait(SkinTrait.class);\n                String[] dat = mechanism.getValue().asString().split(\";\");\n                if (dat.length < 2) {\n                    Debug.echoError(\"Invalid skin_blob input. Must specify texture;signature;name in full.\");\n                    return;\n                }\n                skinTrait.setSkinPersistent(dat.length > 2 ? dat[2] : UUID.randomUUID().toString(), dat[1], dat[0]);\n                if (getCitizen().isSpawned() && getCitizen().getEntity() instanceof SkinnableEntity) {\n                    ((SkinnableEntity) getCitizen().getEntity()).getSkinTracker().notifySkinChange(true);\n                }\n            }\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name skin\n        // @input ElementTag\n        // @description\n        // Sets the skin of an NPC by name.\n        // Call with no value to clear the custom skin value.\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\n        // @tags\n        // <NPCTag.skin>\n        // -->\n        if (mechanism.matches(\"skin\")) {\n            if (!mechanism.hasValue()) {\n                if (getCitizen().hasTrait(SkinTrait.class)) {\n                    getCitizen().getOrAddTrait(SkinTrait.class).clearTexture();\n                }\n            }\n            else {\n                SkinTrait skinTrait = getCitizen().getOrAddTrait(SkinTrait.class);\n                skinTrait.setSkinName(mechanism.getValue().asString());\n            }\n            if (getCitizen().isSpawned()) {\n                getCitizen().despawn(DespawnReason.PENDING_RESPAWN);\n                getCitizen().spawn(getCitizen().getStoredLocation());\n            }\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name auto_update_skin\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether the NPC will automatically update its skin based on the skin name used.\n        // If true, the NPC's skin will change when the relevant account owner changes their skin.\n        // @tags\n        // <NPCTag.auto_update_skin>\n        // -->\n        if (mechanism.matches(\"auto_update_skin\") && mechanism.requireBoolean()) {\n            getCitizen().getOrAddTrait(SkinTrait.class).setShouldUpdateSkins(mechanism.getValue().asBoolean());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name item_type\n        // @input ItemTag\n        // @description\n        // Sets the item type of the item.\n        // -->\n        if (mechanism.matches(\"item_type\") && mechanism.requireObject(ItemTag.class)) {\n            ItemTag item = mechanism.valueAsType(ItemTag.class);\n            Material mat = item.getMaterial().getMaterial();\n            Entity npcEntity = getEntity();\n            if (npcEntity instanceof Item droppedItem) {\n                droppedItem.getItemStack().setType(mat);\n            }\n            else if (npcEntity instanceof ItemFrame itemFrame) {\n                itemFrame.getItem().setType(mat);\n            }\n            else if (npcEntity instanceof FallingBlock) {\n                getCitizen().data().setPersistent(NPC.Metadata.ITEM_ID, mat.name());\n                getCitizen().data().setPersistent(NPC.Metadata.ITEM_DATA, 0);\n            }\n            else {\n                Debug.echoError(\"NPC is the not an item type!\");\n            }\n            if (getCitizen().isSpawned()) {\n                getCitizen().despawn();\n                getCitizen().spawn(getCitizen().getStoredLocation());\n            }\n        }\n\n        if (mechanism.matches(\"spawn\")) {\n            BukkitImplDeprecations.npcSpawnMechanism.warn(mechanism.context);\n            if (mechanism.requireObject(\"Invalid LocationTag specified. Assuming last known NPC location.\", LocationTag.class)) {\n                getCitizen().spawn(mechanism.valueAsType(LocationTag.class));\n            }\n            else {\n                getCitizen().spawn(getCitizen().getStoredLocation());\n            }\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name range\n        // @input ElementTag(Decimal)\n        // @description\n        // Sets the maximum movement distance of the NPC.\n        // @tags\n        // <NPCTag.range>\n        // -->\n        if (mechanism.matches(\"range\") && mechanism.requireFloat()) {\n            getCitizen().getNavigator().getDefaultParameters().range(mechanism.getValue().asFloat());\n            getCitizen().getNavigator().getLocalParameters().range(mechanism.getValue().asFloat());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name attack_range\n        // @input ElementTag(Decimal)\n        // @description\n        // Sets the maximum attack distance of the NPC.\n        // @tags\n        // <NPCTag.attack_range>\n        // -->\n        if (mechanism.matches(\"attack_range\") && mechanism.requireFloat()) {\n            getCitizen().getNavigator().getDefaultParameters().attackRange(mechanism.getValue().asFloat());\n            getCitizen().getNavigator().getLocalParameters().attackRange(mechanism.getValue().asFloat());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name speed\n        // @input ElementTag(Decimal)\n        // @description\n        // Sets the movement speed of the NPC.\n        // @tags\n        // <NPCTag.speed>\n        // -->\n        if (mechanism.matches(\"speed\") && mechanism.requireFloat()) {\n            getCitizen().getNavigator().getDefaultParameters().speedModifier(mechanism.getValue().asFloat());\n            getCitizen().getNavigator().getLocalParameters().speedModifier(mechanism.getValue().asFloat());\n        }\n\n        if (mechanism.matches(\"despawn\")) {\n            BukkitImplDeprecations.npcDespawnMech.warn(mechanism.context);\n            getCitizen().despawn(DespawnReason.PLUGIN);\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name set_sneaking\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether the NPC is sneaking or not. Only works for player-type NPCs.\n        // @tags\n        // <NPCTag.is_sneaking>\n        // -->\n        if (mechanism.matches(\"set_sneaking\") && mechanism.requireBoolean()) {\n            if (!getCitizen().hasTrait(SneakingTrait.class)) {\n                getCitizen().addTrait(SneakingTrait.class);\n            }\n            SneakingTrait trait = getCitizen().getOrAddTrait(SneakingTrait.class);\n            if (trait.isSneaking() && !mechanism.getValue().asBoolean()) {\n                trait.stand();\n            }\n            else if (!trait.isSneaking() && mechanism.getValue().asBoolean()) {\n                trait.sneak();\n            }\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name set_protected\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether or not the NPC is protected.\n        // @tags\n        // <NPCTag.is_protected>\n        // -->\n        if (mechanism.matches(\"set_protected\") && mechanism.requireBoolean()) {\n            getCitizen().setProtected(mechanism.getValue().asBoolean());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name lookclose\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets the NPC's lookclose value.\n        // @tags\n        // <NPCTag.lookclose>\n        // -->\n        if (mechanism.matches(\"lookclose\") && mechanism.requireBoolean()) {\n            getLookCloseTrait().lookClose(mechanism.getValue().asBoolean());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name controllable\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether the NPC is controllable.\n        // @tags\n        // <NPCTag.controllable>\n        // -->\n        if (mechanism.matches(\"controllable\") && mechanism.requireBoolean()) {\n            getCitizen().getOrAddTrait(Controllable.class).setEnabled(mechanism.getValue().asBoolean());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name targetable\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether the NPC is targetable.\n        // @tags\n        // <NPCTag.targetable>\n        // -->\n        if (mechanism.matches(\"targetable\") && mechanism.requireBoolean()) {\n            getCitizen().getOrAddTrait(TargetableTrait.class).setTargetable(mechanism.getValue().asBoolean());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name teleport_on_stuck\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether the NPC teleports when it is stuck.\n        // @tags\n        // <NPCTag.teleport_on_stuck>\n        // -->\n        if (mechanism.matches(\"teleport_on_stuck\") && mechanism.requireBoolean()) {\n            if (mechanism.getValue().asBoolean()) {\n                getNavigator().getDefaultParameters().stuckAction(TeleportStuckAction.INSTANCE);\n            }\n            else {\n                getNavigator().getDefaultParameters().stuckAction(null);\n            }\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name distance_margin\n        // @input ElementTag(Decimal)\n        // @description\n        // Sets the NPC's distance margin.\n        // @tags\n        // <NPCTag.distance_margin>\n        // -->\n        if ((mechanism.matches(\"distance_margin\") || mechanism.matches(\"set_distance\")) && mechanism.requireDouble()) {\n            getNavigator().getDefaultParameters().distanceMargin(mechanism.getValue().asDouble());\n            getNavigator().getLocalParameters().distanceMargin(mechanism.getValue().asDouble());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name path_distance_margin\n        // @input ElementTag(Decimal)\n        // @description\n        // Sets the NPC's path distance margin.\n        // @tags\n        // <NPCTag.path_distance_margin>\n        // -->\n        if (mechanism.matches(\"path_distance_margin\") && mechanism.requireDouble()) {\n            getNavigator().getDefaultParameters().pathDistanceMargin(mechanism.getValue().asDouble());\n            getNavigator().getLocalParameters().pathDistanceMargin(mechanism.getValue().asDouble());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name use_new_finder\n        // @input ElementTag(Boolean)\n        // @description\n        // If input is 'true', causes the NPC to use the 'new' Citizens A-Star pathfinder.\n        // if 'false', causes the NPC to use the 'old' minecraft vanilla mob pathfinder.\n        // @tags\n        // <NPCTag.use_new_finder>\n        // -->\n        if (mechanism.matches(\"use_new_finder\") && mechanism.requireBoolean()) {\n            getNavigator().getDefaultParameters().useNewPathfinder(mechanism.getValue().asBoolean());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name navigator_look_at\n        // @input LocationTag\n        // @description\n        // Sets the location the NPC will currently look at while moving.\n        // Give no value to let the NPC automatically look where it's going.\n        // Should be set after the NPC has started moving.\n        // @tags\n        // <NPCTag.navigator_look_at>\n        // -->\n        if (mechanism.matches(\"navigator_look_at\")) {\n            if (mechanism.hasValue() && mechanism.requireObject(LocationTag.class)) {\n                final LocationTag loc = mechanism.valueAsType(LocationTag.class);\n                getNavigator().getLocalParameters().lookAtFunction((n) -> loc);\n            }\n            else {\n                getNavigator().getLocalParameters().lookAtFunction(null);\n            }\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name name_visible\n        // @input ElementTag\n        // @description\n        // Sets whether the NPC's nameplate is visible. Input is 'true' (always visible), 'false' (never visible), or 'hover' (only visible while looking at the NPC).\n        // @tags\n        // TODO\n        // -->\n        if (mechanism.matches(\"name_visible\")) {\n            getCitizen().data().setPersistent(NPC.Metadata.NAMEPLATE_VISIBLE, mechanism.getValue().asString());\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name glow_color\n        // @input ElementTag\n        // @description\n        // Sets the color the NPC will glow with, when it's glowing. Input must be from <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/ChatColor.html>.\n        // @tags\n        // TODO\n        // -->\n        if (mechanism.matches(\"glow_color\") && mechanism.requireEnum(ChatColor.class)) {\n            getCitizen().getOrAddTrait(ScoreboardTrait.class).setColor(ChatColor.valueOf(mechanism.getValue().asString().toUpperCase()));\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name clear_waypoints\n        // @input None\n        // @description\n        // Clears all waypoint locations in the NPC's path.\n        // @tags\n        // TODO\n        // -->\n        if (mechanism.matches(\"clear_waypoints\")) {\n            Waypoints wp = getCitizen().getOrAddTrait(Waypoints.class);\n            if ((wp.getCurrentProvider() instanceof WaypointProvider.EnumerableWaypointProvider)) {\n                ((List<Waypoint>) ((WaypointProvider.EnumerableWaypointProvider) wp.getCurrentProvider()).waypoints()).clear();\n            }\n            else if ((wp.getCurrentProvider() instanceof WanderWaypointProvider)) {\n                List<Location> locs = ((WanderWaypointProvider) wp.getCurrentProvider()).getRegionCentres();\n                for (Location loc : locs) {\n                    locs.remove(loc); // Manual clear to ensure recalculation for the forwarding list\n                }\n\n            }\n        }\n\n        // <--[mechanism]\n        // @object NPCTag\n        // @name add_waypoint\n        // @input LocationTag\n        // @description\n        // Add a waypoint location to the NPC's path.\n        // @tags\n        // TODO\n        // -->\n        if (mechanism.matches(\"add_waypoint\") && mechanism.requireObject(LocationTag.class)) {\n            Location target = mechanism.valueAsType(LocationTag.class).clone();\n            Waypoints wp = getCitizen().getOrAddTrait(Waypoints.class);\n            if ((wp.getCurrentProvider() instanceof LinearWaypointProvider)) {\n                ((LinearWaypointProvider) wp.getCurrentProvider()).addWaypoint(new Waypoint(target));\n            }\n            else if ((wp.getCurrentProvider() instanceof WaypointProvider.EnumerableWaypointProvider)) {\n                ((List<Waypoint>) ((WaypointProvider.EnumerableWaypointProvider) wp.getCurrentProvider()).waypoints()).add(new Waypoint(target));\n            }\n            else if ((wp.getCurrentProvider() instanceof WanderWaypointProvider)) {\n                ((WanderWaypointProvider) wp.getCurrentProvider()).getRegionCentres().add(target);\n            }\n        }\n\n        tagProcessor.processMechanism(this, mechanism);\n\n        // Pass along to EntityTag mechanism handler if not already handled.\n        if (!mechanism.fulfilled()) {\n            if (isSpawned()) {\n                new EntityTag(getEntity()).adjust(mechanism);\n            }\n        }\n    }\n\n    @Override\n    public boolean advancedMatches(String matcher, TagContext context) {\n        return isSpawned() && getDenizenEntity().tryAdvancedMatcher(matcher, context);\n    }\n\n    /**\n     * Return an appropriate error-header output for this object, if any.\n     */\n    @Override\n    public String getErrorHeaderContext() {\n        return \" with NPC '<A>\" + debuggable() + \"<LR>'\";\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/PlayerTag.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.nms.interfaces.AdvancementHelper;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityHealth;\r\nimport com.denizenscript.denizen.scripts.commands.player.DisguiseCommand;\r\nimport com.denizenscript.denizen.scripts.commands.player.ExperienceCommand;\r\nimport com.denizenscript.denizen.scripts.commands.player.SidebarCommand;\r\nimport com.denizenscript.denizen.scripts.commands.server.BossBarCommand;\r\nimport com.denizenscript.denizen.tags.core.PlayerTagBase;\r\nimport com.denizenscript.denizen.utilities.*;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.utilities.entity.BossBarHelper;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizen.utilities.entity.HideEntitiesHelper;\r\nimport com.denizenscript.denizen.utilities.flags.PlayerFlagHandler;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryViewUtil;\r\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\r\nimport com.denizenscript.denizen.utilities.packets.HideParticles;\r\nimport com.denizenscript.denizen.utilities.packets.ItemChangeMessage;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.*;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagRunnable;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.Deprecations;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport net.citizensnpcs.npc.NPCSelector;\r\nimport org.bukkit.*;\r\nimport org.bukkit.advancement.Advancement;\r\nimport org.bukkit.advancement.AdvancementProgress;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.block.Sign;\r\nimport org.bukkit.block.banner.Pattern;\r\nimport org.bukkit.block.banner.PatternType;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.command.PluginCommand;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.inventory.InventoryType;\r\nimport org.bukkit.inventory.*;\r\nimport org.bukkit.map.MapView;\r\nimport org.bukkit.scoreboard.Scoreboard;\r\nimport org.bukkit.scoreboard.Team;\r\nimport org.bukkit.util.RayTraceResult;\r\n\r\nimport java.util.*;\r\n\r\npublic class PlayerTag implements ObjectTag, Adjustable, EntityFormObject, FlaggableObject {\r\n\r\n    // <--[ObjectType]\r\n    // @name PlayerTag\r\n    // @prefix p\r\n    // @base EntityTag\r\n    // @implements FlaggableObject\r\n    // @ExampleTagBase player\r\n    // @ExampleValues <player>\r\n    // @ExampleForReturns\r\n    // - kill %VALUE%\r\n    // @ExampleForReturns\r\n    // - heal %VALUE%\r\n    // @ExampleForReturns\r\n    // - narrate \"hello there!\" targets:%VALUE%\r\n    // @format\r\n    // The identity format for players is the UUID of the relevant player.\r\n    //\r\n    // @description\r\n    // A PlayerTag represents a player in the game.\r\n    //\r\n    // This object type is flaggable.\r\n    // Flags on this object type will be stored in the file \"plugins/Denizen/player_flags/(UUID).dat\",\r\n    // with automatic loading only when the player is online and caching for interacting with offline player flags.\r\n    //\r\n    // -->\r\n\r\n    /////////////////////\r\n    //   STATIC METHODS\r\n    /////////////////\r\n\r\n    public static PlayerTag mirrorBukkitPlayer(OfflinePlayer player) {\r\n        if (player == null) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new PlayerTag(player);\r\n        }\r\n    }\r\n\r\n    static Map<String, UUID> playerNames = new HashMap<>();\r\n\r\n    /**\r\n     * Notes that the player exists, for easy PlayerTag valueOf handling.\r\n     */\r\n    public static void notePlayer(OfflinePlayer player) {\r\n        UUID uuid = player.getUniqueId();\r\n        Player onlinePlayer = player.getPlayer();\r\n        notePlayer(onlinePlayer != null ? onlinePlayer.getName() : NMSHandler.playerHelper.getOfflineData(uuid).getName(), uuid);\r\n    }\r\n\r\n    public static void notePlayer(String name, UUID uuid) {\r\n        if (name == null) {\r\n            Debug.echoError(\"Null named player \" + uuid + \" - may be file corruption, or player data imported from non-bukkit server?\");\r\n            return;\r\n        }\r\n        playerNames.putIfAbsent(CoreUtilities.toLowerCase(name), uuid);\r\n    }\r\n\r\n    public static boolean isNoted(OfflinePlayer player) {\r\n        return playerNames.containsValue(player.getUniqueId());\r\n    }\r\n\r\n    public static Map<String, UUID> getAllPlayers() {\r\n        return playerNames;\r\n    }\r\n\r\n    /////////////////////\r\n    //   OBJECT FETCHER\r\n    /////////////////\r\n\r\n    @Fetchable(\"p\")\r\n    public static PlayerTag valueOf(String string, TagContext context) {\r\n        return valueOfInternal(string, context, true);\r\n    }\r\n\r\n    public static String playerByNameMessage = BukkitImplDeprecations.playerByNameWarning.message;\r\n\r\n    public static PlayerTag valueOfInternal(String string, TagContext context, boolean defaultAnnounce) {\r\n        if (string == null) {\r\n            return null;\r\n        }\r\n        boolean announce = context == null ? defaultAnnounce : context.showErrors();\r\n        string = CoreUtilities.toLowerCase(string);\r\n        if (string.startsWith(\"p@\")) {\r\n            string = string.substring(\"p@\".length());\r\n        }\r\n        if (string.length() == 36 && string.indexOf('-') >= 0) {\r\n            try {\r\n                UUID uuid = UUID.fromString(string);\r\n                if (uuid != null) {\r\n                    OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);\r\n                    if (player != null) {\r\n                        return new PlayerTag(player);\r\n                    }\r\n                }\r\n            }\r\n            catch (IllegalArgumentException e) {\r\n                // Nothing\r\n            }\r\n        }\r\n        // Match as a player name\r\n        if (string.length() <= 16 && playerNames.containsKey(string)) {\r\n            OfflinePlayer player = Bukkit.getOfflinePlayer(playerNames.get(string));\r\n            if (announce && (context == null || context.script != null)) { // 'script != null' check is to allow ex command usage silently\r\n                BukkitImplDeprecations.playerByNameWarning.message = playerByNameMessage + \" Player named '\" + player.getName() + \"' has UUID: \" + player.getUniqueId();\r\n                BukkitImplDeprecations.playerByNameWarning.warn(context);\r\n            }\r\n            return new PlayerTag(player);\r\n        }\r\n        if (announce) {\r\n            Debug.log(\"Minor: Invalid Player! '\" + string + \"' could not be found.\");\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static boolean matches(String arg) {\r\n        if (arg == null) {\r\n            return false;\r\n        }\r\n        arg = CoreUtilities.toLowerCase(arg);\r\n        if (arg.startsWith(\"p@\")) {\r\n            return true;\r\n        }\r\n        if (arg.length() == 36 && arg.indexOf('-') >= 0) {\r\n            try {\r\n                UUID uuid = UUID.fromString(arg);\r\n                if (uuid != null) {\r\n                    OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);\r\n                    if (player != null && (player.isOnline() || player.hasPlayedBefore())) {\r\n                        return true;\r\n                    }\r\n                }\r\n            }\r\n            catch (IllegalArgumentException e) {\r\n                // Nothing\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static boolean playerNameIsValid(String name) {\r\n        return playerNames.containsKey(CoreUtilities.toLowerCase(name));\r\n    }\r\n\r\n    /////////////////////\r\n    //   CONSTRUCTORS\r\n    /////////////////\r\n\r\n    public PlayerTag(OfflinePlayer player) {\r\n        this(player.getUniqueId());\r\n    }\r\n\r\n    public PlayerTag(UUID uuid) {\r\n        this.uuid = uuid;\r\n    }\r\n\r\n    public PlayerTag(Player player) {\r\n        this((OfflinePlayer) player);\r\n        if (EntityTag.isNPC(player)) {\r\n            throw new IllegalStateException(\"NPCs are not allowed as PlayerTag objects!\");\r\n        }\r\n    }\r\n\r\n    /////////////////////\r\n    //   INSTANCE FIELDS/METHODS\r\n    /////////////////\r\n\r\n    @Override\r\n    public AbstractFlagTracker getFlagTracker() {\r\n        return PlayerFlagHandler.getTrackerFor(getUUID());\r\n    }\r\n\r\n    @Override\r\n    public void reapplyTracker(AbstractFlagTracker tracker) {\r\n        // Nothing to do.\r\n    }\r\n\r\n    UUID uuid;\r\n\r\n    public boolean isValid() {\r\n        OfflinePlayer pl = getOfflinePlayer();\r\n        if (pl != null && pl.hasPlayedBefore()) {\r\n            return true;\r\n        }\r\n        return getPlayerEntity() != null;\r\n    }\r\n\r\n    public Player getPlayerEntity() {\r\n        return Bukkit.getPlayer(uuid);\r\n    }\r\n\r\n    public UUID getUUID() {\r\n        return uuid;\r\n    }\r\n\r\n    public OfflinePlayer getOfflinePlayer() {\r\n        return Bukkit.getOfflinePlayer(uuid);\r\n    }\r\n\r\n    public ImprovedOfflinePlayer getNBTEditor() {\r\n        ImprovedOfflinePlayer result = ImprovedOfflinePlayer.offlinePlayers.get(uuid);\r\n        if (result == null || (!result.modified && result.timeLastLoaded + Settings.worldPlayerDataMaxCacheTicks < DenizenCore.currentTimeMonotonicMillis)) {\r\n            result = NMSHandler.playerHelper.getOfflineData(uuid);\r\n            if (result != null) {\r\n                ImprovedOfflinePlayer.offlinePlayers.put(uuid, result);\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n\r\n    @Override\r\n    public EntityTag getDenizenEntity() {\r\n        return new EntityTag(getPlayerEntity());\r\n    }\r\n\r\n    public NPCTag getSelectedNPC() {\r\n        if (Depends.citizens != null && CitizensAPI.hasImplementation()) {\r\n            NPC npc = CitizensAPI.getDefaultNPCSelector().getSelected(getPlayerEntity());\r\n            if (npc != null) {\r\n                return new NPCTag(npc);\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public String getName() {\r\n        return getOfflinePlayer().getName();\r\n    }\r\n\r\n    @Override\r\n    public LocationTag getLocation() {\r\n        if (isOnline()) {\r\n            return new LocationTag(getPlayerEntity().getLocation());\r\n        }\r\n        else {\r\n            return new LocationTag(getNBTEditor().getLocation());\r\n        }\r\n    }\r\n\r\n    public int getRemainingAir() {\r\n        if (isOnline()) {\r\n            return getPlayerEntity().getRemainingAir();\r\n        }\r\n        else {\r\n            return getNBTEditor().getRemainingAir();\r\n        }\r\n    }\r\n\r\n    public int getMaximumAir() {\r\n        if (isOnline()) {\r\n            return getPlayerEntity().getMaximumAir();\r\n        }\r\n        else {\r\n            return 300;\r\n        }\r\n    }\r\n\r\n    public double getHealth() {\r\n        if (isOnline()) {\r\n            return getPlayerEntity().getHealth();\r\n        }\r\n        else {\r\n            return getNBTEditor().getHealthFloat();\r\n        }\r\n    }\r\n\r\n    public double getMaxHealth() {\r\n        if (isOnline()) {\r\n            return getPlayerEntity().getMaxHealth();\r\n        }\r\n        else {\r\n            return getNBTEditor().getMaxHealth();\r\n        }\r\n    }\r\n\r\n    public int getFoodLevel() {\r\n        if (isOnline()) {\r\n            return getPlayerEntity().getFoodLevel();\r\n        }\r\n        else {\r\n            return getNBTEditor().getFoodLevel();\r\n        }\r\n    }\r\n\r\n    public LocationTag getEyeLocation() {\r\n        if (isOnline()) {\r\n            return new LocationTag(getPlayerEntity().getEyeLocation());\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public InventoryTag getInventory() {\r\n        if (isOnline()) {\r\n            return InventoryTag.mirrorBukkitInventory(getPlayerEntity().getInventory());\r\n        }\r\n        else {\r\n            return new InventoryTag(getNBTEditor());\r\n        }\r\n    }\r\n\r\n    public CraftingInventory getBukkitWorkbench() {\r\n        if (isOnline()) {\r\n            if (InventoryViewUtil.getType(getPlayerEntity().getOpenInventory()) == InventoryType.WORKBENCH\r\n                    || InventoryViewUtil.getType(getPlayerEntity().getOpenInventory()) == InventoryType.CRAFTING) {\r\n                return (CraftingInventory) InventoryViewUtil.getTopInventory(getPlayerEntity().getOpenInventory());\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public InventoryTag getWorkbench() {\r\n        if (isOnline()) {\r\n            CraftingInventory workbench = getBukkitWorkbench();\r\n            if (workbench != null) {\r\n                return new InventoryTag(workbench, getPlayerEntity());\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public InventoryTag getEnderChest() {\r\n        if (isOnline()) {\r\n            return new InventoryTag(getPlayerEntity().getEnderChest(), getPlayerEntity());\r\n        }\r\n        else {\r\n            return new InventoryTag(getNBTEditor(), true);\r\n        }\r\n    }\r\n\r\n    public WorldTag getWorldTag() {\r\n        if (isOnline()) {\r\n            return new WorldTag(getPlayerEntity().getWorld());\r\n        }\r\n        else {\r\n            return new WorldTag(getLocation().getWorldName());\r\n        }\r\n    }\r\n\r\n    public World getWorld() {\r\n        if (isOnline()) {\r\n            return getPlayerEntity().getWorld();\r\n        }\r\n        else {\r\n            return getLocation().getWorld();\r\n        }\r\n    }\r\n\r\n    public ItemTag getHeldItem() {\r\n        return new ItemTag(getPlayerEntity().getEquipment().getItemInMainHand());\r\n    }\r\n\r\n    public ItemTag getOffhandItem() {\r\n        return new ItemTag(getPlayerEntity().getEquipment().getItemInOffHand());\r\n    }\r\n\r\n    public void decrementStatistic(Statistic statistic, int amount) {\r\n        getOfflinePlayer().decrementStatistic(statistic, amount);\r\n    }\r\n\r\n    public void decrementStatistic(Statistic statistic, EntityType entity, int amount) {\r\n        if (statistic.getType() == Statistic.Type.ENTITY) {\r\n            getOfflinePlayer().decrementStatistic(statistic, entity, amount);\r\n        }\r\n    }\r\n\r\n    public void decrementStatistic(Statistic statistic, Material material, int amount) {\r\n        if (statistic.getType() == Statistic.Type.BLOCK || statistic.getType() == Statistic.Type.ITEM) {\r\n            getOfflinePlayer().decrementStatistic(statistic, material, amount);\r\n        }\r\n    }\r\n\r\n    public void incrementStatistic(Statistic statistic, int amount) {\r\n        getOfflinePlayer().incrementStatistic(statistic, amount);\r\n    }\r\n\r\n    public void incrementStatistic(Statistic statistic, EntityType entity, int amount) {\r\n        if (statistic.getType() == Statistic.Type.ENTITY) {\r\n            getOfflinePlayer().incrementStatistic(statistic, entity, amount);\r\n        }\r\n    }\r\n\r\n    public void incrementStatistic(Statistic statistic, Material material, int amount) {\r\n        if (statistic.getType() == Statistic.Type.BLOCK  || statistic.getType() == Statistic.Type.ITEM) {\r\n            getOfflinePlayer().incrementStatistic(statistic, material, amount);\r\n        }\r\n    }\r\n\r\n    public void setStatistic(Statistic statistic, int amount) {\r\n        getOfflinePlayer().setStatistic(statistic, amount);\r\n    }\r\n\r\n    public void setStatistic(Statistic statistic, EntityType entity, int amount) {\r\n        if (statistic.getType() == Statistic.Type.ENTITY) {\r\n            getOfflinePlayer().setStatistic(statistic, entity, amount);\r\n        }\r\n    }\r\n\r\n    public void setStatistic(Statistic statistic, Material material, int amount) {\r\n        if (statistic.getType() == Statistic.Type.BLOCK || statistic.getType() == Statistic.Type.ITEM) {\r\n            getOfflinePlayer().setStatistic(statistic, material, amount);\r\n        }\r\n    }\r\n\r\n    public boolean isOnline() {\r\n        return getPlayerEntity() != null;\r\n    }\r\n\r\n    public void setBedSpawnLocation(Location location) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().setBedSpawnLocation(location);\r\n        }\r\n        else {\r\n            getNBTEditor().setBedSpawnLocation(location);\r\n        }\r\n    }\r\n\r\n    public void setLocation(Location location) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().teleport(location);\r\n        }\r\n        else {\r\n            getNBTEditor().setLocation(location);\r\n        }\r\n    }\r\n\r\n    public void setMaximumAir(int air) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().setMaximumAir(air);\r\n        }\r\n        else {\r\n            Debug.echoError(\"Cannot set the maximum air of an offline player!\");\r\n        }\r\n    }\r\n\r\n    public void setRemainingAir(int air) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().setRemainingAir(air);\r\n        }\r\n        else {\r\n            getNBTEditor().setRemainingAir(air);\r\n        }\r\n    }\r\n\r\n    public void setHealth(double health) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().setHealth(health);\r\n        }\r\n        else {\r\n            getNBTEditor().setHealthFloat((float) health);\r\n        }\r\n    }\r\n\r\n    public void setMaxHealth(double maxHealth) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().setMaxHealth(maxHealth);\r\n        }\r\n        else {\r\n            getNBTEditor().setMaxHealth(maxHealth);\r\n        }\r\n    }\r\n\r\n    public void setFoodLevel(int foodLevel) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().setFoodLevel(foodLevel);\r\n        }\r\n        else {\r\n            getNBTEditor().setFoodLevel(foodLevel);\r\n        }\r\n    }\r\n\r\n    public float getExp() {\r\n        if (isOnline()) {\r\n            return getPlayerEntity().getExp();\r\n        }\r\n        else {\r\n            return getNBTEditor().getExp();\r\n        }\r\n    }\r\n\r\n    public void setExp(float xp) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().setExp(xp);\r\n        }\r\n        else {\r\n            getNBTEditor().setExp(xp);\r\n        }\r\n    }\r\n\r\n    public int getTotalExperience() {\r\n        if (isOnline()) {\r\n            return getPlayerEntity().getTotalExperience();\r\n        }\r\n        else {\r\n            return getNBTEditor().getTotalExperience();\r\n        }\r\n    }\r\n\r\n    public void setTotalExperience(int xp) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().setTotalExperience(xp);\r\n        }\r\n        else {\r\n            getNBTEditor().setTotalExperience(xp);\r\n        }\r\n    }\r\n\r\n    public int getLevel() {\r\n        if (isOnline()) {\r\n            return getPlayerEntity().getLevel();\r\n        }\r\n        else {\r\n            return getNBTEditor().getLevel();\r\n        }\r\n    }\r\n\r\n    public void setLevel(int level) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().setLevel(level);\r\n        }\r\n        else {\r\n            getNBTEditor().setLevel(level);\r\n        }\r\n    }\r\n\r\n    public void setGameMode(GameMode mode) {\r\n        if (isOnline()) {\r\n            getPlayerEntity().setGameMode(mode);\r\n        }\r\n        else {\r\n            getNBTEditor().setGameMode(mode);\r\n        }\r\n    }\r\n\r\n    public boolean hasChunkLoaded(Chunk chunk) {\r\n        return NMSHandler.playerHelper.hasChunkLoaded(getPlayerEntity(), chunk);\r\n    }\r\n\r\n    /////////////////////\r\n    //   ObjectTag Methods\r\n    /////////////////\r\n\r\n    private String prefix = \"Player\";\r\n\r\n    @Override\r\n    public String getPrefix() {\r\n        return prefix;\r\n    }\r\n\r\n    @Override\r\n    public PlayerTag setPrefix(String prefix) {\r\n        this.prefix = prefix;\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public String debuggable() {\r\n        return \"<LG>p@<Y>\" + uuid + \"<GR> (\" + getOfflinePlayer().getName() + \")\";\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnique() {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public String identify() {\r\n        return \"p@\" + uuid;\r\n    }\r\n\r\n    @Override\r\n    public String identifySimple() {\r\n        return \"p@\" + getOfflinePlayer().getName();\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public Object getJavaObject() {\r\n        return getOfflinePlayer();\r\n    }\r\n\r\n    @Override\r\n    public int hashCode() {\r\n        return getUUID().hashCode();\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object other) {\r\n        if (!(other instanceof PlayerTag)) {\r\n            return false;\r\n        }\r\n        return getUUID().equals(((PlayerTag) other).getUUID());\r\n    }\r\n\r\n    @Override\r\n    public boolean isTruthy() {\r\n        return isOnline();\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\r\n\r\n        /////////////////////\r\n        //   OFFLINE ATTRIBUTES\r\n        /////////////////\r\n\r\n        // Defined in EntityTag\r\n        tagProcessor.registerTag(ElementTag.class, \"is_player\", (attribute, object) -> {\r\n            return new ElementTag(true);\r\n        });\r\n\r\n        /////////////////////\r\n        //   DENIZEN ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.chat_history_list>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of the last 10 things the player has said, less if the player hasn't said all that much.\r\n        // Works with offline players, if the player previously joined and typed in chat since the last server restart.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"chat_history_list\", (attribute, object) -> {\r\n            List<String> history = PlayerTagBase.playerChatHistory.get(object.getUUID());\r\n            return history == null ? new ListTag() : new ListTag(history, true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.chat_history[(<#>)]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the last thing the player said.\r\n        // If a number is specified, returns an earlier thing the player said.\r\n        // Works with offline players.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"chat_history\", (attribute, object) -> {\r\n            int x = 1;\r\n            if (attribute.hasParam() && ArgumentHelper.matchesInteger(attribute.getParam())) {\r\n                x = attribute.getIntParam();\r\n            }\r\n            List<String> messages = PlayerTagBase.playerChatHistory.get(object.getUUID());\r\n            if (messages == null || messages.size() < x || x < 1) {\r\n                return null;\r\n            }\r\n            return new ElementTag(messages.get(x - 1), true);\r\n        });\r\n\r\n        /////////////////////\r\n        //   ECONOMY ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.formatted_money>\r\n        // @returns ElementTag\r\n        // @plugin Vault\r\n        // @description\r\n        // Returns the formatted form of the player's money balance in the registered Economy system.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"formatted_money\", (attribute, object) -> {\r\n            if (Depends.economy == null) {\r\n                if (!attribute.hasAlternative()) {\r\n                    Debug.echoError(\"No economy loaded! Have you installed Vault and a compatible economy plugin?\");\r\n                }\r\n                return null;\r\n            }\r\n            return new ElementTag(Depends.economy.format(Depends.economy.getBalance(object.getOfflinePlayer())), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.money>\r\n        // @returns ElementTag(Decimal)\r\n        // @plugin Vault\r\n        // @description\r\n        // Returns the amount of money the player has with the registered Economy system.\r\n        // May work offline depending on economy provider.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"money\", (attribute, object) -> {\r\n            if (Depends.economy == null) {\r\n                if (!attribute.hasAlternative()) {\r\n                    Debug.echoError(\"No economy loaded! Have you installed Vault and a compatible economy plugin?\");\r\n                }\r\n                return null;\r\n            }\r\n\r\n            if (attribute.startsWith(\"formatted\", 2)) {\r\n                attribute.fulfill(1);\r\n                BukkitImplDeprecations.playerMoneyFormatTag.warn(attribute.context);\r\n                return new ElementTag(Depends.economy.format(Depends.economy.getBalance(object.getOfflinePlayer())));\r\n            }\r\n\r\n            if (attribute.startsWith(\"currency_singular\", 2)) {\r\n                attribute.fulfill(1);\r\n                BukkitImplDeprecations.oldEconomyTags.warn(attribute.context);\r\n                return new ElementTag(Depends.economy.currencyNameSingular());\r\n            }\r\n\r\n            if (attribute.startsWith(\"currency\", 2)) {\r\n                attribute.fulfill(1);\r\n                BukkitImplDeprecations.oldEconomyTags.warn(attribute.context);\r\n                return new ElementTag(Depends.economy.currencyNamePlural());\r\n            }\r\n\r\n            return new ElementTag(Depends.economy.getBalance(object.getOfflinePlayer()));\r\n\r\n        });\r\n\r\n        /////////////////////\r\n        //   ENTITY LIST ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.target[(<matcher>)]>\r\n        // @returns EntityTag\r\n        // @description\r\n        // Returns the entity that the player is looking at, within a maximum range of 50 blocks,\r\n        // or null if the player is not looking at an entity.\r\n        // Optionally, specify an entity type matcher to only count matches as possible targets.\r\n        // -->\r\n        registerOnlineOnlyTag(ObjectTag.class, \"target\", (attribute, object) -> {\r\n            double range = 50;\r\n            String matcher = attribute.hasParam() ? attribute.getParam() : null;\r\n\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.target[(<matcher>)].within[(<#.#>)]>\r\n            // @returns EntityTag\r\n            // @description\r\n            // Returns the living entity that the player is looking at within the specified range limit,\r\n            // or null if the player is not looking at an entity.\r\n            // Optionally, specify an entity type matcher to only count matches as possible targets.\r\n            // -->\r\n            if (attribute.startsWith(\"within\", 2) && attribute.hasContext(2)) {\r\n                range = attribute.getDoubleContext(2);\r\n                attribute.fulfill(1);\r\n            }\r\n            Location eyeLoc = object.getEyeLocation();\r\n            RayTraceResult result = eyeLoc.getWorld().rayTrace(eyeLoc, eyeLoc.getDirection(), range, FluidCollisionMode.NEVER, true, 0.01, (e) -> {\r\n                if (e.getUniqueId().equals(object.getUUID())) {\r\n                    return false;\r\n                }\r\n                if (matcher != null) {\r\n                    return new EntityTag(e).tryAdvancedMatcher(matcher, attribute.context);\r\n                }\r\n                return true;\r\n            });\r\n            if (result == null || result.getHitEntity() == null) {\r\n                return null;\r\n            }\r\n            return new EntityTag(result.getHitEntity()).getDenizenObject();\r\n        });\r\n\r\n        /////////////////////\r\n        //   LOCATION ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.bed_spawn>\r\n        // @returns LocationTag\r\n        // @mechanism PlayerTag.bed_spawn_location\r\n        // @description\r\n        // Returns the location of the player's bed spawn location, or null if it doesn't exist.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(LocationTag.class, \"bed_spawn\", (attribute, object) -> {\r\n            Location bedSpawn = object.isOnline() ? NMSHandler.playerHelper.getBedSpawnLocation(object.getPlayerEntity()) : object.getOfflinePlayer().getBedSpawnLocation();\r\n            return bedSpawn != null ? new LocationTag(bedSpawn) : null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.calculated_bed_spawn>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns the player's calculated bed spawn location.\r\n        // This is a location around their set spawn where they could actually spawn, such as a safe block next to a bed.\r\n        // See <@link tag PlayerTag.bed_spawn> for the actual spawn location they have set.\r\n        // -->\r\n        registerOnlineOnlyTag(LocationTag.class, \"calculated_bed_spawn\", (attribute, object) -> {\r\n            try {\r\n                NMSHandler.chunkHelper.changeChunkServerThread(object.getWorld());\r\n                Location calculatedBedSpawn = object.getPlayerEntity().getBedSpawnLocation();\r\n                return calculatedBedSpawn != null ? new LocationTag(calculatedBedSpawn) : null;\r\n            }\r\n            finally {\r\n                NMSHandler.chunkHelper.restoreServerThread(object.getWorld());\r\n            }\r\n        });\r\n\r\n        registerOfflineTag(ObjectTag.class, \"location\", (attribute, object) -> {\r\n            if (object.isOnline() && !object.getPlayerEntity().isDead()) {\r\n                return new EntityTag(object.getPlayerEntity()).doLocationTag(attribute);\r\n            }\r\n            return object.getLocation();\r\n        });\r\n\r\n        registerOfflineTag(WorldTag.class, \"world\", (attribute, object) -> {\r\n            return object.getWorldTag();\r\n        });\r\n\r\n        /////////////////////\r\n        //   STATE ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.item_cooldown[<material>]>\r\n        // @returns DurationTag\r\n        // @description\r\n        // Returns the cooldown duration remaining on player's material.\r\n        // -->\r\n        registerOnlineOnlyTag(DurationTag.class, \"item_cooldown\", (attribute, object) -> {\r\n            MaterialTag mat = new ElementTag(attribute.getParam()).asType(MaterialTag.class, attribute.context);\r\n            if (mat != null) {\r\n                return new DurationTag((long) object.getPlayerEntity().getCooldown(mat.getMaterial()));\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.first_played_time>\r\n        // @returns TimeTag\r\n        // @description\r\n        // Returns the time of when the player first logged on to this server.\r\n        // Works with offline players.\r\n        // -->\r\n        tagProcessor.registerTag(TimeTag.class, \"first_played_time\", (attribute, object) -> {\r\n            return new TimeTag(object.getOfflinePlayer().getFirstPlayed());\r\n        });\r\n        tagProcessor.registerTag(DurationTag.class, \"first_played\", (attribute, object) -> {\r\n            BukkitImplDeprecations.playerTimePlayedTags.warn(attribute.context);\r\n            return new DurationTag(object.getOfflinePlayer().getFirstPlayed() / 50);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.has_played_before>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns true if the player has played before, or is currently online. Returns false if this PlayerTag refers to a UUID that has never been on the server.\r\n        // Works with offline players.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"has_played_before\", (attribute, object) -> {\r\n            return new ElementTag(object.isValid());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.exhaustion>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism PlayerTag.exhaustion\r\n        // @description\r\n        // Returns the player's exhaustion value. Exhaustion is increased in vanilla when a player sprints or jumps, and is used to reduce food saturation over time.\r\n        // This can reach a maximum value of 40, and decreases by 4 every tick.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"exhaustion\", (attribute, object) -> {\r\n            if (object.isOnline()) {\r\n                return new ElementTag(object.getPlayerEntity().getExhaustion());\r\n            }\r\n            else {\r\n                return new ElementTag(object.getNBTEditor().getExhaustion());\r\n            }\r\n        });\r\n\r\n        // Handle EntityTag oxygen tags here to allow getting them when the player is offline\r\n        registerOfflineTag(DurationTag.class, \"max_oxygen\", (attribute, object) -> {\r\n            return new DurationTag((long) object.getMaximumAir());\r\n        });\r\n\r\n        registerOfflineTag(DurationTag.class, \"oxygen\", (attribute, object) -> {\r\n            if (attribute.startsWith(\"max\", 2)) {\r\n                BukkitImplDeprecations.entityMaxOxygenTag.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                return new DurationTag((long) object.getMaximumAir());\r\n            }\r\n            return new DurationTag((long) object.getRemainingAir());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.health_is_scaled>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player's health bar is currently being scaled.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"health_is_scaled\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().isHealthScaled());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.health_scale>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism PlayerTag.health_scale\r\n        // @description\r\n        // Returns the current scale for the player's health bar.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"health_scale\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().getHealthScale());\r\n        });\r\n\r\n        // Handle EntityTag health tags here to allow getting them when the player is offline\r\n        registerOfflineTag(ElementTag.class, \"formatted_health\", (attribute, object) -> {\r\n            Double maxHealth = attribute.hasParam() ? attribute.getDoubleParam() : null;\r\n            return EntityHealth.getHealthFormatted(new EntityTag(object.getPlayerEntity()), maxHealth);\r\n        });\r\n\r\n        registerOfflineTag(ElementTag.class, \"health_percentage\", (attribute, object) -> {\r\n            double maxHealth = object.getPlayerEntity().getMaxHealth();\r\n            if (attribute.hasParam()) {\r\n                maxHealth = attribute.getIntParam();\r\n            }\r\n            return new ElementTag((object.getPlayerEntity().getHealth() / maxHealth) * 100);\r\n        });\r\n\r\n        registerOfflineTag(ElementTag.class, \"health_max\", (attribute, object) -> {\r\n            return new ElementTag(object.getMaxHealth());\r\n        });\r\n\r\n        registerOfflineTag(ElementTag.class, \"health\", (attribute, object) -> {\r\n            if (attribute.startsWith(\"is_scaled\", 2)) {\r\n                attribute.fulfill(1);\r\n                BukkitImplDeprecations.entityHealthTags.warn(attribute.context);\r\n                return new ElementTag(object.getPlayerEntity().isHealthScaled());\r\n            }\r\n\r\n            if (attribute.startsWith(\"scale\", 2)) {\r\n                attribute.fulfill(1);\r\n                BukkitImplDeprecations.entityHealthTags.warn(attribute.context);\r\n                return new ElementTag(object.getPlayerEntity().getHealthScale());\r\n            }\r\n            if (attribute.startsWith(\"formatted\", 2)) {\r\n                BukkitImplDeprecations.entityHealthTags.warn(attribute.context);\r\n                Double maxHealth = attribute.hasContext(2) ? attribute.getDoubleContext(2) : null;\r\n                attribute.fulfill(1);\r\n                return EntityHealth.getHealthFormatted(new EntityTag(object.getPlayerEntity()), maxHealth);\r\n            }\r\n            if (attribute.startsWith(\"percentage\", 2)) {\r\n                BukkitImplDeprecations.entityHealthTags.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                double maxHealth = object.getPlayerEntity().getMaxHealth();\r\n                if (attribute.hasParam()) {\r\n                    maxHealth = attribute.getIntParam();\r\n                }\r\n                return new ElementTag((object.getPlayerEntity().getHealth() / maxHealth) * 100);\r\n            }\r\n            if (attribute.startsWith(\"max\", 2)) {\r\n                BukkitImplDeprecations.entityHealthTags.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getMaxHealth());\r\n            }\r\n            return new ElementTag(object.getHealth());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.is_banned>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player is banned.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_banned\", (attribute, object) -> {\r\n            if (object.getName() == null) {\r\n                return new ElementTag(false);\r\n            }\r\n            BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());\r\n            if (ban == null) {\r\n                return new ElementTag(false);\r\n            }\r\n            else if (ban.getExpiration() == null) {\r\n                return new ElementTag(true);\r\n            }\r\n            return new ElementTag(ban.getExpiration().after(new Date()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.is_online>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player is currently online.\r\n        // Works with offline players (returns false in that case).\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_online\", (attribute, object) -> {\r\n            return new ElementTag(object.isOnline());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.is_op>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism PlayerTag.is_op\r\n        // @description\r\n        // Returns whether the player is a full server operator.\r\n        // Works with offline players.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"is_op\", (attribute, object) -> {\r\n            return new ElementTag(object.getOfflinePlayer().isOp());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.whitelisted>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism PlayerTag.whitelisted\r\n        // @description\r\n        // Returns whether the player is whitelisted.\r\n        // Works with offline players.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"whitelisted\", (attribute, object) -> {\r\n            return new ElementTag(object.getOfflinePlayer().isWhitelisted());\r\n        }, \"is_whitelisted\");\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.last_played_time>\r\n        // @returns TimeTag\r\n        // @description\r\n        // Returns the time of when the player was last seen.\r\n        // Works with offline players.\r\n        // Not very useful for online players.\r\n        // -->\r\n        tagProcessor.registerTag(TimeTag.class, \"last_played_time\", (attribute, object) -> {\r\n            if (object.isOnline()) {\r\n                return TimeTag.now();\r\n            }\r\n            return new TimeTag(object.getOfflinePlayer().getLastPlayed());\r\n        });\r\n        tagProcessor.registerTag(DurationTag.class, \"last_played\", (attribute, object) -> {\r\n            BukkitImplDeprecations.playerTimePlayedTags.warn(attribute.context);\r\n            if (object.isOnline()) {\r\n                return new DurationTag(System.currentTimeMillis() / 50);\r\n            }\r\n            return new DurationTag(object.getOfflinePlayer().getLastPlayed() / 50);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.groups[(<world>)]>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all groups the player is in.\r\n        // May work with offline players, depending on permission plugin.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"groups\", (attribute, object) -> {\r\n            if (Depends.permissions == null) {\r\n                if (!attribute.hasAlternative()) {\r\n                    Debug.echoError(\"No permission system loaded! Have you installed Vault and a compatible permissions plugin?\");\r\n                }\r\n                return null;\r\n            }\r\n            ListTag list = new ListTag();\r\n            WorldTag world = null;\r\n            if (attribute.hasParam()) {\r\n                world = attribute.paramAsType(WorldTag.class);\r\n                if (world == null) {\r\n                    Debug.echoError(\"Invalid world specified: \" + attribute.getParam());\r\n                    return null;\r\n                }\r\n            }\r\n            for (String group : Depends.permissions.getGroups()) {\r\n                if (Depends.permissions.playerInGroup(world != null ? world.getName() : null, object.getOfflinePlayer(), group)) {\r\n                    list.addObject(new ElementTag(group, true));\r\n                }\r\n            }\r\n            return list;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.ban_expiration_time>\r\n        // @returns TimeTag\r\n        // @description\r\n        // Returns the expiration of the player's ban, if they are banned.\r\n        // Potentially can be null.\r\n        // -->\r\n        tagProcessor.registerTag(TimeTag.class, \"ban_expiration_time\", (attribute, object) -> {\r\n            if (object.getName() == null) {\r\n                return null;\r\n            }\r\n            BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());\r\n            if (ban == null || ban.getExpiration() == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {\r\n                return null;\r\n            }\r\n            return new TimeTag(ban.getExpiration().getTime());\r\n        });\r\n        tagProcessor.registerTag(DurationTag.class, \"ban_expiration\", (attribute, object) -> {\r\n            if (object.getName() == null) {\r\n                return null;\r\n            }\r\n            BukkitImplDeprecations.playerTimePlayedTags.warn(attribute.context);\r\n            BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());\r\n            if (ban == null || ban.getExpiration() == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {\r\n                return null;\r\n            }\r\n            return new DurationTag(ban.getExpiration().getTime() / 50);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.ban_reason>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the reason for the player's ban, if they are banned.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"ban_reason\", (attribute, object) -> {\r\n            if (object.getName() == null) {\r\n                return null;\r\n            }\r\n            BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());\r\n            if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {\r\n                return null;\r\n            }\r\n            return new ElementTag(ban.getReason(), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.ban_created_time>\r\n        // @returns TimeTag\r\n        // @description\r\n        // Returns when the player's ban was created, if they are banned.\r\n        // -->\r\n        tagProcessor.registerTag(TimeTag.class, \"ban_created_time\", (attribute, object) -> {\r\n            if (object.getName() == null) {\r\n                return null;\r\n            }\r\n            BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());\r\n            if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {\r\n                return null;\r\n            }\r\n            return new TimeTag(ban.getCreated().getTime());\r\n        });\r\n        tagProcessor.registerTag(DurationTag.class, \"ban_created\", (attribute, object) -> {\r\n            if (object.getName() == null) {\r\n                return null;\r\n            }\r\n            Deprecations.timeTagRewrite.warn(attribute.context);\r\n            BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());\r\n            if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {\r\n                return null;\r\n            }\r\n            return new DurationTag(ban.getCreated().getTime() / 50);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.ban_source>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the source of the player's ban, if they are banned.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"ban_source\", (attribute, object) -> {\r\n            if (object.getName() == null) {\r\n                return null;\r\n            }\r\n            BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());\r\n            if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {\r\n                return null;\r\n            }\r\n            return new ElementTag(ban.getSource(), true);\r\n        });\r\n\r\n        tagProcessor.registerTag(ObjectTag.class, \"ban_info\", (attribute, object) -> {\r\n            if (object.getName() == null) {\r\n                return null;\r\n            }\r\n            BukkitImplDeprecations.playerBanInfoTags.warn(attribute.context);\r\n            BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());\r\n            if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {\r\n                return null;\r\n            }\r\n\r\n            if (attribute.startsWith(\"expiration\", 2) && ban.getExpiration() != null) {\r\n                attribute.fulfill(1);\r\n                return new DurationTag(ban.getExpiration().getTime() / 50);\r\n            }\r\n\r\n            else if (attribute.startsWith(\"reason\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(ban.getReason());\r\n            }\r\n\r\n            else if (attribute.startsWith(\"created\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new DurationTag(ban.getCreated().getTime() / 50);\r\n            }\r\n\r\n            else if (attribute.startsWith(\"source\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(ban.getSource());\r\n            }\r\n\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.in_group[<group_name>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player is in the specified group.\r\n        // (May work with offline players, depending on your permissions system.)\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"in_group\", (attribute, object) -> {\r\n            if (Depends.permissions == null) {\r\n                if (!attribute.hasAlternative()) {\r\n                    Debug.echoError(\"No permission system loaded! Have you installed Vault and a compatible permissions plugin?\");\r\n                }\r\n                return null;\r\n            }\r\n\r\n            String group = attribute.getParam();\r\n\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.in_group[<group_name>].global>\r\n            // @returns ElementTag(Boolean)\r\n            // @description\r\n            // Returns whether the player has the group with no regard to the\r\n            // player's current world.\r\n            // (Works with offline players)\r\n            // (Note: This may or may not be functional with your permissions system.)\r\n            // -->\r\n\r\n            // Non-world specific permission\r\n            if (attribute.startsWith(\"global\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(Depends.permissions.playerInGroup(null, object.getOfflinePlayer(), group));\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.in_group[<group_name>].world[<world>]>\r\n            // @returns ElementTag(Boolean)\r\n            // @description\r\n            // Returns whether the player has the group in regards to a specific world.\r\n            // (Works with offline players)\r\n            // (Note: This may or may not be functional with your permissions system.)\r\n            // -->\r\n\r\n            // Permission in certain world\r\n            else if (attribute.startsWith(\"world\", 2)) {\r\n                WorldTag world = null;\r\n                if (attribute.hasContext(2)) {\r\n                    world = attribute.contextAsType(2, WorldTag.class);\r\n                    if (world == null) {\r\n                        Debug.echoError(\"Invalid world specified: \" + attribute.getContext(2));\r\n                        return null;\r\n                    }\r\n                }\r\n                attribute.fulfill(1);\r\n                return new ElementTag(Depends.permissions.playerInGroup(world != null ? world.getName() : null, object.getOfflinePlayer(), group));\r\n            }\r\n\r\n            // Permission in current world\r\n            else if (object.isOnline()) {\r\n                return new ElementTag(Depends.permissions.playerInGroup(object.getPlayerEntity(), group));\r\n            }\r\n            else if (Depends.permissions != null) {\r\n                return new ElementTag(Depends.permissions.playerInGroup(null, object.getOfflinePlayer(), group));\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.has_permission[permission.node]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player has the specified node.\r\n        // (May work with offline players, depending on your permissions system.)\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"has_permission\", (attribute, object) -> {\r\n            String permission = attribute.getParam();\r\n\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.has_permission[permission.node].global>\r\n            // @returns ElementTag(Boolean)\r\n            // @description\r\n            // Returns whether the player has the specified node, regardless of world.\r\n            // (Works with offline players)\r\n            // (Note: this may or may not be functional with your permissions system.)\r\n            // -->\r\n\r\n            // Non-world specific permission\r\n            if (attribute.startsWith(\"global\", 2)) {\r\n                if (Depends.permissions == null) {\r\n                    if (!attribute.hasAlternative()) {\r\n                        Debug.echoError(\"No permission system loaded! Have you installed Vault and a compatible permissions plugin?\");\r\n                    }\r\n                    return null;\r\n                }\r\n\r\n                attribute.fulfill(1);\r\n                return new ElementTag(Depends.permissions.playerHas(null, object.getOfflinePlayer(), permission));\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.has_permission[permission.node].world[<world>]>\r\n            // @returns ElementTag(Boolean)\r\n            // @description\r\n            // Returns whether the player has the specified node in regards to the\r\n            // specified world.\r\n            // (Works with offline players)\r\n            // (Note: This may or may not be functional with your permissions system.)\r\n            // -->\r\n\r\n            // Permission in certain world\r\n            else if (attribute.startsWith(\"world\", 2)) {\r\n                String world = attribute.getContext(2);\r\n                if (Depends.permissions == null) {\r\n                    if (!attribute.hasAlternative()) {\r\n                        Debug.echoError(\"No permission system loaded! Have you installed Vault and a compatible permissions plugin?\");\r\n                    }\r\n                    return null;\r\n                }\r\n                attribute.fulfill(1);\r\n                if (world.startsWith(\"w@\")) {\r\n                    world = world.substring(2);\r\n                }\r\n                return new ElementTag(Depends.permissions.playerHas(world, object.getOfflinePlayer(), permission));\r\n            }\r\n\r\n            // Permission in current world\r\n            else if (object.isOnline()) {\r\n                return new ElementTag(object.getPlayerEntity().hasPermission(permission));\r\n            }\r\n            else if (Depends.permissions != null) {\r\n                return new ElementTag(Depends.permissions.playerHas(null, object.getOfflinePlayer(), permission));\r\n            }\r\n            return null;\r\n        }, \"permission\");\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.statistic[<statistic>]>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the player's current value for the specified statistic.\r\n        // Valid statistics: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Statistic.html>\r\n        // Works with offline players.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"statistic\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            Statistic statistic;\r\n            try {\r\n                statistic = Statistic.valueOf(attribute.getParam().toUpperCase());\r\n            }\r\n            catch (IllegalArgumentException ex) {\r\n                attribute.echoError(\"Statistic '\" + attribute.getParam() + \"' does not exist: \" + ex.getMessage());\r\n                return null;\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.statistic[<statistic>].qualifier[<material>/<entity>]>\r\n            // @returns ElementTag(Number)\r\n            // @description\r\n            // Returns the player's current value for the specified statistic, with the\r\n            // specified qualifier, which can be either an entity or material.\r\n            // Valid statistics: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Statistic.html>\r\n            // To check a statistic type dynamically, refer to <@link tag server.statistic_type>.\r\n            // -->\r\n            if (attribute.startsWith(\"qualifier\", 2)) {\r\n                ObjectTag obj = ObjectFetcher.pickObjectFor(attribute.getContext(2), attribute.context);\r\n                attribute.fulfill(1);\r\n                try {\r\n                    if (obj instanceof MaterialTag) {\r\n                        return new ElementTag(object.getOfflinePlayer().getStatistic(statistic, ((MaterialTag) obj).getMaterial()));\r\n                    }\r\n                    else if (obj instanceof EntityTag) {\r\n                        return new ElementTag(object.getOfflinePlayer().getStatistic(statistic, ((EntityTag) obj).getBukkitEntityType()));\r\n                    }\r\n                    else {\r\n                        return null;\r\n                    }\r\n                }\r\n                catch (Exception e) {\r\n                    Debug.echoError(\"Invalid statistic: \" + statistic + \" for this player!\");\r\n                    return null;\r\n                }\r\n            }\r\n            try {\r\n                return new ElementTag(object.getOfflinePlayer().getStatistic(statistic));\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(\"Invalid statistic: \" + statistic + \" for this player!\");\r\n                return null;\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.uuid>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the UUID of the player.\r\n        // Works with offline players.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"uuid\", (attribute, object) -> {\r\n            return new ElementTag(object.getUUID().toString());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.list_name>\r\n        // @returns ElementTag\r\n        // @mechanism PlayerTag.player_list_name\r\n        // @description\r\n        // Returns the name of the player as shown in the player list.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"list_name\", (attribute, object) -> {\r\n            return new ElementTag(PaperAPITools.instance.getPlayerListName(object.getPlayerEntity()), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.display_name>\r\n        // @returns ElementTag\r\n        // @mechanism PlayerTag.display_name\r\n        // @description\r\n        // Returns the display name of the player, which may contain prefixes and suffixes, colors, etc.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"display_name\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().getDisplayName(), true);\r\n        });\r\n\r\n        // Documented in EntityTag\r\n        tagProcessor.registerTag(ElementTag.class, \"name\", (attribute, object) -> {\r\n            if (attribute.startsWith(\"list\", 2) && object.isOnline()) {\r\n                BukkitImplDeprecations.playerNameTags.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getPlayerEntity().getPlayerListName(), true);\r\n            }\r\n            if (attribute.startsWith(\"display\", 2) && object.isOnline()) {\r\n                BukkitImplDeprecations.playerNameTags.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getPlayerEntity().getDisplayName(), true);\r\n            }\r\n            return new ElementTag(object.getName(), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.client_brand>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the brand of the client, as sent via the \"minecraft:brand\" packet.\r\n        // On normal clients, will say \"vanilla\". On broken clients, will say \"unknown\". Modded clients will identify themselves (though not guaranteed!).\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"client_brand\", (attribute, object) -> {\r\n            return new ElementTag(PaperAPITools.instance.getClientBrand(object.getPlayerEntity()), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.locale>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the current locale of the player.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"locale\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().getLocale(), true);\r\n        });\r\n\r\n        /////////////////////\r\n        //   INVENTORY ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.inventory>\r\n        // @returns InventoryTag\r\n        // @description\r\n        // Returns a InventoryTag of the player's current inventory.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(InventoryTag.class, \"inventory\", (attribute, object) -> {\r\n            return object.getInventory();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.enderchest>\r\n        // @returns InventoryTag\r\n        // @description\r\n        // Gets the player's enderchest inventory.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(InventoryTag.class, \"enderchest\", (attribute, object) -> {\r\n            return object.getEnderChest();\r\n        });\r\n\r\n        /////////////////////\r\n        //   ONLINE ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.open_inventory>\r\n        // @returns InventoryTag\r\n        // @description\r\n        // Gets the inventory the player currently has open. If the player has no open\r\n        // inventory, this returns the player's inventory.\r\n        // -->\r\n        registerOnlineOnlyTag(InventoryTag.class, \"open_inventory\", (attribute, object) -> {\r\n            return InventoryTag.mirrorBukkitInventory(InventoryViewUtil.getTopInventory(object.getPlayerEntity().getOpenInventory()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.discovered_recipes>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of the recipes the player has discovered, in the Namespace:Key format, for example \"minecraft:gold_nugget\".\r\n        // -->\r\n        registerOnlineOnlyTag(ListTag.class, \"discovered_recipes\", (attribute, object) -> {\r\n            ListTag result = new ListTag();\r\n            for (NamespacedKey recipe : object.getPlayerEntity().getDiscoveredRecipes()) {\r\n                result.addObject(new ElementTag(recipe.toString(), true));\r\n            }\r\n            return result;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.selected_trade_index>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the index of the trade the player is currently viewing, if any.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"selected_trade_index\", (attribute, object) -> {\r\n            if (InventoryViewUtil.getTopInventory(object.getPlayerEntity().getOpenInventory()) instanceof MerchantInventory) {\r\n                return new ElementTag(((MerchantInventory) InventoryViewUtil.getTopInventory(object.getPlayerEntity().getOpenInventory()))\r\n                        .getSelectedRecipeIndex() + 1);\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // [tag]\r\n        // @attribute <PlayerTag.selected_trade>\r\n        // @returns TradeTag\r\n        // @description\r\n        // Returns the trade the player is currently viewing, if any.\r\n        // This is almost completely broke and only works if the player has placed items in the trade slots.\r\n        //\r\n        registerOnlineOnlyTag(TradeTag.class, \"selected_trade\", (attribute, object) -> {\r\n            Inventory playerInventory = InventoryViewUtil.getTopInventory(object.getPlayerEntity().getOpenInventory());\r\n            if (playerInventory instanceof MerchantInventory && ((MerchantInventory) playerInventory).getSelectedRecipe() != null) {\r\n                return new TradeTag(((MerchantInventory) playerInventory).getSelectedRecipe()).duplicate();\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.item_on_cursor>\r\n        // @returns ItemTag\r\n        // @mechanism PlayerTag.item_on_cursor\r\n        // @description\r\n        // Returns the item on the player's cursor, if any. This includes\r\n        // chest interfaces, inventories, and hotbars, etc.\r\n        // -->\r\n        registerOnlineOnlyTag(ItemTag.class, \"item_on_cursor\", (attribute, object) -> {\r\n            return new ItemTag(object.getPlayerEntity().getItemOnCursor());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.held_item_slot>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the slot location of the player's selected item.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"held_item_slot\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().getInventory().getHeldItemSlot() + 1);\r\n        });\r\n\r\n        registerOnlineOnlyTag(ObjectTag.class, \"item_in_hand\", (attribute, object) -> {\r\n            if (attribute.startsWith(\"slot\", 2)) {\r\n                BukkitImplDeprecations.playerItemInHandSlotTag.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getPlayerEntity().getInventory().getHeldItemSlot() + 1);\r\n            }\r\n            return object.getHeldItem();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.sidebar_lines>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns the current lines set on the player's Sidebar via <@link command sidebar>.\r\n        // -->\r\n        registerOnlineOnlyTag(ListTag.class, \"sidebar_lines\", (attribute, object) -> {\r\n            Sidebar sidebar = SidebarCommand.getSidebar(object);\r\n            if (sidebar == null) {\r\n                return null;\r\n            }\r\n            return new ListTag(sidebar.getLinesText(), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.sidebar_title>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the current title set on the player's Sidebar via <@link command sidebar>.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"sidebar_title\", (attribute, object) -> {\r\n            Sidebar sidebar = SidebarCommand.getSidebar(object);\r\n            if (sidebar == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(sidebar.getTitle(), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.sidebar_scores>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns the current scores set on the player's Sidebar via <@link command sidebar>,\r\n        // in the same order as <@link tag PlayerTag.sidebar_lines>.\r\n        // -->\r\n        registerOnlineOnlyTag(ListTag.class, \"sidebar_scores\", (attribute, object) -> {\r\n            Sidebar sidebar = SidebarCommand.getSidebar(object);\r\n            if (sidebar == null) {\r\n                return null;\r\n            }\r\n            ListTag scores = new ListTag();\r\n            for (int score : sidebar.getScores()) {\r\n                scores.add(String.valueOf(score));\r\n            }\r\n            return scores;\r\n        });\r\n\r\n        registerOnlineOnlyTag(ObjectTag.class, \"sidebar\", (attribute, object) -> {\r\n            BukkitImplDeprecations.playerSidebarTags.warn(attribute.context);\r\n            if (attribute.startsWith(\"lines\", 2)) {\r\n                attribute.fulfill(1);\r\n                Sidebar sidebar = SidebarCommand.getSidebar(object);\r\n                if (sidebar == null) {\r\n                    return null;\r\n                }\r\n                return new ListTag(sidebar.getLinesText());\r\n            }\r\n            if (attribute.startsWith(\"title\", 2)) {\r\n                attribute.fulfill(1);\r\n                Sidebar sidebar = SidebarCommand.getSidebar(object);\r\n                if (sidebar == null) {\r\n                    return null;\r\n                }\r\n                return new ElementTag(sidebar.getTitle());\r\n            }\r\n            if (attribute.startsWith(\"scores\", 2)) {\r\n                attribute.fulfill(1);\r\n                Sidebar sidebar = SidebarCommand.getSidebar(object);\r\n                if (sidebar == null) {\r\n                    return null;\r\n                }\r\n                ListTag scores = new ListTag();\r\n                for (int score : sidebar.getScores()) {\r\n                    scores.add(String.valueOf(score));\r\n                }\r\n                return scores;\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.skin_blob>\r\n        // @returns ElementTag\r\n        // @mechanism PlayerTag.skin_blob\r\n        // @description\r\n        // Returns the player's current skin blob.\r\n        // In the format: \"texture;signature\" (two values separated by a semicolon).\r\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"skin_blob\", (attribute, object) -> {\r\n            return new ElementTag(NMSHandler.instance.getProfileEditor().getPlayerSkinBlob(object.getPlayerEntity()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.skull_skin>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the player's current skin blob, formatted for input to a Player Skull item.\r\n        // In the format: \"UUID|Texture|Name\" (three values separated by pipes).\r\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"skull_skin\", (attribute, object) -> {\r\n            String skin = NMSHandler.instance.getProfileEditor().getPlayerSkinBlob(object.getPlayerEntity());\r\n            if (skin == null) {\r\n                return null;\r\n            }\r\n            int semicolon = skin.indexOf(';');\r\n            return new ElementTag(object.getPlayerEntity().getUniqueId() + \"|\" + skin.substring(0, semicolon) + \"|\" + object.getName());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.skull_item>\r\n        // @returns ItemTag\r\n        // @description\r\n        // Returns a Player_Head item with the skin of the player.\r\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\r\n        // -->\r\n        registerOnlineOnlyTag(ItemTag.class, \"skull_item\", (attribute, object) -> {\r\n            ItemStack item = new ItemStack(Material.PLAYER_HEAD);\r\n            item = NMSHandler.itemHelper.setSkullSkin(item, NMSHandler.instance.getPlayerProfile(object.getPlayerEntity()));\r\n            return new ItemTag(item);\r\n        });\r\n\r\n        registerOnlineOnlyTag(ObjectTag.class, \"attack_cooldown\", (attribute, object) -> {\r\n            BukkitImplDeprecations.playerAttackCooldownTags.warn(attribute.context);\r\n            if (attribute.startsWith(\"duration\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new DurationTag((long) NMSHandler.playerHelper\r\n                        .ticksPassedDuringCooldown(object.getPlayerEntity()));\r\n            }\r\n            else if (attribute.startsWith(\"max_duration\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new DurationTag((long) NMSHandler.playerHelper\r\n                        .getMaxAttackCooldownTicks(object.getPlayerEntity()));\r\n            }\r\n\r\n            else if (attribute.startsWith(\"percent\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getPlayerEntity().getAttackCooldown() * 100);\r\n            }\r\n\r\n            Debug.echoError(\"The tag 'player.attack_cooldown...' must be followed by a sub-tag.\");\r\n\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.main_hand>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the player's main hand, either LEFT or RIGHT.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"main_hand\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().getMainHand().toString());\r\n        });\r\n\r\n        /////////////////////\r\n        //   CITIZENS ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.selected_npc>\r\n        // @returns NPCTag\r\n        // @mechanism PlayerTag.selected_npc\r\n        // @description\r\n        // Returns the NPCTag that the player currently has selected with '/npc select', null if no NPC selected.\r\n        // -->\r\n        registerOnlineOnlyTag(NPCTag.class, \"selected_npc\", (attribute, object) -> {\r\n            if (object.getPlayerEntity().hasMetadata(\"selected\")) {\r\n                return object.getSelectedNPC();\r\n            }\r\n            return null;\r\n        });\r\n\r\n        /////////////////////\r\n        //   CONVERSION ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.entity>\r\n        // @returns EntityTag\r\n        // @description\r\n        // Returns the EntityTag object of the player.\r\n        // (Note: This should never actually be needed. PlayerTags are considered valid EntityTags.)\r\n        // -->\r\n        registerOnlineOnlyTag(EntityTag.class, \"entity\", (attribute, object) -> {\r\n            return new EntityTag(object.getPlayerEntity());\r\n        });\r\n\r\n        /////////////////////\r\n        //   IDENTIFICATION ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.ip_address>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the player's IP address, without port or hostname.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"ip_address\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().getAddress().getAddress().toString());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.ip>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the player's IP address host name.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"ip\", (attribute, object) -> {\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.ip.address_only>\r\n            // @returns ElementTag\r\n            // @description\r\n            // Returns the player's IP address with port (without triggering an rdns lookup).\r\n            // -->\r\n            if (attribute.startsWith(\"address_only\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getPlayerEntity().getAddress().toString());\r\n            }\r\n            String host = object.getPlayerEntity().getAddress().getHostName();\r\n\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.ip.address>\r\n            // @returns ElementTag\r\n            // @description\r\n            // Returns the player's IP address with port (usually including an rdns host path).\r\n            // -->\r\n            if (attribute.startsWith(\"address\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getPlayerEntity().getAddress().toString());\r\n            }\r\n            return new ElementTag(host);\r\n        }, \"host_name\");\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.nameplate>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the displayed text in the nameplate of the player.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"nameplate\", (attribute, object) -> {\r\n            return new ElementTag(NMSHandler.instance.getProfileEditor().getPlayerName(object.getPlayerEntity()), true);\r\n        });\r\n\r\n        /////////////////////\r\n        //   LOCATION ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.compass_target>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns the location of the player's compass target.\r\n        // -->\r\n        registerOnlineOnlyTag(LocationTag.class, \"compass_target\", (attribute, object) -> {\r\n            Location target = object.getPlayerEntity().getCompassTarget();\r\n            if (target != null) {\r\n                return new LocationTag(target);\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.chunk_loaded[<chunk>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player has the chunk loaded on their client.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"chunk_loaded\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            ChunkTag chunk = attribute.paramAsType(ChunkTag.class);\r\n            if (chunk == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(chunk.isLoadedSafe() && object.hasChunkLoaded(chunk.getChunkForTag(attribute)));\r\n        });\r\n\r\n        /////////////////////\r\n        //   STATE ATTRIBUTES\r\n        /////////////////\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.can_fly>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism PlayerTag.can_fly\r\n        // @description\r\n        // Returns whether the player is allowed to fly.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"can_fly\", (attribute, object) -> {\r\n            if (object.isOnline()) {\r\n                return new ElementTag(object.getPlayerEntity().getAllowFlight());\r\n            }\r\n            else {\r\n                return new ElementTag(object.getNBTEditor().getAllowFlight());\r\n            }\r\n        }, \"allowed_flight\");\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.fly_speed>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism PlayerTag.fly_speed\r\n        // @description\r\n        // Returns the speed the player can fly at.\r\n        // Default value is '0.2'.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"fly_speed\", (attribute, object) -> {\r\n            if (object.isOnline()) {\r\n                return new ElementTag(object.getPlayerEntity().getFlySpeed());\r\n            }\r\n            else {\r\n                return new ElementTag(object.getNBTEditor().getFlySpeed());\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.walk_speed>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism PlayerTag.walk_speed\r\n        // @description\r\n        // Returns the speed the player can walk at.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"walk_speed\", (attribute, object) -> {\r\n            if (object.isOnline()) {\r\n                return new ElementTag(object.getPlayerEntity().getWalkSpeed());\r\n            }\r\n            else {\r\n                return new ElementTag(object.getNBTEditor().getWalkSpeed());\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.saturation>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism PlayerTag.saturation\r\n        // @description\r\n        // Returns the current food saturation of the player.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"saturation\", (attribute, object) -> {\r\n            if (object.isOnline()) {\r\n                return new ElementTag(object.getPlayerEntity().getSaturation());\r\n            }\r\n            else {\r\n                return new ElementTag(object.getNBTEditor().getSaturation());\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.formatted_food_level[(<max>)]>\r\n        // @returns ElementTag\r\n        // @mechanism PlayerTag.food_level\r\n        // @description\r\n        // Returns a 'formatted' value of the player's current food level.\r\n        // May be 'starving', 'famished', 'parched, 'hungry', or 'healthy'.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"formatted_food_level\", (attribute, object) -> {\r\n            double maxHunger = object.getPlayerEntity().getMaxHealth();\r\n            if (attribute.hasParam()) {\r\n                maxHunger = attribute.getIntParam();\r\n            }\r\n            attribute.fulfill(1);\r\n            int foodLevel = object.getFoodLevel();\r\n            if (foodLevel / maxHunger < .10) {\r\n                return new ElementTag(\"starving\");\r\n            }\r\n            else if (foodLevel / maxHunger < .40) {\r\n                return new ElementTag(\"famished\");\r\n            }\r\n            else if (foodLevel / maxHunger < .75) {\r\n                return new ElementTag(\"parched\");\r\n            }\r\n            else if (foodLevel / maxHunger < 1) {\r\n                return new ElementTag(\"hungry\");\r\n            }\r\n            else {\r\n                return new ElementTag(\"healthy\");\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.food_level>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism PlayerTag.food_level\r\n        // @description\r\n        // Returns the current food level (aka hunger) of the player.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"food_level\", (attribute, object) -> {\r\n            if (attribute.startsWith(\"formatted\", 2)) {\r\n                BukkitImplDeprecations.playerFoodLevelFormatTag.warn(attribute.context);\r\n                double maxHunger = object.getPlayerEntity().getMaxHealth();\r\n                if (attribute.hasContext(2)) {\r\n                    maxHunger = attribute.getIntContext(2);\r\n                }\r\n                attribute.fulfill(1);\r\n                int foodLevel = object.getFoodLevel();\r\n                if (foodLevel / maxHunger < .10) {\r\n                    return new ElementTag(\"starving\");\r\n                }\r\n                else if (foodLevel / maxHunger < .40) {\r\n                    return new ElementTag(\"famished\");\r\n                }\r\n                else if (foodLevel / maxHunger < .75) {\r\n                    return new ElementTag(\"parched\");\r\n                }\r\n                else if (foodLevel / maxHunger < 1) {\r\n                    return new ElementTag(\"hungry\");\r\n                }\r\n                else {\r\n                    return new ElementTag(\"healthy\");\r\n                }\r\n            }\r\n            return new ElementTag(object.getFoodLevel());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.gamemode>\r\n        // @returns ElementTag\r\n        // @mechanism PlayerTag.gamemode\r\n        // @description\r\n        // Returns the name of the gamemode the player is currently set to.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"gamemode\", (attribute, object) -> {\r\n            if (object.isOnline()) {\r\n                return new ElementTag(object.getPlayerEntity().getGameMode());\r\n            }\r\n            return new ElementTag(object.getNBTEditor().getGameMode());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.is_blocking>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player is currently blocking.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"is_blocking\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().isBlocking());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.ping>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the player's current ping.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"ping\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().getPing());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.is_flying>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player is currently flying.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"is_flying\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().isFlying());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.is_sneaking>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player is currently sneaking.\r\n        // -->\r\n        if (!Denizen.supportsPaper || NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) {\r\n            registerOnlineOnlyTag(ElementTag.class, \"is_sneaking\", (attribute, object) -> {\r\n                return new ElementTag(object.getPlayerEntity().isSneaking());\r\n            });\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.is_sprinting>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism PlayerTag.sprinting\r\n        // @description\r\n        // Returns whether the player is currently sprinting.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"is_sprinting\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().isSprinting());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.has_advancement[<advancement>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the player has completed the specified advancement.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"has_advancement\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            Advancement adv = AdvancementHelper.getAdvancement(attribute.getParam());\r\n            if (adv == null) {\r\n                if (!attribute.hasAlternative()) {\r\n                    Debug.echoError(\"Advancement '\" + attribute.getParam() + \"' does not exist.\");\r\n                }\r\n                return null;\r\n            }\r\n            AdvancementProgress progress = object.getPlayerEntity().getAdvancementProgress(adv);\r\n            return new ElementTag(progress.isDone());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.advancements>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of the names of all advancements the player has completed.\r\n        // -->\r\n        registerOnlineOnlyTag(ListTag.class, \"advancements\", (attribute, object) -> {\r\n            ListTag list = new ListTag();\r\n            Bukkit.advancementIterator().forEachRemaining((adv) -> {\r\n                if (object.getPlayerEntity().getAdvancementProgress(adv).isDone()) {\r\n                    list.add(adv.getKey().toString());\r\n                }\r\n            });\r\n            return list;\r\n        }, \"list_advancements\");\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.time_asleep>\r\n        // @returns DurationTag\r\n        // @description\r\n        // Returns the time the player has been asleep.\r\n        // -->\r\n        registerOnlineOnlyTag(DurationTag.class, \"time_asleep\", (attribute, object) -> {\r\n            return new DurationTag((long) object.getPlayerEntity().getSleepTicks());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.time>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the time the player is currently experiencing.\r\n        // This time could differ from the time that the rest of the world is currently experiencing if <@link command time> is being used on the player.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"time\", (attribute, object) -> {\r\n            return new ElementTag(object.getPlayerEntity().getPlayerTime());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.weather>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the type of weather the player is experiencing. This will be different\r\n        // from the weather currently in the world that the player is residing in if\r\n        // the weather is currently being forced onto the player.\r\n        // Returns null if the player does not currently have any forced weather.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"weather\", (attribute, object) -> {\r\n            if (object.getPlayerEntity().getPlayerWeather() != null) {\r\n                return new ElementTag(object.getPlayerEntity().getPlayerWeather());\r\n            }\r\n            else {\r\n                return null;\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.calculate_xp>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the calculated total amount of XP the player has, based on the amount of experience needed per level, for each level the player has.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"calculate_xp\", (attribute, object) -> {\r\n            int level = object.getLevel();\r\n            return new ElementTag(ExperienceCommand.TOTAL_XP_FOR_LEVEL(level) + (object.getExp() * ExperienceCommand.XP_FOR_NEXT_LEVEL(level)));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.xp_level>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the number of XP levels the player has.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"xp_level\", (attribute, object) -> {\r\n            return new ElementTag(object.getLevel());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.xp_to_next_level>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the amount of XP the player needs to get to the next level.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"xp_to_next_level\", (attribute, object) -> {\r\n            return new ElementTag(ExperienceCommand.XP_FOR_NEXT_LEVEL(object.getLevel()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.xp_total>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the total amount of experience points the player has.\r\n        // This is how much XP the player has ever received, not a current value.\r\n        // To get the current total, use <@link tag PlayerTag.calculate_xp>.\r\n        // Works with offline players.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"xp_total\", (attribute, object) -> {\r\n            return new ElementTag(object.getTotalExperience());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.xp>\r\n        // @returns ElementTag(Decimal)\r\n        // @description\r\n        // Returns the percentage of experience points to the next level.\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"xp\", (attribute, object) -> {\r\n            if (attribute.startsWith(\"level\", 2)) {\r\n                BukkitImplDeprecations.playerXpTags.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getLevel());\r\n            }\r\n            if (attribute.startsWith(\"to_next_level\", 2)) {\r\n                BukkitImplDeprecations.playerXpTags.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(ExperienceCommand.XP_FOR_NEXT_LEVEL(object.getLevel()));\r\n            }\r\n            if (attribute.startsWith(\"total\", 2)) {\r\n                BukkitImplDeprecations.playerXpTags.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getTotalExperience());\r\n            }\r\n            return new ElementTag(object.getExp() * 100);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.chat_prefix>\r\n        // @returns ElementTag\r\n        // @plugin Vault\r\n        // @mechanism PlayerTag.chat_prefix\r\n        // @description\r\n        // Returns the player's chat prefix.\r\n        // NOTE: May work with offline players.\r\n        // Requires a Vault-compatible chat plugin.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"chat_prefix\", (attribute, object) -> {\r\n            if (Depends.chat == null) {\r\n                if (!attribute.hasAlternative()) {\r\n                    Debug.echoError(\"'chat_prefix' tag unavailable: Vault and a chat plugin are required.\");\r\n                }\r\n                return null;\r\n            }\r\n            String prefix = Depends.chat.getPlayerPrefix(object.getWorld().getName(), object.getOfflinePlayer());\r\n            if (prefix == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(prefix, true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.chat_suffix>\r\n        // @returns ElementTag\r\n        // @plugin Vault\r\n        // @mechanism PlayerTag.chat_suffix\r\n        // @description\r\n        // Returns the player's chat suffix.\r\n        // NOTE: May work with offline players.\r\n        // Requires a Vault-compatible chat plugin.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"chat_suffix\", (attribute, object) -> {\r\n            if (Depends.chat == null) {\r\n                if (!attribute.hasAlternative()) {\r\n                    Debug.echoError(\"'chat_suffix' tag unavailable: Vault and a chat plugin are required.\");\r\n                }\r\n                return null;\r\n            }\r\n            String suffix = Depends.chat.getPlayerSuffix(object.getWorld().getName(), object.getOfflinePlayer());\r\n            if (suffix == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(suffix, true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.fake_block_locations>\r\n        // @returns ListTag(LocationTag)\r\n        // @description\r\n        // Returns a list of locations that the player will see a fake block at, as set by <@link command showfake> or connected commands.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"fake_block_locations\", (attribute, object) -> {\r\n            ListTag list = new ListTag();\r\n            FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(object.getUUID());\r\n            if (map != null) {\r\n                for (LocationTag loc : map.byLocation.keySet()) {\r\n                    list.addObject(loc.clone());\r\n                }\r\n            }\r\n            return list;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.fake_block[<location>]>\r\n        // @returns MaterialTag\r\n        // @description\r\n        // Returns the fake material that the player will see at the input location, as set by <@link command showfake> or connected commands.\r\n        // Works best alongside <@link tag PlayerTag.fake_block_locations>.\r\n        // Returns null if the player doesn't have a fake block at the location.\r\n        // -->\r\n        tagProcessor.registerTag(MaterialTag.class, \"fake_block\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            LocationTag input = attribute.paramAsType(LocationTag.class);\r\n            FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(object.getUUID());\r\n            if (map != null) {\r\n                FakeBlock block = map.byLocation.get(input);\r\n                if (block != null) {\r\n                    return block.material;\r\n                }\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.fake_entities>\r\n        // @returns ListTag(EntityTag)\r\n        // @description\r\n        // Returns a list of fake entities the player can see, as set by <@link command fakespawn>.\r\n        // Note that these entities are not being tracked by the server, so many operations may not be possible on them.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"fake_entities\", (attribute, object) -> {\r\n            ListTag list = new ListTag();\r\n            FakeEntity.FakeEntityMap map = FakeEntity.playersToEntities.get(object.getUUID());\r\n            if (map != null) {\r\n                for (Map.Entry<Integer, FakeEntity> entry : map.byId.entrySet()) {\r\n                    list.addObject(entry.getValue().entity);\r\n                }\r\n            }\r\n            return list;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.disguise_to_self[(<player>)]>\r\n        // @returns EntityTag\r\n        // @group properties\r\n        // @description\r\n        // Returns the fake entity used to disguise the entity in the player's self-view (only relevant to players), either globally (if no context input given), or to the specified player.\r\n        // Relates to <@link command disguise>.\r\n        // -->\r\n        tagProcessor.registerTag(EntityTag.class, \"disguise_to_self\", (attribute, object) -> {\r\n            HashMap<UUID, DisguiseCommand.TrackedDisguise> map = DisguiseCommand.disguises.get(object.getUUID());\r\n            if (map == null) {\r\n                return null;\r\n            }\r\n            DisguiseCommand.TrackedDisguise disguise;\r\n            if (attribute.hasParam()) {\r\n                PlayerTag player = attribute.paramAsType(PlayerTag.class);\r\n                if (player == null) {\r\n                    attribute.echoError(\"Invalid player for is_disguised tag.\");\r\n                    return null;\r\n                }\r\n                disguise = map.get(player.getUUID());\r\n                if (disguise == null) {\r\n                    disguise = map.get(null);\r\n                }\r\n            }\r\n            else {\r\n                disguise = map.get(null);\r\n            }\r\n            if (disguise == null) {\r\n                return null;\r\n            }\r\n            if (disguise.fakeToSelf == null) {\r\n                return null;\r\n            }\r\n            return disguise.fakeToSelf.entity;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.spectator_target>\r\n        // @returns EntityTag\r\n        // @mechanism PlayerTag.spectator_target\r\n        // @description\r\n        // Returns the entity that a spectator-mode player is currently spectating, if any.\r\n        // -->\r\n        registerOnlineOnlyTag(ObjectTag.class, \"spectator_target\", (attribute, object) -> {\r\n            if (object.getPlayerEntity().getGameMode() != GameMode.SPECTATOR) {\r\n                return null;\r\n            }\r\n            Entity target = object.getPlayerEntity().getSpectatorTarget();\r\n            if (target == null) {\r\n                return null;\r\n            }\r\n            return new EntityTag(target).getDenizenObject();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.packets_sent>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns a total count of how many network packets have been sent to this player while they have been online.\r\n        // It may be ideal to change setting \"Packets.Auto init\" in the Denizen config to \"true\" to guarantee this tag functions as expected.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"packets_sent\", (attribute, object) -> {\r\n            NetworkInterceptHelper.enable();\r\n            return new ElementTag(NMSHandler.packetHelper.getPacketStats(object.getPlayerEntity(), true));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.packets_received>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns a total count of how many network packets have been received from this player while they have been online.\r\n        // It may be ideal to change setting \"Packets.Auto init\" in the Denizen config to \"true\" to guarantee this tag functions as expected.\r\n        // -->\r\n        registerOnlineOnlyTag(ElementTag.class, \"packets_received\", (attribute, object) -> {\r\n            NetworkInterceptHelper.enable();\r\n            return new ElementTag(NMSHandler.packetHelper.getPacketStats(object.getPlayerEntity(), false));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.fish_hook>\r\n        // @returns EntityTag\r\n        // @description\r\n        // Returns the fishing hook a player has cast (if any).\r\n        // -->\r\n        registerOnlineOnlyTag(EntityTag.class, \"fish_hook\", (attribute, object) -> {\r\n            FishHook hook = NMSHandler.fishingHelper.getHookFrom(object.getPlayerEntity());\r\n            if (hook == null) {\r\n                return null;\r\n            }\r\n            return new EntityTag(hook);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.spawn_forced>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism PlayerTag.spawn_forced\r\n        // @description\r\n        // Returns whether the player's bed spawn location is forced (ie still valid even if a bed is missing).\r\n        // -->\r\n        registerOfflineTag(ElementTag.class, \"spawn_forced\", (attribute, object) -> {\r\n            if (object.isOnline()) {\r\n                return new ElementTag(NMSHandler.playerHelper.getSpawnForced(object.getPlayerEntity()));\r\n            }\r\n            return new ElementTag(object.getNBTEditor().isSpawnForced());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.last_action_time>\r\n        // @returns TimeTag\r\n        // @description\r\n        // Returns the time of the last direct input from the player. Internally used with <@link tag server.idle_timeout>.\r\n        // -->\r\n        registerOnlineOnlyTag(TimeTag.class, \"last_action_time\", (attribute, object) -> {\r\n            // The internal time values use monotonic time - this converts to real time.\r\n            long playerMilliTime = NMSHandler.playerHelper.getLastActionTime(object.getPlayerEntity());\r\n            return new TimeTag(CoreUtilities.monotonicMillisToReal(playerMilliTime));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.scoreboard_id>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ID of the scoreboard from <@link command scoreboard> that a player is currently viewing, if any.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"scoreboard_id\", (attribute, object) -> {\r\n            String id = ScoreboardHelper.viewerMap.get(object.getUUID());\r\n            if (id == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(id);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.scoreboard_team_name[(<board>)]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the name of the team the player is in for a given scoreboard, if any.\r\n        // If no scoreboard is specified, uses the default (main) board.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"scoreboard_team_name\", (attribute, object) -> {\r\n            Scoreboard board = attribute.hasParam() ? ScoreboardHelper.getScoreboard(attribute.getParam()) : ScoreboardHelper.getMain();\r\n            Team team = board.getEntryTeam(object.getName());\r\n            if (team == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(team.getName());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.bossbar_ids>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all bossbars from <@link command bossbar> that this player can see.\r\n        // Does not list bossbars created by any other source.\r\n        // -->\r\n        registerOnlineOnlyTag(ListTag.class, \"bossbar_ids\", (attribute, object) -> {\r\n            ListTag result = new ListTag();\r\n            for (Map.Entry<String, BossBar> bar : BossBarCommand.bossBarMap.entrySet()) {\r\n                if (bar.getValue().getPlayers().contains(object.getPlayerEntity())) {\r\n                    result.addObject(new ElementTag(bar.getKey(), true));\r\n                }\r\n            }\r\n            return result;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PlayerTag.tab_completions[<command>]>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all tab completions for the given plaintext of a command.\r\n        // Input is formatted equivalent to if it were typed into a chat bar, minus the '/' slash at the start.\r\n        // Input must necessarily contain at least one space.\r\n        // For example: \"<player.tab_completions[npc ]>\" will return all /NPC sub command names available to the player.\r\n        // This is only compatible with commands registered in Spigot. Meaning in particular, vanilla commands are not recognized or supported.\r\n        // -->\r\n        registerOnlineOnlyTag(ListTag.class, \"tab_completions\", (attribute, object) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            String cmdFull = attribute.getParam();\r\n            int space = cmdFull.indexOf(' ');\r\n            if (space == -1) {\r\n                attribute.echoError(\"Invalid command input '\" + cmdFull + \"': must have at least one space\");\r\n                return null;\r\n            }\r\n            String cmdName = cmdFull.substring(0, space);\r\n            PluginCommand actualCmd = Bukkit.getPluginCommand(cmdName);\r\n            if (actualCmd == null) {\r\n                attribute.echoError(\"Unknown command '\" + cmdName + \"'\");\r\n                return null;\r\n            }\r\n            String args = cmdFull.substring(space + 1);\r\n            ListTag result = new ListTag();\r\n            for (String str : actualCmd.tabComplete(object.getPlayerEntity(), cmdName, CoreUtilities.split(args, ' ').toArray(new String[0]))) {\r\n                result.addObject(new ElementTag(str, true));\r\n            }\r\n            return result;\r\n        });\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.skin_model>\r\n            // @returns ElementTag\r\n            // @description\r\n            // Returns the player's skin model, either CLASSIC or SLIM.\r\n            // -->\r\n            registerOnlineOnlyTag(ElementTag.class, \"skin_model\", (attribute, object) -> {\r\n                return MultiVersionHelper1_18.getSkinModel(object.getPlayerEntity());\r\n            });\r\n        }\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <PlayerTag.last_death_location>\r\n            // @returns LocationTag\r\n            // @mechanism PlayerTag.last_death_location\r\n            // @description\r\n            // Returns the location where the player last died, if any.\r\n            // Works with offline players.\r\n            // -->\r\n            registerOfflineTag(LocationTag.class, \"last_death_location\", (attribute, object) -> {\r\n                Location deathLoc = object.getOfflinePlayer().getLastDeathLocation();\r\n                return deathLoc != null ? new LocationTag(deathLoc) : null;\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object PlayerTag\r\n            // @name last_death_location\r\n            // @input LocationTag\r\n            // @description\r\n            // Sets the player's last death location, note that this only updates clientside when the player respawns.\r\n            // Works with offline players.\r\n            // See also <@link mechanism PlayerTag.refresh_player>.\r\n            // @tags\r\n            // <PlayerTag.last_death_location>\r\n            // -->\r\n            registerOfflineMechanism(\"last_death_location\", LocationTag.class, (object, mechanism, input) -> {\r\n                if (object.isOnline()) {\r\n                    object.getPlayerEntity().setLastDeathLocation(input);\r\n                }\r\n                else {\r\n                    object.getNBTEditor().setLastDeathLocation(input);\r\n                }\r\n            });\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name refresh_player\r\n        // @input None\r\n        // @description\r\n        // Refreshes the player's client, resending some internal data.\r\n        // -->\r\n        registerOnlineOnlyMechanism(\"refresh_player\", (object, mechanism) -> {\r\n            NMSHandler.playerHelper.refreshPlayer(object.getPlayerEntity());\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name whitelisted\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the player is whitelisted.\r\n        // Works with offline players.\r\n        // @tags\r\n        // <PlayerTag.whitelisted>\r\n        // -->\r\n        tagProcessor.registerMechanism(\"whitelisted\", false, ElementTag.class, (object, mechanism, input) -> {\r\n            if (mechanism.requireBoolean()) {\r\n                object.getOfflinePlayer().setWhitelisted(input.asBoolean());\r\n            }\r\n        }, \"is_whitelisted\");\r\n\r\n\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name bed_spawn_location\r\n        // @input LocationTag\r\n        // @description\r\n        // Sets the bed location that the player respawns at.\r\n        // Provide no input to unset.\r\n        // @tags\r\n        // <PlayerTag.bed_spawn>\r\n        // -->\r\n        registerOfflineMechanism(\"bed_spawn_location\", (object, mechanism) -> {\r\n            if (!mechanism.hasValue()) {\r\n                object.setBedSpawnLocation(null);\r\n            }\r\n            else if (mechanism.requireObject(LocationTag.class)) {\r\n                object.setBedSpawnLocation(mechanism.valueAsType(LocationTag.class));\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name spawn_forced\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the player's bed spawn location is forced (ie still valid even if a bed is missing).\r\n        // @tags\r\n        // <PlayerTag.spawn_forced>\r\n        // -->\r\n        registerOfflineMechanism(\"spawn_forced\", ElementTag.class, (object, mechanism, input) -> {\r\n            if (!mechanism.requireBoolean()) {\r\n                return;\r\n            }\r\n            if (object.isOnline()) {\r\n                NMSHandler.playerHelper.setSpawnForced(object.getPlayerEntity(), input.asBoolean());\r\n            }\r\n            else {\r\n                object.getNBTEditor().setSpawnForced(input.asBoolean());\r\n            }\r\n        });\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n\r\n            // <--[mechanism]\r\n            // @object PlayerTag\r\n            // @name links\r\n            // @input ListTag(MapTag)\r\n            // @description\r\n            // Sends the specified list of server links to the player. This will override existing links player has.\r\n            // Each item in the list must be a MapTag in <@link language Server Links Format>.\r\n            // Generally prefer <@link mechanism PlayerTag.add_links>.\r\n            // -->\r\n            registerOnlineOnlyMechanism(\"links\", ListTag.class, (player, mechanism, input) -> {\r\n                player.getPlayerEntity().sendLinks(Utilities.replaceServerLinks(Bukkit.getServerLinks().copy(), input, mechanism.context));\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object PlayerTag\r\n            // @name add_links\r\n            // @input ListTag(MapTag)\r\n            // @description\r\n            // Adds the specified list of server links to the player. Each item in the list must be a MapTag in <@link language Server Links Format>.\r\n            // -->\r\n            registerOnlineOnlyMechanism(\"add_links\", ListTag.class, (player, mechanism, input) -> {\r\n                player.getPlayerEntity().sendLinks(Utilities.fillServerLinks(Bukkit.getServerLinks().copy(), input, mechanism.context));\r\n            });\r\n        }\r\n    }\r\n\r\n    public static ObjectTagProcessor<PlayerTag> tagProcessor = new ObjectTagProcessor<>();\r\n\r\n    /**\r\n     * Needed for validation on tags that mess with 'getNBTEditor' or similar special calls for offline-player-compatibility logic.\r\n     */\r\n    public static <R extends ObjectTag> void registerOfflineTag(Class<R> returnType, String name, TagRunnable.ObjectInterface<PlayerTag, R> runnable, String... variants) {\r\n        tagProcessor.registerTag(returnType, name, (attribute, object) -> {\r\n            if (!object.isValid()) {\r\n                if (!attribute.hasAlternative()) {\r\n                    attribute.echoError(\"Player is not considered valid in tag '\" + attribute.getAttributeWithoutParam(1) + \"' for player: \" + object.debuggable());\r\n                }\r\n                return null;\r\n            }\r\n            return runnable.run(attribute, object);\r\n        }, variants);\r\n    }\r\n\r\n    public static <R extends ObjectTag> void registerOnlineOnlyTag(Class<R> returnType, String name, TagRunnable.ObjectInterface<PlayerTag, R> runnable, String... variants) {\r\n        tagProcessor.registerTag(returnType, name, (attribute, object) -> {\r\n            if (!object.isOnline()) {\r\n                if (!attribute.hasAlternative()) {\r\n                    attribute.echoError(\"Player is not online, but tag '\" + attribute.getAttributeWithoutParam(1) + \"' requires the player be online, for player: \" + object.debuggable());\r\n                }\r\n                return null;\r\n            }\r\n            return runnable.run(attribute, object);\r\n        }, variants);\r\n    }\r\n\r\n    public static void registerOnlineOnlyMechanism(String name, Mechanism.GenericMechRunnerInterface<PlayerTag> runnable) {\r\n        tagProcessor.registerMechanism(name, false, (object, mechanism) -> {\r\n            if (!object.isOnline()) {\r\n                mechanism.echoError(\"Player is not online, but mechanism '\" + name + \"' requires the player be online, for player: \" + object.debuggable());\r\n                return;\r\n            }\r\n            runnable.run(object, mechanism);\r\n        });\r\n    }\r\n\r\n    public static <P extends ObjectTag> void registerOnlineOnlyMechanism(String name, Class<P> paramType, Mechanism.ObjectInputMechRunnerInterface<PlayerTag, P> runnable) {\r\n        tagProcessor.registerMechanism(name, false, paramType, (object, mechanism, input) -> {\r\n            if (!object.isOnline()) {\r\n                mechanism.echoError(\"Player is not online, but mechanism '\" + name + \"' requires the player be online, for player: \" + object.debuggable());\r\n                return;\r\n            }\r\n            runnable.run(object, mechanism, input);\r\n        });\r\n    }\r\n\r\n    public static <P extends ObjectTag> void registerOfflineMechanism(String name, Class<P> paramType, Mechanism.ObjectInputMechRunnerInterface<PlayerTag, P> runnable, String... deprecatedVariants) {\r\n        tagProcessor.registerMechanism(name, false, paramType, (object, mechanism, input) -> {\r\n            if (!object.isValid()) {\r\n                mechanism.echoError(\"Player is not considered valid in mechanism '\" + name + \"' for player: \" + object.debuggable());\r\n                return;\r\n            }\r\n            runnable.run(object, mechanism, input);\r\n        }, deprecatedVariants);\r\n    }\r\n\r\n    public static void registerOfflineMechanism(String name, Mechanism.GenericMechRunnerInterface<PlayerTag> runnable, String... deprecatedVariants) {\r\n        tagProcessor.registerMechanism(name, false, (object, mechanism) -> {\r\n            if (!object.isValid()) {\r\n                mechanism.echoError(\"Player is not considered valid in mechanism '\" + name + \"' for player: \" + object.debuggable());\r\n                return;\r\n            }\r\n            runnable.run(object, mechanism);\r\n        }, deprecatedVariants);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return tagProcessor.getObjectAttribute(this, attribute);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getNextObjectTypeDown() {\r\n        if (isOnline()) {\r\n            return new EntityTag(getPlayerEntity());\r\n        }\r\n        return new ElementTag(identify());\r\n    }\r\n\r\n    public void applyProperty(Mechanism mechanism) {\r\n        mechanism.echoError(\"Cannot apply properties to a player!\");\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name send_climbable_materials\r\n        // @input ListTag(MaterialTag)\r\n        // @description\r\n        // Sends the player a list of climbable materials.\r\n        // To climb a block, the player has to stand in it, which means only non-full blocks can be climbed.\r\n        // Note that this gets reset once the player rejoins or once server resources are reloaded (see <@link event server resources reloaded>).\r\n        // @tags\r\n        // <server.vanilla_tagged_materials[<tag>]>\r\n        // @example\r\n        // # Lets the linked player climb iron_bars, while keeping other climbable materials climbable\r\n        // - adjust <player> send_climbable_materials:<server.vanilla_tagged_materials[climbable].include[iron_bars]>\r\n        // @example\r\n        // # Lets the linked player climb only acacia_buttons, making all other materials non-climbable.\r\n        // - adjust <player> send_climbable_materials:acacia_button\r\n        // -->\r\n        if (mechanism.matches(\"send_climbable_materials\") && mechanism.requireObject(ListTag.class)) {\r\n            List<MaterialTag> materialTags = mechanism.valueAsType(ListTag.class).filter(MaterialTag.class, mechanism.context);\r\n            List<Material> materials = new ArrayList<>();\r\n            for (MaterialTag materialTag : materialTags) {\r\n                Material material = materialTag.getMaterial();\r\n                if (!material.isBlock()) {\r\n                    mechanism.echoError(\"Invalid material specified '\" + material.name() + \"': must be a block material.\");\r\n                    continue;\r\n                }\r\n                materials.add(material);\r\n            }\r\n            NMSHandler.playerHelper.sendClimbableMaterials(getPlayerEntity(), materials);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name noclip\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // When true, causes the server to allow the player to noclip (ie, walk through blocks without being prevented).\r\n        // This is purely serverside. The client will still not walk through blocks.\r\n        // This is useful alongside <@link command showfake>.\r\n        // Note that this may sometimes be imperfect / sometimes momentarily continue to clip block.\r\n        // Note that this may also prevent other collisions (eg projectile impact) but is not guaranteed to.\r\n        // -->\r\n        if (mechanism.matches(\"noclip\") && mechanism.hasValue()) {\r\n            if (mechanism.getValue().asBoolean()) {\r\n                DenizenPacketHandler.forceNoclip.add(getUUID());\r\n            }\r\n            else {\r\n                DenizenPacketHandler.forceNoclip.remove(getUUID());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name respawn\r\n        // @input None\r\n        // @description\r\n        // Forces the player to respawn if they are on the death screen.\r\n        // -->\r\n        if (mechanism.matches(\"respawn\")) {\r\n            NMSHandler.packetHelper.respawn(getPlayerEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name vision\r\n        // @input ElementTag\r\n        // @description\r\n        // Changes the player's vision to that of the provided entity type. Valid types:\r\n        // ENDERMAN, CAVE_SPIDER, SPIDER, CREEPER\r\n        // Provide no value to reset the player's vision.\r\n        // Note: This is powered by a bug in Minecraft that has been present for a long time, but may at some point be 'fixed' by Mojang.\r\n        // -->\r\n        if (mechanism.matches(\"vision\")) {\r\n            if (!mechanism.hasValue()) {\r\n                NMSHandler.packetHelper.forceSpectate(getPlayerEntity(), getPlayerEntity());\r\n                return;\r\n            }\r\n            if (mechanism.requireEnum(EntityType.class)) {\r\n                NMSHandler.packetHelper.setVision(getPlayerEntity(), mechanism.getValue().asEnum(EntityType.class));\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name level\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the level on the player. Does not affect the current progression of experience towards next level.\r\n        // @tags\r\n        // <PlayerTag.xp_level>\r\n        // -->\r\n        if (mechanism.matches(\"level\") && mechanism.requireInteger()) {\r\n            setLevel(mechanism.getValue().asInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name item_slot\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the inventory slot that the player has selected.\r\n        // Works with offline players.\r\n        // @tags\r\n        // <PlayerTag.held_item_slot>\r\n        // -->\r\n        if (mechanism.matches(\"item_slot\") && mechanism.requireInteger()) {\r\n            if (isOnline()) {\r\n                getPlayerEntity().getInventory().setHeldItemSlot(mechanism.getValue().asInt() - 1);\r\n            }\r\n            else {\r\n                getNBTEditor().setItemInHand(mechanism.getValue().asInt() - 1);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name window_property\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets various properties of a window the player has open, such as the open page in a lectern.\r\n        // Input is of the form PROPERTY,VALUE where the value is a number.\r\n        // Note that any adjusted window properties are entirely clientside.\r\n        // Valid properties: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/InventoryView.Property.html>\r\n        // -->\r\n        if (mechanism.matches(\"window_property\")) {\r\n            String[] split = mechanism.getValue().asString().split(\",\", 2);\r\n            if (split.length != 2) {\r\n                Debug.echoError(\"Invalid input! Must be in the form PROPERTY,VALUE\");\r\n            }\r\n            else {\r\n                try {\r\n                    getPlayerEntity().setWindowProperty(InventoryView.Property.valueOf(split[0].toUpperCase()), Integer.parseInt(split[1]));\r\n                }\r\n                catch (NumberFormatException e) {\r\n                    Debug.echoError(\"Input value must be a number!\");\r\n                }\r\n                catch (IllegalArgumentException e) {\r\n                    Debug.echoError(\"Must specify a valid window property!\");\r\n                }\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name item_on_cursor\r\n        // @input ItemTag\r\n        // @description\r\n        // Sets the item on the player's cursor.\r\n        // This includes chest interfaces, inventories, and hotbars, etc.\r\n        // @tags\r\n        // <PlayerTag.item_on_cursor>\r\n        // -->\r\n        if (mechanism.matches(\"item_on_cursor\") && mechanism.requireObject(ItemTag.class)) {\r\n            getPlayerEntity().setItemOnCursor(mechanism.valueAsType(ItemTag.class).getItemStack());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name award_advancement\r\n        // @input ElementTag\r\n        // @description\r\n        // Awards an advancement to the player.\r\n        // @tags\r\n        // <PlayerTag.has_advancement[<name>]>\r\n        // -->\r\n        if (mechanism.matches(\"award_advancement\")) {\r\n            Advancement adv = AdvancementHelper.getAdvancement(mechanism.getValue().asString());\r\n            if (adv == null) {\r\n                if (mechanism.shouldDebug()) {\r\n                    Debug.echoError(\"Advancement '\" + mechanism.getValue().asString() + \"' does not exist.\");\r\n                }\r\n                return;\r\n            }\r\n            AdvancementProgress prog = getPlayerEntity().getAdvancementProgress(adv);\r\n            for (String criteria : prog.getRemainingCriteria()) {\r\n                prog.awardCriteria(criteria);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name revoke_advancement\r\n        // @input ElementTag\r\n        // @description\r\n        // Un-awards an advancement from the player.\r\n        // @tags\r\n        // <PlayerTag.has_advancement[<name>]>\r\n        // -->\r\n        if (mechanism.matches(\"revoke_advancement\")) {\r\n            Advancement adv = AdvancementHelper.getAdvancement(mechanism.getValue().asString());\r\n            if (adv == null) {\r\n                if (mechanism.shouldDebug()) {\r\n                    Debug.echoError(\"Advancement '\" + mechanism.getValue().asString() + \"' does not exist.\");\r\n                }\r\n                return;\r\n            }\r\n            AdvancementProgress prog = getPlayerEntity().getAdvancementProgress(adv);\r\n            for (String criteria : prog.getAwardedCriteria()) {\r\n                prog.revokeCriteria(criteria);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name fake_absorption_health\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Shows the player fake absorption health that persists on damage.\r\n        // -->\r\n        if (mechanism.matches(\"fake_absorption_health\") && mechanism.requireFloat()) {\r\n            NMSHandler.packetHelper.setFakeAbsorption(getPlayerEntity(), mechanism.getValue().asFloat());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name health_scale\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the 'health scale' on the Player. Each heart equals '2'. The standard health scale is\r\n        // 20, so for example, indicating a value of 40 will display double the amount of hearts\r\n        // standard.\r\n        // Player relogging will reset this mechanism.\r\n        // @tags\r\n        // <PlayerTag.health_scale>\r\n        // -->\r\n        if (mechanism.matches(\"health_scale\") && mechanism.requireDouble()) {\r\n            getPlayerEntity().setHealthScale(mechanism.getValue().asDouble());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name scale_health\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Enables or disables the health scale value. Disabling will result in the standard\r\n        // amount of hearts being shown.\r\n        // @tags\r\n        // <PlayerTag.health_is_scaled>\r\n        // -->\r\n        if (mechanism.matches(\"scale_health\") && mechanism.requireBoolean()) {\r\n            getPlayerEntity().setHealthScaled(mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // Allow offline editing of health values\r\n        if (mechanism.matches(\"max_health\") && mechanism.requireDouble()) {\r\n            setMaxHealth(mechanism.getValue().asDouble());\r\n        }\r\n\r\n        if (mechanism.matches(\"health\") && mechanism.requireDouble()) {\r\n            setHealth(mechanism.getValue().asDouble());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name resource_pack\r\n        // @input ElementTag(|ElementTag)\r\n        // @deprecated Use the \"resourcepack\" command instead of this old mechanism.\r\n        // @description\r\n        // Deprecated, use <@link command resourcepack>.\r\n        // -->\r\n        if (mechanism.matches(\"resource_pack\") || mechanism.matches(\"texture_pack\")) {\r\n            BukkitImplDeprecations.playerResourcePackMech.warn(mechanism.context);\r\n            String pack = mechanism.getValue().asString();\r\n            int pipe = pack.indexOf('|');\r\n            if (pipe > 0) {\r\n                String hash = pack.substring(pipe + 1);\r\n                pack = pack.substring(0, pipe);\r\n                if (hash.length() != 40) {\r\n                    Debug.echoError(\"Invalid resource_pack hash. Should be 40 characters of hexadecimal data.\");\r\n                    return;\r\n                }\r\n                byte[] hashData = new byte[20];\r\n                for (int i = 0; i < 20; i++) {\r\n                    hashData[i] = (byte) Integer.parseInt(hash.substring(i * 2, i * 2 + 2), 16);\r\n                }\r\n                getPlayerEntity().setResourcePack(pack, hashData);\r\n            }\r\n            else {\r\n                getPlayerEntity().setResourcePack(pack);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name saturation\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the current food saturation level of a player.\r\n        // Works with offline players.\r\n        // @tags\r\n        // <PlayerTag.saturation>\r\n        // -->\r\n        if (mechanism.matches(\"saturation\") && mechanism.requireFloat()) {\r\n            if (isOnline()) {\r\n                getPlayerEntity().setSaturation(mechanism.getValue().asFloat());\r\n            }\r\n            else {\r\n                getNBTEditor().setSaturation(mechanism.getValue().asFloat());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name send_map\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Forces a player to receive the entirety of the specified map ID instantly.\r\n        // This is mainly used as a way to correct bugs in map rendering.\r\n        // -->\r\n        if (mechanism.matches(\"send_map\") && mechanism.requireInteger()) {\r\n            MapView map = Bukkit.getServer().getMap((short) mechanism.getValue().asInt());\r\n            if (map != null) {\r\n                getPlayerEntity().sendMap(map);\r\n            }\r\n            else {\r\n                Debug.echoError(\"No map found for ID \" + mechanism.getValue().asInt() + \"!\");\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name food_level\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the current food level of a player. Typically, '20' is full.\r\n        // @tags\r\n        // <PlayerTag.food_level>\r\n        // -->\r\n        if (mechanism.matches(\"food_level\") && mechanism.requireInteger()) {\r\n            setFoodLevel(mechanism.getValue().asInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name can_fly\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the player is allowed to fly.\r\n        // Works with offline players.\r\n        // @tags\r\n        // <PlayerTag.can_fly>\r\n        // -->\r\n        if (mechanism.matches(\"can_fly\") && mechanism.requireBoolean()) {\r\n            if (isOnline()) {\r\n                getPlayerEntity().setAllowFlight(mechanism.getValue().asBoolean());\r\n            }\r\n            else {\r\n                getNBTEditor().setAllowFlight(mechanism.getValue().asBoolean());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name fly_speed\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the fly speed of the player. Valid range is 0.0 to 1.0\r\n        // Works with offline players.\r\n        // @tags\r\n        // <PlayerTag.fly_speed>\r\n        // -->\r\n        if (mechanism.matches(\"fly_speed\") && mechanism.requireFloat()) {\r\n            float val = mechanism.getValue().asFloat();\r\n            if (val < -1 || val > 1) {\r\n                mechanism.echoError(\"Invalid speed specified. Must be between -1 and 1.\");\r\n                return;\r\n            }\r\n            if (isOnline()) {\r\n                getPlayerEntity().setFlySpeed(val);\r\n            }\r\n            else {\r\n                getNBTEditor().setFlySpeed(val);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name flying\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the player is flying.\r\n        // @tags\r\n        // <PlayerTag.is_flying>\r\n        // -->\r\n        if (mechanism.matches(\"flying\") && mechanism.requireBoolean()) {\r\n            boolean doFly = mechanism.getValue().asBoolean();\r\n            if (doFly && !getPlayerEntity().getAllowFlight()) {\r\n                Debug.echoError(\"Must adjust 'can_fly:true' before you can adjust 'flying:true'\");\r\n                return;\r\n            }\r\n            getPlayerEntity().setFlying(doFly);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name sprinting\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the player is sprinting.\r\n        // @tags\r\n        // <PlayerTag.is_sprinting>\r\n        // -->\r\n        if (mechanism.matches(\"sprinting\") && mechanism.requireBoolean()) {\r\n            getPlayerEntity().setSprinting(mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name gamemode\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the game mode of the player.\r\n        // Valid gamemodes are survival, creative, adventure, and spectator.\r\n        // Works with offline players.\r\n        // @tags\r\n        // <PlayerTag.gamemode>\r\n        // -->\r\n        if (mechanism.matches(\"gamemode\") && mechanism.requireEnum(GameMode.class)) {\r\n            setGameMode(GameMode.valueOf(mechanism.getValue().asString().toUpperCase()));\r\n        }\r\n\r\n        if (mechanism.matches(\"kick\")) {\r\n            BukkitImplDeprecations.oldKickMech.warn(mechanism.context);\r\n            getPlayerEntity().kickPlayer(mechanism.getValue().asString());\r\n        }\r\n\r\n        if (mechanism.matches(\"weather\") && mechanism.requireEnum(WeatherType.class)) {\r\n            BukkitImplDeprecations.oldWeatherMech.warn(mechanism.context);\r\n            getPlayerEntity().setPlayerWeather(WeatherType.valueOf(mechanism.getValue().asString().toUpperCase()));\r\n        }\r\n\r\n        if (mechanism.matches(\"reset_weather\")) {\r\n            BukkitImplDeprecations.oldWeatherMech.warn(mechanism.context);\r\n            getPlayerEntity().resetPlayerWeather();\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name player_list_name\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the entry that is shown in the 'player list' that is shown when pressing tab.\r\n        // @tags\r\n        // <PlayerTag.list_name>\r\n        // -->\r\n        if (mechanism.matches(\"player_list_name\")) {\r\n            PaperAPITools.instance.setPlayerListName(getPlayerEntity(), mechanism.getValue().asString());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name display_name\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the name displayed for the player when chatting.\r\n        // This only applies if there's a chat plugin using it.\r\n        // @tags\r\n        // <PlayerTag.display_name>\r\n        // -->\r\n        if (mechanism.matches(\"display_name\")) {\r\n            getPlayerEntity().setDisplayName(mechanism.getValue().asString());\r\n            return;\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name show_workbench\r\n        // @input LocationTag\r\n        // @description\r\n        // Shows the player a workbench GUI corresponding to a given location.\r\n        // -->\r\n        if (mechanism.matches(\"show_workbench\") && mechanism.requireObject(LocationTag.class)) {\r\n            getPlayerEntity().openWorkbench(mechanism.valueAsType(LocationTag.class), true);\r\n            return;\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name location\r\n        // @input LocationTag\r\n        // @description\r\n        // If the player is online, teleports the player to a given location.\r\n        // Otherwise, sets the player's next spawn location.\r\n        // @tags\r\n        // <PlayerTag.location>\r\n        // -->\r\n        if (mechanism.matches(\"location\") && mechanism.requireObject(LocationTag.class)) {\r\n            setLocation(mechanism.valueAsType(LocationTag.class));\r\n        }\r\n\r\n        if (mechanism.matches(\"time\") && mechanism.requireInteger()) {\r\n            BukkitImplDeprecations.oldTimeMech.warn(mechanism.context);\r\n            getPlayerEntity().setPlayerTime(mechanism.getValue().asInt(), true);\r\n        }\r\n\r\n        if (mechanism.matches(\"freeze_time\")) {\r\n            BukkitImplDeprecations.oldTimeMech.warn(mechanism.context);\r\n            if (mechanism.requireInteger(\"Invalid integer specified. Assuming current world time.\")) {\r\n                getPlayerEntity().setPlayerTime(mechanism.getValue().asInt(), false);\r\n            }\r\n            else {\r\n                getPlayerEntity().setPlayerTime(getPlayerEntity().getWorld().getTime(), false);\r\n            }\r\n        }\r\n\r\n        if (mechanism.matches(\"reset_time\")) {\r\n            BukkitImplDeprecations.oldTimeMech.warn(mechanism.context);\r\n            getPlayerEntity().resetPlayerTime();\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name walk_speed\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the walk speed of the player. The standard value is '0.2'. Valid range is 0.0 to 1.0\r\n        // Works with offline players.\r\n        // @tags\r\n        // <PlayerTag.walk_speed>\r\n        // -->\r\n        if (mechanism.matches(\"walk_speed\") && mechanism.requireFloat()) {\r\n            float val = mechanism.getValue().asFloat();\r\n            if (val < -1 || val > 1) {\r\n                mechanism.echoError(\"Invalid speed specified. Must be between -1 and 1.\");\r\n                return;\r\n            }\r\n            if (isOnline()) {\r\n                getPlayerEntity().setWalkSpeed(val);\r\n            }\r\n            else {\r\n                getNBTEditor().setWalkSpeed(val);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name exhaustion\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the exhaustion level of a player.\r\n        // Works with offline players.\r\n        // @tags\r\n        // <PlayerTag.exhaustion>\r\n        // -->\r\n        if (mechanism.matches(\"exhaustion\") && mechanism.requireFloat()) {\r\n            if (isOnline()) {\r\n                getPlayerEntity().setExhaustion(mechanism.getValue().asFloat());\r\n            }\r\n            else {\r\n                getNBTEditor().setExhaustion(mechanism.getValue().asFloat());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name show_entity\r\n        // @input EntityTag\r\n        // @description\r\n        // Shows the player a previously hidden entity.\r\n        // To show for everyone, use <@link mechanism EntityTag.show_to_players>.\r\n        // See also <@link mechanism PlayerTag.hide_entity>.\r\n        // -->\r\n        if (mechanism.matches(\"show_entity\") && mechanism.requireObject(EntityTag.class)) {\r\n            HideEntitiesHelper.unhideEntity(getPlayerEntity(), mechanism.valueAsType(EntityTag.class).getBukkitEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name hide_entity\r\n        // @input EntityTag\r\n        // @description\r\n        // Hides an entity from the player.\r\n        // To hide from everyone, use <@link mechanism EntityTag.hide_from_players>.\r\n        // See also <@link mechanism PlayerTag.show_entity>.\r\n        // -->\r\n        if (mechanism.matches(\"hide_entity\")) {\r\n            if (!mechanism.getValue().asString().isEmpty()) {\r\n                ListTag split = mechanism.valueAsType(ListTag.class);\r\n                if (split.size() > 0 && new ElementTag(split.get(0)).matchesType(EntityTag.class)) {\r\n                    EntityTag entity = EntityTag.valueOf(split.get(0), mechanism.context);\r\n                    if (!entity.isSpawnedOrValidForTag()) {\r\n                        Debug.echoError(\"Can't hide the unspawned entity '\" + split.get(0) + \"'!\");\r\n                    }\r\n                    else {\r\n                        HideEntitiesHelper.hideEntity(getPlayerEntity(), entity.getBukkitEntity());\r\n                    }\r\n                }\r\n                else {\r\n                    Debug.echoError(\"'\" + split.get(0) + \"' is not a valid entity!\");\r\n                }\r\n            }\r\n            else {\r\n                Debug.echoError(\"Must specify an entity to hide!\");\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name hide_entities\r\n        // @input ElementTag\r\n        // @description\r\n        // Hides a matchable type of entity from the player. Can use any advanced entity matchers per <@link language Advanced Object Matching>.\r\n        // To hide a specific entity from the player, use <@link mechanism PlayerTag.hide_entity>.\r\n        // To remove hide sets, use <@link mechanism PlayerTag.unhide_entities>.\r\n        // Note that dynamic matchables like 'entity_flagged' will behave in unexpected ways when dynamically changing.\r\n        // -->\r\n        if (mechanism.matches(\"hide_entities\") && mechanism.hasValue()) {\r\n            HideEntitiesHelper.PlayerHideMap map = HideEntitiesHelper.getPlayerMapFor(getUUID());\r\n            String hideMe = mechanism.getValue().asString();\r\n            map.matchersHidden.add(hideMe);\r\n            if (isOnline()) {\r\n                for (Entity ent : getPlayerEntity().getWorld().getEntities()) {\r\n                    if (new EntityTag(ent).tryAdvancedMatcher(hideMe, mechanism.context) && map.shouldHide(ent)) {\r\n                        NMSHandler.entityHelper.sendHidePacket(getPlayerEntity(), ent);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name unhide_entities\r\n        // @input ElementTag\r\n        // @description\r\n        // Removes any entity hides added by <@link mechanism PlayerTag.hide_entities>. Input must exactly match the input given to the hide mechanism.\r\n        // -->\r\n        if (mechanism.matches(\"unhide_entities\") && mechanism.hasValue()) {\r\n            HideEntitiesHelper.PlayerHideMap map = HideEntitiesHelper.getPlayerMapFor(getUUID());\r\n            String unhideMe = mechanism.getValue().asString();\r\n            map.matchersHidden.remove(unhideMe);\r\n            if (map.matchersHidden.isEmpty() && map.entitiesHidden.isEmpty() && map.overridinglyShow.isEmpty()) {\r\n                HideEntitiesHelper.playerHides.remove(getUUID());\r\n            }\r\n            if (isOnline()) {\r\n                for (Entity ent : getPlayerEntity().getWorld().getEntities()) {\r\n                    if (new EntityTag(ent).tryAdvancedMatcher(unhideMe, mechanism.context) && !map.shouldHide(ent)) {\r\n                        NMSHandler.entityHelper.sendShowPacket(getPlayerEntity(), ent);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        if (mechanism.matches(\"show_boss_bar\")) {\r\n            BukkitImplDeprecations.oldBossBarMech.warn(mechanism.context);\r\n            if (!mechanism.getValue().asString().isEmpty()) {\r\n                String[] split = mechanism.getValue().asString().split(\"\\\\|\", 2);\r\n                if (split.length == 2 && new ElementTag(split[0]).isDouble()) {\r\n                    BossBarHelper.showSimpleBossBar(getPlayerEntity(), split[1], new ElementTag(split[0]).asDouble() * (1.0 / 200.0));\r\n                }\r\n                else {\r\n                    BossBarHelper.showSimpleBossBar(getPlayerEntity(), split[0], 1.0);\r\n                }\r\n            }\r\n            else {\r\n                BossBarHelper.removeSimpleBossBar(getPlayerEntity());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name fake_experience\r\n        // @input ElementTag(Decimal)(|ElementTag(Number))\r\n        // @description\r\n        // Shows the player a fake experience bar, with a number between 0.0 and 1.0 to specify how far along the bar is.\r\n        // Use with no input value to reset to the player's normal experience.\r\n        // Optionally, you can specify a fake experience level.\r\n        // For example: - adjust <player> fake_experience:0.5|5\r\n        // -->\r\n        if (mechanism.matches(\"fake_experience\")) {\r\n            if (!mechanism.getValue().asString().isEmpty()) {\r\n                String[] split = mechanism.getValue().asString().split(\"\\\\|\", 2);\r\n                if (split.length > 0 && new ElementTag(split[0]).isFloat()) {\r\n                    if (split.length > 1 && new ElementTag(split[1]).isInt()) {\r\n                        getPlayerEntity().sendExperienceChange(new ElementTag(split[0]).asFloat(), new ElementTag(split[1]).asInt());\r\n                    }\r\n                    else {\r\n                        getPlayerEntity().sendExperienceChange(new ElementTag(split[0]).asFloat());\r\n                    }\r\n                }\r\n                else {\r\n                    mechanism.echoError(\"'\" + split[0] + \"' is not a valid decimal number!\");\r\n                }\r\n            }\r\n            else {\r\n                getPlayerEntity().sendExperienceChange(getPlayerEntity().getExp(), getPlayerEntity().getLevel());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name fake_health\r\n        // @input ElementTag(Decimal)(|ElementTag(Number)(|ElementTag(Decimal)))\r\n        // @description\r\n        // Shows the player a fake health bar, with a number between 0 and 20, where 1 is half of a heart.\r\n        // Use with no input value to reset to the player's normal health.\r\n        // Optionally, you can specify a fake food level, between 0 and 20.\r\n        // You can also optionally specify a food saturation level between 0 and 10.\r\n        // For example:\r\n        // - adjust <player> fake_health:1\r\n        // - adjust <player> fake_health:10|15\r\n        // - adjust <player> fake_health:<player.health>|3|0\r\n        // -->\r\n        if (mechanism.matches(\"fake_health\")) {\r\n            if (!mechanism.getValue().asString().isEmpty()) {\r\n                String[] split = mechanism.getValue().asString().split(\"\\\\|\", 3);\r\n                if (split.length > 0 && new ElementTag(split[0]).isFloat()) {\r\n                    if (split.length > 1 && new ElementTag(split[1]).isInt()) {\r\n                        if (split.length > 2 && new ElementTag(split[2]).isFloat()) {\r\n                            NMSHandler.packetHelper.showHealth(getPlayerEntity(), new ElementTag(split[0]).asFloat(),\r\n                                    new ElementTag(split[1]).asInt(), new ElementTag(split[2]).asFloat());\r\n                        }\r\n                        else {\r\n                            NMSHandler.packetHelper.showHealth(getPlayerEntity(), new ElementTag(split[0]).asFloat(),\r\n                                    new ElementTag(split[1]).asInt(), getPlayerEntity().getSaturation());\r\n                        }\r\n                    }\r\n                    else {\r\n                        NMSHandler.packetHelper.showHealth(getPlayerEntity(), new ElementTag(split[0]).asFloat(),\r\n                                getPlayerEntity().getFoodLevel(), getPlayerEntity().getSaturation());\r\n                    }\r\n                }\r\n                else {\r\n                    Debug.echoError(\"'\" + split[0] + \"' is not a valid decimal number!\");\r\n                }\r\n            }\r\n            else {\r\n                NMSHandler.packetHelper.resetHealth(getPlayerEntity());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name fake_mount_health\r\n        // @input ElementTag(Decimal)|ElementTag(Decimal)\r\n        // @description\r\n        // Shows the player a fake health bar for their mounted. Specify both the current and maximum health values.\r\n        // Use with no input value to reset to the real health value.\r\n        // Using a health of '0' will make your mount look dead but continue to function.\r\n        // For example:\r\n        // - adjust <player> fake_mount_health:10|15\r\n        // -->\r\n        if (mechanism.matches(\"fake_mount_health\")) {\r\n            if (!isOnline() || !getPlayerEntity().isInsideVehicle()) {\r\n                mechanism.echoError(\"Cannot run fake_mount_health - player is offline or unmounted.\");\r\n                return;\r\n            }\r\n            Entity vehicle = getPlayerEntity().getVehicle();\r\n            if (!(vehicle instanceof LivingEntity)) {\r\n                mechanism.echoError(\"Cannot run fake_mount_health - vehicle is not a living entity.\");\r\n                return;\r\n            }\r\n            LivingEntity liveVehicle = (LivingEntity) vehicle;\r\n            double current, maximum;\r\n            if (mechanism.hasValue()) {\r\n                ListTag input = mechanism.valueAsType(ListTag.class);\r\n                if (input.size() != 2) {\r\n                    mechanism.echoError(\"Cannot run fake_mount_health - improper input.\");\r\n                    return;\r\n                }\r\n                current = new ElementTag(input.get(0)).asDouble();\r\n                maximum = new ElementTag(input.get(1)).asDouble();\r\n            }\r\n            else {\r\n                current = liveVehicle.getHealth();\r\n                maximum = liveVehicle.getMaxHealth();\r\n            }\r\n            NMSHandler.packetHelper.showMobHealth(getPlayerEntity(), liveVehicle, current, maximum);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name fake_entity_health\r\n        // @input MapTag\r\n        // @description\r\n        // Shows the player a fake health number for a given entity.\r\n        // Input is a map with 'entity' as the target entity, and 'health' as the health amount.\r\n        // Optionally add 'max' to set the max health too.\r\n        // Using health of '0' can cause an entity to look dead.\r\n        // For example:\r\n        // - adjust <player> fake_entity_health:[entity=<player.target>;health=0]\r\n        // -->\r\n        if (mechanism.matches(\"fake_entity_health\") && mechanism.requireObject(MapTag.class)) {\r\n            if (!isOnline()) {\r\n                mechanism.echoError(\"Cannot run fake_entity_health - player is offline.\");\r\n                return;\r\n            }\r\n            MapTag map = mechanism.valueAsType(MapTag.class);\r\n            EntityTag entity = map.getObjectAs(\"entity\", EntityTag.class, mechanism.context);\r\n            ElementTag healthObject = map.getElement(\"health\");\r\n            ElementTag maxObject = map.getElement(\"max\");\r\n            if (healthObject == null) {\r\n                mechanism.echoError(\"Cannot run fake_entity_health - input map is missing 'health' key.\");\r\n                return;\r\n            }\r\n            double health = healthObject.asDouble();\r\n            if (entity == null || !entity.isLivingEntity()) {\r\n                mechanism.echoError(\"Cannot run fake_entity_health - entity is invalid or not living.\");\r\n                return;\r\n            }\r\n            double max = maxObject == null ? entity.getLivingEntity().getMaxHealth() : maxObject.asDouble();\r\n            NMSHandler.packetHelper.showMobHealth(getPlayerEntity(), entity.getLivingEntity(), health, max);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name fake_equipment\r\n        // @input EntityTag(|ElementTag|ItemTag)\r\n        // @description\r\n        // Shows the player fake equipment on the specified living entity, which has no real non-visual effects.\r\n        // Input is in the form Entity|Slot|Item, where the slot can be one of the following: HAND, OFF_HAND, BOOTS, LEGS, CHEST, HEAD\r\n        // Optionally, exclude the slot and item to stop showing the fake equipment, if any, on the specified entity.\r\n        // For example:\r\n        // - adjust <player> fake_equipment:<[some_entity]>|chest|diamond_chestplate\r\n        // - adjust <player> fake_equipment:<player>|head|jack_o_lantern\r\n        // Consider instead using <@link command fakeequip>.\r\n        // -->\r\n        if (mechanism.matches(\"fake_equipment\")) {\r\n            if (!mechanism.getValue().asString().isEmpty()) {\r\n                String[] split = mechanism.getValue().asString().split(\"\\\\|\", 3);\r\n                if (split.length > 0 && new ElementTag(split[0]).matchesType(EntityTag.class)) {\r\n                    String slot = split.length > 1 ? split[1].toUpperCase() : null;\r\n                    if (split.length > 1 && (new ElementTag(slot).matchesEnum(EquipmentSlot.class)\r\n                            || slot.equals(\"MAIN_HAND\") || slot.equals(\"BOOTS\"))) {\r\n                        if (split.length > 2 && new ElementTag(split[2]).matchesType(ItemTag.class)) {\r\n                            if (slot.equals(\"MAIN_HAND\")) {\r\n                                slot = \"HAND\";\r\n                            }\r\n                            else if (slot.equals(\"BOOTS\")) {\r\n                                slot = \"FEET\";\r\n                            }\r\n                            NMSHandler.packetHelper.showEquipment(getPlayerEntity(),\r\n                                    new ElementTag(split[0]).asType(EntityTag.class, mechanism.context).getLivingEntity(),\r\n                                    EquipmentSlot.valueOf(slot),\r\n                                    new ElementTag(split[2]).asType(ItemTag.class, mechanism.context).getItemStack());\r\n                        }\r\n                        else if (split.length > 2) {\r\n                            Debug.echoError(\"'\" + split[2] + \"' is not a valid ItemTag!\");\r\n                        }\r\n                    }\r\n                    else if (split.length > 1) {\r\n                        Debug.echoError(\"'\" + split[1] + \"' is not a valid slot; must be HAND, OFF_HAND, BOOTS, LEGS, CHEST, or HEAD!\");\r\n                    }\r\n                    else {\r\n                        NMSHandler.packetHelper.resetEquipment(getPlayerEntity(),\r\n                                new ElementTag(split[0]).asType(EntityTag.class, mechanism.context).getLivingEntity());\r\n                    }\r\n                }\r\n                else {\r\n                    Debug.echoError(\"'\" + split[0] + \"' is not a valid EntityTag!\");\r\n                }\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name fov_multiplier\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the player's field of view multiplier.\r\n        // Leave input empty to reset.\r\n        // Note: Values outside a (-1, 1) range will have little effect on the player's fov.\r\n        // -->\r\n        if (mechanism.matches(\"fov_multiplier\")) {\r\n            if (mechanism.hasValue() && mechanism.requireFloat()) {\r\n                NMSHandler.packetHelper.setFieldOfView(getPlayerEntity(), mechanism.getValue().asFloat());\r\n            }\r\n            else {\r\n                NMSHandler.packetHelper.setFieldOfView(getPlayerEntity(), Float.NaN);\r\n            }\r\n        }\r\n\r\n        if (mechanism.matches(\"item_message\")) {\r\n            BukkitImplDeprecations.itemMessage.warn(mechanism.context);\r\n            ItemChangeMessage.sendMessage(getPlayerEntity(), mechanism.getValue().asString());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name show_endcredits\r\n        // @input None\r\n        // @description\r\n        // Shows the player the end credits.\r\n        // -->\r\n        if (mechanism.matches(\"show_endcredits\")) {\r\n            NMSHandler.playerHelper.showEndCredits(getPlayerEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name show_demo\r\n        // @input None\r\n        // @description\r\n        // Shows the player the demo screen.\r\n        // -->\r\n        if (mechanism.matches(\"show_demo\")) {\r\n            NMSHandler.packetHelper.showDemoScreen(getPlayerEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name spectator_target\r\n        // @input EntityTag\r\n        // @description\r\n        // Switches the player to spectator mode and causes them to immediately start spectating an entity.\r\n        // To instead fake this effect, use <@link mechanism PlayerTag.spectate>\r\n        // Give no input to detach the player from any target.\r\n        // @tags\r\n        // <PlayerTag.spectator_target>\r\n        // -->\r\n        if (mechanism.matches(\"spectator_target\")) {\r\n            if (mechanism.hasValue()) {\r\n                getPlayerEntity().setGameMode(GameMode.SPECTATOR);\r\n                getPlayerEntity().setSpectatorTarget(mechanism.valueAsType(EntityTag.class).getBukkitEntity());\r\n            }\r\n            else if (getPlayerEntity().getGameMode() == GameMode.SPECTATOR) {\r\n                getPlayerEntity().setSpectatorTarget(null);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name spectate\r\n        // @input EntityTag\r\n        // @description\r\n        // Forces the player to spectate from the entity's point of view, using a packet (meaning, the player starts spectating clientside, but not serverside).\r\n        // The player will not move from their existing location serverside.\r\n        // To cause real spectator mode spectating, use <@link mechanism PlayerTag.spectator_target>\r\n        // Note that in some cases you may want to force the player into the spectate gamemode prior to using this mechanism.\r\n        // Note: They cannot cancel the spectating without a re-log -- you must make them spectate themselves to cancel the effect.\r\n        // Like: - adjust <player> spectate:<player>\r\n        // -->\r\n        if (mechanism.matches(\"spectate\") && mechanism.requireObject(EntityTag.class)) {\r\n            NMSHandler.packetHelper.forceSpectate(getPlayerEntity(), mechanism.valueAsType(EntityTag.class).getBukkitEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name open_book\r\n        // @input None\r\n        // @description\r\n        // Forces the player to open the written book in their hand.\r\n        // The book can safely be removed from the player's hand without the player closing the book.\r\n        // -->\r\n        if (mechanism.matches(\"open_book\")) {\r\n            ItemStack book = getPlayerEntity().getEquipment().getItemInMainHand();\r\n            if (book.getType() == Material.WRITTEN_BOOK) {\r\n                getPlayerEntity().openBook(book);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name open_offhand_book\r\n        // @input None\r\n        // @description\r\n        // Forces the player to open the written book in their offhand.\r\n        // The book can safely be removed from the player's offhand without the player closing the book.\r\n        // -->\r\n        if (mechanism.matches(\"open_offhand_book\")) {\r\n            ItemStack book = getPlayerEntity().getEquipment().getItemInOffHand();\r\n            if (book.getType() == Material.WRITTEN_BOOK) {\r\n                getPlayerEntity().openBook(book);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name show_book\r\n        // @input ItemTag\r\n        // @description\r\n        // Displays a book to a player. Must be a WRITTEN_BOOK item.\r\n        // For simple usage, consider specifying a book script name as the input.\r\n        // -->\r\n        if (mechanism.matches(\"show_book\")\r\n                && mechanism.requireObject(ItemTag.class)) {\r\n            ItemTag book = mechanism.valueAsType(ItemTag.class);\r\n            if (book.getBukkitMaterial() != Material.WRITTEN_BOOK) {\r\n                mechanism.echoError(\"show_book mechanism must have a written book as input.\");\r\n                return;\r\n            }\r\n            getPlayerEntity().openBook(book.getItemStack());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name resend_recipes\r\n        // @input None\r\n        // @description\r\n        // Sends the player a list of the full details of all recipes on the server.\r\n        // This is useful when reloading new item scripts with custom recipes.\r\n        // This will automatically resend discovered recipes at the same time (otherwise the player will seemingly have no recipes unlocked).\r\n        // -->\r\n        if (mechanism.matches(\"resend_recipes\")) {\r\n            NMSHandler.playerHelper.resendRecipeDetails(getPlayerEntity());\r\n            NMSHandler.playerHelper.resendDiscoveredRecipes(getPlayerEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name resend_discovered_recipes\r\n        // @input None\r\n        // @description\r\n        // Sends the player the full list of recipes they have discovered over again.\r\n        // This is useful when used alongside <@link mechanism PlayerTag.quietly_discover_recipe>.\r\n        // -->\r\n        if (mechanism.matches(\"resend_discovered_recipes\")) {\r\n            NMSHandler.playerHelper.resendDiscoveredRecipes(getPlayerEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name quietly_discover_recipe\r\n        // @input ListTag\r\n        // @description\r\n        // Causes the player to discover a recipe, or list of recipes, without being notified or updated about this happening.\r\n        // Generally helpful to follow this with <@link mechanism PlayerTag.resend_discovered_recipes>.\r\n        // Input is in the Namespace:Key format, for example \"minecraft:gold_nugget\".\r\n        // -->\r\n        if (mechanism.matches(\"quietly_discover_recipe\")) {\r\n            for (String keyText : mechanism.valueAsType(ListTag.class)) {\r\n                NamespacedKey key = Utilities.parseNamespacedKey(keyText);\r\n                NMSHandler.playerHelper.quietlyAddRecipe(getPlayerEntity(), key);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name discover_recipe\r\n        // @input ListTag\r\n        // @description\r\n        // Causes the player to discover a recipe, or list of recipes. Input is in the Namespace:Key format, for example \"minecraft:gold_nugget\".\r\n        // -->\r\n        if (mechanism.matches(\"discover_recipe\")) {\r\n            List<NamespacedKey> keys = new ArrayList<>();\r\n            for (String key : mechanism.valueAsType(ListTag.class)) {\r\n                keys.add(Utilities.parseNamespacedKey(key));\r\n            }\r\n            getPlayerEntity().discoverRecipes(keys);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name forget_recipe\r\n        // @input ListTag\r\n        // @description\r\n        // Causes the player to forget ('undiscover') a recipe, or list of recipes. Input is in the Namespace:Key format, for example \"minecraft:gold_nugget\".\r\n        // -->\r\n        if (mechanism.matches(\"forget_recipe\")) {\r\n            List<NamespacedKey> keys = new ArrayList<>();\r\n            for (String key : mechanism.valueAsType(ListTag.class)) {\r\n                keys.add(Utilities.parseNamespacedKey(key));\r\n            }\r\n            getPlayerEntity().undiscoverRecipes(keys);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name edit_sign\r\n        // @input LocationTag\r\n        // @description\r\n        // Allows the player to edit an existing sign. To create a sign, see <@link command Sign>.\r\n        // Give no input to make a fake edit interface.\r\n        // -->\r\n        if (mechanism.matches(\"edit_sign\")) {\r\n            if (mechanism.hasValue() && mechanism.requireObject(LocationTag.class)) {\r\n                BlockState state = mechanism.valueAsType(LocationTag.class).getBlockState();\r\n                if (!(state instanceof Sign)) {\r\n                    mechanism.echoError(\"Invalid location specified: must be a sign.\");\r\n                    return;\r\n                }\r\n                if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n                    NMSHandler.packetHelper.showSignEditor(getPlayerEntity(), state.getLocation());\r\n                    return;\r\n                }\r\n                getPlayerEntity().openSign((Sign) state);\r\n            }\r\n            else {\r\n                NMSHandler.packetHelper.showSignEditor(getPlayerEntity(), null);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name tab_list_info\r\n        // @input ElementTag\r\n        // @description\r\n        // Show the player some text in the header and footer area in their tab list.\r\n        // - adjust <player> tab_list_info:<header>|<footer>\r\n        // -->\r\n        if (mechanism.matches(\"tab_list_info\")) {\r\n            if (!mechanism.getValue().asString().isEmpty()) {\r\n                String[] split = mechanism.getValue().asString().split(\"\\\\|\", 2);\r\n                if (split.length > 0) {\r\n                    String header = split[0];\r\n                    String footer = split.length > 1 ? split[1] : \"\";\r\n                    NMSHandler.packetHelper.showTabListHeaderFooter(getPlayerEntity(), header, footer);\r\n                }\r\n                else {\r\n                    mechanism.echoError(\"Must specify a header and footer to show!\");\r\n                }\r\n            }\r\n            else {\r\n                getPlayerEntity().setPlayerListHeaderFooter(null, null);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name sign_update\r\n        // @input ElementTag\r\n        // @description\r\n        // Shows the player fake lines on a sign, with input in the format of LocationTag|ListTag.\r\n        // -->\r\n        if (mechanism.matches(\"sign_update\")) {\r\n            if (!mechanism.getValue().asString().isEmpty()) {\r\n                String[] split = mechanism.getValue().asString().split(\"\\\\|\", 2);\r\n                if (LocationTag.matches(split[0]) && split.length > 1) {\r\n                    ListTag lines = ListTag.valueOf(split[1], mechanism.context);\r\n                    LocationTag location = LocationTag.valueOf(split[0], mechanism.context);\r\n                    PaperAPITools.instance.sendSignUpdate(getPlayerEntity(), location, lines.toArray(new String[4]));\r\n                }\r\n                else {\r\n                    Debug.echoError(\"Must specify a valid location and at least one sign line!\");\r\n                }\r\n            }\r\n            else {\r\n                Debug.echoError(\"Must specify a valid location and at least one sign line!\");\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name banner_update\r\n        // @input ElementTag\r\n        // @description\r\n        // Shows the player fake patterns on a banner. Input must be in the form: \"LOCATION|COLOR/PATTERN|...\"\r\n        // As of Minecraft 1.13, the base color is unique material types, and so <@link command showfake> must be used for base color changes.\r\n        // For the list of possible patterns, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/banner/PatternType.html>.\r\n        // -->\r\n        if (mechanism.matches(\"banner_update\")) {\r\n            if (mechanism.getValue().asString().length() > 0) {\r\n                String[] split = mechanism.getValue().asString().split(\"\\\\|\");\r\n                List<Pattern> patterns = new ArrayList<>();\r\n                if (LocationTag.matches(split[0]) && split.length > 1) {\r\n                    List<String> splitList;\r\n                    for (int i = 1; i < split.length; i++) {\r\n                        String string = split[i];\r\n                        if (i == 1 && !string.contains(\"/\")) {\r\n                            continue; // Comapt with old input format that had base_color\r\n                        }\r\n                        try {\r\n                            splitList = CoreUtilities.split(string, '/', 2);\r\n                            patterns.add(new Pattern(DyeColor.valueOf(splitList.get(0).toUpperCase()),\r\n                                    PatternType.valueOf(splitList.get(1).toUpperCase())));\r\n                        }\r\n                        catch (Exception e) {\r\n                            Debug.echoError(\"Could not apply pattern to banner: \" + string);\r\n                        }\r\n                    }\r\n                    LocationTag location = LocationTag.valueOf(split[0], mechanism.context);\r\n                    NMSHandler.packetHelper.showBannerUpdate(getPlayerEntity(), location, patterns);\r\n                }\r\n                else {\r\n                    Debug.echoError(\"Must specify a valid location and pattern list!\");\r\n                }\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name stop_sound\r\n        // @input ElementTag\r\n        // @description\r\n        // Stops all sounds of the specified type for the player.\r\n        // Valid types are AMBIENT, BLOCKS, HOSTILE, MASTER, MUSIC, NEUTRAL, PLAYERS, RECORDS, VOICE, and WEATHER\r\n        // Instead of a type, you can specify a full sound key, which usually has the 'minecraft:' prefix.\r\n        // If no sound type is specified, all types will be stopped.\r\n        // -->\r\n        if (mechanism.matches(\"stop_sound\")) {\r\n            SoundCategory category = null;\r\n            NamespacedKey key = null;\r\n            if (mechanism.hasValue()) {\r\n                if (mechanism.getValue().matchesEnum(SoundCategory.class)) {\r\n                    category = mechanism.getValue().asEnum(SoundCategory.class);\r\n                    if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n                        getPlayerEntity().stopSound(category);\r\n                        return;\r\n                    }\r\n                }\r\n                else {\r\n                    key = Utilities.parseNamespacedKey(mechanism.getValue().asString());\r\n                }\r\n            }\r\n            NMSHandler.playerHelper.stopSound(getPlayerEntity(), key, category);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name update_advancements\r\n        // @input None\r\n        // @description\r\n        // Updates the player's client-side advancements to match their server data.\r\n        // -->\r\n        if (mechanism.matches(\"update_advancements\")) {\r\n            NMSHandler.advancementHelper.update(getPlayerEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name name\r\n        // @input ElementTag\r\n        // @description\r\n        // Changes the name on this player's nameplate.\r\n        // @tags\r\n        // <PlayerTag.name>\r\n        // -->\r\n        if (mechanism.matches(\"name\") && mechanism.hasValue()) {\r\n            String name = mechanism.getValue().asString();\r\n            if (name.length() > 16) {\r\n                Debug.echoError(\"Must specify a name with no more than 16 characters.\");\r\n            }\r\n            else {\r\n                NMSHandler.instance.getProfileEditor().setPlayerName(getPlayerEntity(), mechanism.getValue().asString());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name skin\r\n        // @input ElementTag\r\n        // @description\r\n        // Changes the skin of the player to the skin of the given player name.\r\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\r\n        // -->\r\n        if (mechanism.matches(\"skin\") && mechanism.hasValue()) {\r\n            String name = mechanism.getValue().asString();\r\n            if (name.length() > 16) {\r\n                mechanism.echoError(\"Must specify a name with no more than 16 characters.\");\r\n                return;\r\n            }\r\n            PaperAPITools.instance.setSkin(getPlayerEntity(), name);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name skin_blob\r\n        // @input ElementTag\r\n        // @description\r\n        // Changes the skin of the player to the specified blob.\r\n        // In the format: \"texture;signature\" (two values separated by a semicolon).\r\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\r\n        // @tags\r\n        // <PlayerTag.skin_blob>\r\n        // -->\r\n        if (mechanism.matches(\"skin_blob\") && mechanism.hasValue()) {\r\n            PaperAPITools.instance.setSkinBlob(getPlayerEntity(), mechanism.getValue().asString());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name is_op\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes whether the player is a server operator or not.\r\n        // @tags\r\n        // <PlayerTag.is_op>\r\n        // -->\r\n        if (mechanism.matches(\"is_op\") && mechanism.requireBoolean()) {\r\n            ImprovedOfflinePlayer.invalidateNow(getUUID());\r\n            getOfflinePlayer().setOp(mechanism.getValue().asBoolean());\r\n        }\r\n\r\n        if (mechanism.matches(\"money\") && mechanism.requireDouble() && Depends.economy != null) {\r\n            BukkitImplDeprecations.oldMoneyMech.warn(mechanism.context);\r\n            double bal = Depends.economy.getBalance(getOfflinePlayer());\r\n            double goal = mechanism.getValue().asDouble();\r\n            if (goal > bal) {\r\n                Depends.economy.depositPlayer(getOfflinePlayer(), goal - bal);\r\n            }\r\n            else if (bal > goal) {\r\n                Depends.economy.withdrawPlayer(getOfflinePlayer(), bal - goal);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name chat_prefix\r\n        // @input ElementTag\r\n        // @plugin Vault\r\n        // @description\r\n        // Set the player's chat prefix.\r\n        // Requires a Vault-compatible chat plugin.\r\n        // @tags\r\n        // <PlayerTag.chat_prefix>\r\n        // -->\r\n        if (mechanism.matches(\"chat_prefix\")) {\r\n            if (Depends.chat == null) {\r\n                Debug.echoError(\"Chat_Prefix mechanism invalid: No linked Chat plugin.\");\r\n                return;\r\n            }\r\n            Depends.chat.setPlayerPrefix(getPlayerEntity(), mechanism.getValue().asString());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name chat_suffix\r\n        // @input ElementTag\r\n        // @plugin Vault\r\n        // @description\r\n        // Set the player's chat suffix.\r\n        // Requires a Vault-compatible chat plugin.\r\n        // @tags\r\n        // <PlayerTag.chat_suffix>\r\n        // -->\r\n        if (mechanism.matches(\"chat_suffix\")) {\r\n            if (Depends.chat == null) {\r\n                Debug.echoError(\"Chat_Suffix mechanism invalid: No linked Chat plugin.\");\r\n                return;\r\n            }\r\n            Depends.chat.setPlayerSuffix(getPlayerEntity(), mechanism.getValue().asString());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name selected_npc\r\n        // @input NPCTag\r\n        // @description\r\n        // Sets the NPC that the player has selected.\r\n        // @tags\r\n        // <PlayerTag.selected_npc>\r\n        // -->\r\n        if (mechanism.matches(\"selected_npc\") && Depends.citizens != null && mechanism.requireObject(NPCTag.class)) {\r\n            ((NPCSelector) CitizensAPI.getDefaultNPCSelector()).select(getPlayerEntity(), mechanism.valueAsType(NPCTag.class).getCitizen());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name hide_particles\r\n        // @input ElementTag\r\n        // @description\r\n        // Hides a certain type of particle from the player.\r\n        // Input is the particle type name - refer to <@link tag server.particle_types>.\r\n        // Give no input to remove all hides from a player.\r\n        // Hides will persist through players reconnecting, but not through servers restarting.\r\n        // -->\r\n        if (mechanism.matches(\"hide_particles\")) {\r\n            if (!mechanism.hasValue()) {\r\n                HideParticles.hidden.remove(getUUID());\r\n            }\r\n            else {\r\n                NetworkInterceptHelper.enable();\r\n                HashSet<Particle> particles = HideParticles.hidden.computeIfAbsent(getUUID(), k -> new HashSet<>());\r\n                Particle particle = Particle.valueOf(mechanism.getValue().asString().toUpperCase());\r\n                particles.add(particle);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name send_to\r\n        // @input ElementTag\r\n        // @plugin BungeeCord\r\n        // @description\r\n        // Sends the player to the specified Bungee server.\r\n        // This also works with other Bungee-Messaging compatible proxy systems, such as Velocity.\r\n        // -->\r\n        if (mechanism.matches(\"send_to\") && mechanism.hasValue()) {\r\n            if (!isOnline()) {\r\n                Debug.echoError(\"Cannot use send_to on offline player.\");\r\n                return;\r\n            }\r\n            Depends.bungeeSendPlayer(getPlayerEntity(), mechanism.getValue().asString());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object PlayerTag\r\n        // @name send_server_brand\r\n        // @input ElementTag\r\n        // @description\r\n        // Sends the player a fake server brand, that will be displayed in the F3 Debug screen.\r\n        // -->\r\n        if (mechanism.matches(\"send_server_brand\") && mechanism.hasValue()) {\r\n            if (!isOnline()) {\r\n                Debug.echoError(\"Cannot use send_server_brand on offline player.\");\r\n                return;\r\n            }\r\n            NMSHandler.packetHelper.sendBrand(getPlayerEntity(), mechanism.getValue().asString());\r\n        }\r\n\r\n        tagProcessor.processMechanism(this, mechanism);\r\n\r\n        // Pass along to EntityTag mechanism handler if not already handled.\r\n        if (!mechanism.fulfilled()) {\r\n            if (isOnline()) {\r\n                new EntityTag(getPlayerEntity()).adjust(mechanism);\r\n            }\r\n            else {\r\n                if (mechanism.matches(\"show_to_players\")) {\r\n                    HideEntitiesHelper.removeHide(null, getUUID());\r\n                }\r\n                if (mechanism.matches(\"hide_from_players\")) {\r\n                    HideEntitiesHelper.addHide(null, getUUID());\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean advancedMatches(String matcher, TagContext context) {\r\n        return isOnline() && getDenizenEntity().tryAdvancedMatcher(matcher, context);\r\n    }\r\n\r\n    /**\r\n     * Return an appropriate error-header output for this object, if any.\r\n     */\r\n    @Override\r\n    public String getErrorHeaderContext() {\r\n        return \" with player '<A>\" + getName() + \"<LR>'\";\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/PluginTag.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.flags.RedirectionFlagTracker;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.plugin.Plugin;\r\n\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class PluginTag implements ObjectTag, FlaggableObject {\r\n\r\n    // <--[ObjectType]\r\n    // @name PluginTag\r\n    // @prefix pl\r\n    // @base ElementTag\r\n    // @implements FlaggableObject\r\n    // @ExampleTagBase plugin[Denizen]\r\n    // @ExampleValues Denizen\r\n    // @format\r\n    // The identity format for plugins is the plugin's registered name.\r\n    // For example, 'pl@Denizen'.\r\n    //\r\n    // @description\r\n    // A PluginTag represents a Bukkit plugin on the server.\r\n    //\r\n    // This object type is flaggable.\r\n    // Flags on this object type will be stored in the server saves file, under special sub-key \"__plugins\"\r\n    //\r\n    // -->\r\n\r\n    //////////////////\r\n    //    Object Fetcher\r\n    ////////////////\r\n\r\n    @Fetchable(\"pl\")\r\n    public static PluginTag valueOf(String string, TagContext context) {\r\n        if (string == null) {\r\n            return null;\r\n        }\r\n        string = CoreUtilities.toLowerCase(string).replace(\"pl@\", \"\");\r\n        try {\r\n            // Attempt to match from plugin list, as PluginManager#getPlugin is case sensitive\r\n            for (Plugin plugin : Bukkit.getServer().getPluginManager().getPlugins()) {\r\n                if (string.equalsIgnoreCase(plugin.getName())) {\r\n                    return new PluginTag(plugin);\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(\"Invalid plugin name specified, or plugin is not enabled: \" + string);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static boolean matches(String arg) {\r\n        if (CoreUtilities.toLowerCase(arg).startsWith(\"pl@\")) {\r\n            return true;\r\n        }\r\n        for (Plugin plugin : Bukkit.getServer().getPluginManager().getPlugins()) {\r\n            if (arg.equalsIgnoreCase(plugin.getName())) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    /////////////////////\r\n    //   Constructors\r\n    //////////////////\r\n\r\n    public PluginTag(Plugin plugin) {\r\n        this.plugin = plugin;\r\n    }\r\n\r\n    /////////////////////\r\n    //   Instance Fields/Methods\r\n    /////////////////\r\n\r\n    private Plugin plugin;\r\n\r\n    public Plugin getPlugin() {\r\n        return plugin;\r\n    }\r\n\r\n    /////////////////////\r\n    //  ObjectTag Methods\r\n    ///////////////////\r\n\r\n    private String prefix = \"Plugin\";\r\n\r\n    @Override\r\n    public String getPrefix() {\r\n        return prefix;\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnique() {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public String identify() {\r\n        return \"pl@\" + plugin.getName();\r\n    }\r\n\r\n    @Override\r\n    public String identifySimple() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public Object getJavaObject() {\r\n        return plugin;\r\n    }\r\n\r\n    @Override\r\n    public PluginTag setPrefix(String prefix) {\r\n        this.prefix = prefix;\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public AbstractFlagTracker getFlagTracker() {\r\n        return new RedirectionFlagTracker(DenizenCore.serverFlagMap, \"__plugins.\" + plugin.getName().replace(\".\", \"&dot\"));\r\n    }\r\n\r\n    @Override\r\n    public void reapplyTracker(AbstractFlagTracker tracker) {\r\n        // Nothing to do.\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\r\n\r\n        // <--[tag]\r\n        // @attribute <PluginTag.name>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Gets the name of this plugin.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"name\", (attribute, object) -> {\r\n            return new ElementTag(object.plugin.getName());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PluginTag.version>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Gets the version for the plugin specified.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"version\", (attribute, object) -> {\r\n            return new ElementTag(object.plugin.getDescription().getVersion());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PluginTag.description>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Gets the description for the plugin specified.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"description\", (attribute, object) -> {\r\n            return new ElementTag(object.plugin.getDescription().getDescription());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PluginTag.authors>\r\n        // @returns ListTag\r\n        // @description\r\n        // Gets the list of authors for the plugin specified.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"authors\", (attribute, object) -> {\r\n            return new ListTag(object.plugin.getDescription().getAuthors());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PluginTag.depends>\r\n        // @returns ListTag\r\n        // @description\r\n        // Gets the list of hard dependencies for the plugin specified.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"depends\", (attribute, object) -> {\r\n            return new ListTag(object.plugin.getDescription().getDepend());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PluginTag.soft_depends>\r\n        // @returns ListTag\r\n        // @description\r\n        // Gets the list of soft dependencies for the plugin specified.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"soft_depends\", (attribute, object) -> {\r\n            return new ListTag(object.plugin.getDescription().getSoftDepend());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PluginTag.commands>\r\n        // @returns MapTag(MapTag)\r\n        // @description\r\n        // Gets a map of commands registered this plugin registers by default.\r\n        // Note that dynamically registered commands won't show up (for example, command scripts won't be listed under Denizen).\r\n        // Map key is command name, map value is a sub-mapping with keys:\r\n        // description (ElementTag), usage (ElementTag), permission (ElementTag), aliases (ListTag)\r\n        // Not all keys will be present.\r\n        // For example, <plugin[denizen].commands.get[ex]> will return a MapTag with:\r\n        // [description=Executes a Denizen script command.;usage=/ex (-q) <Denizen script command> (arguments);permission=denizen.ex]\r\n        // -->\r\n        tagProcessor.registerTag(MapTag.class, \"commands\", (attribute, object) -> {\r\n            Map<String, Map<String, Object>> commands = object.plugin.getDescription().getCommands();\r\n            MapTag output = new MapTag();\r\n            if (commands == null || commands.isEmpty()) {\r\n                return output;\r\n            }\r\n            for (Map.Entry<String, Map<String, Object>> command : commands.entrySet()) {\r\n                MapTag dataMap = new MapTag();\r\n                if (command.getValue().containsKey(\"description\")) {\r\n                    dataMap.putObject(\"description\", new ElementTag(command.getValue().get(\"description\").toString(), true));\r\n                }\r\n                if (command.getValue().containsKey(\"usage\")) {\r\n                    dataMap.putObject(\"usage\", new ElementTag(command.getValue().get(\"usage\").toString(), true));\r\n                }\r\n                if (command.getValue().containsKey(\"permission\")) {\r\n                    dataMap.putObject(\"permission\", new ElementTag(command.getValue().get(\"permission\").toString(), true));\r\n                }\r\n                if (command.getValue().containsKey(\"aliases\")) {\r\n                    Object obj = command.getValue().get(\"aliases\");\r\n                    if (obj instanceof List) {\r\n                        ListTag aliases = new ListTag();\r\n                        for (Object entry : (List) obj) {\r\n                            aliases.addObject(new ElementTag(String.valueOf(entry), true));\r\n                        }\r\n                        dataMap.putObject(\"aliases\", aliases);\r\n                    }\r\n                }\r\n                output.putObject(command.getKey(), dataMap);\r\n            }\r\n            return output;\r\n        });\r\n    }\r\n\r\n    public static ObjectTagProcessor<PluginTag> tagProcessor = new ObjectTagProcessor<>();\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return tagProcessor.getObjectAttribute(this, attribute);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/PolygonTag.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.utilities.NotedAreaTracker;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.flags.SavableMapFlagTracker;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.notable.Notable;\r\nimport com.denizenscript.denizencore.objects.notable.Note;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport org.bukkit.Location;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.function.Predicate;\r\n\r\npublic class PolygonTag implements ObjectTag, Cloneable, Notable, Adjustable, AreaContainmentObject, FlaggableObject {\r\n\r\n    // <--[ObjectType]\r\n    // @name PolygonTag\r\n    // @prefix polygon\r\n    // @base ElementTag\r\n    // @implements FlaggableObject, AreaObject\r\n    // @ExampleTagBase polygon[my_noted_polygon]\r\n    // @ExampleValues my_polygon_note\r\n    // @ExampleForReturns\r\n    // - note %VALUE% as:my_new_polygon\r\n    // @format\r\n    // The identity format for polygons is <world>,<y-min>,<y-max>,<x1>,<z1>,... (the x,z pair repeats for as many points as the polygon has).\r\n    //\r\n    // @description\r\n    // A PolygonTag represents a polygonal region in the world.\r\n    //\r\n    // The word 'polygon' means an arbitrary 2D shape.\r\n    // PolygonTags, in addition to a 2D polygon, contain a minimum and maximum Y coordinate, to allow them to function in 3D.\r\n    //\r\n    // PolygonTags are NOT polyhedra.\r\n    //\r\n    // A PolygonTag with 4 points at right angles would cover an area also possible to be defined by a CuboidTag, however all other shapes a PolygonTag can form are unique.\r\n    //\r\n    // Compared to CuboidTags, PolygonTags are generally slower to process and more complex to work with, but offer the benefit of supporting more intricate shapes.\r\n    //\r\n    // Note that forming invalid polygons (duplicate corners, impossible shapes, etc) will not necessarily give any error message, and may cause weird results.\r\n    //\r\n    // This object type can be noted.\r\n    //\r\n    // This object type is flaggable when it is noted.\r\n    // Flags on this object type will be stored in the notables.yml file.\r\n    //\r\n    // @Matchable\r\n    // Refer to <@link objecttype areaobject>'s matchable list.\r\n    //\r\n    // -->\r\n\r\n    public WorldTag world;\r\n\r\n    public double yMin = 0, yMax = 0;\r\n\r\n    public List<Corner> corners = new ArrayList<>();\r\n\r\n    public Corner boxMin = new Corner(), boxMax = new Corner();\r\n\r\n    public String noteName = null, priorNoteName = null;\r\n\r\n    public AbstractFlagTracker flagTracker = null;\r\n\r\n    public static boolean preferInclusive = false;\r\n\r\n    public static class Corner {\r\n        public double x, z;\r\n\r\n        public Corner() {\r\n        }\r\n\r\n        public Corner(double x, double z) {\r\n            this.x = x;\r\n            this.z = z;\r\n        }\r\n    }\r\n\r\n    public PolygonTag(WorldTag world) {\r\n        this.world = world;\r\n    }\r\n\r\n    public PolygonTag(WorldTag world, double yMin, double yMax, List<Corner> corners) {\r\n        this.world = world;\r\n        this.yMin = yMin;\r\n        this.yMax = yMax;\r\n        for (Corner corner : corners) {\r\n            this.corners.add(new Corner(corner.x, corner.z));\r\n        }\r\n        recalculateBox();\r\n    }\r\n\r\n    public void recalculateBox() {\r\n        if (corners.size() == 0) {\r\n            return;\r\n        }\r\n        Corner firstCorner = corners.get(0);\r\n        boxMin.x = firstCorner.x;\r\n        boxMin.z = firstCorner.z;\r\n        boxMax.x = firstCorner.x;\r\n        boxMax.z = firstCorner.z;\r\n        for (Corner corner : corners) {\r\n            recalculateToFit(corner);\r\n        }\r\n    }\r\n\r\n    public void recalculateToFit(Corner corner) {\r\n        boxMin.x = Math.min(boxMin.x, corner.x);\r\n        boxMin.z = Math.min(boxMin.z, corner.z);\r\n        boxMax.x = Math.max(boxMax.x, corner.x);\r\n        boxMax.z = Math.max(boxMax.z, corner.z);\r\n    }\r\n\r\n    @Override\r\n    public PolygonTag clone() {\r\n        return new PolygonTag(world, yMin, yMax, corners);\r\n    }\r\n\r\n    @Fetchable(\"polygon\")\r\n    public static PolygonTag valueOf(String string, TagContext context) {\r\n        if (string.startsWith(\"polygon@\")) {\r\n            string = string.substring(\"polygon@\".length());\r\n        }\r\n        if (string.contains(\"@\")) {\r\n            return null;\r\n        }\r\n        if (!TagManager.isStaticParsing) {\r\n            Notable saved = NoteManager.getSavedObject(string);\r\n            if (saved instanceof PolygonTag) {\r\n                return (PolygonTag) saved;\r\n            }\r\n        }\r\n        List<String> parts = CoreUtilities.split(string, ',');\r\n        if (parts.size() < 3) {\r\n            return null;\r\n        }\r\n        WorldTag world = new WorldTag(parts.get(0));\r\n        for (int i = 1; i < parts.size(); i++) {\r\n            if (!ArgumentHelper.matchesDouble(parts.get(i))) {\r\n                return null;\r\n            }\r\n        }\r\n        PolygonTag toReturn = new PolygonTag(world);\r\n        toReturn.yMin = Double.parseDouble(parts.get(1));\r\n        toReturn.yMax = Double.parseDouble(parts.get(2));\r\n        for (int i = 3; i < parts.size(); i += 2) {\r\n            Corner corner = new Corner(Double.parseDouble(parts.get(i)), Double.parseDouble(parts.get(i + 1)));\r\n            toReturn.corners.add(corner);\r\n        }\r\n        toReturn.recalculateBox();\r\n        return toReturn;\r\n    }\r\n\r\n    public static boolean matches(String string) {\r\n        if (valueOf(string, CoreUtilities.noDebugContext) != null) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag duplicate() {\r\n        PolygonTag self = refreshState();\r\n        if (self.noteName != null) {\r\n            return this;\r\n        }\r\n        return clone();\r\n    }\r\n\r\n    @Override\r\n    public int hashCode() {\r\n        if (noteName != null) {\r\n            return noteName.hashCode();\r\n        }\r\n        return (int) (boxMin.x + boxMin.z * 7 + boxMax.x * 29 + boxMax.z * 61);\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object other) {\r\n        if (!(other instanceof PolygonTag)) {\r\n            return false;\r\n        }\r\n        PolygonTag poly2 = (PolygonTag) other;\r\n        if ((noteName == null) != (poly2.noteName == null)) {\r\n            return false;\r\n        }\r\n        if (noteName != null) {\r\n            return noteName.equals(poly2.noteName);\r\n        }\r\n        if (poly2.corners.size() != corners.size()) {\r\n            return false;\r\n        }\r\n        if (!world.getName().equals(poly2.world.getName())) {\r\n            return false;\r\n        }\r\n        if (yMin != poly2.yMin || yMax != poly2.yMax) {\r\n            return false;\r\n        }\r\n        for (int i = 0; i < corners.size(); i++) {\r\n            Corner corner1 = corners.get(i);\r\n            Corner corner2 = poly2.corners.get(i);\r\n            if (corner1.x != corner2.x || corner1.z != corner2.z) {\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public String getNoteName() {\r\n        return noteName;\r\n    }\r\n\r\n    @Override\r\n    public boolean doesContainLocation(Location loc) {\r\n        return preferInclusive ? containsInclusive(loc) : containsPrecise(loc);\r\n    }\r\n\r\n    public boolean containsPrecise(Location loc) {\r\n        if (loc.getWorld() == null) {\r\n            return false;\r\n        }\r\n        if (!loc.getWorld().getName().equals(world.getName())) {\r\n            return false;\r\n        }\r\n        double x = loc.getX();\r\n        double z = loc.getZ();\r\n        if (x < boxMin.x || x > boxMax.x || z < boxMin.z || z > boxMax.z) {\r\n            return false;\r\n        }\r\n        double y = loc.getY();\r\n        if (y < yMin || y > yMax) {\r\n            return false;\r\n        }\r\n        boolean isInside = false;\r\n        for (int i = 0; i < corners.size(); i++) {\r\n            Corner start = corners.get(i);\r\n            Corner end = (i + 1 == corners.size() ? corners.get(0) : corners.get(i + 1));\r\n            if (((start.z > z) != (end.z > z)) && (x < (end.x - start.x) * (z - start.z) / (end.z - start.z) + start.x)) {\r\n                isInside = !isInside;\r\n            }\r\n        }\r\n        return isInside;\r\n    }\r\n\r\n    public boolean containsInclusive(Location loc) {\r\n        if (loc.getWorld() == null) {\r\n            return false;\r\n        }\r\n        if (!loc.getWorld().getName().equals(world.getName())) {\r\n            return false;\r\n        }\r\n        int targetY = loc.getBlockY();\r\n        if (targetY < Math.floor(yMin) || targetY > Math.floor(yMax)) {\r\n            return false;\r\n        }\r\n        int targetX = loc.getBlockX();\r\n        int targetZ = loc.getBlockZ();\r\n        boolean isInside = false;\r\n        for (int i = 0; i < corners.size(); i++) {\r\n            Corner start = corners.get(i);\r\n            Corner end = (i + 1 == corners.size() ? corners.get(0) : corners.get(i + 1));\r\n            int xStart = (int) Math.floor(start.x);\r\n            int zStart = (int) Math.floor(start.z);\r\n            int xEnd = (int) Math.floor(end.x);\r\n            int zEnd = (int) Math.floor(end.z);\r\n            if (xEnd == targetX && zEnd == targetZ) {\r\n                return true; // exactly on corner\r\n            }\r\n            int x1, x2, z1, z2;\r\n            if (xEnd > xStart) {\r\n                x1 = xStart;\r\n                x2 = xEnd;\r\n                z1 = zStart;\r\n                z2 = zEnd;\r\n            }\r\n            else {\r\n                x1 = xEnd;\r\n                x2 = xStart;\r\n                z1 = zEnd;\r\n                z2 = zStart;\r\n            }\r\n            if (x1 <= targetX && targetX <= x2) {\r\n                long crossProduct = ((long) targetZ - (long) z1) * (long) (x2 - x1) - ((long) z2 - (long) z1) * (long) (targetX - x1);\r\n                if (crossProduct == 0) {\r\n                    if ((z1 <= targetZ) == (targetZ <= z2)) {\r\n                        return true; // exactly along edge\r\n                    }\r\n                }\r\n                else if (crossProduct < 0 && (x1 != targetX)) {\r\n                    isInside = !isInside;\r\n                }\r\n            }\r\n        }\r\n        return isInside;\r\n    }\r\n\r\n    public List<LocationTag> generateFlatBlockShell(double y, boolean inclusive) {\r\n        int max = Settings.blockTagsMaxBlocks();\r\n        ArrayList<LocationTag> toOutput = new ArrayList<>();\r\n        for (int x = (int) Math.floor(boxMin.x); x < boxMax.x; x++) {\r\n            for (int z = (int) Math.floor(boxMin.z); z < boxMax.z; z++) {\r\n                LocationTag possible = new LocationTag(x + 0.5, y, z + 0.5, world.getName());\r\n                if (inclusive ? containsInclusive(possible) : containsPrecise(possible)) {\r\n                    toOutput.add(possible);\r\n                }\r\n                max--;\r\n                if (max <= 0) {\r\n                    return toOutput;\r\n                }\r\n            }\r\n        }\r\n        return toOutput;\r\n    }\r\n\r\n    @Override\r\n    public ListTag getShell() {\r\n        return getShellInternal(preferInclusive);\r\n    }\r\n\r\n    public ListTag getShellInternal(boolean inclusive) {\r\n        int max = Settings.blockTagsMaxBlocks();\r\n        ListTag addTo = new ListTag();\r\n        List<LocationTag> flatShell = generateFlatBlockShell(yMin, inclusive);\r\n        for (LocationTag loc : flatShell) {\r\n            addTo.addObject(loc.clone());\r\n        }\r\n        max -= flatShell.size();\r\n        if (max <= 0) {\r\n            return addTo;\r\n        }\r\n        for (LocationTag loc : flatShell) {\r\n            LocationTag newLoc = loc.clone();\r\n            newLoc.setY(yMax);\r\n            addTo.addObject(newLoc);\r\n        }\r\n        max -= flatShell.size();\r\n        if (max <= 0) {\r\n            return addTo;\r\n        }\r\n        int per = (int) (yMax - yMin);\r\n        if (per == 0) {\r\n            return addTo;\r\n        }\r\n        for (int i = 0; i < corners.size(); i++) {\r\n            Corner start = corners.get(i);\r\n            Corner end = (i + 1 == corners.size() ? corners.get(0) : corners.get(i + 1));\r\n            double xMove = (end.x - start.x);\r\n            double zMove = (end.z - start.z);\r\n            double len = Math.sqrt(xMove * xMove + zMove * zMove);\r\n            if (len < 0.1) {\r\n                continue;\r\n            }\r\n            xMove /= len;\r\n            zMove /= len;\r\n            double xSpot = start.x;\r\n            double zSpot = start.z;\r\n            max -= (int) (len + 1);\r\n            if (max <= 0) {\r\n                return addTo;\r\n            }\r\n            for (int j = 0; j < len; j++) {\r\n                for (double y = yMin + 1; y < yMax; y++) {\r\n                    addTo.addObject(new LocationTag(xSpot, y, zSpot, world.getName()));\r\n                }\r\n                max -= per;\r\n                if (max <= 0) {\r\n                    return addTo;\r\n                }\r\n                xSpot += xMove;\r\n                zSpot += zMove;\r\n            }\r\n        }\r\n        return addTo;\r\n    }\r\n\r\n    @Override\r\n    public ListTag getBlocks(Predicate<Location> test) {\r\n        return getBlocksInternal(test, preferInclusive);\r\n    }\r\n\r\n    public ListTag getBlocksInternal(Predicate<Location> test, boolean inclusive) {\r\n        int max = Settings.blockTagsMaxBlocks();\r\n        ListTag addTo = new ListTag();\r\n        List<LocationTag> flatShell = generateFlatBlockShell(yMin, inclusive);\r\n        for (double y = yMin; y < yMax; y++) {\r\n            for (LocationTag loc : flatShell) {\r\n                LocationTag newLoc = loc.clone();\r\n                newLoc.setY(y);\r\n                if (test == null || test.test(newLoc)) {\r\n                    addTo.addObject(newLoc);\r\n                }\r\n            }\r\n            max -= flatShell.size();\r\n            if (max <= 0) {\r\n                return addTo;\r\n            }\r\n        }\r\n        return addTo;\r\n    }\r\n\r\n    public void addOutline2D(double y, ListTag addTo) {\r\n        int max = Settings.blockTagsMaxBlocks();\r\n        for (int i = 0; i < corners.size(); i++) {\r\n            Corner start = corners.get(i);\r\n            Corner end = (i + 1 == corners.size() ? corners.get(0) : corners.get(i + 1));\r\n            double xMove = (end.x - start.x);\r\n            double zMove = (end.z - start.z);\r\n            double len = Math.sqrt(xMove * xMove + zMove * zMove);\r\n            if (len < 0.1) {\r\n                continue;\r\n            }\r\n            xMove /= len;\r\n            zMove /= len;\r\n            double xSpot = start.x;\r\n            double zSpot = start.z;\r\n            max -= (int) (len + 1);\r\n            if (max <= 0) {\r\n                return;\r\n            }\r\n            for (int j = 0; j < len; j++) {\r\n                addTo.addObject(new LocationTag(xSpot, y, zSpot, world.getName()));\r\n                xSpot += xMove;\r\n                zSpot += zMove;\r\n            }\r\n        }\r\n    }\r\n\r\n    public ListTag getOutline() {\r\n        int max = Settings.blockTagsMaxBlocks();\r\n        ListTag output = new ListTag();\r\n        addOutline2D(yMin, output);\r\n        if (max <= output.size()) {\r\n            return output;\r\n        }\r\n        if (yMin != yMax) {\r\n            addOutline2D(yMax, output);\r\n        }\r\n        max -= output.size();\r\n        if (max <= 0) {\r\n            return output;\r\n        }\r\n        int per = (int) (yMax - yMin);\r\n        if (per == 0) {\r\n            return output;\r\n        }\r\n        for (Corner corner : corners) {\r\n            for (double y = yMin + 1; y < yMax; y++) {\r\n                output.addObject(new LocationTag(corner.x, y, corner.z, world.getName()));\r\n            }\r\n            max -= per;\r\n            if (max <= 0) {\r\n                return output;\r\n            }\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnique() {\r\n        return noteName != null;\r\n    }\r\n\r\n    @Override\r\n    @Note(\"Polygons\")\r\n    public Object getSaveObject() {\r\n        YamlConfiguration section = new YamlConfiguration();\r\n        section.set(\"object\", identifyFull());\r\n        section.set(\"flags\", flagTracker.toString());\r\n        return section;\r\n    }\r\n\r\n    @Override\r\n    public void makeUnique(String id) {\r\n        PolygonTag toNote = clone();\r\n        toNote.noteName = id;\r\n        toNote.flagTracker = new SavableMapFlagTracker();\r\n        NoteManager.saveAs(toNote, id);\r\n        NotedAreaTracker.add(toNote);\r\n    }\r\n\r\n    @Override\r\n    public void forget() {\r\n        if (noteName == null) {\r\n            return;\r\n        }\r\n        priorNoteName = noteName;\r\n        NotedAreaTracker.remove(this);\r\n        NoteManager.remove(this);\r\n        noteName = null;\r\n        flagTracker = null;\r\n    }\r\n\r\n    @Override\r\n    public PolygonTag refreshState() {\r\n        if (noteName == null && priorNoteName != null) {\r\n            Notable note = NoteManager.getSavedObject(priorNoteName);\r\n            if (note instanceof PolygonTag) {\r\n                return (PolygonTag) note;\r\n            }\r\n            priorNoteName = null;\r\n        }\r\n        return this;\r\n    }\r\n\r\n    String prefix = \"Polygon\";\r\n\r\n    @Override\r\n    public String getPrefix() {\r\n        return prefix;\r\n    }\r\n\r\n    @Override\r\n    public PolygonTag setPrefix(String prefix) {\r\n        this.prefix = prefix;\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public String debuggable() {\r\n        PolygonTag self = refreshState();\r\n        if (self.isUnique()) {\r\n            return \"<LG>polygon@<Y>\" + self.noteName + \" <GR>(\" + self.identifyFull() + \")\";\r\n        }\r\n        else {\r\n            return self.identifyFull().replace(\",\", \"<LG>, <Y>\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String identify() {\r\n        PolygonTag self = refreshState();\r\n        if (self.isUnique()) {\r\n            return \"polygon@\" + self.noteName;\r\n        }\r\n        else {\r\n            return self.identifyFull();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String identifySimple() {\r\n        return identify();\r\n    }\r\n\r\n    public String identifyFull() {\r\n        StringBuilder sb = new StringBuilder();\r\n        sb.append(\"polygon@\").append(world.getName()).append(\",\").append(yMin).append(\",\").append(yMax).append(\",\");\r\n        for (Corner corner : corners) {\r\n            sb.append(corner.x).append(\",\").append(corner.z).append(\",\");\r\n        }\r\n        if (corners.size() > 0) {\r\n            sb.setLength(sb.length() - 1);\r\n        }\r\n        return sb.toString();\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public AbstractFlagTracker getFlagTracker() {\r\n        return flagTracker;\r\n    }\r\n\r\n    @Override\r\n    public void reapplyTracker(AbstractFlagTracker tracker) {\r\n        if (noteName != null) {\r\n            this.flagTracker = tracker;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getReasonNotFlaggable() {\r\n        if (noteName == null) {\r\n            return \"the area is not noted - only noted areas can hold flags\";\r\n        }\r\n        return \"unknown reason - something went wrong\";\r\n    }\r\n\r\n    @Override\r\n    public CuboidTag getCuboidBoundary() {\r\n        LocationTag min = new LocationTag(Math.floor(boxMin.x), Math.floor(yMin), Math.floor(boxMin.z), world.getName());\r\n        LocationTag max = new LocationTag(Math.ceil(boxMax.x), Math.ceil(yMax), Math.ceil(boxMax.z), world.getName());\r\n        return new CuboidTag(min, max);\r\n    }\r\n\r\n    @Override\r\n    public WorldTag getWorld() {\r\n        return world;\r\n    }\r\n\r\n    @Override\r\n    public PolygonTag withWorld(WorldTag world) {\r\n        PolygonTag toReturn = clone();\r\n        toReturn.world = world;\r\n        return toReturn;\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\r\n        AreaContainmentObject.register(PolygonTag.class, tagProcessor);\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.max_y>\r\n        // @returns ElementTag(Decimal)\r\n        // @description\r\n        // Returns the maximum Y level for this polygon.\r\n        // @example\r\n        // # For example, this might return something like:\r\n        // # \"The maximum Y level for the polygon, 'my_polygon', is 73!\"\r\n        // - narrate \"The maximum Y level for the polygon, 'my_polygon', is <polygon[my_polygon].max_y>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"max_y\", (attribute, polygon) -> {\r\n            return new ElementTag(polygon.yMax);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.min_y>\r\n        // @returns ElementTag(Decimal)\r\n        // @description\r\n        // Returns the minimum Y level for this polygon.\r\n        // @example\r\n        // # For example, this might return something like:\r\n        // # \"The minimum Y level for the polygon, 'my_polygon', is 62!\"\r\n        // - narrate \"The minimum Y level for the polygon, 'my_polygon', is <polygon[my_polygon].min_y>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"min_y\", (attribute, polygon) -> {\r\n            return new ElementTag(polygon.yMin);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.note_name>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Gets the name of a noted PolygonTag. If the polygon isn't noted, this is null.\r\n        // @example\r\n        // # For example, this might return something like:\r\n        // # \"The polygon you are currently in is noted as: my_polygon!\"\r\n        // - narrate \"The polygon you are currently in is noted as: <player.location.areas[polygons].first.note_name.if_null[null! You aren't in a polygon]>!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"note_name\", (attribute, polygon) -> {\r\n            String noteName = NoteManager.getSavedId(polygon);\r\n            if (noteName == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(noteName);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.corners>\r\n        // @returns ListTag(LocationTag)\r\n        // @description\r\n        // Returns a list of the polygon's corners, as locations with Y coordinate set to the y-min value.\r\n        // @example\r\n        // # Displays a debugblock at each corner of the polygon \"my_polygon\".\r\n        // - debugblock <polygon[my_polygon].corners>\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"corners\", (attribute, polygon) -> {\r\n            ListTag list = new ListTag();\r\n            for (Corner corner : polygon.corners) {\r\n                list.addObject(new LocationTag(corner.x, polygon.yMin, corner.z, polygon.world.getName()));\r\n            }\r\n            return list;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.shift[<vector>]>\r\n        // @returns PolygonTag\r\n        // @description\r\n        // Returns a copy of the polygon, with all coordinates shifted by the given location-vector.\r\n        // @example\r\n        // # Notes the polygon \"my_polygon\" as \"my_shifted_polygon\" but shifted over by 25,25,25.\r\n        // - note <polygon[my_polygon].shift[25,25,25]> as:my_shifted_polygon\r\n        // -->\r\n        tagProcessor.registerTag(PolygonTag.class, \"shift\", (attribute, polygon) -> {\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"PolygonTag.shift[...] tag must have an input.\");\r\n                return null;\r\n            }\r\n            LocationTag shift = attribute.paramAsType(LocationTag.class);\r\n            PolygonTag toReturn = polygon.clone();\r\n            toReturn.yMin += shift.getY();\r\n            toReturn.yMax += shift.getY();\r\n            for (Corner corner : toReturn.corners) {\r\n                corner.x += shift.getX();\r\n                corner.z += shift.getZ();\r\n            }\r\n            toReturn.boxMin.x += shift.getX();\r\n            toReturn.boxMin.z += shift.getZ();\r\n            toReturn.boxMax.x += shift.getX();\r\n            toReturn.boxMax.z += shift.getZ();\r\n            return toReturn;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.with_corner[<location>]>\r\n        // @returns PolygonTag\r\n        // @mechanism PolygonTag.add_corner\r\n        // @description\r\n        // Returns a copy of the polygon, with the specified corner added to the end of the corner list.\r\n        // @example\r\n        // # Notes a new polygon with a new corner added.\r\n        // # If the new corner has a location of \"10,66,-2\", and \"my_polygon\" has corners \"0.0,0.0\", \"7.0,7.0\", and \"-5.0,6.0\",\r\n        // # then \"my_new_polygon\" will have corners  \"0.0,0.0\", \"7.0,7.0\", \"-5.0,6.0\", and \"10.0,-2.0\".\r\n        // - note <polygon[my_polygon].with_corner[10,66,-2]> as:my_new_polygon\r\n        // -->\r\n        tagProcessor.registerTag(PolygonTag.class, \"with_corner\", (attribute, polygon) -> {\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"PolygonTag.with_corner[...] tag must have an input.\");\r\n                return null;\r\n            }\r\n            LocationTag corner = attribute.paramAsType(LocationTag.class);\r\n            PolygonTag toReturn = polygon.clone();\r\n            Corner added = new Corner(corner.getX(), corner.getZ());\r\n            toReturn.corners.add(added);\r\n            toReturn.recalculateToFit(added);\r\n            return toReturn;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.with_y_min[<#.#>]>\r\n        // @returns PolygonTag\r\n        // @description\r\n        // Returns a copy of the polygon, with the specified minimum-Y value.\r\n        // @example\r\n        // # Notes a new polygon from \"my_polygon\" with the minimum-Y value being 10.\r\n        // # For example, if \"my_polygon\" had a maximum-Y of 50 and a minimum-Y of 30, then \"my_new_polygon\" will have a maximum-Y of 50 and a minimum-Y of 10.\r\n        // - note <polygon[my_polygon].with_y_min[10]> as:my_new_polygon\r\n        // -->\r\n        tagProcessor.registerTag(PolygonTag.class, \"with_y_min\", (attribute, polygon) -> {\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"PolygonTag.with_y_min[...] tag must have an input.\");\r\n                return null;\r\n            }\r\n            PolygonTag toReturn = polygon.clone();\r\n            toReturn.yMin = attribute.getDoubleParam();\r\n            return toReturn;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.with_y_max[<#.#>]>\r\n        // @returns PolygonTag\r\n        // @description\r\n        // Returns a copy of the polygon, with the specified maximum-Y value.\r\n        // @example\r\n        // # Notes a new polygon from \"my_polygon\" with the maximum-Y value being 10.\r\n        // # For example, if \"my_polygon\" had a maximum-Y of 50 and a minimum-Y of 30, then \"my_new_polygon\" will have a maximum-Y of 70 and a minimum-Y of 30.\r\n        // - note <polygon[my_polygon].with_y_max[70]> as:my_new_polygon\r\n        // -->\r\n        tagProcessor.registerTag(PolygonTag.class, \"with_y_max\", (attribute, polygon) -> {\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"PolygonTag.with_y_max[...] tag must have an input.\");\r\n                return null;\r\n            }\r\n            PolygonTag toReturn = polygon.clone();\r\n            toReturn.yMax = attribute.getDoubleParam();\r\n            return toReturn;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.include_y[<#.#>]>\r\n        // @returns PolygonTag\r\n        // @description\r\n        // Returns a copy of the polygon, with the specified Y value included as part of the Y range (expanding the Y-min or Y-max as needed).\r\n        // @example\r\n        // # Notes \"my_polygon\" as \"my_new_polygon\" but with the player's Y location included in the Y range.\r\n        // # For example, if \"my_polygon\" has a maximum-Y of 80 and a minimum-Y of 60,\r\n        // # and the player's Y location was 95, then \"my_new_polygon\" will have a maximum-Y of 95 and a minimum-Y of 60.\r\n        // # However, if the player's Y location was 50, then the maximum-Y value would stay the same and the minimum-Y value would change to 50 instead.\r\n        // - note <polygon[my_polygon].include_y[<player.location.y>]> as:my_new_polygon\r\n        // -->\r\n        tagProcessor.registerTag(PolygonTag.class, \"include_y\", (attribute, polygon) -> {\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"PolygonTag.include_y[...] tag must have an input.\");\r\n                return null;\r\n            }\r\n            PolygonTag toReturn = polygon.clone();\r\n            double y = attribute.getDoubleParam();\r\n            toReturn.yMin = Math.min(y, toReturn.yMin);\r\n            toReturn.yMax = Math.max(y, toReturn.yMax);\r\n            return toReturn;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.outline_2d[<#.#>]>\r\n        // @returns ListTag(LocationTag)\r\n        // @description\r\n        // Returns a list of locations along the 2D outline of this polygon, at the specified Y level (roughly a block-width of separation between each).\r\n        // @example\r\n        // # Plays the \"flame\" effect along the 2D outline of the polygon, \"my_polygon\" at the player's Y location.\r\n        // - playeffect effect:flame at:<polygon[my_polygon].outline_2d[<player.location.y>]> offset:0.0\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"outline_2d\", (attribute, polygon) -> {\r\n            if (!attribute.hasParam()) {\r\n                attribute.echoError(\"PolygonTag.outline_2d[...] tag must have an input.\");\r\n                return null;\r\n            }\r\n            double y = attribute.getDoubleParam();\r\n            ListTag output = new ListTag();\r\n            polygon.addOutline2D(y, output);\r\n            return output;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.outline>\r\n        // @returns ListTag(LocationTag)\r\n        // @description\r\n        // Returns a list of locations along the 3D outline of this polygon (roughly a block-width of separation between each).\r\n        // @example\r\n        // # Plays the \"flame\" effect along the outline of the polygon, \"my_polygon\".\r\n        // - playeffect effect:flame at:<polygon[my_polygon].outline> offset:0.0\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"outline\", (attribute, polygon) -> {\r\n            return polygon.getOutline();\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object PolygonTag\r\n        // @name add_corner\r\n        // @input LocationTag\r\n        // @description\r\n        // Adds a corner to the end of the polygon's corner list.\r\n        // @tags\r\n        // <PolygonTag.with_corner[<location>]>\r\n        // @example\r\n        // # Adjusts the polygon to have a new corner added.\r\n        // # If the new corner has a location of \"10,66,-2\", and \"my_polygon\" has corners \"0.0,0.0\", \"7.0,7.0\", and \"-5.0,6.0\",\r\n        // # then \"my_new_polygon\" will have corners  \"0.0,0.0\", \"7.0,7.0\", \"-5.0,6.0\", and \"10.0,-2.0\".\r\n        // - adjust <polygon[my_polygon]> add_corner:10,66,-2\r\n        // -->\r\n        tagProcessor.registerMechanism(\"add_corner\", false, LocationTag.class, (object, mechanism, location) -> {\r\n            if (object.noteName != null) {\r\n                NotedAreaTracker.remove(object);\r\n            }\r\n            Corner newCorner = new Corner(location.getX(), location.getZ());\r\n            object.corners.add(newCorner);\r\n            object.recalculateToFit(newCorner);\r\n            if (object.noteName != null) {\r\n                NotedAreaTracker.add(object);\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.contains_inclusive[<location>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns a boolean indicating whether the specified location is inside this polygon.\r\n        // Uses block-inclusive containment: contains a wider section of blocks along the edge (this mode is equivalent to WorldEdit's block selection).\r\n        // @example\r\n        // - if <polygon[my_polygon].contains_inclusive[<player.location>]>:\r\n        //     - narrate \"The player's location is contained within the polygon!\"\r\n        // - else:\r\n        //     - narrate \"The player's location is not contained within the polygon!\"\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, LocationTag.class, \"contains_inclusive\", (attribute, polygon, loc) -> {\r\n            return new ElementTag(polygon.containsInclusive(loc));\r\n        }, \"contains_location\");\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.blocks_inclusive[(<matcher>)]>\r\n        // @returns ListTag(LocationTag)\r\n        // @description\r\n        // Returns each block location within the polygon.\r\n        // Optionally, specify a material matcher to only return locations with that block type.\r\n        // Uses block-inclusive containment: contains a wider section of blocks along the edge (this mode is equivalent to WorldEdit's block selection).\r\n        // @example\r\n        // # Displays a debugblock at each block of wool contained within the polygon \"my_polygon\".\r\n        // - debugblock <polygon[my_polygon].blocks_inclusive[*wool]>\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"blocks_inclusive\", (attribute, polygon) -> {\r\n            if (attribute.hasParam()) {\r\n                NMSHandler.chunkHelper.changeChunkServerThread(polygon.getWorld().getWorld());\r\n                try {\r\n                    String matcher = attribute.getParam();\r\n                    Predicate<Location> predicate = (l) -> new LocationTag(l).tryAdvancedMatcher(matcher, attribute.context);\r\n                    return polygon.getBlocksInternal(predicate, true);\r\n                }\r\n                finally {\r\n                    NMSHandler.chunkHelper.restoreServerThread(polygon.getWorld().getWorld());\r\n                }\r\n            }\r\n            return polygon.getBlocksInternal(null, true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <PolygonTag.shell_inclusive>\r\n        // @returns ListTag(LocationTag)\r\n        // @description\r\n        // Returns each block location on the 3D outer shell of the polygon.\r\n        // Uses block-inclusive containment: contains a wider section of blocks along the edge (this mode is equivalent to WorldEdit's block selection).\r\n        // @example\r\n        // # Plays the \"flame\" effect at the outer 3D shell of the polygon \"my_polygon\".\r\n        // - playeffect effect:flame at:<polygon[my_polygon].shell_inclusive> offset:0.0\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"shell_inclusive\", (attribute, polygon) -> {\r\n            return polygon.getShellInternal(true);\r\n        });\r\n    }\r\n\r\n    public static ObjectTagProcessor<PolygonTag> tagProcessor = new ObjectTagProcessor<>();\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return tagProcessor.getObjectAttribute(this, attribute);\r\n    }\r\n\r\n    public void applyProperty(Mechanism mechanism) {\r\n        if (noteName != null) {\r\n            mechanism.echoError(\"Cannot apply properties to noted objects.\");\r\n            return;\r\n        }\r\n        adjust(mechanism);\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n        tagProcessor.processMechanism(this, mechanism);\r\n    }\r\n\r\n    @Override\r\n    public boolean advancedMatches(String matcher, TagContext context) {\r\n        String matcherLow = CoreUtilities.toLowerCase(matcher);\r\n        if (matcherLow.equals(\"polygon\")) {\r\n            return true;\r\n        }\r\n        return areaBaseAdvancedMatches(matcher);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/TradeTag.java",
    "content": "package com.denizenscript.denizen.objects;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.MerchantRecipe;\r\n\r\npublic class TradeTag implements ObjectTag, Adjustable {\r\n\r\n    // <--[ObjectType]\r\n    // @name TradeTag\r\n    // @prefix trade\r\n    // @base ElementTag\r\n    // @implements PropertyHolderObject\r\n    // @ExampleTagBase trade[trade[result=diamond;max_uses=10;inputs=stick]]\r\n    // @ExampleValues trade[result=diamond;max_uses=10;inputs=stick]\r\n    // @format\r\n    // The identity format for trades is just the text 'trade'. All other data is specified through properties.\r\n    //\r\n    // @description\r\n    // Merchant trades are the parts of a special merchant inventory that is typically viewed by right clicking\r\n    // a villager entity. Any number of trades can fit in a single merchant inventory.\r\n    //\r\n    // Trades are represented by TradeTags.\r\n    //\r\n    // The properties that can be used to customize a merchant trade are:\r\n    //\r\n    // result=<item>\r\n    // inputs=<item>(|<item>)\r\n    // uses=<number of uses>\r\n    // max_uses=<maximum number of uses>\r\n    // has_xp=true/false\r\n    //\r\n    // For example, the following command opens a virtual merchant inventory with two merchant trades.\r\n    // The first trade offers a sponge for two emeralds, can be used up to 10 times,\r\n    // and offers XP upon a successful transaction.\r\n    // The second trade has zero maximum uses and displays a barrier in the input and output slots.\r\n    // <code>\r\n    // - opentrades trade[max_uses=10;inputs=emerald[quantity=2];result=sponge]|trade[inputs=barrier;result=barrier]\r\n    // </code>\r\n    //\r\n    // -->\r\n\r\n    @Fetchable(\"trade\")\r\n    public static TradeTag valueOf(String string, TagContext context) {\r\n        if (string == null) {\r\n            return null;\r\n        }\r\n        if (ObjectFetcher.isObjectWithProperties(string)) {\r\n            return ObjectFetcher.getObjectFromWithProperties(TradeTag.class, string, context);\r\n        }\r\n        string = CoreUtilities.toLowerCase(string).replace(\"trade@\", \"\");\r\n        if (string.toLowerCase().matches(\"trade\")) {\r\n            return new TradeTag(null);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static boolean matches(String str) {\r\n        return valueOf(str, CoreUtilities.noDebugContext) != null;\r\n    }\r\n\r\n    public TradeTag(MerchantRecipe recipe) {\r\n        if (recipe == null) {\r\n            recipe = new MerchantRecipe(new ItemStack(Material.DIRT), 0); // Dirt instead of air because Paper will get upset at air\r\n        }\r\n        this.recipe = recipe;\r\n    }\r\n\r\n    public static MerchantRecipe duplicateRecipe(MerchantRecipe oldRecipe) {\r\n        return duplicateRecipe(oldRecipe.getResult(), oldRecipe);\r\n    }\r\n\r\n    public static MerchantRecipe duplicateRecipe(ItemStack result, MerchantRecipe oldRecipe) {\r\n        MerchantRecipe newRecipe;\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            newRecipe = new MerchantRecipe(result, oldRecipe.getUses(), oldRecipe.getMaxUses(), oldRecipe.hasExperienceReward(), oldRecipe.getVillagerExperience(), oldRecipe.getPriceMultiplier(), oldRecipe.getDemand(), oldRecipe.getSpecialPrice());\r\n        }\r\n        else {\r\n            newRecipe = new MerchantRecipe(result, oldRecipe.getUses(), oldRecipe.getMaxUses(), oldRecipe.hasExperienceReward(), oldRecipe.getVillagerExperience(), oldRecipe.getPriceMultiplier());\r\n        }\r\n        newRecipe.setIngredients(oldRecipe.getIngredients());\r\n        return newRecipe;\r\n    }\r\n\r\n    @Override\r\n    public TradeTag duplicate() {\r\n        MerchantRecipe result = duplicateRecipe(recipe);\r\n        return new TradeTag(result);\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return identify();\r\n    }\r\n\r\n    private MerchantRecipe recipe;\r\n\r\n    public MerchantRecipe getRecipe() {\r\n        return recipe;\r\n    }\r\n\r\n    public void setRecipe(MerchantRecipe recipe) {\r\n        this.recipe = recipe;\r\n    }\r\n\r\n    @Override\r\n    public String getPrefix() {\r\n        return \"trade\";\r\n    }\r\n\r\n    @Override\r\n    public TradeTag setPrefix(String prefix) {\r\n        return this;\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnique() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public String debuggable() {\r\n        return \"<LG>trade@trade<Y>\" + PropertyParser.getPropertiesDebuggable(this);\r\n    }\r\n\r\n    @Override\r\n    public String identify() {\r\n        return \"trade@trade\" + PropertyParser.getPropertiesString(this);\r\n    }\r\n\r\n    @Override\r\n    public String identifySimple() {\r\n        return identify();\r\n    }\r\n\r\n    @Override\r\n    public Object getJavaObject() {\r\n        return recipe;\r\n    }\r\n\r\n    public static void register() {\r\n        PropertyParser.registerPropertyTagHandlers(TradeTag.class, tagProcessor);\r\n    }\r\n\r\n    public static ObjectTagProcessor<TradeTag> tagProcessor = new ObjectTagProcessor<>();\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return tagProcessor.getObjectAttribute(this, attribute);\r\n    }\r\n\r\n    @Override\r\n    public void applyProperty(Mechanism mechanism) {\r\n        adjust(mechanism);\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n        tagProcessor.processMechanism(this, mechanism);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/WorldTag.java",
    "content": "package com.denizenscript.denizen.objects;\n\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizen.utilities.flags.WorldFlagHandler;\nimport com.denizenscript.denizen.utilities.world.GameRuleReflect;\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\nimport com.denizenscript.denizencore.flags.FlaggableObject;\nimport com.denizenscript.denizencore.objects.Adjustable;\nimport com.denizenscript.denizencore.objects.Fetchable;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.tags.ObjectTagProcessor;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.tags.TagRunnable;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.citizensnpcs.api.CitizensAPI;\nimport net.citizensnpcs.api.npc.NPC;\nimport org.bukkit.*;\nimport org.bukkit.boss.DragonBattle;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.world.TimeSkipEvent;\nimport org.bukkit.util.BoundingBox;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\npublic class WorldTag implements ObjectTag, Adjustable, FlaggableObject {\n\n    // <--[ObjectType]\n    // @name WorldTag\n    // @prefix w\n    // @base ElementTag\n    // @implements FlaggableObject\n    // @ExampleTagBase player.location.world\n    // @ExampleValues <player.location.world>,space\n    // @ExampleForReturns\n    // - adjust %VALUE% destroy\n    // @ExampleForReturns\n    // - adjust %VALUE% full_time:0\n    // @format\n    // The identity format for worlds is the name of the world it should be associated with.\n    // For example, to reference the world named 'world1', use simply 'world1'.\n    // World names are case insensitive.\n    //\n    // @description\n    // A WorldTag represents a world on the server.\n    //\n    // This object type is flaggable.\n    // Flags on this object type will be stored in the world folder in a file named 'denizen_flags.dat', like \"server/world/denizen_flags.dat\".\n    //\n    // @Matchable\n    // WorldTag matchers, sometimes identified as \"<world>\":\n    // \"world\" plaintext: always matches.\n    // World name: matches if the world has the given world name, using advanced matchers.\n    // \"world_flagged:<flag>\": a Flag Matchable for WorldTag flags.\n    //\n    // -->\n\n    @Fetchable(\"w\")\n    public static WorldTag valueOf(String string, TagContext context) {\n        return valueOf(string, context == null || context.showErrors());\n    }\n\n    public static WorldTag valueOf(String string, boolean announce) {\n        if (string == null) {\n            return null;\n        }\n        string = string.replace(\"w@\", \"\");\n        World returnable = null;\n        for (World world : Bukkit.getWorlds()) {\n            if (world.getName().equalsIgnoreCase(string)) {\n                returnable = world;\n            }\n        }\n        if (returnable != null) {\n            return new WorldTag(returnable);\n        }\n        else if (announce) {\n            Debug.echoError(\"Invalid World! '\" + string + \"' could not be found.\");\n        }\n        return null;\n    }\n\n    public static boolean matches(String arg) {\n        arg = arg.replace(\"w@\", \"\");\n        World returnable = null;\n        for (World world : Bukkit.getWorlds()) {\n            if (world.getName().equalsIgnoreCase(arg)) {\n                returnable = world;\n            }\n        }\n        return returnable != null;\n    }\n\n    @Override\n    public AbstractFlagTracker getFlagTracker() {\n        return WorldFlagHandler.worldFlagTrackers.get(getName());\n    }\n\n    @Override\n    public void reapplyTracker(AbstractFlagTracker tracker) {\n        // Nothing to do.\n    }\n\n    @Override\n    public String getReasonNotFlaggable() {\n        return \"is the world loaded?\";\n    }\n\n    public World getWorld() {\n        return Bukkit.getWorld(world_name);\n    }\n\n    public String getName() {\n        return world_name;\n    }\n\n    public List<Entity> getEntities() {\n        return getWorld().getEntities();\n    }\n\n    public List<Entity> getEntitiesForTag() {\n        NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n        try {\n            return getWorld().getEntities();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    public final Collection<Entity> getPossibleEntitiesForBoundary(BoundingBox box) {\n        // Bork-prevention: getNearbyEntities loops over chunks, so for large boxes just get the direct entity list, as that's probably better than a loop over unloaded chunks\n        if (box.getWidthX() > 512 || box.getWidthZ() > 512) {\n            return getWorld().getEntities();\n        }\n        return getWorld().getNearbyEntities(box);\n    }\n\n    public Collection<Entity> getPossibleEntitiesForBoundaryForTag(BoundingBox box) {\n        NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n        try {\n            return getPossibleEntitiesForBoundary(box);\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    public List<LivingEntity> getLivingEntitiesForTag() {\n        NMSHandler.chunkHelper.changeChunkServerThread(getWorld());\n        try {\n            return getWorld().getLivingEntities();\n        }\n        finally {\n            NMSHandler.chunkHelper.restoreServerThread(getWorld());\n        }\n    }\n\n    public <T> T getGameRuleOrDefault(GameRule<T> gameRule) {\n        World world = getWorld();\n        T value = world.getGameRuleValue(gameRule);\n        if (value == null) {\n            value = world.getGameRuleDefault(gameRule);\n            if (value == null) {\n                throw new IllegalStateException(\"World \" + world_name + \" contains no GameRule \" + GameRuleReflect.getName(gameRule));\n            }\n        }\n        return value;\n    }\n\n    private String prefix;\n    String world_name;\n\n    public WorldTag(World world) {\n        this(null, world);\n    }\n\n    public WorldTag(String worldName) {\n        prefix = \"World\";\n        this.world_name = worldName;\n    }\n\n    public WorldTag(String prefix, World world) {\n        if (prefix == null) {\n            this.prefix = \"World\";\n        }\n        else {\n            this.prefix = prefix;\n        }\n        this.world_name = world.getName();\n    }\n\n    @Override\n    public String getPrefix() {\n        return prefix;\n    }\n\n    @Override\n    public boolean isUnique() {\n        return true;\n    }\n\n    @Override\n    public String identify() {\n        return \"w@\" + world_name;\n\n    }\n\n    @Override\n    public String identifySimple() {\n        return identify();\n    }\n\n    @Override\n    public String toString() {\n        return identify();\n    }\n\n    @Override\n    public Object getJavaObject() {\n        return getWorld();\n    }\n\n    @Override\n    public ObjectTag setPrefix(String prefix) {\n        this.prefix = prefix;\n        return this;\n    }\n\n    public static void register() {\n\n        AbstractFlagTracker.registerFlagHandlers(tagProcessor);\n\n        /////////////////////\n        //   ENTITY LIST ATTRIBUTES\n        /////////////////\n\n        // <--[tag]\n        // @attribute <WorldTag.entities[(<matcher>)]>\n        // @returns ListTag(EntityTag)\n        // @description\n        // Returns a list of entities in this world.\n        // Optionally specify an entity type matcher to filter down to.\n        // -->\n        registerTag(ListTag.class, \"entities\", (attribute, object) -> {\n            ListTag entities = new ListTag();\n            String matcher = attribute.hasParam() ? attribute.getParam() : null;\n            for (Entity entity : object.getEntitiesForTag()) {\n                EntityTag current = new EntityTag(entity);\n                if (matcher == null || current.tryAdvancedMatcher(matcher, attribute.context)) {\n                    entities.addObject(current.getDenizenObject());\n                }\n            }\n            return entities;\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.living_entities>\n        // @returns ListTag(EntityTag)\n        // @description\n        // Returns a list of living entities in this world.\n        // This includes Players, mobs, NPCs, etc., but excludes dropped items, experience orbs, etc.\n        // -->\n        registerTag(ListTag.class, \"living_entities\", (attribute, object) -> {\n            ListTag entities = new ListTag();\n            for (Entity entity : object.getLivingEntitiesForTag()) {\n                entities.addObject(new EntityTag(entity).getDenizenObject());\n            }\n            return entities;\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.players>\n        // @returns ListTag(PlayerTag)\n        // @description\n        // Returns a list of online players in this world.\n        // -->\n        registerTag(ListTag.class, \"players\", (attribute, object) -> {\n            ListTag players = new ListTag();\n            for (Player player : object.getWorld().getPlayers()) {\n                if (!EntityTag.isNPC(player)) {\n                    players.addObject(new PlayerTag(player));\n                }\n            }\n            return players;\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.spawned_npcs>\n        // @returns ListTag(NPCTag)\n        // @description\n        // Returns a list of spawned NPCs in this world.\n        // -->\n        registerTag(ListTag.class, \"spawned_npcs\", (attribute, object) -> {\n            ListTag npcs = new ListTag();\n            World thisWorld = object.getWorld();\n            for (NPC npc : CitizensAPI.getNPCRegistry()) {\n                if (npc.isSpawned() && npc.getStoredLocation().getWorld().equals(thisWorld)) {\n                    npcs.addObject(new NPCTag(npc));\n                }\n            }\n            return npcs;\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.npcs>\n        // @returns ListTag(NPCTag)\n        // @description\n        // Returns a list of all NPCs in this world.\n        // -->\n        registerTag(ListTag.class, \"npcs\", (attribute, object) -> {\n            ListTag npcs = new ListTag();\n            World thisWorld = object.getWorld();\n            for (NPC npc : CitizensAPI.getNPCRegistry()) {\n                Location location = npc.getStoredLocation();\n                if (location != null) {\n                    World world = location.getWorld();\n                    if (world != null && world.equals(thisWorld)) {\n                        npcs.addObject(new NPCTag(npc));\n                    }\n                }\n            }\n            return npcs;\n        });\n\n        /////////////////////\n        //   GEOGRAPHY ATTRIBUTES\n        /////////////////\n\n        // <--[tag]\n        // @attribute <WorldTag.can_generate_structures>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the world will generate structures.\n        // -->\n        registerTag(ElementTag.class, \"can_generate_structures\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().canGenerateStructures());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.loaded_chunks>\n        // @returns ListTag(ChunkTag)\n        // @description\n        // Returns a list of all the currently loaded chunks.\n        // -->\n        registerTag(ListTag.class, \"loaded_chunks\", (attribute, object) -> {\n            ListTag chunks = new ListTag();\n            for (Chunk ent : object.getWorld().getLoadedChunks()) {\n                chunks.addObject(new ChunkTag(ent));\n            }\n\n            return chunks;\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.sea_level>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the level of the sea.\n        // -->\n        registerTag(ElementTag.class, \"sea_level\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getSeaLevel());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.max_height>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the maximum block height of the world.\n        // -->\n        registerTag(ElementTag.class, \"max_height\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getMaxHeight());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.min_height>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the minimum block height of the world.\n        // -->\n        registerTag(ElementTag.class, \"min_height\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getMinHeight());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.spawn_location>\n        // @returns LocationTag\n        // @mechanism WorldTag.spawn_location\n        // @description\n        // Returns the spawn location of the world.\n        // -->\n        registerTag(LocationTag.class, \"spawn_location\", (attribute, object) -> {\n            return new LocationTag(object.getWorld().getSpawnLocation());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.world_type>\n        // @returns ElementTag\n        // @description\n        // Returns the world type of the world.\n        // Can return any enum from: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/WorldType.html>\n        // -->\n        registerTag(ElementTag.class, \"world_type\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getWorldType().getName());\n        });\n\n        /////////////////////\n        //   IDENTIFICATION ATTRIBUTES\n        /////////////////\n\n        // <--[tag]\n        // @attribute <WorldTag.name>\n        // @returns ElementTag\n        // @description\n        // Returns the name of the world.\n        // -->\n        tagProcessor.registerTag(ElementTag.class, \"name\", (attribute, object) -> {\n            return new ElementTag(object.world_name);\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.seed>\n        // @returns ElementTag\n        // @description\n        // Returns the world seed.\n        // -->\n        registerTag(ElementTag.class, \"seed\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getSeed());\n        });\n\n        /////////////////////\n        //   SETTINGS ATTRIBUTES\n        /////////////////\n\n        // <--[tag]\n        // @attribute <WorldTag.allows_animals>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether animals can spawn in this world.\n        // -->\n        registerTag(ElementTag.class, \"allows_animals\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getAllowAnimals());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.allows_monsters>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether monsters can spawn in this world.\n        // -->\n        registerTag(ElementTag.class, \"allows_monsters\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getAllowMonsters());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.allows_pvp>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether player versus player combat is allowed in this world.\n        // -->\n        registerTag(ElementTag.class, \"allows_pvp\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getPVP());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.auto_save>\n        // @returns ElementTag(Boolean)\n        // @mechanism WorldTag.auto_save\n        // @description\n        // Returns whether the world automatically saves.\n        // -->\n        registerTag(ElementTag.class, \"auto_save\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().isAutoSave());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.ambient_spawn_limit>\n        // @returns ElementTag(Number)\n        // @mechanism WorldTag.ambient_spawn_limit\n        // @description\n        // Returns the number of ambient mobs that can spawn in a chunk in this world.\n        // -->\n        registerTag(ElementTag.class, \"ambient_spawn_limit\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getAmbientSpawnLimit());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.animal_spawn_limit>\n        // @returns ElementTag(Number)\n        // @mechanism WorldTag.animal_spawn_limit\n        // @description\n        // Returns the number of animals that can spawn in a chunk in this world.\n        // -->\n        registerTag(ElementTag.class, \"animal_spawn_limit\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getAnimalSpawnLimit());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.monster_spawn_limit>\n        // @returns ElementTag(Number)\n        // @mechanism WorldTag.monster_spawn_limit\n        // @description\n        // Returns the number of monsters that can spawn in a chunk in this world.\n        // -->\n        registerTag(ElementTag.class, \"monster_spawn_limit\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getMonsterSpawnLimit());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.water_animal_spawn_limit>\n        // @returns ElementTag(Number)\n        // @mechanism WorldTag.water_animal_spawn_limit\n        // @description\n        // Returns the number of water animals that can spawn in a chunk in this world.\n        // -->\n        registerTag(ElementTag.class, \"water_animal_spawn_limit\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getWaterAnimalSpawnLimit());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.difficulty>\n        // @returns ElementTag\n        // @mechanism WorldTag.difficulty\n        // @description\n        // Returns the name of the difficulty level.\n        // -->\n        registerTag(ElementTag.class, \"difficulty\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getDifficulty());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.hardcore>\n        // @returns ElementTag(Boolean)\n        // @mechanism WorldTag.hardcore\n        // @description\n        // Returns whether the world is in hardcore mode.\n        // -->\n        registerTag(ElementTag.class, \"hardcore\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().isHardcore());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.keep_spawn>\n        // @returns ElementTag(Boolean)\n        // @mechanism WorldTag.keep_spawn\n        // @description\n        // Returns whether the world's spawn area should be kept loaded into memory.\n        // -->\n        registerTag(ElementTag.class, \"keep_spawn\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getKeepSpawnInMemory());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.ticks_per_animal_spawn>\n        // @returns DurationTag\n        // @mechanism WorldTag.ticks_per_animal_spawns\n        // @description\n        // Returns the world's ticks per animal spawn value.\n        // -->\n        registerTag(DurationTag.class, \"ticks_per_animal_spawn\", (attribute, object) -> {\n            return new DurationTag(object.getWorld().getTicksPerAnimalSpawns());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.ticks_per_monster_spawn>\n        // @returns DurationTag\n        // @mechanism WorldTag.ticks_per_monster_spawns\n        // @description\n        // Returns the world's ticks per monster spawn value.\n        // -->\n        registerTag(DurationTag.class, \"ticks_per_monster_spawn\", (attribute, object) -> {\n            return new DurationTag(object.getWorld().getTicksPerMonsterSpawns());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.duration_since_created>\n        // @mechanism WorldTag.duration_since_created\n        // @returns DurationTag\n        // @description\n        // Returns the total duration of time since this world was first created.\n        // -->\n        registerTag(DurationTag.class, \"duration_since_created\", (attribute, object) -> {\n            return new DurationTag(object.getWorld().getGameTime());\n        });\n\n        /////////////////////\n        //   TIME ATTRIBUTES\n        /////////////////\n\n        // <--[tag]\n        // @attribute <WorldTag.time>\n        // @returns ElementTag(Number)\n        // @mechanism WorldTag.time\n        // @description\n        // Returns the relative in-game time of this world.\n        // -->\n        registerTag(ObjectTag.class, \"time\", (attribute, object) -> {\n            // <--[tag]\n            // @attribute <WorldTag.time.duration>\n            // @returns DurationTag\n            // @description\n            // Deprecated in favor of <@link tag WorldTag.time_duration>\n            // @deprecated Use <@link tag WorldTag.time_duration> instead.\n            // -->\n            if (attribute.startsWith(\"duration\", 2)) {\n                BukkitImplDeprecations.timeSubTags.warn(attribute.context);\n                attribute.fulfill(1);\n                return new DurationTag(object.getWorld().getTime());\n            }\n\n            // <--[tag]\n            // @attribute <WorldTag.time.full>\n            // @returns DurationTag\n            // @description\n            // Deprecated in favor of <@link tag WorldTag.time_full>\n            // @deprecated Use <@link tag WorldTag.time_full> instead.\n            // -->\n            else if (attribute.startsWith(\"full\", 2)) {\n                BukkitImplDeprecations.timeSubTags.warn(attribute.context);\n                attribute.fulfill(1);\n                return new DurationTag(object.getWorld().getFullTime());\n            }\n\n            // <--[tag]\n            // @attribute <WorldTag.time.period>\n            // @returns ElementTag\n            // @description\n            // Deprecated in favor of <@link tag WorldTag.time_period>\n            // @deprecated Use <@link tag WorldTag.time_period> instead.\n            // -->\n            else if (attribute.startsWith(\"period\", 2)) {\n                BukkitImplDeprecations.timeSubTags.warn(attribute.context);\n                attribute.fulfill(1);\n\n                long time = object.getWorld().getTime();\n                String period;\n\n                if (time >= 23000) {\n                    period = \"dawn\";\n                }\n                else if (time >= 13500) {\n                    period = \"night\";\n                }\n                else if (time >= 12500) {\n                    period = \"dusk\";\n                }\n                else {\n                    period = \"day\";\n                }\n\n                return new ElementTag(period);\n            }\n            else {\n                return new ElementTag(object.getWorld().getTime());\n            }\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.time_duration>\n        // @returns DurationTag\n        // @description\n        // Returns the relative in-game time of this world as a duration.\n        // -->\n        registerTag(DurationTag.class, \"time_duration\", (attribute, object) -> {\n            return new DurationTag(object.getWorld().getTime());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.time_full>\n        // @returns DurationTag\n        // @description\n        // Returns the in-game time of this world.\n        // -->\n        registerTag(DurationTag.class, \"time_full\", (attribute, object) -> {\n            return new DurationTag(object.getWorld().getFullTime());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.time_period>\n        // @returns ElementTag\n        // @description\n        // Returns the time as 'day', 'night', 'dawn', or 'dusk'.\n        // -->\n        registerTag(ElementTag.class, \"time_period\", (attribute, object) -> {\n            long time = object.getWorld().getTime();\n            String period;\n\n            if (time >= 23000) {\n                period = \"dawn\";\n            }\n            else if (time >= 13500) {\n                period = \"night\";\n            }\n            else if (time >= 12500) {\n                period = \"dusk\";\n            }\n            else {\n                period = \"day\";\n            }\n            return new ElementTag(period);\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.moon_phase>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the current phase of the moon, as a number from 1 to 8.\n        // -->\n        registerTag(ElementTag.class, \"moon_phase\", (attribute, object) -> {\n            return new ElementTag((int) ((object.getWorld().getFullTime() / 24000) % 8) + 1);\n        }, \"moonphase\");\n\n        /////////////////////\n        //   WEATHER ATTRIBUTES\n        /////////////////\n\n        // <--[tag]\n        // @attribute <WorldTag.has_storm>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether there is currently a storm in this world.\n        // ie, whether it is raining. To check for thunder, use <@link tag WorldTag.thundering>.\n        // -->\n        registerTag(ElementTag.class, \"has_storm\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().hasStorm());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.thunder_duration>\n        // @returns DurationTag\n        // @mechanism WorldTag.thunder_duration\n        // @description\n        // Returns the duration of thunder.\n        // -->\n        registerTag(DurationTag.class, \"thunder_duration\", (attribute, object) -> {\n            return new DurationTag((long) object.getWorld().getThunderDuration());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.thundering>\n        // @returns ElementTag(Boolean)\n        // @mechanism WorldTag.thundering\n        // @description\n        // Returns whether it is currently thundering in this world.\n        // -->\n        registerTag(ElementTag.class, \"thundering\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().isThundering());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.weather_duration>\n        // @returns DurationTag\n        // @mechanism WorldTag.weather_duration\n        // @description\n        // Returns the duration of storms.\n        // -->\n        registerTag(DurationTag.class, \"weather_duration\", (attribute, object) -> {\n            return new DurationTag((long) object.getWorld().getWeatherDuration());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.environment>\n        // @returns ElementTag\n        // @description\n        // Returns the environment of the world: NORMAL, NETHER, or THE_END.\n        // -->\n        registerTag(ElementTag.class, \"environment\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getEnvironment());\n        });\n\n        /////////////////////\n        //   WORLD BORDER ATTRIBUTES\n        /////////////////\n\n        // <--[tag]\n        // @attribute <WorldTag.border_size>\n        // @returns ElementTag(Decimal)\n        // @description\n        // Returns the size of the world border in this world.\n        // -->\n        registerTag(ElementTag.class, \"border_size\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getWorldBorder().getSize());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.border_center>\n        // @returns LocationTag\n        // @description\n        // Returns the center of the world border in this world.\n        // -->\n        registerTag(LocationTag.class, \"border_center\", (attribute, object) -> {\n            return new LocationTag(object.getWorld().getWorldBorder().getCenter());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.border_damage>\n        // @returns ElementTag(Decimal)\n        // @description\n        // Returns the amount of damage caused by crossing the world border in this world.\n        // -->\n        registerTag(ElementTag.class, \"border_damage\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getWorldBorder().getDamageAmount());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.border_damage_buffer>\n        // @returns ElementTag(Decimal)\n        // @description\n        // Returns the damage buffer of the world border in this world.\n        // -->\n        registerTag(ElementTag.class, \"border_damage_buffer\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getWorldBorder().getDamageBuffer());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.border_warning_distance>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the warning distance of the world border in this world.\n        // -->\n        registerTag(ElementTag.class, \"border_warning_distance\", (attribute, object) -> {\n            return new ElementTag(object.getWorld().getWorldBorder().getWarningDistance());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.border_warning_time>\n        // @returns DurationTag\n        // @description\n        // Returns warning time of the world border in this world as a duration.\n        // -->\n        registerTag(DurationTag.class, \"border_warning_time\", (attribute, object) -> {\n            return new DurationTag(object.getWorld().getWorldBorder().getWarningTime());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.gamerule[<gamerule>]>\n        // @returns ElementTag\n        // @description\n        // Returns the current value of the specified gamerule in the world.\n        // -->\n        registerTag(ElementTag.class, \"gamerule\", (attribute, object) -> {\n            if (!attribute.hasParam()) {\n                attribute.echoError(\"The tag 'worldtag.gamerule[...]' must have an input value.\");\n                return null;\n            }\n            GameRule<?> rule = GameRuleReflect.getByName(attribute.getParam());\n            if (rule == null) {\n                attribute.echoError(\"Invalid game rule specified: \" + attribute.getParam() + '.');\n                return null;\n            }\n            Object result = GameRuleReflect.getValue(object.getWorld(), rule);\n            return new ElementTag(String.valueOf(result), true);\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.gamerule_map>\n        // @returns MapTag\n        // @description\n        // Returns a map of all the current values of all gamerules in the world.\n        // -->\n        registerTag(MapTag.class, \"gamerule_map\", (attribute, object) -> {\n            MapTag map = new MapTag();\n            for (GameRule<?> rule : GameRuleReflect.values()) {\n                Object result = GameRuleReflect.getValue(object.getWorld(), rule);\n                if (result != null) {\n                    map.putObject(GameRuleReflect.getName(rule), new ElementTag(result.toString(), true));\n                }\n            }\n            return map;\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.dragon_portal_location>\n        // @returns LocationTag\n        // @description\n        // Returns the location of the ender dragon exit portal, if any (only for end worlds).\n        // -->\n        registerTag(LocationTag.class, \"dragon_portal_location\", (attribute, object) -> {\n            DragonBattle battle = object.getWorld().getEnderDragonBattle();\n            if (battle == null) {\n                attribute.echoError(\"Provided world is not an end world!\");\n                return null;\n            }\n            if (battle.getEndPortalLocation() == null) {\n                return null;\n            }\n            return new LocationTag(battle.getEndPortalLocation());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.ender_dragon>\n        // @returns EntityTag\n        // @description\n        // Returns the ender dragon entity currently fighting in this world, if any (only for end worlds).\n        // -->\n        registerTag(EntityTag.class, \"ender_dragon\", (attribute, object) -> {\n            DragonBattle battle = object.getWorld().getEnderDragonBattle();\n            if (battle == null) {\n                return null;\n            }\n            if (battle.getEnderDragon() == null) {\n                return null;\n            }\n            return new EntityTag(battle.getEnderDragon());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.gateway_locations>\n        // @returns ListTag(LocationTag)\n        // @description\n        // Returns a list of possible gateway portal locations, if any (only for end worlds).\n        // Not all of these will necessarily generate.\n        // In current implementation, this is a list of exactly 20 locations in a circle around the world origin (with radius of 96 blocks).\n        // -->\n        registerTag(ListTag.class, \"gateway_locations\", (attribute, object) -> {\n            DragonBattle battle = object.getWorld().getEnderDragonBattle();\n            if (battle == null) {\n                return null;\n            }\n            ListTag list = new ListTag();\n            for (int i = 0; i < 20; i++) {\n                // This math based on EndDragonFight#spawnNewGateway\n                int x = (int) Math.floor(96.0D * Math.cos(2.0D * (-Math.PI + (Math.PI / 20.0) * i)));\n                int z = (int) Math.floor(96.0D * Math.sin(2.0D * (-Math.PI + (Math.PI / 20.0) * i)));\n                list.addObject(new LocationTag(object.getWorld(), x, 75, z));\n            }\n            return list;\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.biomes>\n        // @returns ListTag(BiomeTag)\n        // @description\n        // Returns a list of all biomes in this world (including custom biomes).\n        // -->\n        registerTag(ListTag.class, \"biomes\", (attribute, object) -> {\n            ListTag output = new ListTag();\n            for (BiomeNMS biome : NMSHandler.instance.getBiomes(object.getWorld())) {\n                output.addObject(new BiomeTag(biome));\n            }\n            return output;\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.view_distance>\n        // @returns ElementTag(Number)\n        // @mechanism WorldTag.view_distance\n        // @group properties\n        // @description\n        // Returns the view distance of this world. Chunks are visible to players inside this radius.\n        // See also <@link tag WorldTag.simulation_distance>\n        // -->\n        registerTag(ElementTag.class, \"view_distance\", (attribute, world) -> {\n            // Note: mechanism is paper-only, in PaperWorldProperties\n            return new ElementTag(world.getWorld().getViewDistance());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.simulation_distance>\n        // @returns ElementTag(Number)\n        // @mechanism WorldTag.simulation_distance\n        // @group properties\n        // @description\n        // Returns the simulation distance of this world. Chunks inside of this radius to players are ticked and processed.\n        // See also <@link tag WorldTag.view_distance>\n        // -->\n        registerTag(ElementTag.class, \"simulation_distance\", (attribute, world) -> {\n            // Note: mechanism is paper-only, in PaperWorldProperties\n            return new ElementTag(world.getWorld().getSimulationDistance());\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.enough_sleeping[(<#>)]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether enough players are sleeping to prepare for the night to advance.\n        // Typically used before checking <@link tag WorldTag.enough_deep_sleeping>\n        // By default, automatically checks the players_sleeping_percentage gamerule,\n        // but this can optionally be overridden by specifying a percentage integer.\n        // Any integer above 100 will always yield 'false'. Requires at least one player to be sleeping to return 'true'.\n        // -->\n        registerTag(ElementTag.class, \"enough_sleeping\", (attribute, world) -> {\n            int percentage;\n            if (attribute.hasParam()) {\n                percentage = attribute.getIntParam();\n            }\n            else {\n                percentage = world.getGameRuleOrDefault(GameRule.PLAYERS_SLEEPING_PERCENTAGE);\n            }\n            return new ElementTag(NMSHandler.worldHelper.areEnoughSleeping(world.getWorld(), percentage));\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.enough_deep_sleeping[(<#>)]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether enough players have been in bed long enough for the night to advance (generally 100 ticks).\n        // Loops through all online players, so is typically used after checking <@link tag WorldTag.enough_sleeping>\n        // By default, automatically checks the players_sleeping_percentage gamerule,\n        // but this can optionally be overridden by specifying a percentage integer.\n        // Any integer above 100 will always yield 'false'. Requires at least one player to be sleeping to return 'true'.\n        // -->\n        registerTag(ElementTag.class, \"enough_deep_sleeping\", (attribute, world) -> {\n            int percentage;\n            if (attribute.hasParam()) {\n                percentage = attribute.getIntParam();\n            }\n            else {\n                percentage = world.getGameRuleOrDefault(GameRule.PLAYERS_SLEEPING_PERCENTAGE);\n            }\n            return new ElementTag(NMSHandler.worldHelper.areEnoughDeepSleeping(world.getWorld(), percentage));\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.sky_darkness>\n        // @returns ElementTag(Number)\n        // @description\n        // Returns the current darkness level of the sky in this world.\n        // This is determined by an equation that factors in rain, thunder, and time of day.\n        // When 4 or higher, players are typically allowed to sleep through the night.\n        // -->\n        registerTag(ElementTag.class, \"sky_darkness\", (attribute, world) -> {\n            return new ElementTag(NMSHandler.worldHelper.getSkyDarken(world.getWorld()));\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.is_day>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether it is considered day in this world. Players are not allowed to sleep at this time.\n        // Note that in certain worlds, this and <@link tag WorldTag.is_night> can both be 'false'! (The nether, for example!)\n        // In typical worlds, this is 'true' if <@link tag WorldTag.sky_darkness> is less than 4.\n        // To check the current time without storm interference, see <@link tag WorldTag.time> and related tags.\n        // -->\n        registerTag(ElementTag.class, \"is_day\", (attribute, world) -> {\n            return new ElementTag(NMSHandler.worldHelper.isDay(world.getWorld()));\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.is_night>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether it is considered night in this world. Players are typically allowed to sleep at this time.\n        // Note that in certain worlds, this and <@link tag WorldTag.is_day> can both be 'false'! (The nether, for example!)\n        // In typical worlds, this is 'true' if <@link tag WorldTag.sky_darkness> is 4 or higher.\n        // To check the current time without storm interference, see <@link tag WorldTag.time> and related tags.\n        // -->\n        registerTag(ElementTag.class, \"is_night\", (attribute, world) -> {\n            return new ElementTag(NMSHandler.worldHelper.isNight(world.getWorld()));\n        });\n\n        // <--[tag]\n        // @attribute <WorldTag.first_dragon_killed>\n        // @returns ElementTag(Boolean)\n        // @mechanism WorldTag.first_dragon_killed\n        // @description\n        // Returns whether the ender dragon has been killed in this world before.\n        // Only works if the world is an end world.\n        // -->\n        registerTag(ElementTag.class, \"first_dragon_killed\", (attribute, object) -> {\n            DragonBattle battle = object.getWorld().getEnderDragonBattle();\n            if (battle == null) {\n                attribute.echoError(\"Provided world is not an end world!\");\n                return null;\n            }\n            return new ElementTag(battle.hasBeenPreviouslyKilled());\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name respawn_dragon\n        // @input None\n        // @description\n        // Initiates the respawn sequence of the ender dragon as if a player placed 4 end crystals on the portal.\n        // Only works if the world is an end world.\n        // -->\n        tagProcessor.registerMechanism(\"respawn_dragon\", false, (object, mechanism) -> {\n            DragonBattle battle = object.getWorld().getEnderDragonBattle();\n            if (battle == null) {\n                mechanism.echoError(\"Provided world is not an end world!\");\n                return;\n            }\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) { // workaround for upstream bug\n                battle.initiateRespawn(List.of());\n            }\n            else {\n                battle.initiateRespawn();\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name reset_crystals\n        // @input None\n        // @description\n        // Resets the end crystals located on the obsidian pillars in this world.\n        // Only works if the world is an end world.\n        // -->\n        tagProcessor.registerMechanism(\"reset_crystals\", false, (object, mechanism) -> {\n            DragonBattle battle = object.getWorld().getEnderDragonBattle();\n            if (battle == null) {\n                mechanism.echoError(\"Provided world is not an end world!\");\n                return;\n            }\n            battle.resetCrystals();\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name respawn_phase\n        // @input ElementTag\n        // @description\n        // Set the current respawn phase of the ender dragon. Valid phases can be found at <@link url https://jd.papermc.io/paper/1.21.3/org/bukkit/boss/DragonBattle.RespawnPhase.html>\n        // Only works if the world is an end world.\n        // -->\n        tagProcessor.registerMechanism(\"respawn_phase\", false, ElementTag.class, (object, mechanism, input) -> {\n            DragonBattle battle = object.getWorld().getEnderDragonBattle();\n            if (battle == null) {\n                mechanism.echoError(\"Provided world is not an end world!\");\n                return;\n            }\n            if (mechanism.requireEnum(DragonBattle.RespawnPhase.class)) {\n                battle.setRespawnPhase(input.asEnum(DragonBattle.RespawnPhase.class));\n            }\n        });\n\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\n\n            // <--[mechanism]\n            // @object WorldTag\n            // @name first_dragon_killed\n            // @input ElementTag(Boolean)\n            // @description\n            // Set whether the first ender dragon was killed already.\n            // Toggling this value won't really affect anything in the end world, but may be useful when creating custom end worlds.\n            // Only works if the world is an end world.\n            // @tags\n            // <WorldTag.first_dragon_killed>\n            // -->\n            tagProcessor.registerMechanism(\"first_dragon_killed\", false, ElementTag.class, (object, mechanism, input) -> {\n                DragonBattle battle = object.getWorld().getEnderDragonBattle();\n                if (battle == null) {\n                    mechanism.echoError(\"Provided world is not an end world!\");\n                    return;\n                }\n                if (mechanism.requireBoolean()) {\n                    battle.setPreviouslyKilled(input.asBoolean());\n                }\n            });\n        }\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name ambient_spawn_limit\n        // @input ElementTag(Number)\n        // @description\n        // Sets the limit for number of ambient mobs that can spawn in a chunk in this world.\n        // @tags\n        // <WorldTag.ambient_spawn_limit>\n        // -->\n        tagProcessor.registerMechanism(\"ambient_spawn_limit\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireInteger()) {\n                object.getWorld().setAmbientSpawnLimit(input.asInt());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name animal_spawn_limit\n        // @input ElementTag(Number)\n        // @description\n        // Sets the limit for number of animals that can spawn in a chunk in this world.\n        // @tags\n        // <WorldTag.animal_spawn_limit>\n        // -->\n        tagProcessor.registerMechanism(\"animal_spawn_limit\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireInteger()) {\n                object.getWorld().setAnimalSpawnLimit(input.asInt());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name auto_save\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether the world will automatically save edits.\n        // @tags\n        // <WorldTag.auto_save>\n        // -->\n        tagProcessor.registerMechanism(\"auto_save\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireBoolean()) {\n                object.getWorld().setAutoSave(input.asBoolean());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name difficulty\n        // @input ElementTag\n        // @description\n        // Sets the difficulty level of this world.\n        // Possible values: Peaceful, Easy, Normal, Hard.\n        // @tags\n        // <WorldTag.difficulty>\n        // -->\n        tagProcessor.registerMechanism(\"difficulty\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireEnum(Difficulty.class)) {\n                object.getWorld().setDifficulty(input.asEnum(Difficulty.class));\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name hardcore\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether the world is hardcore mode.\n        // @tags\n        // <WorldTag.hardcore>\n        // -->\n        tagProcessor.registerMechanism(\"hardcore\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireBoolean()) {\n                object.getWorld().setHardcore(input.asBoolean());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name save\n        // @input None\n        // @description\n        // Saves the world to file.\n        // -->\n        tagProcessor.registerMechanism(\"save\", false, (object, mechanism) -> {\n            object.getWorld().save();\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name destroy\n        // @input None\n        // @description\n        // Unloads the world from the server without saving chunks, then destroys all data that is part of the world.\n        // Require config setting 'Commands.Delete.Allow file deletion'.\n        // -->\n        tagProcessor.registerMechanism(\"destroy\", false, (object, mechanism) -> {\n            File folder = object.getWorld().getWorldFolder();\n            object.unloadWorldClean(mechanism, false);\n            if (object.getWorld() != null) {\n                return;\n            }\n            if (!CoreConfiguration.allowFileDeletion) {\n                mechanism.echoError(\"Unable to destroy world due to config setting, refer to 'WorldTag.destroy' meta documentation.\");\n                return;\n            }\n            try {\n                CoreUtilities.deleteDirectory(folder);\n            }\n            catch (Exception ex) {\n                Debug.echoError(ex);\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name force_unload\n        // @input None\n        // @description\n        // Unloads the world from the server without saving chunks.\n        // -->\n        tagProcessor.registerMechanism(\"force_unload\", false, (object, mechanism) -> {\n            object.unloadWorldClean(mechanism, false);\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name full_time\n        // @input ElementTag(Number)\n        // @description\n        // Sets the in-game time on the server.\n        // @tags\n        // <WorldTag.time.full>\n        // -->\n        tagProcessor.registerMechanism(\"full_time\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireInteger()) {\n                object.getWorld().setFullTime(input.asInt());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name keep_spawn\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether the world's spawn area should be kept loaded into memory.\n        // @tags\n        // <WorldTag.keep_spawn>\n        // -->\n        tagProcessor.registerMechanism(\"keep_spawn\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireBoolean()) {\n                object.getWorld().setKeepSpawnInMemory(input.asBoolean());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name monster_spawn_limit\n        // @input ElementTag(Number)\n        // @description\n        // Sets the limit for number of monsters that can spawn in a chunk in this world.\n        // @tags\n        // <WorldTag.monster_spawn_limit>\n        // -->\n        tagProcessor.registerMechanism(\"monster_spawn_limit\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireInteger()) {\n                object.getWorld().setMonsterSpawnLimit(input.asInt());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name allow_pvp\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether player versus player combat is allowed in this world.\n        // @tags\n        // <WorldTag.allows_pvp>\n        // -->\n        tagProcessor.registerMechanism(\"allow_pvp\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireBoolean()) {\n                object.getWorld().setPVP(input.asBoolean());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name spawn_location\n        // @input LocationTag\n        // @description\n        // Sets the spawn location of this world. (This ignores the world value of the LocationTag.)\n        // @tags\n        // <WorldTag.spawn_location>\n        // -->\n        tagProcessor.registerMechanism(\"spawn_location\", false, LocationTag.class, (object, mechanism, input) -> {\n            object.getWorld().setSpawnLocation(input.getBlockX(), input.getBlockY(), input.getBlockZ(), input.getYaw());\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name storming\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether there is a storm.\n        // @tags\n        // <WorldTag.has_storm>\n        // -->\n        tagProcessor.registerMechanism(\"storming\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireBoolean()) {\n                object.getWorld().setStorm(input.asBoolean());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name thunder_duration\n        // @input DurationTag\n        // @description\n        // Sets the duration of thunder.\n        // @tags\n        // <WorldTag.thunder_duration>\n        // -->\n        tagProcessor.registerMechanism(\"thunder_duration\", false, DurationTag.class, (object, mechanism, input) -> {\n            object.getWorld().setThunderDuration(input.getTicksAsInt());\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name thundering\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether it is thundering.\n        // @tags\n        // <WorldTag.thundering>\n        // -->\n        tagProcessor.registerMechanism(\"thundering\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireBoolean()) {\n                object.getWorld().setThundering(input.asBoolean());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name ticks_per_animal_spawns\n        // @input DurationTag\n        // @description\n        // Sets the time between animal spawns.\n        // @tags\n        // <WorldTag.ticks_per_animal_spawn>\n        // -->\n        tagProcessor.registerMechanism(\"ticks_per_animal_spawns\", false, DurationTag.class, (object, mechanism, input) -> {\n            object.getWorld().setTicksPerAnimalSpawns(input.getTicksAsInt());\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name ticks_per_monster_spawns\n        // @input DurationTag\n        // @description\n        // Sets the time between monster spawns.\n        // @tags\n        // <WorldTag.ticks_per_monster_spawn>\n        // -->\n        tagProcessor.registerMechanism(\"ticks_per_monster_spawns\", false, DurationTag.class, (object, mechanism, input) -> {\n            object.getWorld().setTicksPerMonsterSpawns(input.getTicksAsInt());\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name time\n        // @input ElementTag(Number)\n        // @description\n        // Sets the relative in-game time on the server.\n        // @tags\n        // <WorldTag.time>\n        // -->\n        tagProcessor.registerMechanism(\"time\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireInteger()) {\n                object.getWorld().setTime(input.asInt());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name unload\n        // @input None\n        // @description\n        // Unloads the world from the server and saves chunks.\n        // -->\n        tagProcessor.registerMechanism(\"unload\", false, (object, mechanism) -> {\n            object.unloadWorldClean(mechanism, true);\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name water_animal_spawn_limit\n        // @input ElementTag(Number)\n        // @description\n        // Sets the limit for number of water animals that can spawn in a chunk in this world.\n        // @tags\n        // <WorldTag.water_animal_spawn_limit>\n        // -->\n        tagProcessor.registerMechanism(\"water_animal_spawn_limit\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireInteger()) {\n                object.getWorld().setWaterAnimalSpawnLimit(input.asInt());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name weather_duration\n        // @input DurationTag\n        // @description\n        // Set the remaining time of the current conditions.\n        // @tags\n        // <WorldTag.weather_duration>\n        // -->\n        tagProcessor.registerMechanism(\"weather_duration\", false, DurationTag.class, (object, mechanism, input) -> {\n            object.getWorld().setWeatherDuration(input.getTicksAsInt());\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name advance_ticks\n        // @input ElementTag(Number)\n        // @description\n        // Advances this world's day the specified number of ticks WITHOUT firing any events.\n        // Useful for manually adjusting the daylight cycle without firing an event every tick, for example.\n        // -->\n        tagProcessor.registerMechanism(\"advance_ticks\", false, ElementTag.class, (object, mechanism, input) -> {\n            if (mechanism.requireInteger()) {\n                NMSHandler.worldHelper.setDayTime(object.getWorld(), object.getWorld().getFullTime() + input.asInt());\n            }\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name duration_since_created\n        // @input DurationTag\n        // @description\n        // Changes the world's internal time-since-created value.\n        // @tags\n        // <WorldTag.duration_since_created>\n        // -->\n        tagProcessor.registerMechanism(\"duration_since_created\", false, DurationTag.class, (object, mechanism, input) -> {\n            NMSHandler.worldHelper.setGameTime(object.getWorld(), input.getTicks());\n        });\n\n        // <--[mechanism]\n        // @object WorldTag\n        // @name skip_night\n        // @input None\n        // @description\n        // Skips to the next day as if enough players slept through the night.\n        // NOTE: This ignores the advance_time gamerule!\n        // -->\n        tagProcessor.registerMechanism(\"skip_night\", false, (object, mechanism) -> {\n            // general logic from NMS world tick\n            World world = object.getWorld();\n            long worldTime = world.getFullTime();\n            long nextDay = worldTime + 24000L;\n            TimeSkipEvent event = new TimeSkipEvent(world, TimeSkipEvent.SkipReason.NIGHT_SKIP, nextDay - nextDay % 24000L - worldTime);\n            Bukkit.getPluginManager().callEvent(event);\n            if (!event.isCancelled()) {\n                NMSHandler.worldHelper.setDayTime(world, worldTime + event.getSkipAmount());\n            }\n            if (!event.isCancelled()) {\n                NMSHandler.worldHelper.wakeUpAllPlayers(world);\n            }\n            // minor change: prior to 1.18, hasStorm/isRaining was not checked\n            if (object.getGameRuleOrDefault(GameRuleReflect.WEATHER_CYCLE_GAMERULE) && world.hasStorm()) {\n                NMSHandler.worldHelper.clearWeather(world);\n            }\n        });\n    }\n\n    public static ObjectTagProcessor<WorldTag> tagProcessor = new ObjectTagProcessor<>();\n\n    public static <R extends ObjectTag> void registerTag(Class<R> returnType, String name, TagRunnable.ObjectInterface<WorldTag, R> runnable, String... variants) {\n        tagProcessor.registerTag(returnType, name, (attribute, object) -> {\n            if (object.getWorld() == null) {\n                attribute.echoError(\"World '\" + object.world_name + \"' is unloaded, cannot process tag.\");\n                return null;\n            }\n            return runnable.run(attribute, object);\n        }, variants);\n    }\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n        return tagProcessor.getObjectAttribute(this, attribute);\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n        tagProcessor.processMechanism(this, mechanism);\n    }\n\n    public void applyProperty(Mechanism mechanism) {\n        mechanism.echoError(\"Cannot apply properties to a world!\");\n    }\n\n    public void unloadWorldClean(Mechanism mechanism, boolean doSave) {\n        for (Player pl : new ArrayList<>(getWorld().getPlayers())) {\n            if (pl.isOnline()) {\n                mechanism.echoError(\"For WorldTag.\" + mechanism.getName() + \" mechanism, Player \" + pl.getUniqueId() + \"/\" + pl.getName() + \" is inside world and will be kicked.\");\n                pl.kickPlayer(\"World being destroyed.\");\n            }\n        }\n        if (!Bukkit.getServer().unloadWorld(getWorld(), doSave)) {\n            mechanism.echoError(\"WorldTag.\" + mechanism.getName() + \" for world \" + world_name + \" was refused by the System. Are you sure (A) this world is even loaded, (B) all players have been removed, and (C) this is not the default world?\");\n        }\n    }\n\n    @Override\n    public boolean advancedMatches(String matcher, TagContext context) {\n        String matcherLow = CoreUtilities.toLowerCase(matcher);\n        if (matcherLow.equals(\"world\")) {\n            return true;\n        }\n        if (matcherLow.startsWith(\"world_flagged:\")) {\n            return BukkitScriptEvent.coreFlaggedCheck(matcher.substring(\"world_flagged:\".length()), getFlagTracker());\n        }\n        return BukkitScriptEvent.runGenericCheck(matcher, getName());\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/PropertyRegistry.java",
    "content": "package com.denizenscript.denizen.objects.properties;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.*;\r\nimport com.denizenscript.denizen.objects.properties.entity.*;\r\nimport com.denizenscript.denizen.objects.properties.inventory.*;\r\nimport com.denizenscript.denizen.objects.properties.item.*;\r\nimport com.denizenscript.denizen.objects.properties.material.*;\r\nimport com.denizenscript.denizen.objects.properties.trade.*;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\n\r\npublic class PropertyRegistry {\r\n\r\n    public static void registerExtensions() {\r\n        BukkitBinaryTagExtensions.register();\r\n        BukkitColorExtensions.register();\r\n        BukkitElementExtensions.register();\r\n        BukkitListExtensions.register();\r\n        BukkitMapExtensions.register();\r\n        BukkitQueueExtensions.register();\r\n        BukkitScriptExtensions.register();\r\n    }\r\n\r\n    public static void registerMainProperties() {\r\n        registerExtensions();\r\n\r\n        // register core EntityTag properties\r\n        PropertyParser.registerProperty(EntityAge.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityAgeLocked.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityAggressive.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityAI.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityAnger.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityAngry.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityAreaEffectCloud.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityArmorBonus.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityArrowDamage.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityArrowPierceLevel.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityAttributeBaseValues.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityAttributeModifiers.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityArmorPose.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityArms.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityAwake.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityAware.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityBackgroundColor.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityBasePlate.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityBeamTarget.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityBoatType.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityBodyArrows.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityBoundingBox.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityBrightness.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityCanBreakDoors.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityCanJoinRaid.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityCannotEnterHive.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityCharged.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityChestCarrier.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityColor.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityConversionPlayer.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityConversionTime.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityCritical.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityCustomName.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityCustomNameVisible.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityDarkDuration.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityDefaultBackground.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityDirection.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityDisabledSlots.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityDisplay.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityDropsItem.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityEquipment.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityEquipmentDropChance.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityExploredLocations.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityExplosionFire.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityExplosionRadius.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityEyeTargetLocation.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityFirework.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityFireworkLifetime.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityFixed.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityFlags.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityFlower.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityFreezeDuration.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityFramed.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityGlowColor.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityGravity.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityHasNectar.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityHasStung.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityHealth.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityHeight.class, EntityTag.class);\r\n            PropertyParser.registerProperty(EntityHorns.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityHive.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityImmune.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityInterpolationDuration.class, EntityTag.class);\r\n            PropertyParser.registerProperty(EntityInterpolationStart.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityInventory.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityInvulnerable.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityInWaterTime.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityIsShowingBottom.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityItem.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityItemInHand.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityItemInOffHand.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityJumpStrength.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityKnockback.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityLeftRotation.class, EntityTag.class);\r\n            PropertyParser.registerProperty(EntityLineWidth.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityMarker.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityMaterial.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityMaxFuseTicks.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityMaxTemper.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityOnBack.class, EntityTag.class);\r\n            PropertyParser.registerProperty(EntityOpacity.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityPainting.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityPatrolLeader.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityPatrolTarget.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityPickupStatus.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityPivot.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityPlayerCreated.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityPlayingDead.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityPotion.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityPotionEffects.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            PropertyParser.registerProperty(EntityPotionType.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityPowered.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityProfession.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityRiptide.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityRightRotation.class, EntityTag.class);\r\n            PropertyParser.registerProperty(EntityRolling.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityRotation.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityScale.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityScoreboardTags.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntitySeeThrough.class, EntityTag.class);\r\n            PropertyParser.registerProperty(EntityShadowRadius.class, EntityTag.class);\r\n            PropertyParser.registerProperty(EntityShadowStrength.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntitySheared.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityShivering.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityShotAtAngle.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityShulkerPeek.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntitySilent.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntitySitting.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntitySize.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntitySmall.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntitySneezing.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntitySpeed.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntitySpell.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            PropertyParser.registerProperty(EntityState.class, EntityTag.class);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityStepHeight.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityStrength.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityTame.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            PropertyParser.registerProperty(EntityTeleportDuration.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityTemper.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityText.class, EntityTag.class);\r\n            PropertyParser.registerProperty(EntityTextShadowed.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityTrades.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityTranslation.class, EntityTag.class);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            PropertyParser.registerProperty(EntityTrapped.class, EntityTag.class);\r\n            PropertyParser.registerProperty(EntityTrapTime.class, EntityTag.class);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            PropertyParser.registerProperty(EntityVariant.class, EntityTag.class);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityViewRange.class, EntityTag.class);\r\n        }\r\n        PropertyParser.registerProperty(EntityVillagerExperience.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityVillagerLevel.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityVisible.class, EntityTag.class);\r\n        PropertyParser.registerProperty(EntityVisualFire.class, EntityTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(EntityWidth.class, EntityTag.class);\r\n        }\r\n\r\n        // register core InventoryTag properties\r\n        PropertyParser.registerProperty(InventoryContents.class, InventoryTag.class);\r\n        PropertyParser.registerProperty(InventoryHolder.class, InventoryTag.class);\r\n        PropertyParser.registerProperty(InventorySize.class, InventoryTag.class);\r\n        PropertyParser.registerProperty(InventoryTitle.class, InventoryTag.class);\r\n        PropertyParser.registerProperty(InventoryTrades.class, InventoryTag.class);\r\n        PropertyParser.registerProperty(InventoryUniquifier.class, InventoryTag.class);\r\n\r\n        // register core ItemTag properties\r\n        PropertyParser.registerProperty(ItemArmorPose.class, ItemTag.class);  // Special case handling in ItemComponentsPatch\r\n        registerItemProperty(ItemAttributeModifiers.class, \"attribute_modifiers\");\r\n        PropertyParser.registerProperty(ItemAttributeNBT.class, ItemTag.class);\r\n        registerItemProperty(ItemBaseColor.class, \"base_color\");\r\n        registerItemProperty(ItemBlockMaterial.class, \"block_state\");\r\n        registerItemProperty(ItemBook.class, \"writable_book_content\", \"written_book_content\");\r\n        PropertyParser.registerProperty(ItemBookGeneration.class, ItemTag.class); // Part of \"written_book_content\"\r\n        registerItemProperty(ItemDisplayname.class, \"custom_name\");\r\n        registerItemProperty(ItemDurability.class, \"damage\");\r\n        registerItemProperty(ItemCanDestroy.class, \"can_break\");\r\n        PropertyParser.registerProperty(ItemCanPlaceOn.class, ItemTag.class); // Let \"can_place_on\" through, this doesn't cover the entire component\r\n        registerItemProperty(ItemColor.class, \"dyed_color\", \"map_color\"); // Potion color included in ItemPotion's \"potion_contents\"\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            PropertyParser.registerProperty(ItemComponentsPatch.class, ItemTag.class);\r\n            registerItemProperty(ItemCustomData.class, \"custom_data\");\r\n        }\r\n        registerItemProperty(ItemCustomModel.class, \"custom_model_data\");\r\n        registerItemProperty(ItemChargedProjectile.class, \"charged_projectiles\");\r\n        registerItemProperty(ItemEnchantments.class, \"enchantments\", \"stored_enchantments\");\r\n        registerItemProperty(ItemFireworkPower.class, \"fireworks\");\r\n        registerItemProperty(ItemFirework.class, \"fireworks\", \"firework_explosion\");\r\n        PropertyParser.registerProperty(ItemFlags.class, ItemTag.class);\r\n        PropertyParser.registerProperty(ItemFrameInvisible.class, ItemTag.class); // Special case handling in ItemComponentsPatch\r\n        PropertyParser.registerProperty(ItemHidden.class, ItemTag.class); // Relevant components control their own hiding internally\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            PropertyParser.registerProperty(ItemInstrument.class, ItemTag.class); // Special case handling in ItemComponentsPatch\r\n        }\r\n        registerItemProperty(ItemInventoryContents.class, \"container\", \"bundle_contents\");\r\n        registerItemProperty(ItemKnowledgeBookRecipes.class, \"recipes\");\r\n        registerItemProperty(ItemLock.class, \"lock\");\r\n        registerItemProperty(ItemLodestoneLocation.class, \"lodestone_tracker\");\r\n        registerItemProperty(ItemLodestoneTracked.class, \"lodestone_tracker\");\r\n        registerItemProperty(ItemLore.class, \"lore\");\r\n        registerItemProperty(ItemMap.class, \"map_id\");\r\n        PropertyParser.registerProperty(ItemNBT.class, ItemTag.class);\r\n        registerItemProperty(ItemPatterns.class, \"banner_patterns\");\r\n        registerItemProperty(ItemPotion.class, \"potion_contents\");\r\n        PropertyParser.registerProperty(ItemQuantity.class, ItemTag.class);\r\n        PropertyParser.registerProperty(ItemRawNBT.class, ItemTag.class);\r\n        registerItemProperty(ItemRepairCost.class, \"repair_cost\");\r\n        PropertyParser.registerProperty(ItemScript.class, ItemTag.class);\r\n        PropertyParser.registerProperty(ItemSignContents.class, ItemTag.class); // Special case handling in ItemComponentsPatch\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            PropertyParser.registerProperty(ItemSignIsWaxed.class, ItemTag.class); // Special case handling in ItemComponentsPatch\r\n        }\r\n        registerItemProperty(ItemSkullskin.class, \"profile\");\r\n        PropertyParser.registerProperty(ItemSpawnerCount.class, ItemTag.class); // Special case handling in ItemComponentsPatch\r\n        PropertyParser.registerProperty(ItemSpawnerDelay.class, ItemTag.class); // Special case handling in ItemComponentsPatch\r\n        PropertyParser.registerProperty(ItemSpawnerMaxNearbyEntities.class, ItemTag.class); // Special case handling in ItemComponentsPatch\r\n        PropertyParser.registerProperty(ItemSpawnerPlayerRange.class, ItemTag.class); // Special case handling in ItemComponentsPatch\r\n        PropertyParser.registerProperty(ItemSpawnerRange.class, ItemTag.class); // Special case handling in ItemComponentsPatch\r\n        PropertyParser.registerProperty(ItemSpawnerType.class, ItemTag.class); // Special case handling in ItemComponentsPatch\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            registerItemProperty(ItemTrim.class, \"trim\");\r\n        }\r\n        registerItemProperty(ItemUnbreakable.class, \"unbreakable\");\r\n\r\n        // register core MaterialTag properties\r\n        PropertyParser.registerProperty(MaterialAge.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialAttached.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialAttachmentFace.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialBlockType.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialBrewingStand.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialCampfire.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialCount.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialDelay.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialDirectional.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialDistance.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialDrags.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialFaces.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialHalf.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialHinge.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialInstrument.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialLocked.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialLeafSize.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialLevel.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialLightable.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialMode.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialNote.class, MaterialTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            PropertyParser.registerProperty(MaterialOminous.class, MaterialTag.class);\r\n        }\r\n        PropertyParser.registerProperty(MaterialPersistent.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialPower.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialShape.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialSides.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialSnowable.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialSwitchable.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialUnstable.class, MaterialTag.class);\r\n        PropertyParser.registerProperty(MaterialWaterlogged.class, MaterialTag.class);\r\n\r\n        // register core TradeTag properties\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            PropertyParser.registerProperty(TradeDemand.class, TradeTag.class);\r\n        }\r\n        PropertyParser.registerProperty(TradeHasXp.class, TradeTag.class);\r\n        PropertyParser.registerProperty(TradeInputs.class, TradeTag.class);\r\n        PropertyParser.registerProperty(TradeMaxUses.class, TradeTag.class);\r\n        PropertyParser.registerProperty(TradePriceMultiplier.class, TradeTag.class);\r\n        PropertyParser.registerProperty(TradeResult.class, TradeTag.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            PropertyParser.registerProperty(TradeSpecialPrice.class, TradeTag.class);\r\n        }\r\n        PropertyParser.registerProperty(TradeUses.class, TradeTag.class);\r\n        PropertyParser.registerProperty(TradeVillagerXP.class, TradeTag.class);\r\n    }\r\n\r\n    public static void registerItemProperty(Class<? extends Property> propertyClass, String... internalComponents) {\r\n        PropertyParser.registerProperty(propertyClass, ItemTag.class);\r\n        for (String internalComponent : internalComponents) {\r\n            ItemComponentsPatch.registerHandledComponent(internalComponent);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/bukkit/BukkitBinaryTagExtensions.java",
    "content": "package com.denizenscript.denizen.objects.properties.bukkit;\r\n\r\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.BinaryTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.kyori.adventure.nbt.BinaryTagIO;\r\n\r\nimport java.io.ByteArrayInputStream;\r\n\r\npublic class BukkitBinaryTagExtensions {\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <BinaryTag.nbt_to_map>\r\n        // @returns MapTag\r\n        // @group conversion\r\n        // @description\r\n        // Converts raw NBT binary data to a MapTag.\r\n        // This under some circumstances might not return a map, depending on the underlying data.\r\n        // Refer to <@link language Raw NBT Encoding>\r\n        // @example\r\n        // # Reads a player \".dat\" file's NBT data\r\n        // - ~fileread path:data/<player.uuid>.dat save:x\r\n        // - define data <entry[x].data.gzip_decompress.nbt_to_map>\r\n        // # Now do something with \"<[data]>\"\r\n        // -->\r\n        BinaryTag.tagProcessor.registerStaticTag(ObjectTag.class, \"nbt_to_map\", (attribute, object) -> {\r\n            try (ByteArrayInputStream input = new ByteArrayInputStream(object.data)) {\r\n                return ItemRawNBT.nbtTagToObject(BinaryTagIO.reader().read(input), true);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n                return null;\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/bukkit/BukkitColorExtensions.java",
    "content": "package com.denizenscript.denizen.objects.properties.bukkit;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport org.bukkit.Color;\r\nimport org.bukkit.DyeColor;\r\n\r\npublic class BukkitColorExtensions {\r\n\r\n    public static ColorTag fromColor(Color color) {\r\n        return new ColorTag(color.getRed(), color.getGreen(), color.getBlue(), NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) ? color.getAlpha() : 255);\r\n    }\r\n\r\n    public ColorTag fromDyeColor(DyeColor dyeColor) {\r\n        return fromColor(dyeColor.getColor());\r\n    }\r\n\r\n    public static Color getColor(ColorTag tag) {\r\n        if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            return Color.fromRGB(tag.red, tag.green, tag.blue);\r\n        }\r\n        return Color.fromARGB(tag.alpha, tag.red, tag.green, tag.blue);\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <ColorTag.to_particle_offset>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns the color as a particle offset, for use with <@link command playeffect>.\r\n        // @example\r\n        // # Plays the \"SPELL_MOB\" effect above the player's head with the particle offset applied to color the particle.\r\n        // - playeffect at:<player.location.add[0,3,0]> effect:SPELL_MOB data:1 quantity:0 offset:<color[fuchsia].to_particle_offset>\r\n        // -->\r\n        ColorTag.tagProcessor.registerStaticTag(LocationTag.class, \"to_particle_offset\", (attribute, object) -> {\r\n            if (object.red + object.green + object.blue == 0) {\r\n                return new LocationTag(null, 1 / 255f, 0, 0);\r\n            }\r\n            return new LocationTag(null, object.red / 255F, object.green / 255F, object.blue / 255F);\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/bukkit/BukkitElementExtensions.java",
    "content": "package com.denizenscript.denizen.objects.properties.bukkit;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\r\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper;\r\nimport com.denizenscript.denizen.tags.core.CustomColorTagBase;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.HoverFormatHelper;\r\nimport com.denizenscript.denizen.utilities.TextWidthHelper;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.AsciiMatcher;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.Deprecations;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.HoverEvent;\r\n\r\nimport java.io.IOException;\r\nimport java.nio.charset.StandardCharsets;\r\n\r\npublic class BukkitElementExtensions {\r\n\r\n    public static String replaceEssentialsHexColors(char prefix, String input) {\r\n        int hex = input.indexOf(prefix + \"#\");\r\n        while (hex != -1 && hex + 7 < input.length()) {\r\n            StringBuilder converted = new StringBuilder(10);\r\n            converted.append(ChatColor.COLOR_CHAR).append(\"x\");\r\n            for (int i = 0; i < 6; i++) {\r\n                char c = input.charAt(hex + 2 + i);\r\n                if (!ArgumentHelper.HEX_MATCHER.isMatch(c)) {\r\n                    return input;\r\n                }\r\n                converted.append(ChatColor.COLOR_CHAR).append(c);\r\n            }\r\n            input = input.substring(0, hex) + converted + input.substring(hex + 8);\r\n            hex = input.indexOf(prefix + \"#\", hex + 2);\r\n        }\r\n        return input;\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_biome>\r\n        // @returns BiomeTag\r\n        // @group conversion\r\n        // @deprecated use as[biome]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(BiomeTag.class, \"as_biome\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), BiomeTag.valueOf(object.asString(), attribute.context), \"BiomeTag\", attribute.hasAlternative());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_chunk>\r\n        // @returns ChunkTag\r\n        // @group conversion\r\n        // @deprecated use as[chunk]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(ChunkTag.class, \"as_chunk\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), ChunkTag.valueOf(object.asString(), attribute.context), \"ChunkTag\", attribute.hasAlternative());\r\n        }, \"aschunk\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_color>\r\n        // @returns ColorTag\r\n        // @group conversion\r\n        // @deprecated use as[color]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ColorTag.class, \"as_color\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), ColorTag.valueOf(object.asString(), attribute.context), \"ColorTag\", attribute.hasAlternative());\r\n        }, \"ascolor\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_cuboid>\r\n        // @returns CuboidTag\r\n        // @group conversion\r\n        // @deprecated use as[cuboid]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(CuboidTag.class, \"as_cuboid\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), CuboidTag.valueOf(object.asString(), attribute.context), \"CuboidTag\", attribute.hasAlternative());\r\n        }, \"ascuboid\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_ellipsoid>\r\n        // @returns EllipsoidTag\r\n        // @group conversion\r\n        // @deprecated use as[ellipsoid]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(EllipsoidTag.class, \"as_ellipsoid\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), EllipsoidTag.valueOf(object.asString(), attribute.context), \"EllipsoidTag\", attribute.hasAlternative());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_enchantment>\r\n        // @returns EnchantmentTag\r\n        // @group conversion\r\n        // @deprecated use as[enchantment]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(EnchantmentTag.class, \"as_enchantment\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), EnchantmentTag.valueOf(object.asString(), attribute.context), \"EnchantmentTag\", attribute.hasAlternative());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_entity>\r\n        // @returns EntityTag\r\n        // @group conversion\r\n        // @deprecated use as[entity]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(EntityTag.class, \"as_entity\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), EntityTag.valueOf(object.asString(), attribute.context), \"EntityTag\", attribute.hasAlternative());\r\n        }, \"asentity\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_inventory>\r\n        // @returns InventoryTag\r\n        // @group conversion\r\n        // @deprecated use as[inventory]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(InventoryTag.class, \"as_inventory\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), InventoryTag.valueOf(object.asString(), attribute.context), \"InventoryTag\", attribute.hasAlternative());\r\n        }, \"asinventory\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_item>\r\n        // @returns ItemTag\r\n        // @group conversion\r\n        // @deprecated use as[item]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(ItemTag.class, \"as_item\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), ItemTag.valueOf(object.asString(), attribute.context), \"ItemTag\", attribute.hasAlternative());\r\n        }, \"asitem\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_location>\r\n        // @returns LocationTag\r\n        // @group conversion\r\n        // @deprecated use as[location]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(LocationTag.class, \"as_location\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), LocationTag.valueOf(object.asString(), attribute.context), \"LocationTag\", attribute.hasAlternative());\r\n        }, \"aslocation\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_material>\r\n        // @returns MaterialTag\r\n        // @group conversion\r\n        // @deprecated use as[material]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(MaterialTag.class, \"as_material\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), MaterialTag.valueOf(object.asString(), attribute.context), \"MaterialTag\", attribute.hasAlternative());\r\n        }, \"asmaterial\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_npc>\r\n        // @returns NPCTag\r\n        // @group conversion\r\n        // @deprecated use as[npc]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(NPCTag.class, \"as_npc\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), NPCTag.valueOf(object.asString(), attribute.context), \"NPCTag\", attribute.hasAlternative());\r\n        }, \"asnpc\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_player>\r\n        // @returns PlayerTag\r\n        // @group conversion\r\n        // @deprecated use as[player]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(PlayerTag.class, \"as_player\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), PlayerTag.valueOf(object.asString(), attribute.context), \"PlayerTag\", attribute.hasAlternative());\r\n        }, \"asplayer\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_plugin>\r\n        // @returns PluginTag\r\n        // @group conversion\r\n        // @deprecated use as[plugin]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(PluginTag.class, \"as_plugin\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), PluginTag.valueOf(object.asString(), attribute.context), \"PluginTag\", attribute.hasAlternative());\r\n        }, \"asplugin\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_polygon>\r\n        // @returns PolygonTag\r\n        // @group conversion\r\n        // @deprecated use as[polygon]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(PolygonTag.class, \"as_polygon\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), PolygonTag.valueOf(object.asString(), attribute.context), \"PolygonTag\", attribute.hasAlternative());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_trade>\r\n        // @returns TradeTag\r\n        // @group conversion\r\n        // @deprecated use as[trade]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(TradeTag.class, \"as_trade\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), TradeTag.valueOf(object.asString(), attribute.context), \"TradeTag\", attribute.hasAlternative());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.as_world>\r\n        // @returns WorldTag\r\n        // @group conversion\r\n        // @deprecated use as[world]\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.as>\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(WorldTag.class, \"as_world\", (attribute, object) -> {\r\n            Deprecations.asXTags.warn(attribute.context);\r\n            return ElementTag.handleNull(object.asString(), WorldTag.valueOf(object.asString(), attribute.context), \"WorldTag\", attribute.hasAlternative());\r\n        }, \"asworld\");\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.split_lines_by_width[<#>]>\r\n        // @returns ElementTag\r\n        // @group element manipulation\r\n        // @description\r\n        // Returns the element split into separate lines based on a maximum width in pixels per line.\r\n        // This uses character width, so for example 20 \"W\"s and 20 \"i\"s will be treated differently.\r\n        // The width used is based on the vanilla minecraft font. This will not be accurate for other fonts.\r\n        // This only currently supports ASCII symbols properly. Unicode symbols will be estimated as 6 pixels.\r\n        // Spaces will be preferred to become newlines, unless a line does not contain any spaces.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, ElementTag.class, \"split_lines_by_width\", (attribute, object, widthText) -> {\r\n            return new ElementTag(TextWidthHelper.splitLines(object.asString(), widthText.asInt()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.text_width>\r\n        // @returns ElementTag(Number)\r\n        // @group element manipulation\r\n        // @description\r\n        // Returns the width, in pixels, of the text.\r\n        // The width used is based on the vanilla minecraft font. This will not be accurate for other fonts.\r\n        // This only currently supports ASCII symbols properly. Unicode symbols will be estimated as 6 pixels.\r\n        // If the element contains newlines, will return the widest line width.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"text_width\", (attribute, object) -> {\r\n            return new ElementTag(TextWidthHelper.getWidth(object.asString()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.lines_to_colored_list>\r\n        // @returns ListTag\r\n        // @group element manipulation\r\n        // @description\r\n        // Returns a list of lines in the element, with colors spread over the lines manually.\r\n        // Useful for things like item lore.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ListTag.class, \"lines_to_colored_list\", (attribute, object) -> {\r\n            ListTag output = new ListTag();\r\n            String colors = \"\";\r\n            for (String line : CoreUtilities.split(object.asString(), '\\n')) {\r\n                output.add(colors + line);\r\n                colors = org.bukkit.ChatColor.getLastColors(colors + line);\r\n            }\r\n            return output;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.last_color>\r\n        // @returns ElementTag\r\n        // @group text checking\r\n        // @description\r\n        // Returns the ChatColors used last in an element.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"last_color\", (attribute, object) -> {\r\n            return new ElementTag(org.bukkit.ChatColor.getLastColors(object.asString()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.strip_color>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Returns the element with all color encoding stripped.\r\n        // This will remove any/all colors, formats (bold/italic/etc), advanced formats (fonts/clickables/etc), and translate any translatables (&translate, &score, etc).\r\n        // This will automatically translate translatable sections\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"strip_color\", (attribute, object) -> {\r\n            return new ElementTag(FormattedTextHelper.parse(object.asString(), ChatColor.WHITE)[0].toPlainText());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.parse_color[(<prefix>)]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Returns the element with all color codes parsed.\r\n        // Optionally, specify a character to prefix the color ids. Defaults to '&' if not specified.\r\n        // This allows old-style colors like '&b', or Essentials-style hex codes like '&#ff00ff'\r\n        // See also <@link tag ElementTag.parse_minimessage>\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"parse_color\", (attribute, object) -> {\r\n            char prefix = '&';\r\n            if (attribute.hasParam()) {\r\n                prefix = attribute.getParam().charAt(0);\r\n            }\r\n            String parsed = ChatColor.translateAlternateColorCodes(prefix, object.asString());\r\n            parsed = replaceEssentialsHexColors(prefix, parsed);\r\n            return new ElementTag(parsed);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.to_itemscript_hash>\r\n        // @returns ElementTag\r\n        // @group conversion\r\n        // @description\r\n        // Shortens the element down to an itemscript hash ID, made of invisible color codes.\r\n        // This is considered a historical system, no longer relevant to modern Denizen.\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(ElementTag.class, \"to_itemscript_hash\", (attribute, object) -> {\r\n            return new ElementTag(ItemScriptHelper.createItemScriptID(object.asString()));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.to_secret_colors>\r\n        // @returns ElementTag\r\n        // @group conversion\r\n        // @description\r\n        // Hides the element's text in invisible color codes.\r\n        // Inverts <@link tag ElementTag.from_secret_colors>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"to_secret_colors\", (attribute, object) -> {\r\n            String text = object.asString();\r\n            byte[] bytes = text.getBytes(StandardCharsets.UTF_8);\r\n            String hex = CoreUtilities.hexEncode(bytes);\r\n            StringBuilder colors = new StringBuilder(text.length() * 2);\r\n            for (int i = 0; i < hex.length(); i++) {\r\n                colors.append(ChatColor.COLOR_CHAR).append(hex.charAt(i));\r\n            }\r\n            return new ElementTag(colors.toString());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.from_secret_colors>\r\n        // @returns ElementTag\r\n        // @group conversion\r\n        // @description\r\n        // Un-hides the element's text from invisible color codes back to normal text.\r\n        // Inverts <@link tag ElementTag.to_secret_colors>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"from_secret_colors\", (attribute, object) -> {\r\n            String text = object.asString().replace(String.valueOf(ChatColor.COLOR_CHAR), \"\");\r\n            byte[] bytes = CoreUtilities.hexDecode(text);\r\n            return new ElementTag(new String(bytes, StandardCharsets.UTF_8));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.to_raw_json>\r\n        // @returns ElementTag\r\n        // @group conversion\r\n        // @description\r\n        // Converts normal colored text to Minecraft-style \"raw JSON\" format.\r\n        // Inverts <@link tag ElementTag.from_raw_json>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"to_raw_json\", (attribute, object) -> {\r\n            return new ElementTag(FormattedTextHelper.componentToJson(FormattedTextHelper.parse(object.asString(), ChatColor.WHITE)));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.from_raw_json>\r\n        // @returns ElementTag\r\n        // @group conversion\r\n        // @description\r\n        // Un-hides the element's text from invisible color codes back to normal text.\r\n        // Inverts <@link tag ElementTag.to_raw_json>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"from_raw_json\", (attribute, object) -> {\r\n            return new ElementTag(FormattedTextHelper.stringify(FormattedTextHelper.parseJson(object.asString())));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.optimize_json>\r\n        // @returns ElementTag\r\n        // @group conversion\r\n        // @description\r\n        // Tells the formatted text parser to try to produce mininalist JSON text.\r\n        // This is useful in particular for very long text or where text is being sent rapidly/repeatedly.\r\n        // It is not needed in most normal messages.\r\n        // It will produce incompatibility issues if used in items or other locations where raw JSON matching is required.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"optimize_json\", (attribute, object) -> {\r\n            String opti = ChatColor.COLOR_CHAR + \"[optimize=true]\";\r\n            if (object.asString().contains(opti)) {\r\n                return object;\r\n            }\r\n            return new ElementTag(opti + object.asString(), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.hover_item[<item>]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Adds a hover message to the element, which makes the element display the input ItemTag when the mouse is left over it.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // @example\r\n        // - narrate \"You can <element[hover here].custom_color[emphasis].hover_item[<player.item_in_hand>]> to see what you held!\"\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, ItemTag.class, \"hover_item\", (attribute, object, item) -> {\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[hover=SHOW_ITEM;\" + FormattedTextHelper.escape(item.identify()) + \"]\" + object.asString() + ChatColor.COLOR_CHAR + \"[/hover]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.on_hover[<message>]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Adds a hover message to the element, which makes the element display the input hover text when the mouse is left over it.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(ElementTag.class, ObjectTag.class, \"on_hover\", (attribute, object, hover) -> { // non-static due to hacked sub-tag\r\n            HoverEvent.Action type = HoverEvent.Action.SHOW_TEXT;\r\n\r\n            // <--[tag]\r\n            // @attribute <ElementTag.on_hover[<message>].type[<type>]>\r\n            // @returns ElementTag\r\n            // @group text manipulation\r\n            // @description\r\n            // Adds a hover message to the element, which makes the element display the input hover text when the mouse is left over it.\r\n            // Available hover types: SHOW_TEXT, SHOW_ITEM, or SHOW_ENTITY.\r\n            // Note: for \"SHOW_ITEM\", replace the text with a valid ItemTag. For \"SHOW_ENTITY\", replace the text with a valid spawned EntityTag (requires F3+H to see entities).\r\n            // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n            // For show_text, prefer <@link tag ElementTag.on_hover>\r\n            // For show_item, prefer <@link tag ElementTag.hover_item>\r\n            // -->\r\n            if (attribute.startsWith(\"type\", 2)) {\r\n                attribute.fulfill(1);\r\n                type = ElementTag.asEnum(HoverEvent.Action.class, attribute.getParam());\r\n                if (type == null) {\r\n                    attribute.echoError(\"Invalid hover type specified.\");\r\n                    return null;\r\n                }\r\n            }\r\n            String hoverData = HoverFormatHelper.parseObjectToHover(hover, type, attribute);\r\n            if (hoverData == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[hover=\" + type + ';' + FormattedTextHelper.escape(hoverData) + ']'\r\n                    + object.asString() + ChatColor.COLOR_CHAR + \"[/hover]\", true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.click_url[<url>]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Adds a click command to the element, which makes the element open the given URL when clicked.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // @example\r\n        // - narrate \"You can <element[click here].custom_color[emphasis].on_hover[Click me!].click_url[https://denizenscript.com]> to learn about Denizen!\"\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, ElementTag.class, \"click_url\", (attribute, object, url) -> {\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[click=OPEN_URL;\" + FormattedTextHelper.escape(url.toString()) + \"]\" + object.asString() + ChatColor.COLOR_CHAR + \"[/click]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.click_chat[<message>]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Adds a click command to the element, which makes the element pseudo-chat the input message when clicked, for activating interact script chat triggers (<@link language Chat Triggers>).\r\n        // This internally uses the command \"/denizenclickable chat SOME MESSAGE HERE\" (requires players have permission \"denizen.clickable\")\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // @example\r\n        // - narrate \"You can <element[click here].click_chat[hello]> to say hello to an NPC's interact script!\"\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, ElementTag.class, \"click_chat\", (attribute, object, chat) -> {\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[click=RUN_COMMAND;/denizenclickable chat \" + FormattedTextHelper.escape(chat.toString()) + \"]\" + object.asString() + ChatColor.COLOR_CHAR + \"[/click]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.on_click[<command>]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Adds a click command to the element, which makes the element execute the input command when clicked.\r\n        // To execute a command \"/\" should be used at the start. Prior to 1.19, leaving off the \"/\" would display the text as chat. This feature was removed as part of the 1.19 secure chat system.\r\n        // For activating interact script chat triggers (<@link language Chat Triggers>), you can use the command \"/denizenclickable chat SOME MESSAGE HERE\" (requires players have permission \"denizen.clickable\")\r\n        // For that, instead prefer <@link tag ElementTag.click_chat>\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // @example\r\n        // - narrate \"You can <element[click here].on_click[/help]> for help!\"\r\n        // @example\r\n        // - narrate \"You can <element[click here].on_click[/denizenclickable chat hello]> to say hello to an NPC's interact script!\"\r\n        // -->\r\n        ElementTag.tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"on_click\", (attribute, object, command) -> { // non-static due to hacked sub-tag\r\n            String type = \"RUN_COMMAND\";\r\n\r\n            // <--[tag]\r\n            // @attribute <ElementTag.on_click[<message>].type[<type>]>\r\n            // @returns ElementTag\r\n            // @group text manipulation\r\n            // @description\r\n            // Adds a click command to the element, which makes the element execute the input command when clicked.\r\n            // Available command types: OPEN_URL, OPEN_FILE, RUN_COMMAND, SUGGEST_COMMAND, COPY_TO_CLIPBOARD, or CHANGE_PAGE.\r\n            // For example: - narrate \"You can <element[click here].on_click[https://denizenscript.com].type[OPEN_URL]> to learn about Denizen!\"\r\n            // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n            // For run_command, prefer <@link tag ElementTag.on_click>\r\n            // For chat, prefer <@link tag ElementTag.click_chat>\r\n            // For URLs, prefer <@link tag ElementTag.click_url>\r\n            // -->\r\n            if (attribute.startsWith(\"type\", 2)) {\r\n                type = attribute.getContext(2);\r\n                attribute.fulfill(1);\r\n            }\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[click=\" + type + \";\" + FormattedTextHelper.escape(command.asString()) + \"]\"\r\n                    + object.asString() + ChatColor.COLOR_CHAR + \"[/click]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.with_insertion[<message>]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Adds an insertion message to the element, which makes the element insert the input message to chat when shift-clicked.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, ElementTag.class, \"with_insertion\", (attribute, object, insertion) -> {\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[insertion=\"  + FormattedTextHelper.escape(insertion.asString()) + \"]\"\r\n                    + object.asString() + ChatColor.COLOR_CHAR + \"[/insertion]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.no_reset>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Makes a color code (&0123456789abcdef) not reset other formatting details.\r\n        // Use like '<&c.no_reset>' or '<red.no_reset>'.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"no_reset\", (attribute, object) -> {\r\n            if (object.asString().length() == 2 && object.asString().charAt(0) == ChatColor.COLOR_CHAR) {\r\n                return new ElementTag(ChatColor.COLOR_CHAR + \"[color=\" + object.asString().charAt(1) + \"]\");\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.end_format>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Makes a chat format code (&klmno, or &[font=...]) be the end of a format, as opposed to the start.\r\n        // Use like '<&o.end_format>' or '<italic.end_format>'.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"end_format\", (attribute, object) -> {\r\n            if (object.asString().length() == 2 && object.asString().charAt(0) == ChatColor.COLOR_CHAR) {\r\n                return new ElementTag(ChatColor.COLOR_CHAR + \"[reset=\" + object.asString().charAt(1) + \"]\");\r\n            }\r\n            else if (object.asString().startsWith(ChatColor.COLOR_CHAR + \"[font=\") && object.asString().endsWith(\"]\")) {\r\n                return new ElementTag(ChatColor.COLOR_CHAR + \"[reset=font]\");\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.italicize>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Makes the input text italic. Equivalent to \"<&o><ELEMENT_HERE><&o.end_format>\"\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"italicize\", (attribute, object) -> {\r\n            return new ElementTag(ChatColor.ITALIC + object.asString() + ChatColor.COLOR_CHAR + \"[reset=o]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.bold>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Makes the input text bold. Equivalent to \"<&l><ELEMENT_HERE><&l.end_format>\"\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"bold\", (attribute, object) -> {\r\n            return new ElementTag(ChatColor.BOLD + object.asString() + ChatColor.COLOR_CHAR + \"[reset=l]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.underline>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Makes the input text underlined. Equivalent to \"<&n><ELEMENT_HERE><&n.end_format>\"\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"underline\", (attribute, object) -> {\r\n            return new ElementTag(ChatColor.UNDERLINE + object.asString() + ChatColor.COLOR_CHAR + \"[reset=n]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.strikethrough>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Makes the input text struck-through. Equivalent to \"<&m><ELEMENT_HERE><&m.end_format>\"\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"strikethrough\", (attribute, object) -> {\r\n            return new ElementTag(ChatColor.STRIKETHROUGH + object.asString() + ChatColor.COLOR_CHAR + \"[reset=m]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.obfuscate>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Makes the input text obfuscated. Equivalent to \"<&k><ELEMENT_HERE><&k.end_format>\"\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"obfuscate\", (attribute, object) -> {\r\n            return new ElementTag(ChatColor.MAGIC + object.asString() + ChatColor.COLOR_CHAR + \"[reset=k]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.custom_color[<custom_color_name>]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Makes the input text colored by the custom color value based on the common base color names defined in the Denizen config file.\r\n        // If the color name is unrecognized, returns the value of color named 'default'.\r\n        // Default color names are 'base', 'emphasis', 'warning', 'error'.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, ElementTag.class, \"custom_color\", (attribute, object, name) -> {\r\n            String color = CustomColorTagBase.getColor(name.asLowerString(), attribute.context);\r\n            if (color == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[color=f]\" + color + object.asString() + ChatColor.COLOR_CHAR + \"[reset=color]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.color[<color>]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Makes the input text colored by the input color. Equivalent to \"<COLOR><ELEMENT_HERE><COLOR.end_format>\"\r\n        // Color can be a color name, color code, hex, or ColorTag... that is: \".color[gold]\", \".color[6]\", and \".color[#AABB00]\" are all valid.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, ElementTag.class, \"color\", (attribute, object, colorElement) -> {\r\n            String colorName = colorElement.asString();\r\n            String colorOut = null;\r\n            if (colorName.length() == 1) {\r\n                ChatColor color = ChatColor.getByChar(colorName.charAt(0));\r\n                if (color != null) {\r\n                    colorOut = color.toString();\r\n                }\r\n            }\r\n            else if (colorName.length() == 7 && colorName.startsWith(\"#\")) {\r\n                return new ElementTag(ChatColor.COLOR_CHAR + \"[color=\" + colorName + \"]\" + object.asString() + ChatColor.COLOR_CHAR + \"[reset=color]\");\r\n            }\r\n            else if (colorName.length() == 14 && colorName.startsWith(ChatColor.COLOR_CHAR + \"x\")) {\r\n                return new ElementTag(ChatColor.COLOR_CHAR + \"[color=#\" + CoreUtilities.replace(colorName.substring(2), String.valueOf(ChatColor.COLOR_CHAR), \"\") + \"]\" + object.asString() + ChatColor.COLOR_CHAR + \"[reset=color]\");\r\n            }\r\n            else if (colorName.startsWith(\"co@\")) {\r\n                ColorTag color = ColorTag.valueOf(colorName, attribute.context);\r\n                if (color == null && TagManager.isStaticParsing) {\r\n                    return null;\r\n                }\r\n                StringBuilder hex = new StringBuilder(Integer.toHexString(color.asRGB()));\r\n                while (hex.length() < 6) {\r\n                    hex.insert(0, \"0\");\r\n                }\r\n                return new ElementTag(ChatColor.COLOR_CHAR + \"[color=#\" + hex + \"]\" + object.asString() + ChatColor.COLOR_CHAR + \"[reset=color]\");\r\n            }\r\n            if (colorOut == null) {\r\n                try {\r\n                    ChatColor color = ChatColor.of(colorName.toUpperCase());\r\n                    if (color.getColor() == null) {\r\n                        if (!TagManager.isStaticParsing) {\r\n                            attribute.echoError(\"Color '\" + colorName + \"' is valid but is a format code not a real color (for ElementTag.color[...]).\");\r\n                        }\r\n                        return null;\r\n                    }\r\n                    String colorStr = color.toString().replace(String.valueOf(ChatColor.COLOR_CHAR), \"\").replace(\"x\", \"#\");\r\n                    colorOut = ChatColor.COLOR_CHAR + \"[color=\" + colorStr + \"]\";\r\n                }\r\n                catch (IllegalArgumentException ex) {\r\n                    ColorTag color = ColorTag.valueOf(colorName, attribute.context);\r\n                    if (color != null) {\r\n                        StringBuilder hex = new StringBuilder(Integer.toHexString(color.asRGB()));\r\n                        while (hex.length() < 6) {\r\n                            hex.insert(0, \"0\");\r\n                        }\r\n                        return new ElementTag(ChatColor.COLOR_CHAR + \"[color=#\" + hex + \"]\" + object.asString() + ChatColor.COLOR_CHAR + \"[reset=color]\");\r\n                    }\r\n                    if (!TagManager.isStaticParsing) {\r\n                        attribute.echoError(\"Color '\" + colorName + \"' doesn't exist (for ElementTag.color[...]).\");\r\n                    }\r\n                    return null;\r\n                }\r\n            }\r\n            return new ElementTag(colorOut + object.asString() + ChatColor.COLOR_CHAR + \"[reset=color]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.font[<font>]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Makes the input text display with the input font name. Equivalent to \"<&font[new-font]><ELEMENT_HERE><&font[new-font].end_format>\"\r\n        // The default font is \"minecraft:default\".\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, ElementTag.class, \"font\", (attribute, object, fontName) -> {\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[font=\" + fontName + \"]\" + object.asString() + ChatColor.COLOR_CHAR + \"[reset=font]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.rainbow[(<pattern>)]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Returns the element with rainbow colors applied.\r\n        // Optionally, specify a color pattern to follow. By default, this is \"4c6e2ab319d5\".\r\n        // That is, a repeating color of: Red, Orange, Yellow, Green, Cyan, Blue, Purple.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"rainbow\", (attribute, object) -> {\r\n            String str = object.asString();\r\n            String pattern = \"4c6e2ab319d5\";\r\n            if (attribute.hasParam()) {\r\n                pattern = attribute.getParam();\r\n            }\r\n            StringBuilder output = new StringBuilder(str.length() * 3);\r\n            for (int i = 0; i < str.length(); i++) {\r\n                output.append(ChatColor.COLOR_CHAR).append(pattern.charAt(i % pattern.length())).append(str.charAt(i));\r\n            }\r\n            return new ElementTag(output.toString());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.hex_rainbow[(<length>)]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Returns the element with RGB rainbow colors applied.\r\n        // Optionally, specify a length (how many characters before the colors repeat). If unspecified, will use the input element length.\r\n        // If the element starts with a hex color code, that will be used as the starting color of the rainbow.\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, \"hex_rainbow\", (attribute, object) -> {\r\n            String str = object.asString();\r\n            int[] HSB = new int[] { 0, 255, 255 };\r\n            if (str.startsWith(ChatColor.COLOR_CHAR + \"x\") && str.length() > 14) {\r\n                char[] colors = new char[6];\r\n                for (int i = 0; i < 6; i++) {\r\n                    colors[i] = str.charAt(3 + (i * 2));\r\n                }\r\n                int rgb = Integer.parseInt(new String(colors), 16);\r\n                HSB = ColorTag.fromRGB(rgb).toHSB();\r\n                str = str.substring(14);\r\n            }\r\n            float hue = HSB[0] / 255f;\r\n            int length = ChatColor.stripColor(str).length();\r\n            if (length == 0) {\r\n                return new ElementTag(\"\");\r\n            }\r\n            if (attribute.hasParam()) {\r\n                length = attribute.getIntParam();\r\n            }\r\n            float increment = 1.0f / length;\r\n            String addedFormat = \"\";\r\n            StringBuilder output = new StringBuilder(str.length() * 8);\r\n            for (int i = 0; i < str.length(); i++) {\r\n                char c = str.charAt(i);\r\n                if (c == ChatColor.COLOR_CHAR && i + 1 < str.length()) {\r\n                    char c2 = str.charAt(i + 1);\r\n                    if (FORMAT_CODES_MATCHER.isMatch(c2)) {\r\n                        addedFormat += String.valueOf(ChatColor.COLOR_CHAR) + c2;\r\n                    }\r\n                    else {\r\n                        addedFormat = \"\";\r\n                    }\r\n                    i++;\r\n                    continue;\r\n                }\r\n                String hex = Integer.toHexString(ColorTag.fromHSB(HSB).asRGB());\r\n                output.append(FormattedTextHelper.stringifyRGBSpigot(hex)).append(addedFormat).append(c);\r\n                hue += increment;\r\n                HSB[0] = Math.round(hue * 255f);\r\n            }\r\n            return new ElementTag(output.toString());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.color_gradient[from=<color>;to=<color>;(style={RGB}/HSB)]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @description\r\n        // Returns the element with an RGB color gradient applied, with a unique color per character.\r\n        // Specify the input as a map with keys 'from' and 'to' both set to hex colors (or any valid ColorTag).\r\n        // You can also choose a style (defaults to RGB):\r\n        // \"style=RGB\" tends to produce smooth gradients,\r\n        // \"style=HSB\" tends to produce bright rainbow-like color patterns.\r\n        // @example\r\n        // - narrate \"<element[these are the shades of gray].color_gradient[from=white;to=black]>\"\r\n        // @example\r\n        // - narrate \"<element[this looks kinda like fire doesn't it].color_gradient[from=#FF0000;to=#FFFF00]>\"\r\n        // @example\r\n        // - narrate \"<element[this also looks like fire with a different spread].color_gradient[from=#FF0000;to=#FFFF00;style=hsb]>\"\r\n        // @example\r\n        // - narrate \"<element[what a beautiful rainbow this line is].color_gradient[from=#FF0000;to=#0000FF;style=hsb]>\"\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, MapTag.class, \"color_gradient\", (attribute, object, inputMap) -> {\r\n            ColorTag fromColor = inputMap.getRequiredObjectAs(\"from\", ColorTag.class, attribute);\r\n            ColorTag toColor = inputMap.getRequiredObjectAs(\"to\", ColorTag.class, attribute);\r\n            ElementTag style = inputMap.getElement(\"style\", \"RGB\");\r\n            if (fromColor == null || toColor == null) {\r\n                return null;\r\n            }\r\n            if (!style.matchesEnum(BukkitElementExtensions.GradientStyle.class)) {\r\n                attribute.echoError(\"Invalid gradient style '\" + style + \"'\");\r\n                return null;\r\n            }\r\n            String res = doGradient(object.asString(), fromColor, toColor, style.asEnum(GradientStyle.class));\r\n            if (res == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(res);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.hsb_color_gradient[from=<color>;to=<color>]>\r\n        // @returns ElementTag\r\n        // @group text manipulation\r\n        // @deprecated use color_gradient[from=color;to=color;style=HSB]\r\n        // @description\r\n        // Deprecated in favor of using <@link tag ElementTag.color_gradient> with \"style=hsb\"\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(ElementTag.class, MapTag.class, \"hsb_color_gradient\", (attribute, object, inputMap) -> {\r\n            BukkitImplDeprecations.hsbColorGradientTag.warn(attribute.context);\r\n            ColorTag fromColor = inputMap.getRequiredObjectAs(\"from\", ColorTag.class, attribute);\r\n            ColorTag toColor = inputMap.getRequiredObjectAs(\"to\", ColorTag.class, attribute);\r\n            if (fromColor == null || toColor == null) {\r\n                return null;\r\n            }\r\n            String res = doGradient(object.asString(), fromColor, toColor, GradientStyle.HSB);\r\n            if (res == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(res);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ElementTag.snbt_to_map>\r\n        // @returns MapTag\r\n        // @description\r\n        // Parses a raw SNBT string into an NBT MapTag.\r\n        // See <@link language Raw NBT Encoding> for more information on the returned MapTag.\r\n        // See <@link url https://minecraft.wiki/w/NBT_format#SNBT_format> for more information on SNBT.\r\n        // @example\r\n        // # Use to set certain SNBT data onto an entity.\r\n        // - adjust <[entity]> raw_nbt:<[snbt].snbt_to_map>\r\n        // -->\r\n        ElementTag.tagProcessor.registerStaticTag(MapTag.class, \"snbt_to_map\", (attribute, object) -> {\r\n            try {\r\n                return (MapTag) ItemRawNBT.nbtTagToObject(ItemRawNBT.SNBT_PARSER.asCompound(object.asString()));\r\n            }\r\n            catch (IOException e) {\r\n                attribute.echoError(\"Element '<Y>\" + object + \"<W>' isn't valid SNBT: \" + e.getMessage());\r\n                if (CoreConfiguration.debugVerbose) {\r\n                    attribute.echoError(e);\r\n                }\r\n                return null;\r\n            }\r\n        });\r\n    }\r\n\r\n    public enum GradientStyle { RGB, HSB }\r\n\r\n    public static String doGradient(String str, ColorTag fromColor, ColorTag toColor, GradientStyle style) {\r\n        int length = FormattedTextHelper.parse(str, ChatColor.WHITE)[0].toPlainText().length();\r\n        if (length == 0) {\r\n            return \"\";\r\n        }\r\n        if (fromColor == null || toColor == null) {\r\n            return null;\r\n        }\r\n        float r, g, b, x = 0, rMove, gMove, bMove, xMove = 0, toR, toG, toB;\r\n        int[] hsbHelper = null;\r\n        if (style == GradientStyle.RGB) {\r\n            r = ColorTag.fromSRGB(fromColor.red);\r\n            g = ColorTag.fromSRGB(fromColor.green);\r\n            b = ColorTag.fromSRGB(fromColor.blue);\r\n            x = (float) Math.pow(r + g + b, 0.43);\r\n            toR = ColorTag.fromSRGB(toColor.red);\r\n            toG = ColorTag.fromSRGB(toColor.green);\r\n            toB = ColorTag.fromSRGB(toColor.blue);\r\n            float toBrightness = (float) Math.pow(toR + toG + toB, 0.43);\r\n            xMove = (toBrightness - x) / length;\r\n        }\r\n        else {\r\n            hsbHelper = fromColor.toHSB();\r\n            int[] toHSB = toColor.toHSB();\r\n            r = hsbHelper[0];\r\n            g = hsbHelper[1];\r\n            b = hsbHelper[2];\r\n            toR = toHSB[0];\r\n            toG = toHSB[1];\r\n            toB = toHSB[2];\r\n        }\r\n        rMove = (toR - r) / length;\r\n        gMove = (toG - g) / length;\r\n        bMove = (toB - b) / length;\r\n        String addedFormat = \"\";\r\n        StringBuilder output = new StringBuilder(str.length() * 15);\r\n        for (int i = 0; i < str.length(); i++) {\r\n            char c = str.charAt(i);\r\n            if (c == ChatColor.COLOR_CHAR && i + 1 < str.length()) {\r\n                char c2 = str.charAt(i + 1);\r\n                if (FORMAT_CODES_MATCHER.isMatch(c2)) {\r\n                    addedFormat += String.valueOf(ChatColor.COLOR_CHAR) + c2;\r\n                }\r\n                else if (c2 == '[') {\r\n                    int endBracket = str.indexOf(']', i);\r\n                    if (endBracket != -1) {\r\n                        addedFormat += str.substring(i, endBracket + 1);\r\n                        i = endBracket - 1;\r\n                    }\r\n                }\r\n                else {\r\n                    addedFormat = \"\";\r\n                }\r\n                i++;\r\n                continue;\r\n            }\r\n            String hex;\r\n            if (style == GradientStyle.RGB) {\r\n                // Based on https://stackoverflow.com/questions/22607043/color-gradient-algorithm/49321304#49321304\r\n                float newRed = r, newGreen = g, newBlue = b;\r\n                float sum = newRed + newGreen + newBlue;\r\n                if (sum > 0) {\r\n                    float multiplier = (float) Math.pow(x, 1f / 0.43f) / sum;\r\n                    newRed *= multiplier;\r\n                    newGreen *= multiplier;\r\n                    newBlue *= multiplier;\r\n                }\r\n                newRed = ColorTag.toSRGB(newRed);\r\n                newGreen = ColorTag.toSRGB(newGreen);\r\n                newBlue = ColorTag.toSRGB(newBlue);\r\n                hex = Integer.toHexString((((int) newRed) << 16) | (((int) newGreen) << 8) | ((int) newBlue));\r\n                x += xMove;\r\n            }\r\n            else {\r\n                hsbHelper[0] = (int)r;\r\n                hsbHelper[1] = (int)g;\r\n                hsbHelper[2] = (int)b;\r\n                ColorTag currentColor = ColorTag.fromHSB(hsbHelper);\r\n                hex = Integer.toHexString(currentColor.asRGB());\r\n            }\r\n            output.append(FormattedTextHelper.stringifyRGBSpigot(hex)).append(addedFormat).append(str.charAt(i));\r\n            r += rMove;\r\n            g += gMove;\r\n            b += bMove;\r\n        }\r\n        return output.toString();\r\n    }\r\n\r\n    public static AsciiMatcher FORMAT_CODES_MATCHER = new AsciiMatcher(\"klmnoKLMNO\");\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/bukkit/BukkitListExtensions.java",
    "content": "package com.denizenscript.denizen.objects.properties.bukkit;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.utilities.AsciiMatcher;\r\nimport org.bukkit.ChatColor;\r\n\r\nimport java.util.List;\r\n\r\npublic class BukkitListExtensions {\r\n\r\n    public static AsciiMatcher LETTERS = new AsciiMatcher(AsciiMatcher.LETTERS_UPPER + AsciiMatcher.LETTERS_LOWER);\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <ListTag.formatted>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the list in a human-readable format.\r\n        // Note that this will parse the values within the list to be human-readable as well when possible.\r\n        // EG, a list of \"<npc>|<player>|potato\" will return \"GuardNPC, bob, and potato\".\r\n        // The exact formatting rules that will be followed are not guaranteed, other than that it will be a semi-clean human-readable format.\r\n        // -->\r\n        ListTag.tagProcessor.registerTag(ElementTag.class, \"formatted\", (attribute, list) -> {\r\n            if (list.isEmpty()) {\r\n                return new ElementTag(\"\");\r\n            }\r\n            StringBuilder output = new StringBuilder();\r\n            for (int i = 0; i < list.size(); i++) {\r\n                ObjectTag object = list.getObject(i);\r\n                String val = object.toString();\r\n                boolean handled = false;\r\n                if (val.startsWith(\"p@\")) {\r\n                    PlayerTag gotten = object.asType(PlayerTag.class, attribute.context);\r\n                    if (gotten != null) {\r\n                        output.append(gotten.getName());\r\n                        handled = true;\r\n                    }\r\n                }\r\n                if (val.startsWith(\"e@\") || val.startsWith(\"n@\")) {\r\n                    EntityTag gotten = object.asType(EntityTag.class, attribute.context);\r\n                    if (gotten != null) {\r\n                        if (gotten.isValid()) {\r\n                            output.append(gotten.getName());\r\n                        }\r\n                        else {\r\n                            output.append(gotten.getEntityType().getName());\r\n                        }\r\n                        handled = true;\r\n                    }\r\n                }\r\n                if (val.startsWith(\"i@\")) {\r\n                    ItemTag item = object.asType(ItemTag.class, attribute.context);\r\n                    if (item != null) {\r\n                        output.append(item.formattedName());\r\n                        handled = true;\r\n                    }\r\n                }\r\n                if (val.startsWith(\"m@\")) {\r\n                    MaterialTag material = object.asType(MaterialTag.class, attribute.context);\r\n                    if (material != null) {\r\n                        output.append(material.name());\r\n                        handled = true;\r\n                    }\r\n                }\r\n                if (!handled) {\r\n                    String raw = ChatColor.stripColor(DenizenCore.implementation.applyDebugColors(object.debuggable()));\r\n                    int at = raw.indexOf('@');\r\n                    if (at != -1 && LETTERS.isOnlyMatches(raw.substring(0, at))) {\r\n                        raw = raw.substring(at + 1);\r\n                    }\r\n                    output.append(raw);\r\n                }\r\n                if (i == list.size() - 2) {\r\n                    output.append(i == 0 ? \" and \" : \", and \");\r\n                }\r\n                else {\r\n                    output.append(\", \");\r\n                }\r\n            }\r\n            return new ElementTag(output.substring(0, output.length() - 2));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ListTag.to_polygon>\r\n        // @returns PolygonTag\r\n        // @description\r\n        // Converts a list of locations to a PolygonTag.\r\n        // The Y-Min and Y-Max values will be assigned based the range of Y values in the locations given.\r\n        // -->\r\n        ListTag.tagProcessor.registerTag(PolygonTag.class, \"to_polygon\", (attribute, list) -> {\r\n            List<LocationTag> locations = list.filter(LocationTag.class, attribute.context);\r\n            if (locations == null || locations.isEmpty()) {\r\n                return null;\r\n            }\r\n            if (locations.size() > Settings.blockTagsMaxBlocks()) {\r\n                return null;\r\n            }\r\n            PolygonTag polygon = new PolygonTag(new WorldTag(locations.get(0).getWorldName()));\r\n            polygon.yMin = locations.get(0).getY();\r\n            polygon.yMax = polygon.yMin;\r\n            for (LocationTag location : locations) {\r\n                polygon.yMin = Math.min(polygon.yMin, location.getY());\r\n                polygon.yMax = Math.max(polygon.yMax, location.getY());\r\n                polygon.corners.add(new PolygonTag.Corner(location.getX(), location.getZ()));\r\n            }\r\n            polygon.recalculateBox();\r\n            return polygon;\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/bukkit/BukkitMapExtensions.java",
    "content": "package com.denizenscript.denizen.objects.properties.bukkit;\r\n\r\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\r\nimport com.denizenscript.denizencore.objects.core.BinaryTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.kyori.adventure.nbt.BinaryTagIO;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\n\r\nimport java.io.ByteArrayOutputStream;\r\n\r\npublic class BukkitMapExtensions {\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MapTag.map_to_nbt>\r\n        // @returns BinaryTag\r\n        // @group conversion\r\n        // @description\r\n        // Converts the NBT-formatted MapTag to raw binary NBT.\r\n        // Refer to <@link language Raw NBT Encoding>\r\n        // @example\r\n        // # Stores a player \".dat\" file's NBT data\r\n        // # NOTE: replace 'something' with your map data\r\n        // - define playerdata something\r\n        // - define data <[playerdata].map_to_nbt.gzip_compress>\r\n        // - ~filewrite path:data/<player.uuid>.dat data:<[data]>\r\n        // -->\r\n        MapTag.tagProcessor.registerStaticTag(BinaryTag.class, \"map_to_nbt\", (attribute, object) -> {\r\n            try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {\r\n                CompoundBinaryTag tag = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(object, attribute.context, \"(root).\");\r\n                BinaryTagIO.writer().write(tag, output);\r\n                return new BinaryTag(output.toByteArray());\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n                return null;\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/bukkit/BukkitQueueExtensions.java",
    "content": "package com.denizenscript.denizen.objects.properties.bukkit;\r\n\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.core.QueueTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\n\r\npublic class BukkitQueueExtensions {\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <QueueTag.npc>\r\n        // @returns NPCTag\r\n        // @mechanism QueueTag.linked_npc\r\n        // @description\r\n        // Returns the NPCTag linked to a queue.\r\n        // -->\r\n        QueueTag.tagProcessor.registerTag(NPCTag.class, \"npc\", (attribute, object) -> {\r\n            NPCTag npc = null;\r\n            if (object.queue.getLastEntryExecuted() != null) {\r\n                npc = ((BukkitScriptEntryData) object.queue.getLastEntryExecuted().entryData).getNPC();\r\n            }\r\n            else if (object.queue.getEntries().size() > 0) {\r\n                npc = ((BukkitScriptEntryData) object.queue.getEntries().get(0).entryData).getNPC();\r\n            }\r\n            else if (!attribute.hasAlternative()) {\r\n                attribute.echoError(\"Can't determine a linked NPC.\");\r\n            }\r\n            return npc;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <QueueTag.player>\r\n        // @returns PlayerTag\r\n        // @mechanism QueueTag.linked_player\r\n        // @description\r\n        // Returns the PlayerTag linked to a queue.\r\n        // -->\r\n        QueueTag.tagProcessor.registerTag(PlayerTag.class, \"player\", (attribute, object) -> {\r\n            PlayerTag player = null;\r\n            if (object.queue.getLastEntryExecuted() != null) {\r\n                player = ((BukkitScriptEntryData) object.queue.getLastEntryExecuted().entryData).getPlayer();\r\n            }\r\n            else if (object.queue.getEntries().size() > 0) {\r\n                player = ((BukkitScriptEntryData) object.queue.getEntries().get(0).entryData).getPlayer();\r\n            }\r\n            else {\r\n                attribute.echoError(\"Can't determine a linked player.\");\r\n            }\r\n            return player;\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object QueueTag\r\n        // @name linked_player\r\n        // @input PlayerTag\r\n        // @description\r\n        // Sets the linked player for the remainder of the queue.\r\n        // @tags\r\n        // <QueueTag.player>\r\n        // -->\r\n        QueueTag.tagProcessor.registerMechanism(\"linked_player\", false, PlayerTag.class, (queue, mechanism, player) -> {\r\n            for (ScriptEntry entry : queue.queue.getEntries()) {\r\n                BukkitScriptEntryData data = (BukkitScriptEntryData) entry.entryData;\r\n                data.setPlayer(player);\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object QueueTag\r\n        // @name linked_npc\r\n        // @input NPCTag\r\n        // @description\r\n        // Sets the linked NPC for the remainder of the queue.\r\n        // @tags\r\n        // <QueueTag.npc>\r\n        // -->\r\n        QueueTag.tagProcessor.registerMechanism(\"linked_npc\", false, NPCTag.class, (queue, mechanism, npc) -> {\r\n            for (ScriptEntry entry : queue.queue.getEntries()) {\r\n                BukkitScriptEntryData data = (BukkitScriptEntryData) entry.entryData;\r\n                data.setNPC(npc);\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/bukkit/BukkitScriptExtensions.java",
    "content": "package com.denizenscript.denizen.objects.properties.bukkit;\n\nimport com.denizenscript.denizen.scripts.commands.core.CooldownCommand;\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptContainer;\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptHelper;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\nimport com.denizenscript.denizencore.objects.core.TimeTag;\n\npublic class BukkitScriptExtensions {\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <ScriptTag.cooled_down[(<player>)]>\n        // @returns ElementTag(Boolean)\n        // @description\n        // Returns whether the script is currently cooled down for the player. Any global\n        // cooldown present on the script will also be taken into account. Not specifying a player will result in\n        // using the attached player available in the script entry. Not having a valid player will result in 'null'.\n        // -->\n        ScriptTag.tagProcessor.registerTag(ElementTag.class, \"cooled_down\", (attribute, script) -> {\n            PlayerTag player = (attribute.hasParam() ? attribute.paramAsType(PlayerTag.class)\n                    : ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getPlayer());\n            if (player != null && player.isValid()) {\n                return new ElementTag(CooldownCommand.checkCooldown(player, script.getContainer().getName()));\n            }\n            else {\n                return null;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <ScriptTag.cooldown[(<player>)]>\n        // @returns DurationTag\n        // @description\n        // Returns the time left for the player to cooldown for the script.\n        // -->\n        ScriptTag.tagProcessor.registerTag(DurationTag.class, \"cooldown\", (attribute, script) -> {\n            PlayerTag player = (attribute.hasParam() ? attribute.paramAsType(PlayerTag.class)\n                    : ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getPlayer());\n            return CooldownCommand.getCooldownDuration(player, script.getName());\n        });\n\n        // <--[tag]\n        // @attribute <ScriptTag.step[(<player>)]>\n        // @returns ElementTag\n        // @description\n        // Returns the name of a script step that the player is currently on.\n        // Must be an INTERACT script.\n        // -->\n        ScriptTag.tagProcessor.registerTag(ElementTag.class, \"step\", (attribute, script) -> {\n            PlayerTag player = attribute.hasParam() ? attribute.paramAsType(PlayerTag.class) : ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getPlayer();\n            if (player != null && player.isValid()) {\n                return new ElementTag(InteractScriptHelper.getCurrentStep(player, script.getContainer().getName()));\n            }\n            else {\n                return null;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <ScriptTag.step_expiration[(<player>)]>\n        // @returns TimeTag\n        // @description\n        // Returns the time that an interact script step expires at, if applied by <@link command zap> with a duration limit.\n        // -->\n        ScriptTag.tagProcessor.registerTag(TimeTag.class, \"step_expiration\", (attribute, script) -> {\n            PlayerTag player = attribute.hasParam() ? attribute.paramAsType(PlayerTag.class) : ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getPlayer();\n            if (player != null && player.isValid()) {\n                return InteractScriptHelper.getStepExpiration(player, script.getContainer().getName());\n            }\n            else {\n                return null;\n            }\n        });\n\n        // <--[tag]\n        // @attribute <ScriptTag.default_step>\n        // @returns ElementTag\n        // @description\n        // Returns the name of the default step of an interact script.\n        // -->\n        ScriptTag.tagProcessor.registerStaticTag(ElementTag.class, \"default_step\", (attribute, script) -> {\n            String step = ((InteractScriptContainer) script.getContainer()).getDefaultStepName();\n            return new ElementTag(step);\n        });\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAI.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\n\npublic class EntityAI extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name has_ai\n    // @input ElementTag(Boolean)\n    // @description\n    // Controls whether this entity will use the default Minecraft AI to roam and look around.\n    // This tends to have implications for other vanilla functionality, including gravity.\n    // This generally shouldn't be used with NPCs. NPCs do not have vanilla AI, regardless of what this tag returns.\n    // Other programmatic methods of blocking AI might also not be accounted for by this tag.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.isLivingEntityType();\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(getLivingEntity().hasAI());\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        if (mechanism.requireBoolean()) {\n            getLivingEntity().setAI(param.asBoolean());\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"has_ai\";\n    }\n\n    public static void register() {\n        autoRegister(\"has_ai\", EntityAI.class, ElementTag.class, false, \"toggle_ai\");\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAge.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport net.citizensnpcs.trait.Age;\nimport org.bukkit.entity.Ageable;\nimport org.bukkit.entity.Breedable;\n\npublic class EntityAge extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name age\n    // @input ElementTag\n    // @description\n    // Controls the entity's age.\n    // Age moves 1 towards zero each tick.\n    // A newly spawned baby is -24000, a standard adult is 0, an adult that just bred is 6000.\n    // For the mechanism, inputs can be 'baby', 'adult', or a valid age number.\n    // Also available: <@link mechanism EntityTag.age_locked> and <@link tag EntityTag.is_baby>\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof Ageable;\n    }\n\n    public EntityAge(EntityTag entity) {\n        super(entity);\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(as(Ageable.class).getAge());\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        ListTag listHack = param.asType(ListTag.class, mechanism.context);\n        if (listHack.isEmpty()) {\n            mechanism.echoError(\"Missing value for 'age' mechanism!\");\n            return;\n        }\n        String input = CoreUtilities.toLowerCase(listHack.get(0));\n        switch (input) {\n            case \"baby\" -> setAge(-24000);\n            case \"adult\" -> setAge(0);\n            default -> {\n                if (!ArgumentHelper.matchesInteger(input)) {\n                    mechanism.echoError(\"Invalid age '\" + input + \"': must be 'baby', 'adult', or a valid age number.\");\n                    return;\n                }\n                setAge(new ElementTag(input).asInt());\n            }\n        }\n        if (listHack.size() > 1) {\n            BukkitImplDeprecations.oldAgeLockedControls.warn(mechanism.context);\n            if (!(getEntity() instanceof Breedable breedable)) {\n                return;\n            }\n            switch (CoreUtilities.toLowerCase(listHack.get(1))) {\n                case \"locked\" -> breedable.setAgeLock(true);\n                case \"unlocked\" -> breedable.setAgeLock(false);\n                default -> mechanism.echoError(\"Invalid lock state '\" + listHack.get(1) + \"': must be 'locked' or 'unlocked'.\");\n            }\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"age\";\n    }\n\n    public void setAge(int age) {\n        if (object.isCitizensNPC()) {\n            object.getDenizenNPC().getCitizen().getOrAddTrait(Age.class).setAge(age);\n        }\n        else {\n            as(Ageable.class).setAge(age);\n        }\n    }\n\n    public static void register() {\n        autoRegister(\"age\", EntityAge.class, ElementTag.class, false);\n\n        // <--[tag]\n        // @attribute <EntityTag.is_baby>\n        // @returns ElementTag(Boolean)\n        // @mechanism EntityTag.age\n        // @group properties\n        // @description\n        // If the entity is ageable, returns whether the entity is a baby.\n        // -->\n        PropertyParser.registerTag(EntityAge.class, ElementTag.class, \"is_baby\", (attribute, prop) -> {\n            return new ElementTag(!prop.as(Ageable.class).isAdult());\n        });\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAgeLocked.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport net.citizensnpcs.trait.Age;\r\nimport org.bukkit.entity.Breedable;\r\n\r\npublic class EntityAgeLocked extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name age_locked\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Controls whether the entity is locked into its current age.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Breedable;\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag val) {\r\n        return !val.asBoolean();\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(Breedable.class).getAgeLock());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            if (object.isCitizensNPC()) {\r\n                object.getDenizenNPC().getCitizen().getOrAddTrait(Age.class).setLocked(param.asBoolean());\r\n            }\r\n            else {\r\n                as(Breedable.class).setAgeLock(param.asBoolean());\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"age_locked\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"age_locked\", EntityAgeLocked.class, ElementTag.class, false, \"is_age_locked\", \"age_lock\");\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_age_locked>\r\n        // @returns ElementTag(Boolean)\r\n        // @group properties\r\n        // @deprecated use 'age_locked'.\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.age_locked>.\r\n        // -->\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name age_lock\r\n        // @input ElementTag(Boolean)\r\n        // @deprecated use 'age_locked'.\r\n        // @description\r\n        // Deprecated in favor of <@link mechanism EntityTag.age_locked>.\r\n        // -->\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAggressive.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Mob;\r\n\r\npublic class EntityAggressive extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name aggressive\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Controls whether the entity is currently aggressive.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Mob;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(NMSHandler.entityHelper.isAggressive(as(Mob.class)));\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            NMSHandler.entityHelper.setAggressive(as(Mob.class), param.asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"aggressive\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"aggressive\", EntityAggressive.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAnger.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport org.bukkit.entity.Bee;\r\nimport org.bukkit.entity.PigZombie;\r\n\r\npublic class EntityAnger extends EntityProperty<DurationTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name anger\r\n    // @input DurationTag\r\n    // @description\r\n    // Controls the remaining anger time of a PigZombie or Bee.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof PigZombie\r\n                || entity.getBukkitEntity() instanceof Bee;\r\n    }\r\n\r\n    @Override\r\n    public DurationTag getPropertyValue() {\r\n        if (getEntity() instanceof PigZombie entity) {\r\n            return new DurationTag((long) entity.getAnger());\r\n        }\r\n        else if (getEntity() instanceof Bee entity) {\r\n            return new DurationTag((long) entity.getAnger());\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(DurationTag param, Mechanism mechanism) {\r\n        if (mechanism.getValue().isInt()) { // Soft-deprecated - backwards compatibility, as this used to use a tick count\r\n            param = new DurationTag(mechanism.getValue().asLong());\r\n        }\r\n        if (getEntity() instanceof PigZombie entity) {\r\n            entity.setAnger(param.getTicksAsInt());\r\n        }\r\n        else {\r\n            as(Bee.class).setAnger(param.getTicksAsInt());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"anger\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"anger\", EntityAnger.class, DurationTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAngry.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport org.bukkit.entity.PigZombie;\r\nimport org.bukkit.entity.Vindicator;\r\nimport org.bukkit.entity.Wolf;\r\n\r\npublic class EntityAngry extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name angry\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Controls whether a Wolf or PigZombie is angry, or whether a Vindicator is in \"Johnny\" mode.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof PigZombie\r\n                || entity.getBukkitEntity() instanceof Wolf\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18) && entity.getBukkitEntity() instanceof Vindicator);\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag val) {\r\n        return !val.asBoolean();\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (getEntity() instanceof PigZombie entity) {\r\n            return new ElementTag(entity.isAngry());\r\n        }\r\n        else if (getEntity() instanceof Wolf entity) {\r\n            return new ElementTag(entity.isAngry());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18) && getEntity() instanceof Vindicator entity) {\r\n            return new ElementTag(entity.isJohnny());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            if (getEntity() instanceof PigZombie entity) {\r\n                entity.setAngry(param.asBoolean());\r\n            }\r\n            else if (getEntity() instanceof Wolf entity) {\r\n                entity.setAngry(param.asBoolean());\r\n            }\r\n            else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18) && getEntity() instanceof Vindicator entity) {\r\n                entity.setJohnny(param.asBoolean());\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"angry\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"angry\", EntityAngry.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAreaEffectCloud.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.entity.AreaEffectCloudHelper;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Particle;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.potion.PotionEffect;\r\nimport org.bukkit.potion.PotionEffectType;\r\nimport org.bukkit.potion.PotionType;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\n\r\nimport java.util.List;\r\n\r\n// TODO: most of the tags and mechs here need to become properties/be merged into existing properties\r\npublic class EntityAreaEffectCloud implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag &&\r\n                ((EntityTag) entity).getBukkitEntityType() == EntityType.AREA_EFFECT_CLOUD;\r\n    }\r\n\r\n    public static EntityAreaEffectCloud getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityAreaEffectCloud((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"base_potion\", \"particle\", \"duration\", \"radius\", \"reapplication_delay\", \"wait_time\",\r\n            \"has_custom_effect\", \"source\", \"custom_effects\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"clear_custom_effects\", \"remove_custom_effect\", \"custom_effects\", \"particle_color\",\r\n            \"base_potion\", \"duration\", \"duration_on_use\", \"particle\", \"radius\", \"radius_on_use\",\r\n            \"radius_per_tick\", \"reapplication_delay\", \"source\", \"wait_time\"\r\n    };\r\n\r\n    public EntityAreaEffectCloud(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"area_effect_cloud\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.base_potion>\r\n        // @returns ElementTag\r\n        // @mechanism EntityTag.base_potion\r\n        // @deprecated use 'EntityTag.potion_type' on MC 1.20+.\r\n        // @description\r\n        // Deprecated in favor of <@link property EntityTag.potion_type> on MC 1.20+.\r\n        // -->\r\n        if (attribute.startsWith(\"base_potion\")) {\r\n            attribute = attribute.fulfill(1);\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                BukkitImplDeprecations.areaEffectCloudControls.warn(attribute.context);\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.base_potion.type>\r\n            // @returns ElementTag\r\n            // @deprecated use 'EntityTag.potion_type' on MC 1.20+.\r\n            // @description\r\n            // Deprecated in favor of <@link property EntityTag.potion_type> on MC 1.20+.\r\n            // -->\r\n            if (attribute.startsWith(\"type\")) {\r\n                return new ElementTag(getHelper().getBPName())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.base_potion.is_upgraded>\r\n            // @returns ElementTag(Boolean)\r\n            // @deprecated use 'EntityTag.potion_type' on MC 1.20+.\r\n            // @description\r\n            // Deprecated in favor of <@link property EntityTag.potion_type> on MC 1.20+.\r\n            // -->\r\n            if (attribute.startsWith(\"is_upgraded\")) {\r\n                return new ElementTag(getHelper().getBPUpgraded())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.base_potion.is_extended>\r\n            // @returns ElementTag(Boolean)\r\n            // @deprecated use 'EntityTag.potion_type' on MC 1.20+.\r\n            // @description\r\n            // Deprecated in favor of <@link property EntityTag.potion_type> on MC 1.20+.\r\n            // -->\r\n            if (attribute.startsWith(\"is_extended\")) {\r\n                return new ElementTag(getHelper().getBPExtended())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n            return new ElementTag(getHelper().getBPName() + \",\" + getHelper().getBPUpgraded() + \",\" + getHelper().getBPExtended())\r\n                    .getObjectAttribute(attribute);\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.particle>\r\n        // @returns ElementTag\r\n        // @mechanism EntityTag.particle\r\n        // @group properties\r\n        // @description\r\n        // Returns the Area Effect Cloud's particle.\r\n        // -->\r\n        if (attribute.startsWith(\"particle\")) {\r\n            attribute = attribute.fulfill(1);\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.particle.color>\r\n            // @returns ColorTag\r\n            // @deprecated use 'EntityTag.color'.\r\n            // @description\r\n            // Deprecated in favor of <@link property EntityTag.color>.\r\n            // -->\r\n            if (attribute.startsWith(\"color\")) {\r\n                BukkitImplDeprecations.areaEffectCloudControls.warn(attribute.context);\r\n                return BukkitColorExtensions.fromColor(getHelper().getColor())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n            return new ElementTag(getHelper().getParticle())\r\n                    .getObjectAttribute(attribute);\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.duration>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.duration\r\n        // @group properties\r\n        // @description\r\n        // Returns the Area Effect Cloud's duration.\r\n        // -->\r\n        if (attribute.startsWith(\"duration\")) {\r\n            attribute = attribute.fulfill(1);\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.duration.on_use>\r\n            // @returns DurationTag\r\n            // @group properties\r\n            // @description\r\n            // Returns the duration the Area Effect Cloud\r\n            // will increase by when it applies an effect to an entity.\r\n            // -->\r\n            if (attribute.startsWith(\"on_use\")) {\r\n                return new DurationTag(getHelper().getDurationOnUse())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n            return new DurationTag(getHelper().getDuration())\r\n                    .getObjectAttribute(attribute);\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.radius>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism EntityTag.radius\r\n        // @group properties\r\n        // @description\r\n        // Returns the Area Effect Cloud's radius.\r\n        // -->\r\n        if (attribute.startsWith(\"radius\")) {\r\n            attribute = attribute.fulfill(1);\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.radius.on_use>\r\n            // @returns ElementTag(Decimal)\r\n            // @group properties\r\n            // @description\r\n            // Returns the amount the Area Effect Cloud's radius\r\n            // will increase by when it applies an effect to an entity.\r\n            // -->\r\n            if (attribute.startsWith(\"on_use\")) {\r\n                return new ElementTag(getHelper().getRadiusOnUse())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.radius.per_tick>\r\n            // @returns ElementTag(Decimal)\r\n            // @group properties\r\n            // @description\r\n            // Returns the amount the Area Effect Cloud's radius\r\n            // will increase by every tick.\r\n            // -->\r\n            if (attribute.startsWith(\"per_tick\")) {\r\n                return new ElementTag(getHelper().getRadiusPerTick())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n            return new ElementTag(getHelper().getRadius())\r\n                    .getObjectAttribute(attribute);\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.reapplication_delay>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.reapplication_delay\r\n        // @group properties\r\n        // @description\r\n        // Returns the duration an entity will be immune\r\n        // from the Area Effect Cloud's subsequent exposure.\r\n        // -->\r\n        if (attribute.startsWith(\"reapplication_delay\")) {\r\n            return new DurationTag(getHelper().getReappDelay())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.wait_time>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.wait_time\r\n        // @group properties\r\n        // @description\r\n        // Returns the duration before the Area Effect Cloud starts applying potion effects.\r\n        // -->\r\n        if (attribute.startsWith(\"wait_time\")) {\r\n            return new DurationTag(getHelper().getWaitTime())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.has_custom_effect[(<effect>)]>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.custom_effects\r\n        // @deprecated use 'EntityTag.has_effect'.\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.has_effect>.\r\n        // -->\r\n        if (attribute.startsWith(\"has_custom_effect\")) {\r\n            BukkitImplDeprecations.areaEffectCloudControls.warn(attribute.context);\r\n            if (attribute.hasParam()) {\r\n                PotionEffectType effectType = PotionEffectType.getByName(attribute.getParam());\r\n                for (PotionEffect effect : getHelper().getCustomEffects()) {\r\n                    if (effect.getType().equals(effectType)) {\r\n                        return new ElementTag(true).getObjectAttribute(attribute.fulfill(1));\r\n                    }\r\n                }\r\n                return new ElementTag(false).getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n            return new ElementTag(getHelper().hasCustomEffects())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.source>\r\n        // @returns EntityTag\r\n        // @mechanism EntityTag.source\r\n        // @group properties\r\n        // @description\r\n        // Returns the source of the Area Effect Cloud.\r\n        // -->\r\n        if (attribute.startsWith(\"source\")) {\r\n            ProjectileSource shooter = getHelper().getSource();\r\n            if (shooter instanceof LivingEntity) {\r\n                return new EntityTag((LivingEntity) shooter).getDenizenObject()\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.custom_effects>\r\n        // @returns ListTag\r\n        // @mechanism EntityTag.custom_effects\r\n        // @deprecated use 'EntityTag.effects_data'.\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.effects_data>.\r\n        // -->\r\n        if (attribute.startsWith(\"custom_effects\")) {\r\n            BukkitImplDeprecations.areaEffectCloudControls.warn(attribute.context);\r\n            List<PotionEffect> effects = getHelper().getCustomEffects();\r\n            if (!attribute.hasParam()) {\r\n                ListTag list = new ListTag();\r\n                for (PotionEffect effect : effects) {\r\n                    list.add(effect.getType().getName() + \",\" +\r\n                            effect.getAmplifier() + \",\" +\r\n                            new DurationTag((long) effect.getDuration()).identify() + \",\" +\r\n                            effect.isAmbient() + \",\" +\r\n                            effect.hasParticles());\r\n                }\r\n                return list.getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n            int val = attribute.getIntParam() - 1;\r\n            if (val < 0 || val >= effects.size()) {\r\n                return null;\r\n            }\r\n            attribute = attribute.fulfill(1);\r\n            PotionEffect effect = effects.get(val);\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.custom_effects[<#>].type>\r\n            // @returns ElementTag\r\n            // @deprecated use 'EntityTag.effects_data'.\r\n            // @description\r\n            // Deprecated in favor of <@link tag EntityTag.effects_data>.\r\n            // -->\r\n            if (attribute.startsWith(\"type\")) {\r\n                return new ElementTag(effect.getType().getName())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.custom_effects[<#>].amplifier>\r\n            // @returns ElementTag(Number)\r\n            // @deprecated use 'EntityTag.effects_data'.\r\n            // @description\r\n            // Deprecated in favor of <@link tag EntityTag.effects_data>.\r\n            // -->\r\n            if (attribute.startsWith(\"amplifier\")) {\r\n                return new ElementTag(effect.getAmplifier())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.custom_effects[<#>].duration>\r\n            // @returns DurationTag\r\n            // @deprecated use 'EntityTag.effects_data'.\r\n            // @description\r\n            // Deprecated in favor of <@link tag EntityTag.effects_data>.\r\n            // -->\r\n            if (attribute.startsWith(\"duration\")) {\r\n                return new DurationTag((long) effect.getDuration())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.custom_effects[<#>].has_particles>\r\n            // @returns ElementTag(Boolean)\r\n            // @deprecated use 'EntityTag.effects_data'.\r\n            // @description\r\n            // Deprecated in favor of <@link tag EntityTag.effects_data>.\r\n            // -->\r\n            if (attribute.startsWith(\"has_particles\")) {\r\n                return new ElementTag(effect.hasParticles())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.custom_effects[<#>].is_ambient>\r\n            // @returns ElementTag(Boolean)\r\n            // @deprecated use 'EntityTag.effects_data'.\r\n            // @description\r\n            // Deprecated in favor of <@link tag EntityTag.effects_data>.\r\n            // -->\r\n            if (attribute.startsWith(\"is_ambient\")) {\r\n                return new ElementTag(effect.isAmbient())\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n\r\n            return new ElementTag(effect.getType().getName() + \",\" +\r\n                    effect.getAmplifier() + \",\" +\r\n                    new DurationTag((long) effect.getDuration()).identify() + \",\" +\r\n                    effect.isAmbient() + \",\" +\r\n                    effect.hasParticles()).getObjectAttribute(attribute);\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    public AreaEffectCloudHelper getHelper() {\r\n        return new AreaEffectCloudHelper(entity.getBukkitEntity());\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name clear_custom_effects\r\n        // @input None\r\n        // @deprecated use 'EntityTag.potion_effects'.\r\n        // @description\r\n        // Deprecated in favor of <@link mechanism EntityTag.potion_effects>.\r\n        // @tags\r\n        // <EntityTag.custom_effects>\r\n        // -->\r\n        if (mechanism.matches(\"clear_custom_effects\")) {\r\n            BukkitImplDeprecations.areaEffectCloudControls.warn(mechanism.context);\r\n            getHelper().clearEffects();\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name remove_custom_effect\r\n        // @input ElementTag\r\n        // @deprecated use 'EntityTag.potion_effects'.\r\n        // @description\r\n        // Deprecated in favor of <@link mechanism EntityTag.potion_effects>.\r\n        // @tags\r\n        // <EntityTag.custom_effects>\r\n        // -->\r\n        if (mechanism.matches(\"remove_custom_effect\")) {\r\n            BukkitImplDeprecations.areaEffectCloudControls.warn(mechanism.context);\r\n            PotionEffectType type = PotionEffectType.getByName(mechanism.getValue().asString().toUpperCase());\r\n            if (type != null) {\r\n                getHelper().removeEffect(type);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name custom_effects\r\n        // @input ListTag\r\n        // @deprecated use 'EntityTag.potion_effects'.\r\n        // @description\r\n        // Deprecated in favor of <@link mechanism EntityTag.potion_effects>.\r\n        // @tags\r\n        // <EntityTag.custom_effects>\r\n        // -->\r\n        if (mechanism.matches(\"custom_effects\")) {\r\n            BukkitImplDeprecations.areaEffectCloudControls.warn(mechanism.context);\r\n            ListTag list = mechanism.valueAsType(ListTag.class);\r\n            getHelper().clearEffects();\r\n\r\n            for (String item : list) {\r\n                List<String> potionData = CoreUtilities.split(item, ',', 5);\r\n                if (potionData.size() >= 3) {\r\n                    PotionEffectType type = PotionEffectType.getByName(potionData.get(0));\r\n                    ElementTag amplifier = new ElementTag(potionData.get(1));\r\n                    DurationTag duration = DurationTag.valueOf(potionData.get(2), mechanism.context);\r\n                    ElementTag ambient = new ElementTag((potionData.size() > 3) ? potionData.get(3) : \"false\");\r\n                    ElementTag particles = new ElementTag((potionData.size() > 4) ? potionData.get(4) : \"true\");\r\n\r\n                    if (type == null || duration == null || !amplifier.isInt() || !ambient.isBoolean() || !particles.isBoolean()) {\r\n                        mechanism.echoError(item + \" is not a valid potion effect!\");\r\n                    }\r\n                    else {\r\n                        getHelper().addEffect(\r\n                                new PotionEffect(type, duration.getTicksAsInt(), amplifier.asInt(),\r\n                                        ambient.asBoolean(), particles.asBoolean()), true);\r\n                    }\r\n                }\r\n                else {\r\n                    mechanism.echoError(item + \" is not a valid potion effect!\");\r\n                }\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name particle_color\r\n        // @input ColorTag\r\n        // @deprecated use 'EntityTag.color'.\r\n        // @description\r\n        // Deprecated in favor of <@link property EntityTag.color>.\r\n        // @tags\r\n        // <EntityTag.particle.color>\r\n        // -->\r\n        if (mechanism.matches(\"particle_color\") && mechanism.requireObject(ColorTag.class)) {\r\n            BukkitImplDeprecations.areaEffectCloudControls.warn(mechanism.context);\r\n            getHelper().setColor(BukkitColorExtensions.getColor(mechanism.valueAsType(ColorTag.class)));\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name base_potion\r\n        // @input ElementTag\r\n        // @deprecated use 'EntityTag.potion_type' on MC 1.20+.\r\n        // @description\r\n        // Deprecated in favor of <@link property EntityTag.potion_type> on MC 1.20+.\r\n        // @tags\r\n        // <EntityTag.base_potion>\r\n        // -->\r\n        if (mechanism.matches(\"base_potion\")) {\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                BukkitImplDeprecations.areaEffectCloudControls.warn(mechanism.context);\r\n            }\r\n            List<String> data = CoreUtilities.split(mechanism.getValue().asString().toUpperCase(), ',');\r\n            if (data.size() != 3) {\r\n                mechanism.echoError(mechanism.getValue() + \" is not a valid base potion!\");\r\n                return;\r\n            }\r\n            PotionType type = Utilities.elementToEnumlike(new ElementTag(data.get(0), true), PotionType.class);\r\n            if (type == null) {\r\n                mechanism.echoError(mechanism.getValue() + \" is not a valid base potion!\");\r\n                return;\r\n            }\r\n            boolean upgraded = type.isUpgradeable() && CoreUtilities.equalsIgnoreCase(data.get(1), \"true\");\r\n            boolean extended = type.isExtendable() && CoreUtilities.equalsIgnoreCase(data.get(2), \"true\");\r\n            if (extended && upgraded) {\r\n                mechanism.echoError(\"Potion cannot be both upgraded and extended\");\r\n            }\r\n            else {\r\n                getHelper().setBP(type, extended, upgraded);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name duration\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the Area Effect Cloud's duration.\r\n        // @tags\r\n        // <EntityTag.duration>\r\n        // -->\r\n        if (mechanism.matches(\"duration\") && mechanism.requireObject(DurationTag.class)) {\r\n            getHelper().setDuration(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name duration_on_use\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the duration the Area Effect Cloud\r\n        // will increase by when it applies an effect to an entity.\r\n        // @tags\r\n        // <EntityTag.duration.on_use>\r\n        // -->\r\n        if (mechanism.matches(\"duration_on_use\") && mechanism.requireObject(DurationTag.class)) {\r\n            getHelper().setDurationOnUse(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name particle\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the particle of the Area Effect Cloud\r\n        // @tags\r\n        // <EntityTag.particle>\r\n        // -->\r\n        // TODO: some particles require additional data - need a new property that supports playeffect's special_data input\r\n        if (mechanism.matches(\"particle\") && Utilities.requireEnumlike(mechanism, Particle.class)) {\r\n            getHelper().setParticle(mechanism.getValue().asString().toUpperCase());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name radius\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the radius of the Area Effect Cloud\r\n        // @tags\r\n        // <EntityTag.radius>\r\n        // -->\r\n        if (mechanism.matches(\"radius\") && mechanism.requireFloat()) {\r\n            getHelper().setRadius(mechanism.getValue().asFloat());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name radius_on_use\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the radius the Area Effect Cloud\r\n        // will increase by when it applies an effect to an entity.\r\n        // @tags\r\n        // <EntityTag.radius.on_use>\r\n        // -->\r\n        if (mechanism.matches(\"radius_on_use\") && mechanism.requireFloat()) {\r\n            getHelper().setRadiusOnUse(mechanism.getValue().asFloat());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name radius_per_tick\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the radius the Area Effect Cloud\r\n        // will increase by every tick.\r\n        // @tags\r\n        // <EntityTag.radius.per_tick>\r\n        // -->\r\n        if (mechanism.matches(\"radius_per_tick\") && mechanism.requireFloat()) {\r\n            getHelper().setRadiusPerTick(mechanism.getValue().asFloat());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name reapplication_delay\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the duration an entity will be immune\r\n        // from the Area Effect Cloud's subsequent exposure.\r\n        // @tags\r\n        // <EntityTag.reapplication_delay>\r\n        // -->\r\n        if (mechanism.matches(\"reapplication_delay\") && mechanism.requireObject(DurationTag.class)) {\r\n            getHelper().setReappDelay(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name source\r\n        // @input EntityTag\r\n        // @description\r\n        // Sets the source of the Area Effect Cloud\r\n        // @tags\r\n        // <EntityTag.source>\r\n        // -->\r\n        if (mechanism.matches(\"source\") && mechanism.requireObject(EntityTag.class)) {\r\n            getHelper().setSource((ProjectileSource) mechanism.valueAsType(EntityTag.class).getBukkitEntity());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name wait_time\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the duration before the Area Effect Cloud starts applying potion effects.\r\n        // @tags\r\n        // <EntityTag.wait_time>\r\n        // -->\r\n        if (mechanism.matches(\"wait_time\") && mechanism.requireObject(DurationTag.class)) {\r\n            getHelper().setWaitTime(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityArmorBonus.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.attribute.AttributeInstance;\r\n\r\npublic class EntityArmorBonus implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).isLivingEntity();\r\n    }\r\n\r\n    public static EntityArmorBonus getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityArmorBonus((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"armor_bonus\"\r\n    };\r\n\r\n    public EntityArmorBonus(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        double value = getAttribute().getValue();\r\n        return value > 0 ? String.valueOf(value) : null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"armor_bonus\";\r\n    }\r\n\r\n    public AttributeInstance getAttribute() {\r\n        return entity.getLivingEntity().getAttribute(EntityHelper.ATTRIBUTE_ARMOR);\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.armor_bonus>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism EntityTag.armor_bonus\r\n        // @group attributes\r\n        // @description\r\n        // Returns the entity's base armor bonus.\r\n        // -->\r\n        PropertyParser.registerTag(EntityArmorBonus.class, ElementTag.class, \"armor_bonus\", (attribute, object) -> {\r\n            return new ElementTag(object.getAttribute().getValue());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name armor_bonus\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // Sets the entity's base armor bonus.\r\n        // @tags\r\n        // <EntityTag.armor_bonus>\r\n        // -->\r\n        if (mechanism.matches(\"armor_bonus\") && mechanism.requireDouble()) {\r\n            getAttribute().setBaseValue(mechanism.getValue().asDouble());\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityArmorPose.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport org.bukkit.entity.ArmorStand;\r\nimport org.bukkit.util.EulerAngle;\r\n\r\nimport java.util.Iterator;\r\nimport java.util.Map;\r\n\r\npublic class EntityArmorPose implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof ArmorStand;\r\n    }\r\n\r\n    public static EntityArmorPose getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityArmorPose((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"armor_pose\"\r\n    };\r\n\r\n    public EntityArmorPose(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getPoseMap().identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"armor_pose\";\r\n    }\r\n\r\n    public ListTag getPoseList() {\r\n        ArmorStand armorStand = (ArmorStand) entity.getBukkitEntity();\r\n        ListTag list = new ListTag();\r\n        for (PosePart posePart : PosePart.values()) {\r\n            list.add(CoreUtilities.toLowerCase(posePart.name()));\r\n            list.addObject(fromEulerAngle(posePart.getAngle(armorStand)));\r\n        }\r\n        return list;\r\n    }\r\n\r\n    public MapTag getPoseMap() {\r\n        ArmorStand armorStand = (ArmorStand) entity.getBukkitEntity();\r\n        MapTag map = new MapTag();\r\n        for (PosePart posePart : PosePart.values()) {\r\n            map.putObject(CoreUtilities.toLowerCase(posePart.name()), fromEulerAngle(posePart.getAngle(armorStand)));\r\n        }\r\n        return map;\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.armor_pose_map>\r\n        // @returns MapTag\r\n        // @mechanism EntityTag.armor_pose\r\n        // @group attributes\r\n        // @description\r\n        // Returns a map of all poses and angles for the armor stand.\r\n        // For example, head=4.5,3,4.5;body=5.4,3.2,1\r\n        // Angles are in radians!\r\n        // -->\r\n        PropertyParser.registerTag(EntityArmorPose.class, MapTag.class, \"armor_pose_map\", (attribute, entity) -> {\r\n            return entity.getPoseMap();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.armor_pose_list>\r\n        // @returns ListTag\r\n        // @mechanism EntityTag.armor_pose\r\n        // @deprecated Use 'armor_pose_map'\r\n        // @group attributes\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.armor_pose_map>\r\n        // -->\r\n        PropertyParser.registerTag(EntityArmorPose.class, ListTag.class, \"armor_pose_list\", (attribute, entity) -> {\r\n            BukkitImplDeprecations.entityArmorPose.warn(attribute.context);\r\n            return entity.getPoseList();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.armor_pose>\r\n        // @returns LocationTag\r\n        // @mechanism EntityTag.armor_pose\r\n        // @deprecated Use 'armor_pose_map'\r\n        // @group attributes\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.armor_pose_map>\r\n        // -->\r\n        PropertyParser.registerTag(EntityArmorPose.class, LocationTag.class, \"armor_pose\", (attribute, entity) -> {\r\n            BukkitImplDeprecations.entityArmorPose.warn(attribute.context);\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            String name = attribute.getParam();\r\n            PosePart posePart = PosePart.fromName(name);\r\n            if (posePart == null) {\r\n                attribute.echoError(\"Invalid pose part specified: \" + name);\r\n                return null;\r\n            }\r\n            else {\r\n                return fromEulerAngle(posePart.getAngle((ArmorStand) entity.entity.getBukkitEntity()));\r\n            }\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name armor_pose\r\n        // @input MapTag\r\n        // @description\r\n        // Sets the angle for various parts of the armor stand.\r\n        // For example, [head=4.5,3,4.5;body=5.4,3.2,1]\r\n        // Valid parts: HEAD, BODY, LEFT_ARM, RIGHT_ARM, LEFT_LEG, RIGHT_LEG\r\n        // Angles are in radians!\r\n        // Here's a website to help you figure out the correct values: <@link url https://b-universe.github.io/Minecraft-ArmorStand/>.\r\n        // @tags\r\n        // <EntityTag.armor_pose_map>\r\n        // -->\r\n        if (mechanism.matches(\"armor_pose\")) {\r\n            ArmorStand armorStand = (ArmorStand) entity.getBukkitEntity();\r\n            if (mechanism.getValue().asString().contains(\"|\")) { // legacy format\r\n                ListTag list = mechanism.valueAsType(ListTag.class);\r\n                Iterator<String> iterator = list.iterator();\r\n                while (iterator.hasNext()) {\r\n                    String name = iterator.next();\r\n                    String angle = iterator.next();\r\n                    PosePart posePart = PosePart.fromName(name);\r\n                    if (posePart == null) {\r\n                        mechanism.echoError(\"Invalid pose part specified: \" + name + \"; ignoring next: \" + angle);\r\n                    }\r\n                    else {\r\n                        posePart.setAngle(armorStand, toEulerAngle(LocationTag.valueOf(angle, mechanism.context)));\r\n                    }\r\n                }\r\n            }\r\n            else {\r\n                MapTag map = mechanism.valueAsType(MapTag.class);\r\n                for (Map.Entry<StringHolder, ObjectTag> entry : map.entrySet()) {\r\n                    PosePart posePart = PosePart.fromName(entry.getKey().str);\r\n                    if (posePart == null) {\r\n                        mechanism.echoError(\"Invalid pose part specified: \" + entry.getKey().str);\r\n                    }\r\n                    else {\r\n                        posePart.setAngle(armorStand, toEulerAngle(entry.getValue().asType(LocationTag.class, mechanism.context)));\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    public static LocationTag fromEulerAngle(EulerAngle eulerAngle) {\r\n        return new LocationTag(null, eulerAngle.getX(), eulerAngle.getY(), eulerAngle.getZ());\r\n    }\r\n\r\n    public static EulerAngle toEulerAngle(LocationTag location) {\r\n        return new EulerAngle(location.getX(), location.getY(), location.getZ());\r\n    }\r\n\r\n    public enum PosePart {\r\n        HEAD {\r\n            @Override\r\n            EulerAngle getAngle(ArmorStand armorStand) {\r\n                return armorStand.getHeadPose();\r\n            }\r\n\r\n            @Override\r\n            void setAngle(ArmorStand armorStand, EulerAngle eulerAngle) {\r\n                armorStand.setHeadPose(eulerAngle);\r\n            }\r\n        },\r\n        BODY {\r\n            @Override\r\n            EulerAngle getAngle(ArmorStand armorStand) {\r\n                return armorStand.getBodyPose();\r\n            }\r\n\r\n            @Override\r\n            void setAngle(ArmorStand armorStand, EulerAngle eulerAngle) {\r\n                armorStand.setBodyPose(eulerAngle);\r\n            }\r\n        },\r\n        LEFT_ARM {\r\n            @Override\r\n            EulerAngle getAngle(ArmorStand armorStand) {\r\n                return armorStand.getLeftArmPose();\r\n            }\r\n\r\n            @Override\r\n            void setAngle(ArmorStand armorStand, EulerAngle eulerAngle) {\r\n                armorStand.setLeftArmPose(eulerAngle);\r\n            }\r\n        },\r\n        RIGHT_ARM {\r\n            @Override\r\n            EulerAngle getAngle(ArmorStand armorStand) {\r\n                return armorStand.getRightArmPose();\r\n            }\r\n\r\n            @Override\r\n            void setAngle(ArmorStand armorStand, EulerAngle eulerAngle) {\r\n                armorStand.setRightArmPose(eulerAngle);\r\n            }\r\n        },\r\n        LEFT_LEG {\r\n            @Override\r\n            EulerAngle getAngle(ArmorStand armorStand) {\r\n                return armorStand.getLeftLegPose();\r\n            }\r\n\r\n            @Override\r\n            void setAngle(ArmorStand armorStand, EulerAngle eulerAngle) {\r\n                armorStand.setLeftLegPose(eulerAngle);\r\n            }\r\n        },\r\n        RIGHT_LEG {\r\n            @Override\r\n            EulerAngle getAngle(ArmorStand armorStand) {\r\n                return armorStand.getRightLegPose();\r\n            }\r\n\r\n            @Override\r\n            void setAngle(ArmorStand armorStand, EulerAngle eulerAngle) {\r\n                armorStand.setRightLegPose(eulerAngle);\r\n            }\r\n        };\r\n\r\n        abstract EulerAngle getAngle(ArmorStand armorStand);\r\n\r\n        abstract void setAngle(ArmorStand armorStand, EulerAngle eulerAngle);\r\n\r\n        static PosePart fromName(String name) {\r\n            for (PosePart posePart : PosePart.values()) {\r\n                if (posePart.name().equalsIgnoreCase(name)) {\r\n                    return posePart;\r\n                }\r\n            }\r\n            return null;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityArms.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport org.bukkit.entity.ArmorStand;\r\n\r\npublic class EntityArms extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name arms\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Controls whether an armor stand has arms.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof ArmorStand;\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag val) {\r\n        return !val.asBoolean();\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(ArmorStand.class).hasArms());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            as(ArmorStand.class).setArms(param.asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"arms\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"arms\", EntityArms.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityArrowDamage.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.AbstractArrow;\r\n\r\npublic class EntityArrowDamage extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name damage\r\n    // @input ElementTag(Decimal)\r\n    // @description\r\n    // The amount of damage an arrow/trident will inflict.\r\n    // Note that the actual damage dealt by the arrow/trident may be different depending on the projectile's flight speed.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof AbstractArrow;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(AbstractArrow.class).getDamage());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireDouble()) {\r\n            as(AbstractArrow.class).setDamage(value.asDouble());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"damage\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"damage\", EntityArrowDamage.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityArrowPierceLevel.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.AbstractArrow;\r\n\r\npublic class EntityArrowPierceLevel extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name pierce_level\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // The number of entities an arrow will pierce through while flying. Must be between 0 and 127.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof AbstractArrow;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(AbstractArrow.class).getPierceLevel());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireInteger()) {\r\n            as(AbstractArrow.class).setPierceLevel(value.asInt());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"pierce_level\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"pierce_level\", EntityArrowPierceLevel.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAttributeBaseValues.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport org.bukkit.attribute.Attributable;\r\nimport org.bukkit.attribute.Attribute;\r\nimport org.bukkit.attribute.AttributeInstance;\r\n\r\nimport java.util.Map;\r\n\r\npublic class EntityAttributeBaseValues implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Attributable;\r\n    }\r\n\r\n    public static EntityAttributeBaseValues getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityAttributeBaseValues((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"attribute_base_values\"\r\n    };\r\n\r\n    public EntityAttributeBaseValues(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public MapTag attributeBaseValues() {\r\n        MapTag result = new MapTag();\r\n        Attributable ent = getAttributable();\r\n        for (Attribute attr : Utilities.listTypesRaw(Attribute.class)) {\r\n            AttributeInstance instance = ent.getAttribute(attr);\r\n            if (instance != null) {\r\n                result.putObject(Utilities.enumlikeToElement(attr).asString(), new ElementTag(instance.getBaseValue()));\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n\r\n    public Attributable getAttributable() {\r\n        return (Attributable) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        MapTag map = attributeBaseValues();\r\n        return map.isEmpty() ? null : map.savable();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"attribute_base_values\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.has_attribute[<attribute>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the entity has the named attribute.\r\n        // Valid attribute names are listed at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/attribute/Attribute.html>\r\n        // See also <@link language attribute modifiers>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityAttributeBaseValues.class, ElementTag.class, \"has_attribute\", (attribute, object) -> {\r\n            if (!(attribute.hasParam() && Utilities.matchesEnumlike(attribute.getParamElement(), Attribute.class))) {\r\n                attribute.echoError(\"Invalid entity.has_attribute[...] input: must be a valid attribute name.\");\r\n                return null;\r\n            }\r\n            Attribute attr = Utilities.elementToEnumlike(attribute.getParamElement(), Attribute.class);\r\n            return new ElementTag(object.getAttributable().getAttribute(attr) != null);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attribute_value[<attribute>]>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism EntityTag.attribute_base_values\r\n        // @group properties\r\n        // @description\r\n        // Returns the final calculated value of the named attribute for the entity.\r\n        // See also <@link tag EntityTag.has_attribute>.\r\n        // Valid attribute names are listed at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/attribute/Attribute.html>\r\n        // See also <@link language attribute modifiers>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityAttributeBaseValues.class, ElementTag.class, \"attribute_value\", (attribute, object) -> {\r\n            if (!(attribute.hasParam() && Utilities.matchesEnumlike(attribute.getParamElement(), Attribute.class))) {\r\n                attribute.echoError(\"Invalid entity.attribute_value[...] input: must be a valid attribute name.\");\r\n                return null;\r\n            }\r\n            Attribute attr = Utilities.elementToEnumlike(attribute.getParamElement(), Attribute.class);\r\n            AttributeInstance instance = object.getAttributable().getAttribute(attr);\r\n            if (instance == null) {\r\n                attribute.echoError(\"Attribute \" + Utilities.enumlikeToElement(attr) + \" is not applicable to entity of type \" + object.entity.getBukkitEntityType().name());\r\n                return null;\r\n            }\r\n            return new ElementTag(instance.getValue());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attribute_base_value[<attribute>]>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism EntityTag.attribute_base_values\r\n        // @group properties\r\n        // @description\r\n        // Returns the base value of the named attribute for the entity.\r\n        // See also <@link tag EntityTag.has_attribute>.\r\n        // Valid attribute names are listed at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/attribute/Attribute.html>\r\n        // See also <@link language attribute modifiers>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityAttributeBaseValues.class, ElementTag.class, \"attribute_base_value\", (attribute, object) -> {\r\n            if (!(attribute.hasParam() && Utilities.matchesEnumlike(attribute.getParamElement(), Attribute.class))) {\r\n                attribute.echoError(\"Invalid entity.attribute_base_value[...] input: must be a valid attribute name.\");\r\n                return null;\r\n            }\r\n            Attribute attr = Utilities.elementToEnumlike(attribute.getParamElement(), Attribute.class);\r\n            AttributeInstance instance = object.getAttributable().getAttribute(attr);\r\n            if (instance == null) {\r\n                attribute.echoError(\"Attribute \" + Utilities.enumlikeToElement(attr) + \" is not applicable to entity of type \" + object.entity.getBukkitEntityType().name());\r\n                return null;\r\n            }\r\n            return new ElementTag(instance.getBaseValue());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attribute_default_value[<attribute>]>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism EntityTag.attribute_base_values\r\n        // @group properties\r\n        // @description\r\n        // Returns the default value of the named attribute for the entity.\r\n        // See also <@link tag EntityTag.has_attribute>.\r\n        // Valid attribute names are listed at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/attribute/Attribute.html>\r\n        // See also <@link language attribute modifiers>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityAttributeBaseValues.class, ElementTag.class, \"attribute_default_value\", (attribute, object) -> {\r\n            if (!(attribute.hasParam() && Utilities.matchesEnumlike(attribute.getParamElement(), Attribute.class))) {\r\n                attribute.echoError(\"Invalid entity.attribute_default_value[...] input: must be a valid attribute name.\");\r\n                return null;\r\n            }\r\n            Attribute attr = Utilities.elementToEnumlike(attribute.getParamElement(), Attribute.class);\r\n            AttributeInstance instance = object.getAttributable().getAttribute(attr);\r\n            if (instance == null) {\r\n                attribute.echoError(\"Attribute \" + Utilities.enumlikeToElement(attr) + \" is not applicable to entity of type \" + object.entity.getBukkitEntityType().name());\r\n                return null;\r\n            }\r\n            return new ElementTag(instance.getDefaultValue());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name attribute_base_values\r\n        // @input MapTag\r\n        // @description\r\n        // Sets the base value of an entity's attributes.\r\n        // Specify a MapTag where the keys are attribute names, and values are the new base values.\r\n        // Valid attribute names are listed at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/attribute/Attribute.html>\r\n        // See also <@link language attribute modifiers>.\r\n        // @tags\r\n        // <EntityTag.has_attribute>\r\n        // <EntityTag.attribute_default_value>\r\n        // <EntityTag.attribute_base_value>\r\n        // <EntityTag.attribute_value>\r\n        // -->\r\n        if (mechanism.matches(\"attribute_base_values\") && mechanism.requireObject(MapTag.class)) {\r\n            MapTag input = mechanism.valueAsType(MapTag.class);\r\n            Attributable ent = getAttributable();\r\n            for (Map.Entry<StringHolder, ObjectTag> subValue : input.entrySet()) {\r\n                Attribute attr = Utilities.elementToEnumlike(new ElementTag(subValue.getKey().str), Attribute.class);\r\n                AttributeInstance instance = ent.getAttribute(attr);\r\n                if (instance == null) {\r\n                    mechanism.echoError(\"Attribute \" + Utilities.enumlikeToElement(attr) + \" is not applicable to entity of type \" + entity.getBukkitEntityType().name());\r\n                    continue;\r\n                }\r\n                ElementTag value = subValue.getValue().asElement();\r\n                if (!value.isDouble()) {\r\n                    mechanism.echoError(\"Invalid input '\" + value + \"': must be a decimal number.\");\r\n                    continue;\r\n                }\r\n                instance.setBaseValue(value.asDouble());\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAttributeModifiers.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.core.EscapeTagUtil;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.attribute.Attributable;\r\nimport org.bukkit.attribute.Attribute;\r\nimport org.bukkit.attribute.AttributeInstance;\r\nimport org.bukkit.attribute.AttributeModifier;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.inventory.EquipmentSlotGroup;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic class EntityAttributeModifiers implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Attributable;\r\n    }\r\n\r\n    public static EntityAttributeModifiers getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityAttributeModifiers((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"attributes\", \"attribute_modifiers\", \"add_attribute_modifiers\", \"remove_attribute_modifiers\"\r\n    };\r\n\r\n    public EntityAttributeModifiers(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Deprecated\r\n    public static String stringify(AttributeModifier modifier) {\r\n        return EscapeTagUtil.escape(modifier.getName()) + \"/\" + modifier.getAmount() + \"/\" + modifier.getOperation().name()\r\n                + \"/\" + (modifier.getSlot() == null ? \"any\" : modifier.getSlot().name());\r\n    }\r\n\r\n    @Deprecated\r\n    public ListTag getAttributes() {\r\n        ListTag list = new ListTag();\r\n        for (Attribute attribute : Attribute.values()) {\r\n            AttributeInstance instance = getAttributable().getAttribute(attribute);\r\n            if (instance == null) {\r\n                continue;\r\n            }\r\n            StringBuilder modifiers = new StringBuilder();\r\n            for (AttributeModifier modifier : instance.getModifiers()) {\r\n                modifiers.append(\"/\").append(stringify(modifier));\r\n            }\r\n            list.add(EscapeTagUtil.escape(attribute.name()) + \"/\" + instance.getBaseValue() + modifiers);\r\n        }\r\n        return list;\r\n    }\r\n\r\n    public static MapTag mapify(AttributeModifier modifier) {\r\n        MapTag result = new MapTag();\r\n        result.putObject(\"name\", new ElementTag(modifier.getName()));\r\n        result.putObject(\"amount\", new ElementTag(modifier.getAmount()));\r\n        result.putObject(\"operation\", new ElementTag(modifier.getOperation()));\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            result.putObject(\"slot\", new ElementTag(modifier.getSlotGroup().toString(), true));\r\n        }\r\n        else {\r\n            result.putObject(\"slot\", new ElementTag(modifier.getSlot() == null ? \"any\" : modifier.getSlot().name()));\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            result.putObject(\"key\", new ElementTag(Utilities.namespacedKeyToString(modifier.getKey()), true));\r\n        }\r\n        // TODO: remove/deprecate the UUID key\r\n        result.putObject(\"id\", new ElementTag(modifier.getUniqueId().toString()));\r\n        return result;\r\n    }\r\n\r\n    public static AttributeModifier modiferForMap(Attribute attr, MapTag map, TagContext context) {\r\n        ElementTag amount = map.getElement(\"amount\");\r\n        ElementTag operation = map.getElement(\"operation\");\r\n        double amountValue;\r\n        AttributeModifier.Operation operationValue = operation.asEnum(AttributeModifier.Operation.class);\r\n        if (operationValue == null) {\r\n            Debug.echoError(\"Attribute modifier operation '\" + operation + \"' does not exist.\");\r\n            return null;\r\n        }\r\n        try {\r\n            amountValue = Double.parseDouble(amount.toString());\r\n        }\r\n        catch (NumberFormatException ex) {\r\n            Debug.echoError(\"Attribute modifier amount '\" + amount + \"' is not a valid decimal number.\");\r\n            return null;\r\n        }\r\n        if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            return parseLegacyModifier(attr, map, amountValue, operationValue);\r\n        }\r\n        ElementTag key = map.getElement(\"key\");\r\n        if (key == null && map.size() >= 2) {\r\n            BukkitImplDeprecations.pre1_21AttributeFormat.warn(context);\r\n            return parseLegacyModifier(attr, map, amountValue, operationValue);\r\n        }\r\n        if (key == null) {\r\n            Debug.echoError(\"Must specify a key.\");\r\n            return null;\r\n        }\r\n        String slotGroupName = map.getElement(\"slot\", \"any\").asString();\r\n        EquipmentSlotGroup group = EquipmentSlotGroup.getByName(slotGroupName);\r\n        if (group == null) {\r\n            EquipmentSlot slot = ElementTag.asEnum(EquipmentSlot.class, slotGroupName);\r\n            if (slot == null) {\r\n                Debug.echoError(\"Invalid equipment slot group specified: \" + slotGroupName);\r\n                return null;\r\n            }\r\n            group = slot.getGroup();\r\n        }\r\n        return new AttributeModifier(Utilities.parseNamespacedKey(key.asString()), amountValue, operationValue, group);\r\n    }\r\n\r\n    @Deprecated(forRemoval = true)\r\n    public static AttributeModifier parseLegacyModifier(Attribute attr, MapTag map, double amount, AttributeModifier.Operation operation) {\r\n        ElementTag name = map.getElement(\"name\");\r\n        ElementTag slot = map.getElement(\"slot\", \"any\");\r\n        ElementTag id = map.getElement(\"id\");\r\n        UUID idValue;\r\n        try {\r\n            idValue = id == null ? UUID.randomUUID() : UUID.fromString(id.toString());\r\n        }\r\n        catch (IllegalArgumentException ex) {\r\n            Debug.echoError(\"Attribute modifier ID '\" + id + \"' is not a valid UUID.\");\r\n            return null;\r\n        }\r\n        EquipmentSlot slotValue = CoreUtilities.equalsIgnoreCase(slot.toString(), \"any\") ? null : slot.asEnum(EquipmentSlot.class);\r\n        if (slotValue == null && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            EquipmentSlotGroup group = EquipmentSlotGroup.getByName(slot.asString());\r\n            if (group == null) {\r\n                Debug.echoError(\"Invalid equipment slot group specified: \" + slot);\r\n                return null;\r\n            }\r\n            return new AttributeModifier(idValue, name == null ? attr.name() : name.asString(), amount, operation, group);\r\n        }\r\n        return new AttributeModifier(idValue, name == null ? attr.name() : name.toString(), amount, operation, slotValue);\r\n    }\r\n\r\n    public ListTag getAttributeModifierList(AttributeInstance instance) {\r\n        if (instance == null) {\r\n            return null;\r\n        }\r\n        ListTag result = new ListTag();\r\n        for (AttributeModifier modifier : instance.getModifiers()) {\r\n            result.addObject(mapify(modifier));\r\n        }\r\n        if (result.isEmpty()) {\r\n            return null;\r\n        }\r\n        return result;\r\n    }\r\n\r\n    public MapTag getAttributeModifiers() {\r\n        MapTag map = new MapTag();\r\n        for (Attribute attribute : Utilities.listTypesRaw(Attribute.class)) {\r\n            ListTag list = getAttributeModifierList(getAttributable().getAttribute(attribute));\r\n            if (list != null) {\r\n                map.putObject(attribute.name(), list);\r\n            }\r\n        }\r\n        return map;\r\n    }\r\n\r\n    public Attributable getAttributable() {\r\n        return (Attributable) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        MapTag map = getAttributeModifiers();\r\n        return map.isEmpty() ? null : map.savable();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"attribute_modifiers\";\r\n    }\r\n\r\n\r\n    // <--[language]\r\n    // @name Attribute Modifiers\r\n    // @group Properties\r\n    // @description\r\n    // In minecraft, the \"attributes\" system defined certain core numerical values on entities, such as max health or attack damage.\r\n    // The value of an \"attribute\" is determined by its \"base value\" modified mathematically by each of its \"attribute modififers\".\r\n    // \"Attribute modifiers\" can be added either directly to the entity, or onto items - when on an item, an entity can equip it into the correct slot to automatically apply the modifier.\r\n    //\r\n    // These can be read via such tags as <@link tag EntityTag.attribute_modifiers>, <@link tag ItemTag.attribute_modifiers>,\r\n    // <@link tag EntityTag.has_attribute>, <@link tag EntityTag.attribute_value>, <@link tag EntityTag.attribute_base_value>, <@link tag EntityTag.attribute_default_value>, ...\r\n    //\r\n    // These can be modified by such mechanisms as <@link mechanism EntityTag.attribute_base_values>, <@link mechanism EntityTag.attribute_modifiers>, <@link mechanism EntityTag.add_attribute_modifiers>,\r\n    // <@link mechanism EntityTag.remove_attribute_modifiers>, <@link mechanism ItemTag.attribute_modifiers>, <@link mechanism ItemTag.add_attribute_modifiers>, <@link mechanism ItemTag.remove_attribute_modifiers>, ...\r\n    //\r\n    // The input format of each of the 'add' and set mechanisms is slightly complicated: a MapTag where the keys are attribute names, and values are a ListTag of modifiers,\r\n    // where each modifier is itself a MapTag with required keys 'operation' and 'amount', and additionally:\r\n    // Before MC 1.21: optional 'name', 'slot', and 'id' keys.\r\n    // The default ID will be randomly generated, the default name will be the attribute name.\r\n    // After MC 1.21: required 'key' key, and optional 'slot'.\r\n    // The 'key' is the attribute's name/identifier in a \"namespace:key\" format (defaulting to the \"minecraft\" namespace), which has to be distinct to other modifiers of the same type on the object.\r\n    //\r\n    // Valid operations: ADD_NUMBER, ADD_SCALAR, and MULTIPLY_SCALAR_1\r\n    // Valid slots (used up to MC 1.20.6): HAND, OFF_HAND, FEET, LEGS, CHEST, HEAD, ANY\r\n    // Valid slot groups (used on MC 1.20.6+): <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/EquipmentSlotGroup.html>\r\n    // Valid attribute names are listed at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/attribute/Attribute.html>\r\n    // The default slot/slot group is \"any\".\r\n    //\r\n    // Operation names are based on the Bukkit enum.\r\n    // ADD_NUMBER corresponds to Mojang \"ADDITION\" - adds on top of the base value.\r\n    // ADD_SCALAR corresponds to Mojang \"MULTIPLY_BASE\" - adds to the total, multiplied by the base value.\r\n    // MULTIPLY_SCALAR_1 corresponds to Mojang \"MULTIPLY_TOTAL\", multiplies the final value (after both \"add_number\" and \"add_scaler\") by the amount given plus one.\r\n    //\r\n    // They are combined like (pseudo-code):\r\n    // <code>\r\n    // - define x <[base_value]>\r\n    // - foreach <all_modifiers[ADD_NUMBER]>:\r\n    //     - define x:+:<[value]>\r\n    // - define y <[x]>\r\n    // - foreach <all_modifiers[ADD_SCALAR]>:\r\n    //     - define y:+:<[x].mul[<[value]>]>\r\n    // - foreach <all_modifiers[MULTIPLY_SCALAR_1]>:\r\n    //     - define y:*:<[value].add[1]>\r\n    // - determine <[y]>\r\n    // </code>\r\n    //\r\n    // See also <@link url https://minecraft.wiki/w/Attribute#Modifiers>\r\n    //\r\n    // For a quick and dirty in-line input, you can do for example: [generic_max_health=<list[<map[key=my_project:add_health;operation=ADD_NUMBER;amount=20;slot=HEAD]>]>]\r\n    //\r\n    // For more clean/proper input, instead do something like:\r\n    // <code>\r\n    // - definemap attributes:\r\n    //     generic_max_health:\r\n    //         1:\r\n    //             key: my_project:add_health\r\n    //             operation: ADD_NUMBER\r\n    //             amount: 20\r\n    //             slot: head\r\n    // - inventory adjust slot:head add_attribute_modifiers:<[attributes]>\r\n    // </code>\r\n    //\r\n    // When pre-defining a custom item, instead of this, simply use an item script: <@link language item script containers>. That page shows an example of valid attribute modifiers on an item script.\r\n    //\r\n    // -->\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.attribute_modifiers>\r\n        // @returns MapTag\r\n        // @mechanism EntityTag.attribute_modifiers\r\n        // @group properties\r\n        // @description\r\n        // Returns a map of all attribute modifiers on the entity, with key as the attribute name and value as a list of modifiers,\r\n        // where each modifier is a MapTag containing keys 'name', 'amount', 'slot', 'operation', and 'id'.\r\n        // This is formatted in a way that can be sent back into the 'attribute_modifiers' mechanism.\r\n        // See also <@link language attribute modifiers>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityAttributeModifiers.class, MapTag.class, \"attribute_modifiers\", (attribute, object) -> {\r\n            return object.getAttributeModifiers();\r\n        });\r\n\r\n        PropertyParser.registerTag(EntityAttributeModifiers.class, ListTag.class, \"attributes\", (attribute, object) -> {\r\n            BukkitImplDeprecations.legacyAttributeProperties.warn(attribute.context);\r\n            return object.getAttributes();\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name attribute_modifiers\r\n        // @input MapTag\r\n        // @description\r\n        // Sets the attribute modifiers of an entity.\r\n        // This is a SET operation, meaning pre-existing modifiers are removed.\r\n        // For input format details, refer to <@link language attribute modifiers>.\r\n        // @tags\r\n        // <EntityTag.has_attribute>\r\n        // <EntityTag.attribute_modifiers>\r\n        // <EntityTag.attribute_default_value>\r\n        // <EntityTag.attribute_base_value>\r\n        // <EntityTag.attribute_value>\r\n        // -->\r\n        if (mechanism.matches(\"attribute_modifiers\") && mechanism.requireObject(MapTag.class)) {\r\n            try {\r\n                MapTag input = mechanism.valueAsType(MapTag.class);\r\n                Attributable ent = getAttributable();\r\n                for (Map.Entry<StringHolder, ObjectTag> subValue : input.entrySet()) {\r\n                    Attribute attr = Attribute.valueOf(subValue.getKey().str.toUpperCase());\r\n                    AttributeInstance instance = ent.getAttribute(attr);\r\n                    if (instance == null) {\r\n                        mechanism.echoError(\"Attribute \" + attr.name() + \" is not applicable to entity of type \" + entity.getBukkitEntityType().name());\r\n                        continue;\r\n                    }\r\n                    for (AttributeModifier modifier : instance.getModifiers()) {\r\n                        instance.removeModifier(modifier);\r\n                    }\r\n                    for (ObjectTag listValue : CoreUtilities.objectToList(subValue.getValue(), mechanism.context)) {\r\n                        instance.addModifier(modiferForMap(attr, listValue.asType(MapTag.class, mechanism.context), mechanism.context));\r\n                    }\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name add_attribute_modifiers\r\n        // @input MapTag\r\n        // @description\r\n        // Adds attribute modifiers to an entity without altering existing modifiers.\r\n        // For input format details, refer to <@link language attribute modifiers>.\r\n        // @tags\r\n        // <EntityTag.has_attribute>\r\n        // <EntityTag.attribute_modifiers>\r\n        // <EntityTag.attribute_default_value>\r\n        // <EntityTag.attribute_base_value>\r\n        // <EntityTag.attribute_value>\r\n        // -->\r\n        if (mechanism.matches(\"add_attribute_modifiers\") && mechanism.requireObject(MapTag.class)) {\r\n            try {\r\n                MapTag input = mechanism.valueAsType(MapTag.class);\r\n                Attributable ent = getAttributable();\r\n                for (Map.Entry<StringHolder, ObjectTag> subValue : input.entrySet()) {\r\n                    Attribute attr = Attribute.valueOf(subValue.getKey().str.toUpperCase());\r\n                    AttributeInstance instance = ent.getAttribute(attr);\r\n                    if (instance == null) {\r\n                        mechanism.echoError(\"Attribute \" + attr.name() + \" is not applicable to entity of type \" + entity.getBukkitEntityType().name());\r\n                        continue;\r\n                    }\r\n                    for (ObjectTag listValue : CoreUtilities.objectToList(subValue.getValue(), mechanism.context)) {\r\n                        AttributeModifier modifier = modiferForMap(attr, listValue.asType(MapTag.class, mechanism.context), mechanism.context);\r\n                        try {\r\n                            instance.addModifier(modifier);\r\n                        }\r\n                        catch (IllegalArgumentException ex) {\r\n                            if (!ex.getMessage().equals(\"Modifier is already applied on this attribute!\")) {\r\n                                throw ex;\r\n                            }\r\n                            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n                                mechanism.echoError(\"Cannot add attribute with key '\" + modifier.getKey() + \"' as the entity already has a modifier with the same key.\");\r\n                            }\r\n                            else {\r\n                                mechanism.echoError(\"Cannot add attribute with ID '\" + modifier.getUniqueId() + \"' as the entity already has a modifier with the same ID.\");\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name remove_attribute_modifiers\r\n        // @input ListTag\r\n        // @description\r\n        // Removes attribute modifiers from an entity. Specify a list of attribute names or modifier keys (UUIDs on versions below MC 1.21) as input.\r\n        // See also <@link language attribute modifiers>.\r\n        // @tags\r\n        // <EntityTag.has_attribute>\r\n        // <EntityTag.attribute_modifiers>\r\n        // <EntityTag.attribute_default_value>\r\n        // <EntityTag.attribute_base_value>\r\n        // <EntityTag.attribute_value>\r\n        // -->\r\n        if (mechanism.matches(\"remove_attribute_modifiers\") && mechanism.requireObject(ListTag.class)) {\r\n            ArrayList<String> inputList = new ArrayList<>(mechanism.valueAsType(ListTag.class));\r\n            Attributable ent = getAttributable();\r\n            for (String toRemove : new ArrayList<>(inputList)) {\r\n                if (Utilities.matchesEnumlike(new ElementTag(toRemove), Attribute.class)) {\r\n                    inputList.remove(toRemove);\r\n                    Attribute attr = Attribute.valueOf(toRemove.toUpperCase());\r\n                    AttributeInstance instance = ent.getAttribute(attr);\r\n                    if (instance == null) {\r\n                        mechanism.echoError(\"Attribute \" + attr.name() + \" is not applicable to entity of type \" + entity.getBukkitEntityType().name());\r\n                        continue;\r\n                    }\r\n                    for (AttributeModifier modifier : instance.getModifiers()) {\r\n                        instance.removeModifier(modifier);\r\n                    }\r\n                }\r\n            }\r\n            for (String toRemove : inputList) {\r\n                UUID id = null;\r\n                NamespacedKey key = null;\r\n                boolean is1_21 = NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21);\r\n                if (is1_21) {\r\n                    key = Utilities.parseNamespacedKey(toRemove);\r\n                }\r\n                else {\r\n                    id = UUID.fromString(toRemove);\r\n                }\r\n                for (Attribute attr : Utilities.listTypesRaw(Attribute.class)) {\r\n                    AttributeInstance instance = ent.getAttribute(attr);\r\n                    if (instance == null) {\r\n                        continue;\r\n                    }\r\n                    for (AttributeModifier modifer : instance.getModifiers()) {\r\n                        if (is1_21 ? modifer.getKey().equals(key) : modifer.getUniqueId().equals(id)) {\r\n                            instance.removeModifier(modifer);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        if (mechanism.matches(\"attributes\") && mechanism.hasValue()) {\r\n            BukkitImplDeprecations.legacyAttributeProperties.warn(mechanism.context);\r\n            Attributable ent = getAttributable();\r\n            ListTag list = mechanism.valueAsType(ListTag.class);\r\n            for (String str : list) {\r\n                List<String> subList = CoreUtilities.split(str, '/');\r\n                Attribute attr = Attribute.valueOf(EscapeTagUtil.unEscape(subList.get(0)).toUpperCase());\r\n                AttributeInstance instance = ent.getAttribute(attr);\r\n                if (instance == null) {\r\n                    mechanism.echoError(\"Attribute \" + attr.name() + \" is not applicable to entity of type \" + entity.getBukkitEntityType().name());\r\n                    continue;\r\n                }\r\n                instance.setBaseValue(Double.parseDouble(subList.get(1)));\r\n                for (AttributeModifier modifier : instance.getModifiers()) {\r\n                    instance.removeModifier(modifier);\r\n                }\r\n                for (int x = 2; x < subList.size(); x += 4) {\r\n                    String slot = subList.get(x + 3).toUpperCase();\r\n                    AttributeModifier modifier = new AttributeModifier(UUID.randomUUID(), EscapeTagUtil.unEscape(subList.get(x)),\r\n                            Double.parseDouble(subList.get(x + 1)), AttributeModifier.Operation.valueOf(subList.get(x + 2).toUpperCase()),\r\n                                    slot.equals(\"ANY\") ? null : EquipmentSlot.valueOf(slot));\r\n                    instance.addModifier(modifier);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAwake.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.Bat;\n\npublic class EntityAwake extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name awake\n    // @input ElementTag(Boolean)\n    // @description\n    // Controls whether a bat is flying (awake/true) or hanging (asleep/false).\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof Bat;\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(as(Bat.class).isAwake());\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        if (mechanism.requireBoolean()) {\n            as(Bat.class).setAwake(param.asBoolean());\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"awake\";\n    }\n\n    public static void register() {\n        autoRegister(\"awake\", EntityAwake.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAware.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Interaction;\r\nimport org.bukkit.entity.Mob;\r\n\r\npublic class EntityAware extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name is_aware\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // For mobs (<@link tag EntityTag.is_mob>), this is whether the entity is aware of its surroundings.\r\n    // Unaware entities will not perform any actions on their own, such as pathfinding or attacking.\r\n    // Similar to <@link property EntityTag.has_ai>, except allows the entity to be moved by gravity, being pushed or attacked, etc.\r\n    // For interaction entities, this is whether interacting with them should trigger a response (arm swings, sounds, etc.).\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Mob || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && entity.getBukkitEntity() instanceof Interaction);\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getEntity() instanceof Interaction interaction) {\r\n            return new ElementTag(interaction.isResponsive());\r\n        }\r\n        return new ElementTag(as(Mob.class).isAware());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        // Default value is true for mobs, false for interaction entities\r\n        return value.asBoolean() == getEntity() instanceof Mob;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (!mechanism.requireBoolean()) {\r\n            return;\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getEntity() instanceof Interaction interaction) {\r\n            interaction.setResponsive(value.asBoolean());\r\n            return;\r\n        }\r\n        as(Mob.class).setAware(value.asBoolean());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"is_aware\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"is_aware\", EntityAware.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityBackgroundColor.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport org.bukkit.Color;\r\nimport org.bukkit.entity.TextDisplay;\r\n\r\npublic class EntityBackgroundColor extends EntityProperty<ColorTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name background_color\r\n    // @input ColorTag\r\n    // @description\r\n    // A text display entity's background color.\r\n    // Note that this is based on experimental API; while unlikely, breaking changes aren't impossible.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof TextDisplay;\r\n    }\r\n\r\n    @Override\r\n    public ColorTag getPropertyValue() {\r\n        Color backgroundColor = as(TextDisplay.class).getBackgroundColor();\r\n        return BukkitColorExtensions.fromColor(backgroundColor != null ? backgroundColor : Color.WHITE);\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ColorTag value) {\r\n        return value.red == 0 && value.green == 0 && value.blue == 0 && value.alpha == 64;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ColorTag value, Mechanism mechanism) {\r\n        as(TextDisplay.class).setBackgroundColor(BukkitColorExtensions.getColor(value));\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"background_color\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"background_color\", EntityBackgroundColor.class, ColorTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityBasePlate.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport org.bukkit.entity.ArmorStand;\r\n\r\npublic class EntityBasePlate extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name base_plate\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Controls whether an armor stand has a base plate.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof ArmorStand;\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag val) {\r\n        return val.asBoolean();\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(ArmorStand.class).hasBasePlate());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            as(ArmorStand.class).setBasePlate(param.asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"base_plate\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"base_plate\", EntityBasePlate.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityBeamTarget.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.EnderCrystal;\r\n\r\npublic class EntityBeamTarget implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof EnderCrystal;\r\n    }\r\n\r\n    public static EntityBeamTarget getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityBeamTarget((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"beam_target\"\r\n    };\r\n\r\n    public EntityBeamTarget(EntityTag entity) {\r\n        dentity = entity;\r\n    }\r\n\r\n    EntityTag dentity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        Location beamTarget = getCrystal().getBeamTarget();\r\n        return beamTarget != null ? new LocationTag(beamTarget).identify() : null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"beam_target\";\r\n    }\r\n\r\n    public EnderCrystal getCrystal() {\r\n        return (EnderCrystal) dentity.getBukkitEntity();\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.beam_target>\r\n        // @returns LocationTag\r\n        // @mechanism EntityTag.beam_target\r\n        // @group properties\r\n        // @description\r\n        // Returns the target location of the ender crystal's beam, if any.\r\n        // -->\r\n        PropertyParser.registerTag(EntityBeamTarget.class, LocationTag.class, \"beam_target\", (attribute, object) -> {\r\n            Location beamTarget = object.getCrystal().getBeamTarget();\r\n            if (beamTarget != null) {\r\n                return new LocationTag(beamTarget);\r\n            }\r\n            return null;\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name beam_target\r\n        // @input LocationTag\r\n        // @description\r\n        // Sets a new target location for the ender crystal's beam.\r\n        // Provide no input to remove the beam.\r\n        // @tags\r\n        // <EntityTag.beam_target>\r\n        // -->\r\n        if (mechanism.matches(\"beam_target\")) {\r\n            if (mechanism.hasValue()) {\r\n                if (mechanism.requireObject(LocationTag.class)) {\r\n                    getCrystal().setBeamTarget(mechanism.valueAsType(LocationTag.class));\r\n                }\r\n            }\r\n            else {\r\n                getCrystal().setBeamTarget(null);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityBoatType.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.TreeSpecies;\r\nimport org.bukkit.entity.Boat;\r\n\r\npublic class EntityBoatType extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name boat_type\r\n    // @input ElementTag\r\n    // @description\r\n    // Controls the wood type of the boat.\r\n    // Valid wood types can be found here: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/TreeSpecies.html>\r\n    // Deprecated in versions 1.19 and above. Use <@link property EntityTag.color>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag boat) {\r\n        return boat.getBukkitEntity() instanceof Boat;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            return null;\r\n        }\r\n        return new ElementTag(as(Boat.class).getWoodType());\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getTagValue(Attribute attribute) {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            BukkitImplDeprecations.boatType.warn(attribute.context);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            BukkitImplDeprecations.gettingBoatType.warn(attribute.context);\r\n        }\r\n        return new ElementTag(as(Boat.class).getWoodType());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"boat_type\";\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag type, Mechanism mechanism) {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            BukkitImplDeprecations.boatType.warn(mechanism.context);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            BukkitImplDeprecations.settingBoatType.warn(mechanism.context);\r\n            return;\r\n        }\r\n        if (mechanism.requireEnum(TreeSpecies.class)) {\r\n            as(Boat.class).setWoodType(type.asEnum(TreeSpecies.class));\r\n        }\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"boat_type\", EntityBoatType.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityBodyArrows.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\n\npublic class EntityBodyArrows implements Property {\n\n    public static boolean describes(ObjectTag object) {\n        return object instanceof EntityTag\n                && ((EntityTag) object).isLivingEntity();\n    }\n\n    public static EntityBodyArrows getFrom(ObjectTag object) {\n        if (!describes(object)) {\n            return null;\n        }\n        else {\n            return new EntityBodyArrows((EntityTag) object);\n        }\n    }\n\n    public static final String[] handledMechs = new String[] {\n            \"body_arrows\", \"clear_body_arrows\"\n    };\n\n    public EntityBodyArrows(EntityTag entity) {\n        this.entity = entity;\n    }\n\n    EntityTag entity;\n\n    public int getBodyArrows() {\n        return entity.getLivingEntity().getArrowsInBody();\n    }\n\n    public void setBodyArrows(int numArrows) {\n        entity.getLivingEntity().setArrowsInBody(numArrows);\n    }\n\n    @Override\n    public String getPropertyString() {\n        int numArrows = getBodyArrows();\n        return numArrows == 0 ? null : String.valueOf(numArrows);\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"body_arrows\";\n    }\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <EntityTag.body_arrows>\n        // @returns ElementTag(Number)\n        // @mechanism EntityTag.body_arrows\n        // @group properties\n        // @description\n        // Returns the number of arrows stuck in the entity's body.\n        // Note: Body arrows will only be visible for players or player-type npcs.\n        // -->\n        PropertyParser.registerTag(EntityBodyArrows.class, ElementTag.class, \"body_arrows\", (attribute, object) -> {\n            return new ElementTag(object.getBodyArrows());\n        });\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name body_arrows\n        // @input ElementTag(Number)\n        // @description\n        // Sets the number of arrows stuck in the entity's body.\n        // Note: Body arrows will only be visible for players or player-type npcs.\n        // @tags\n        // <EntityTag.body_arrows>\n        // -->\n        if (mechanism.matches(\"body_arrows\") && mechanism.requireInteger()) {\n            setBodyArrows(mechanism.getValue().asInt());\n        }\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name clear_body_arrows\n        // @input None\n        // @description\n        // Clears all arrows stuck in the entity's body.\n        // Note: Body arrows will only be visible for players or player-type npcs.\n        // @tags\n        // <EntityTag.body_arrows>\n        // -->\n        if (mechanism.matches(\"clear_body_arrows\")) {\n            setBodyArrows(0);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityBoundingBox.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.util.BoundingBox;\r\n\r\nimport java.util.HashSet;\r\nimport java.util.List;\r\nimport java.util.Set;\r\nimport java.util.UUID;\r\n\r\npublic class EntityBoundingBox implements Property {\r\n\r\n    public static boolean describes(ObjectTag object) {\r\n        return object instanceof EntityTag;\r\n    }\r\n\r\n    public static EntityBoundingBox getFrom(ObjectTag object) {\r\n        if (!describes(object)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityBoundingBox((EntityTag) object);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"bounding_box\"\r\n    };\r\n\r\n    public static Set<UUID> modifiedBoxes = new HashSet<>();\r\n\r\n    public static void remove(UUID uuid) {\r\n        modifiedBoxes.remove(uuid);\r\n    }\r\n\r\n    public EntityBoundingBox(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public ListTag getBoundingBox() {\r\n        BoundingBox boundingBox = entity.getBukkitEntity().getBoundingBox();\r\n        ListTag list = new ListTag();\r\n        list.addObject(new LocationTag(entity.getWorld(), boundingBox.getMin()));\r\n        list.addObject(new LocationTag(entity.getWorld(), boundingBox.getMax()));\r\n        return list;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (entity.isCitizensNPC()) {\r\n            return null;\r\n        }\r\n        if (modifiedBoxes.contains(entity.getUUID())) {\r\n            return getBoundingBox().identify();\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"bounding_box\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.bounding_box>\r\n        // @returns ListTag(LocationTag)\r\n        // @mechanism EntityTag.bounding_box\r\n        // @group properties\r\n        // @description\r\n        // Returns the collision bounding box of the entity in the format \"<low>|<high>\", essentially a cuboid with decimals.\r\n        // -->\r\n        PropertyParser.registerTag(EntityBoundingBox.class, ListTag.class, \"bounding_box\", (attribute, object) -> {\r\n            return object.getBoundingBox();\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name bounding_box\r\n        // @input ListTag(LocationTag)\r\n        // @description\r\n        // Changes the collision bounding box of the entity in the format \"<low>|<high>\", essentially a cuboid with decimals.\r\n        // @tags\r\n        // <EntityTag.bounding_box>\r\n        // -->\r\n        if (mechanism.matches(\"bounding_box\") && mechanism.requireObject(ListTag.class)) {\r\n            if (entity.isCitizensNPC()) {\r\n                // TODO: Allow editing NPC boxes properly?\r\n                return;\r\n            }\r\n            List<LocationTag> locations = mechanism.valueAsType(ListTag.class).filter(LocationTag.class, mechanism.context);\r\n            if (locations.size() == 2) {\r\n                NMSHandler.entityHelper.setBoundingBox(entity.getBukkitEntity(), BoundingBox.of(locations.get(0), locations.get(1)));\r\n                modifiedBoxes.add(entity.getUUID());\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Must specify exactly 2 LocationTags in the format '<low>|<high>'!\");\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityBrightness.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport org.bukkit.entity.Display;\r\n\r\npublic class EntityBrightness extends EntityProperty<MapTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name brightness\r\n    // @input MapTag\r\n    // @description\r\n    // A map of the display entity's brightness override (if any), containing \"block\" and \"sky\" keys, each with a brightness level between 0 and 15.\r\n    // For the mechanism: provide no input to remove the brightness override.\r\n    // @example\r\n    // # Sets the brightness of the display entity to be as if the block was well lit by a torch in a cave (no sky light).\r\n    // - adjust <[some_entity]> brightness:[sky=0;block=15]\r\n    // @example\r\n    // # Spawns a block_display entity of a stone that is middle brightness from both the sky and block sources.\r\n    // - spawn block_display[material=stone;brightness=[sky=7;block=7]] <player.location>\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public MapTag getPropertyValue() {\r\n        Display.Brightness brightness = as(Display.class).getBrightness();\r\n        if (brightness == null) {\r\n            return null;\r\n        }\r\n        MapTag brightnessMap = new MapTag();\r\n        brightnessMap.putObject(\"block\", new ElementTag(brightness.getBlockLight()));\r\n        brightnessMap.putObject(\"sky\", new ElementTag(brightness.getSkyLight()));\r\n        return brightnessMap;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(MapTag value, Mechanism mechanism) {\r\n        if (value == null) {\r\n            as(Display.class).setBrightness(null);\r\n            return;\r\n        }\r\n        Display.Brightness brightness = as(Display.class).getBrightness();\r\n        ElementTag blockLightInput = value.getElement(\"block\");\r\n        int blockLight = blockLightInput != null ?\r\n                blockLightInput.isInt() ? blockLightInput.asInt() : -1\r\n                : brightness != null ? brightness.getBlockLight() : 0;\r\n        if (blockLight < 0 || blockLight > 15) {\r\n            mechanism.echoError(\"Invalid 'block' brightness, must be a number between 0 and 15.\");\r\n            return;\r\n        }\r\n        ElementTag skyLightInput = value.getElement(\"sky\");\r\n        int skyLight = skyLightInput != null ?\r\n                skyLightInput.isInt() ? skyLightInput.asInt() : -1\r\n                : brightness != null ? brightness.getSkyLight() : 0;\r\n        if (skyLight < 0 || skyLight > 15) {\r\n            mechanism.echoError(\"Invalid 'sky' brightness, must be a number between 0 and 15.\");\r\n            return;\r\n        }\r\n        as(Display.class).setBrightness(new Display.Brightness(blockLight, skyLight));\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"brightness\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegisterNullable(\"brightness\", EntityBrightness.class, MapTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityCanBreakDoors.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Zombie;\r\n\r\npublic class EntityCanBreakDoors extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name can_break_doors\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Whether a zombie can break doors.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Zombie;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(Zombie.class).canBreakDoors());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            as(Zombie.class).setCanBreakDoors(param.asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"can_break_doors\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"can_break_doors\", EntityCanBreakDoors.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityCanJoinRaid.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Raider;\r\n\r\npublic class EntityCanJoinRaid extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name can_join_raid\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Controls whether a raider mob (like a pillager), is allowed to join active raids.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Raider;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(Raider.class).isCanJoinRaid());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            as(Raider.class).setCanJoinRaid(param.asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"can_join_raid\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"can_join_raid\", EntityCanJoinRaid.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityCannotEnterHive.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport org.bukkit.entity.Bee;\r\n\r\npublic class EntityCannotEnterHive extends EntityProperty<DurationTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name cannot_enter_hive\r\n    // @input DurationTag\r\n    // @description\r\n    // Controls the minimum duration until a Bee is able to enter a hive.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Bee;\r\n    }\r\n\r\n    @Override\r\n    public DurationTag getPropertyValue() {\r\n        return new DurationTag((long) as(Bee.class).getCannotEnterHiveTicks());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(DurationTag param, Mechanism mechanism) {\r\n        as(Bee.class).setCannotEnterHiveTicks(param.getTicksAsInt());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"cannot_enter_hive\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"cannot_enter_hive\", EntityCannotEnterHive.class, DurationTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityCharged.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.*;\r\n\r\npublic class EntityCharged extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name charged\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // If the entity is wither_skull, controls whether the skull is charged. Charged skulls are blue.\r\n    // If the entity is a vex, controls whether the vex is charging. Charging vexes have red lines.\r\n    // This is a visual effect, and does not cause the vex to actually charge at anyone.\r\n    // If the entity is a guardian, controls whether the guardian's laser is active.\r\n    // Note that guardians also require a target to use their laser, see <@link command attack>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof WitherSkull\r\n                || entity.getBukkitEntity() instanceof Vex\r\n                || entity.getBukkitEntity() instanceof Guardian;\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag val) {\r\n        return !val.asBoolean();\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (getEntity() instanceof WitherSkull entity) {\r\n            return new ElementTag(entity.isCharged());\r\n        }\r\n        else if (getEntity() instanceof Vex entity) {\r\n            return new ElementTag(entity.isCharging());\r\n        }\r\n        else if (getEntity() instanceof Guardian entity) {\r\n            return new ElementTag(entity.hasLaser());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            if (getEntity() instanceof WitherSkull entity) {\r\n                entity.setCharged(param.asBoolean());\r\n            }\r\n            else if (getEntity() instanceof Vex entity) {\r\n                entity.setCharging(param.asBoolean());\r\n            }\r\n            else if (getEntity() instanceof Guardian entity) {\r\n                entity.setLaser(param.asBoolean());\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"charged\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"charged\", EntityCharged.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityChestCarrier.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport org.bukkit.entity.ChestedHorse;\r\n\r\npublic class EntityChestCarrier extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name carries_chest\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Controls whether a horse-like entity is carrying a chest.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n          return entity.getBukkitEntity() instanceof ChestedHorse;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(ChestedHorse.class).isCarryingChest());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            as(ChestedHorse.class).setCarryingChest(param.asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"carries_chest\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"carries_chest\", EntityChestCarrier.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityColor.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\r\nimport com.denizenscript.denizen.utilities.MultiVersionHelper1_19;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.DyeColor;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.Registry;\r\nimport org.bukkit.entity.*;\r\n\r\nimport java.util.Arrays;\r\n\r\npublic class EntityColor extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name color\r\n    // @input ElementTag\r\n    // @description\r\n    // If the entity can have a color, controls the entity's color.\r\n    // For the available color options, refer to <@link language Entity Color Types>.\r\n    // -->\r\n\r\n    // TODO once 1.20 is the minimum supported version, can reference the enum directly\r\n    public static final EntityType MOOSHROOM_ENTITY_TYPE = Registry.ENTITY_TYPE.get(NamespacedKey.minecraft(\"mooshroom\"));\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        EntityType type = entity.getBukkitEntityType();\r\n        return type == EntityType.SHEEP ||\r\n                type == EntityType.HORSE ||\r\n                type == EntityType.WOLF ||\r\n                type == EntityType.OCELOT ||\r\n                type == EntityType.RABBIT ||\r\n                type == EntityType.LLAMA ||\r\n                type == EntityType.PARROT ||\r\n                type == EntityType.SHULKER ||\r\n                type == MOOSHROOM_ENTITY_TYPE ||\r\n                type == EntityType.CAT ||\r\n                type == EntityType.FOX ||\r\n                type == EntityType.PANDA ||\r\n                type == EntityType.ARROW ||\r\n                type == EntityType.VILLAGER ||\r\n                type == EntityType.ZOMBIE_VILLAGER ||\r\n                type == EntityType.TRADER_LLAMA ||\r\n                type == EntityType.TROPICAL_FISH ||\r\n                type == EntityType.GOAT ||\r\n                type == EntityType.AXOLOTL ||\r\n                type == EntityType.AREA_EFFECT_CLOUD ||\r\n                (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && MultiVersionHelper1_19.colorIsApplicable(type));\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return getCleanedValue(false);\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getTagValue(Attribute attribute) {\r\n        return getCleanedValue(true);\r\n    }\r\n\r\n    public ElementTag getCleanedValue(boolean includeDeprecated) {\r\n        String color = getColor(includeDeprecated);\r\n        return color == null ? null : new ElementTag(CoreUtilities.toLowerCase(color));\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"color\";\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag color, Mechanism mechanism) {\r\n        EntityType type = getType();\r\n        if (type == EntityType.HORSE && mechanism.requireObject(ListTag.class)) {\r\n            ListTag list = mechanism.valueAsType(ListTag.class);\r\n            Horse horse = as(Horse.class);\r\n            ElementTag horseColor = new ElementTag(list.get(0));\r\n            if (horseColor.matchesEnum(Horse.Color.class)) {\r\n                horse.setColor(horseColor.asEnum(Horse.Color.class));\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Invalid horse color specified: \" + horseColor);\r\n            }\r\n            if (list.size() > 1) {\r\n                ElementTag style = list.getObject(1).asElement();\r\n                if (style.matchesEnum(Horse.Style.class)) {\r\n                    horse.setStyle(style.asEnum(Horse.Style.class));\r\n                }\r\n                else {\r\n                    mechanism.echoError(\"Invalid horse style specified: \" + style);\r\n                }\r\n            }\r\n        }\r\n        else if (type == EntityType.SHEEP && mechanism.requireEnum(DyeColor.class)) {\r\n            as(Sheep.class).setColor(color.asEnum(DyeColor.class));\r\n        }\r\n        else if (type == EntityType.WOLF && mechanism.requireEnum(DyeColor.class)) {\r\n            as(Wolf.class).setCollarColor(color.asEnum(DyeColor.class));\r\n        }\r\n        else if (type == EntityType.OCELOT && mechanism.requireEnum(Ocelot.Type.class)) { // TODO: Deprecate?\r\n            as(Ocelot.class).setCatType(color.asEnum(Ocelot.Type.class));\r\n        }\r\n        else if (type == EntityType.RABBIT && mechanism.requireEnum(Rabbit.Type.class)) {\r\n            as(Rabbit.class).setRabbitType(color.asEnum(Rabbit.Type.class));\r\n        }\r\n        else if ((type == EntityType.LLAMA || type == EntityType.TRADER_LLAMA) && mechanism.requireEnum(Llama.Color.class)) {\r\n            as(Llama.class).setColor(color.asEnum(Llama.Color.class));\r\n        }\r\n        else if (type == EntityType.PARROT && mechanism.requireEnum(Parrot.Variant.class)) {\r\n            as(Parrot.class).setVariant(color.asEnum(Parrot.Variant.class));\r\n        }\r\n        else if (type == EntityType.SHULKER && mechanism.requireEnum(DyeColor.class)) {\r\n            as(Shulker.class).setColor(color.asEnum(DyeColor.class));\r\n        }\r\n        else if (type == MOOSHROOM_ENTITY_TYPE && mechanism.requireEnum(MushroomCow.Variant.class)) {\r\n            as(MushroomCow.class).setVariant(color.asEnum(MushroomCow.Variant.class));\r\n        }\r\n        else if (type == EntityType.TROPICAL_FISH && mechanism.requireObject(ListTag.class)) {\r\n            ListTag list = mechanism.valueAsType(ListTag.class);\r\n            TropicalFish fish = as(TropicalFish.class);\r\n            ElementTag pattern = list.getObject(0).asElement();\r\n            if (pattern.matchesEnum(TropicalFish.Pattern.class)) {\r\n                fish.setPattern(pattern.asEnum(TropicalFish.Pattern.class));\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Invalid tropical fish pattern specified: \" + pattern);\r\n            }\r\n            if (list.size() > 1) {\r\n                ElementTag fishColor = list.getObject(1).asElement();\r\n                if (fishColor.matchesEnum(DyeColor.class)) {\r\n                    fish.setBodyColor(fishColor.asEnum(DyeColor.class));\r\n                }\r\n                else {\r\n                    mechanism.echoError(\"Invalid color specified: \" + fishColor);\r\n                }\r\n            }\r\n            if (list.size() > 2) {\r\n                ElementTag patternColor = list.getObject(2).asElement();\r\n                if (patternColor.matchesEnum(DyeColor.class)) {\r\n                    fish.setPatternColor(patternColor.asEnum(DyeColor.class));\r\n                }\r\n                else {\r\n                    mechanism.echoError(\"Invalid pattern color specified: \" + patternColor);\r\n                }\r\n            }\r\n        }\r\n        else if (type == EntityType.FOX && mechanism.requireEnum(Fox.Type.class)) {\r\n            as(Fox.class).setFoxType(color.asEnum(Fox.Type.class));\r\n        }\r\n        else if (type == EntityType.CAT && mechanism.requireObject(ListTag.class)) {\r\n            Cat cat = as(Cat.class);\r\n            ListTag list = mechanism.valueAsType(ListTag.class);\r\n            ElementTag input = list.getObject(0).asElement();\r\n            Cat.Type catType = Utilities.elementToEnumlike(input, Cat.Type.class);\r\n            if (catType != null) {\r\n                cat.setCatType(catType);\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Invalid cat type specified: \" + input);\r\n            }\r\n            if (list.size() > 1) {\r\n                ElementTag collarColor = list.getObject(1).asElement();\r\n                if (collarColor.matchesEnum(DyeColor.class)) {\r\n                    cat.setCollarColor(collarColor.asEnum(DyeColor.class));\r\n                }\r\n                else {\r\n                    mechanism.echoError(\"Invalid color specified: \" + collarColor);\r\n                }\r\n            }\r\n        }\r\n        else if (type == EntityType.PANDA && mechanism.requireObject(ListTag.class)) {\r\n            Panda panda = as(Panda.class);\r\n            ListTag list = mechanism.valueAsType(ListTag.class);\r\n            ElementTag mainGene = list.getObject(0).asElement();\r\n            if (mainGene.matchesEnum(Panda.Gene.class)) {\r\n                panda.setMainGene(mainGene.asEnum(Panda.Gene.class));\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Invalid panda gene specified: \" + mainGene);\r\n            }\r\n            if (list.size() > 1) {\r\n                ElementTag hiddenGene = list.getObject(1).asElement();\r\n                if (hiddenGene.matchesEnum(Panda.Gene.class)) {\r\n                    panda.setHiddenGene(hiddenGene.asEnum(Panda.Gene.class));\r\n                }\r\n                else {\r\n                    mechanism.echoError(\"Invalid panda hidden gene specified: \" + hiddenGene);\r\n                }\r\n            }\r\n        }\r\n        // TODO This technically has registries on all supported versions\r\n        else if (type == EntityType.VILLAGER && Utilities.requireEnumlike(mechanism, Villager.Type.class)) {\r\n            as(Villager.class).setVillagerType(Utilities.elementToEnumlike(mechanism.getValue(), Villager.Type.class));\r\n        }\r\n        else if (type == EntityType.ZOMBIE_VILLAGER && Utilities.requireEnumlike(mechanism, Villager.Type.class)) {\r\n            as(ZombieVillager.class).setVillagerType(Utilities.elementToEnumlike(mechanism.getValue(), Villager.Type.class));\r\n        }\r\n        else if (type == EntityType.ARROW && mechanism.requireObject(ColorTag.class)) {\r\n            as(Arrow.class).setColor(BukkitColorExtensions.getColor(mechanism.valueAsType(ColorTag.class)));\r\n        }\r\n        else if (type == EntityType.GOAT) {\r\n            as(Goat.class).setScreaming(color.asLowerString().equals(\"screaming\"));\r\n        }\r\n        else if (type == EntityType.AXOLOTL && mechanism.requireEnum(Axolotl.Variant.class)) {\r\n            as(Axolotl.class).setVariant(color.asEnum(Axolotl.Variant.class));\r\n        }\r\n        else if (type == EntityType.AREA_EFFECT_CLOUD && mechanism.requireObject(ColorTag.class)) {\r\n            as(AreaEffectCloud.class).setColor(BukkitColorExtensions.getColor(mechanism.valueAsType(ColorTag.class)));\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && MultiVersionHelper1_19.colorIsApplicable(type)) {\r\n            MultiVersionHelper1_19.setColor(getEntity(), mechanism);\r\n        }\r\n    }\r\n\r\n    public String getColor(boolean includeDeprecated) {\r\n        EntityType type = getType();\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && MultiVersionHelper1_19.colorIsApplicable(type)) {\r\n            return MultiVersionHelper1_19.getColor(getEntity(), includeDeprecated);\r\n        }\r\n        if (getEntity() instanceof MushroomCow mushroomCow) {\r\n            return mushroomCow.getVariant().name();\r\n        }\r\n        return switch (type) {\r\n            case HORSE -> {\r\n                Horse horse = as(Horse.class);\r\n                yield horse.getColor().name() + \"|\" + horse.getStyle().name();\r\n            }\r\n            case SHEEP -> as(Sheep.class).getColor().name();\r\n            case WOLF -> as(Wolf.class).getCollarColor().name();\r\n            case OCELOT -> {\r\n                if (includeDeprecated) {\r\n                    yield as(Ocelot.class).getCatType().name();\r\n                }\r\n                yield null;\r\n            }\r\n            case RABBIT -> as(Rabbit.class).getRabbitType().name();\r\n            case LLAMA, TRADER_LLAMA -> as(Llama.class).getColor().name();\r\n            case PARROT -> as(Parrot.class).getVariant().name();\r\n            case SHULKER -> {\r\n                DyeColor color = as(Shulker.class).getColor();\r\n                yield  color == null ? null : color.name();\r\n            }\r\n            case TROPICAL_FISH -> {\r\n                TropicalFish fish = as(TropicalFish.class);\r\n                yield new ListTag(Arrays.asList(fish.getPattern().name(), fish.getBodyColor().name(), fish.getPatternColor().name())).identify();\r\n            }\r\n            case FOX -> as(Fox.class).getFoxType().name();\r\n            case CAT -> {\r\n                Cat cat = as(Cat.class);\r\n                // TODO once 1.21 is the minimum supported version, replace with direct registry-based handling\r\n                if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n                    yield Utilities.namespacedKeyToString(cat.getCatType().getKey()) + \"|\" + cat.getCollarColor().name();\r\n                }\r\n                yield cat.getCatType() + \"|\" + cat.getCollarColor().name();\r\n            }\r\n            case PANDA -> {\r\n                Panda panda = as(Panda.class);\r\n                yield panda.getMainGene().name() + \"|\" + panda.getHiddenGene().name();\r\n            }\r\n            // TODO This technically has registries on all supported versions\r\n            case VILLAGER -> String.valueOf(as(Villager.class).getVillagerType());\r\n            case ZOMBIE_VILLAGER -> String.valueOf(as(ZombieVillager.class).getVillagerType());\r\n            case ARROW -> {\r\n                try {\r\n                    yield BukkitColorExtensions.fromColor(as(Arrow.class).getColor()).identify();\r\n                }\r\n                catch (Exception e) {\r\n                    yield null;\r\n                }\r\n            }\r\n            case GOAT -> as(Goat.class).isScreaming() ? \"screaming\" : \"normal\";\r\n            case AXOLOTL -> as(Axolotl.class).getVariant().name();\r\n            case AREA_EFFECT_CLOUD -> BukkitColorExtensions.fromColor(as(AreaEffectCloud.class).getColor()).identify();\r\n            default -> null;\r\n        };\r\n    }\r\n\r\n    public ListTag getAllowedColors() {\r\n        EntityType type = getType();\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && MultiVersionHelper1_19.colorIsApplicable(type)) {\r\n            return MultiVersionHelper1_19.getAllowedColors(type);\r\n        }\r\n        if (type == MOOSHROOM_ENTITY_TYPE) {\r\n            return Utilities.listTypes(MushroomCow.Variant.class);\r\n        }\r\n        return switch (type) {\r\n            case HORSE -> {\r\n                ListTag horseColors = Utilities.listTypes(Horse.Color.class);\r\n                horseColors.addAll(Utilities.listTypes(Horse.Style.class));\r\n                yield horseColors;\r\n            }\r\n            case SHEEP, WOLF, SHULKER -> Utilities.listTypes(DyeColor.class);\r\n            case RABBIT -> Utilities.listTypes(Rabbit.Type.class);\r\n            case LLAMA, TRADER_LLAMA -> Utilities.listTypes(Llama.Color.class);\r\n            case PARROT -> Utilities.listTypes(Parrot.Variant.class);\r\n            case TROPICAL_FISH -> {\r\n                ListTag patterns = Utilities.listTypes(TropicalFish.Pattern.class);\r\n                patterns.addAll(Utilities.listTypes(DyeColor.class));\r\n                yield patterns;\r\n            }\r\n            case FOX -> Utilities.listTypes(Fox.Type.class);\r\n            case CAT -> Utilities.listTypes(Cat.Type.class);\r\n            case PANDA -> Utilities.listTypes(Panda.Gene.class);\r\n            // TODO This technically has registries on all supported versions\r\n            case VILLAGER, ZOMBIE_VILLAGER -> Utilities.listTypes(Villager.Type.class);\r\n            case GOAT -> {\r\n                ListTag result = new ListTag();\r\n                result.add(\"screaming\");\r\n                result.add(\"normal\");\r\n                yield result;\r\n            }\r\n            case AXOLOTL -> Utilities.listTypes(Axolotl.Variant.class);\r\n            default -> null; // includes Ocelot (deprecated) and arrow/area effect cloud (ColorTag)\r\n        };\r\n    }\r\n\r\n    // <--[language]\r\n    // @name Entity Color Types\r\n    // @group Properties\r\n    // @description\r\n    // This is a quick rundown of the styling information used to handle the coloration of a mob in <@link property EntityTag.color>.\r\n    // The list of values can be gotten in-script via <@link tag EntityTag.allowed_colors>.\r\n    //\r\n    // For horses, the format is COLOR|STYLE,\r\n    //          where COLOR is BLACK, BROWN, CHESTNUT, CREAMY, DARK_BROWN, GRAY, or WHITE.\r\n    //          and where STYLE is WHITE, WHITE_DOTS, WHITEFIELD, BLACK_DOTS, or NONE.\r\n    // For rabbits, the types are BROWN, WHITE, BLACK, BLACK_AND_WHITE, GOLD, SALT_AND_PEPPER, or THE_KILLER_BUNNY.\r\n    // For cats (not ocelots), the format is TYPE|COLOR (see below).\r\n    //          The types are TABBY, BLACK, RED, SIAMESE, BRITISH_SHORTHAIR, CALICO, PERSIAN, RAGDOLL, WHITE, JELLIE, and ALL_BLACK.\r\n    // For parrots, the types are BLUE, CYAN, GRAY, GREEN, or RED.\r\n    // For llamas, the types are CREAMY, WHITE, BROWN, and GRAY.\r\n    // For mushroom_cows, the types are RED and BROWN.\r\n    // For foxes, the types are RED and SNOW.\r\n    // For pandas, the format is MAIN_GENE|HIDDEN_GENE.\r\n    //          The gene types are NORMAL, LAZY, WORRIED, PLAYFUL, BROWN, WEAK, and AGGRESSIVE.\r\n    // For villagers and zombie_villagers, the types are DESERT, JUNGLE, PLAINS, SAVANNA, SNOW, SWAMP, and TAIGA.\r\n    // For tropical_fish, the input is PATTERN|BODYCOLOR|PATTERNCOLOR, where BodyColor and PatterenColor are both DyeColor (see below),\r\n    //          and PATTERN is KOB, SUNSTREAK, SNOOPER, DASHER, BRINELY, SPOTTY, FLOPPER, STRIPEY, GLITTER, BLOCKFISH, BETTY, is CLAYFISH.\r\n    // For sheep, wolf, and shulker entities, the input is a Dye Color.\r\n    // For Tipped Arrow and Area effect cloud entities, the input is a ColorTag.\r\n    // For goats, the input is SCREAMING or NORMAL.\r\n    // For axolotl, the types are BLUE, CYAN, GOLD, LUCY, or WILD.\r\n    // For frogs, the types are TEMPERATE, WARM, or COLD.\r\n    // For boats, type types are ACACIA, BAMBOO, BIRCH, CHERRY, DARK_OAK, JUNGLE, MANGROVE, OAK, or SPRUCE.\r\n    //\r\n    // For all places where a DyeColor is needed, the options are:\r\n    // BLACK, BLUE, BROWN, CYAN, GRAY, GREEN, LIGHT_BLUE, LIGHT_GRAY, LIME, MAGENTA, ORANGE, PINK, PURPLE, RED, WHITE, or YELLOW.\r\n    // -->\r\n\r\n    public static void register() {\r\n        autoRegister(\"color\", EntityColor.class, ElementTag.class, false);\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.allowed_colors>\r\n        // @returns ListTag\r\n        // @mechanism EntityTag.color\r\n        // @group properties\r\n        // @description\r\n        // If the entity can have a color, returns the list of allowed colors.\r\n        // See also <@link language Entity Color Types>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityColor.class, ListTag.class, \"allowed_colors\", (attribute, object) -> {\r\n            return object.getAllowedColors();\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityConversionPlayer.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport org.bukkit.OfflinePlayer;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.ZombieVillager;\n\npublic class EntityConversionPlayer implements Property {\n\n    public static boolean describes(ObjectTag object) {\n        if (!(object instanceof EntityTag)) {\n            return false;\n        }\n        Entity entity = ((EntityTag) object).getBukkitEntity();\n        return entity instanceof ZombieVillager;\n    }\n\n    public static EntityConversionPlayer getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        }\n        else {\n            return new EntityConversionPlayer((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledMechs = new String[] {\n            \"conversion_player\"\n    };\n\n    public EntityConversionPlayer(EntityTag ent) {\n        entity = ent;\n    }\n\n    EntityTag entity;\n\n    @Override\n    public String getPropertyString() {\n        OfflinePlayer player = getZombieVillager().getConversionPlayer();\n        if (player != null && player.hasPlayedBefore()) {\n            return new PlayerTag(player).identify();\n        }\n        return null;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"conversion_player\";\n    }\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <EntityTag.conversion_player>\n        // @returns PlayerTag\n        // @mechanism EntityTag.conversion_player\n        // @group properties\n        // @description\n        // Returns the player that caused a zombie villager to start converting back to a villager, if any.\n        // -->\n        PropertyParser.registerTag(EntityConversionPlayer.class, PlayerTag.class, \"conversion_player\", (attribute, object) -> {\n            OfflinePlayer player = object.getZombieVillager().getConversionPlayer();\n            if (player != null && player.hasPlayedBefore()) {\n                return new PlayerTag(player);\n            }\n            return null;\n        });\n    }\n\n    public ZombieVillager getZombieVillager() {\n        return (ZombieVillager) entity.getBukkitEntity();\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name conversion_player\n        // @input PlayerTag\n        // @description\n        // Sets the player that caused a zombie villager to start converting back to a villager.\n        // Give no input to remove the player ID from the zombie-villager.\n        // @tags\n        // <EntityTag.conversion_player>\n        // -->\n        if (mechanism.matches(\"conversion_player\")) {\n            if (mechanism.hasValue()) {\n                if (mechanism.requireObject(PlayerTag.class)) {\n                    getZombieVillager().setConversionPlayer(mechanism.valueAsType(PlayerTag.class).getOfflinePlayer());\n                }\n            }\n            else {\n                getZombieVillager().setConversionPlayer(null);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityConversionTime.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport org.bukkit.entity.*;\n\npublic class EntityConversionTime implements Property {\n\n    public static boolean describes(ObjectTag object) {\n        if (!(object instanceof EntityTag)) {\n            return false;\n        }\n        Entity entity = ((EntityTag) object).getBukkitEntity();\n        return (entity instanceof Zombie && !(entity instanceof PigZombie))\n                || entity instanceof Skeleton;\n    }\n\n    public static EntityConversionTime getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        }\n        else {\n            return new EntityConversionTime((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledMechs = new String[] {\n            \"conversion_duration\", \"drowned_conversion_duration\"\n    };\n\n    public EntityConversionTime(EntityTag ent) {\n        entity = ent;\n    }\n\n    EntityTag entity;\n\n    @Override\n    public String getPropertyString() {\n        if (isConverting()) {\n            return getConversionTime().identify();\n        }\n        return null;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"conversion_duration\";\n    }\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <EntityTag.conversion_duration>\n        // @returns DurationTag\n        // @mechanism EntityTag.conversion_duration\n        // @group properties\n        // @description\n        // Returns the duration of time until an entity completes a conversion. See <@link tag EntityTag.is_converting> for examples of conversions.\n        // When this value hits 0, the conversion completes.\n        // See also <@link tag EntityTag.in_water_duration>\n        // -->\n        PropertyParser.registerTag(EntityConversionTime.class, DurationTag.class, \"conversion_duration\", (attribute, object) -> {\n            if (!object.isConverting()) {\n                attribute.echoError(\"This entity is not converting!\");\n                return null;\n            }\n            return object.getConversionTime();\n        }, \"drowned_conversion_duration\");\n\n        // <--[tag]\n        // @attribute <EntityTag.is_converting>\n        // @returns ElementTag(Boolean)\n        // @group properties\n        // @description\n        // Returns whether an entity is currently undergoing a conversion process. This can be:\n        // A zombie villager being cured,\n        // A zombie becoming a drowned (See also <@link tag EntityTag.in_water_duration>),\n        // A husk becoming a zombie, or\n        // A skeleton becoming a stray.\n        // -->\n        PropertyParser.registerTag(EntityConversionTime.class, ElementTag.class, \"is_converting\", (attribute, object) -> {\n            return new ElementTag(object.isConverting());\n        });\n    }\n\n    public boolean isZombie() {\n        return entity.getBukkitEntity() instanceof Zombie;\n    }\n\n    public boolean isSkeleton() {\n        return entity.getBukkitEntity() instanceof Skeleton;\n    }\n\n    public Zombie getZombie() {\n        return (Zombie) entity.getBukkitEntity();\n    }\n\n    public Skeleton getSkeleton() {\n        return (Skeleton) entity.getBukkitEntity();\n    }\n\n    public boolean isConverting() {\n        if (isZombie()) {\n            return getZombie().isConverting();\n        }\n        else if (isSkeleton()) {\n            return getSkeleton().isConverting();\n        }\n        return false;\n    }\n\n    public DurationTag getConversionTime() {\n        if (isZombie()) {\n            return new DurationTag((long) getZombie().getConversionTime());\n        }\n        else if (isSkeleton()) {\n            return new DurationTag((long) getSkeleton().getConversionTime());\n        }\n        return null;\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name conversion_duration\n        // @input DurationTag\n        // @description\n        // Sets the duration of time until an entity completes a conversion. See <@link tag EntityTag.is_converting> for examples of conversions.\n        // When this value hits 0, the conversion completes.\n        // @tags\n        // <EntityTag.conversion_duration>\n        // -->\n        if ((mechanism.matches(\"conversion_duration\") || mechanism.matches(\"drowned_conversion_duration\")) && mechanism.requireObject(DurationTag.class)) {\n            if (isZombie()) {\n                getZombie().setConversionTime(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\n            }\n            else if (isSkeleton()) {\n                getSkeleton().setConversionTime(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityCritical.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.AbstractArrow;\r\n\r\npublic class EntityCritical implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof AbstractArrow;\r\n    }\r\n\r\n    public static EntityCritical getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityCritical((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"critical\"\r\n    };\r\n\r\n    public EntityCritical(EntityTag entity) {\r\n        critical = entity;\r\n    }\r\n\r\n    EntityTag critical;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getAbstractArrow().isCritical() ? \"true\" : null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"critical\";\r\n    }\r\n\r\n    public AbstractArrow getAbstractArrow() {\r\n        return (AbstractArrow) critical.getBukkitEntity();\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.critical>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.critical\r\n        // @group properties\r\n        // @description\r\n        // If the entity is an arrow or trident, returns whether the arrow/trident is critical.\r\n        // -->\r\n        PropertyParser.registerTag(EntityCritical.class, ElementTag.class, \"critical\", (attribute, object) -> {\r\n            return new ElementTag(object.getAbstractArrow().isCritical());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name critical\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes whether an arrow/trident is critical.\r\n        // @tags\r\n        // <EntityTag.critical>\r\n        // -->\r\n        if (mechanism.matches(\"critical\") && mechanism.requireBoolean()) {\r\n            getAbstractArrow().setCritical(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityCustomName.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\n\r\npublic class EntityCustomName implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag;\r\n    }\r\n\r\n    public static EntityCustomName getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityCustomName((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public EntityCustomName(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return PaperAPITools.instance.getCustomName(entity.getBukkitEntity());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"custom_name\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.custom_name>\r\n        // @returns ElementTag\r\n        // @mechanism EntityTag.custom_name\r\n        // @group attributes\r\n        // @description\r\n        // Returns the entity's custom name (as set by plugin or name tag item), if any.\r\n        // -->\r\n        PropertyParser.registerTag(EntityCustomName.class, ElementTag.class, \"custom_name\", (attribute, object) -> {\r\n            String name = PaperAPITools.instance.getCustomName(object.entity.getBukkitEntity());\r\n            if (name == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(name, true);\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name custom_name\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the custom name (equivalent to a name tag item) of the entity.\r\n        // Provide no input to remove the custom name.\r\n        // @tags\r\n        // <EntityTag.custom_name>\r\n        // -->\r\n        PropertyParser.registerMechanism(EntityCustomName.class, \"custom_name\", (object, mechanism) -> {\r\n            PaperAPITools.instance.setCustomName(object.entity.getBukkitEntity(), mechanism.value != null ? CoreUtilities.clearNBSPs(mechanism.getValue().asString()) : null);\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityCustomNameVisible.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\n\r\npublic class EntityCustomNameVisible implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag;\r\n    }\r\n\r\n    public static EntityCustomNameVisible getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityCustomNameVisible((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"custom_name_visibility\", \"custom_name_visible\"\r\n    };\r\n\r\n    public EntityCustomNameVisible(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return entity.getBukkitEntity().isCustomNameVisible() ? \"true\" : \"false\";\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"custom_name_visible\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.custom_name_visible>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.custom_name_visible\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity's custom name is visible.\r\n        // -->\r\n        PropertyParser.registerTag(EntityCustomNameVisible.class, ElementTag.class, \"custom_name_visible\", (attribute, object) -> {\r\n            return new ElementTag(object.entity.getBukkitEntity().isCustomNameVisible());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name custom_name_visible\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the entity's custom name is visible.\r\n        // @tags\r\n        // <EntityTag.custom_name_visible>\r\n        // -->\r\n        if ((mechanism.matches(\"custom_name_visibility\") || mechanism.matches(\"custom_name_visible\"))\r\n                && mechanism.requireBoolean()) {\r\n            entity.getBukkitEntity().setCustomNameVisible(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityDarkDuration.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.GlowSquid;\r\n\r\npublic class EntityDarkDuration implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof GlowSquid;\r\n    }\r\n\r\n    public static EntityDarkDuration getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityDarkDuration((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"dark_duration\"\r\n    };\r\n\r\n    public EntityDarkDuration(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return new DurationTag((long) getGlowSquid().getDarkTicksRemaining()).identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"dark_duration\";\r\n    }\r\n\r\n    public GlowSquid getGlowSquid() {\r\n        return (GlowSquid) entity.getBukkitEntity();\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.dark_duration>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.dark_duration\r\n        // @group attributes\r\n        // @description\r\n        // Returns the duration remaining before a glow squid starts glowing.\r\n        // -->\r\n        PropertyParser.registerTag(EntityDarkDuration.class, DurationTag.class, \"dark_duration\", (attribute, object) -> {\r\n            return new DurationTag((long) object.getGlowSquid().getDarkTicksRemaining());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name dark_duration\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the duration remaining before a glow squid starts glowing.\r\n        // @tags\r\n        // <EntityTag.dark_duration>\r\n        // -->\r\n        if (mechanism.matches(\"dark_duration\") && mechanism.requireObject(DurationTag.class)) {\r\n            getGlowSquid().setDarkTicksRemaining(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityDefaultBackground.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.TextDisplay;\r\n\r\npublic class EntityDefaultBackground extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name default_background\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Whether a text display entity's background is default (same as the chat window), or custom set (see <@link property EntityTag.background_color>).\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof TextDisplay;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(TextDisplay.class).isDefaultBackground());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return !value.asBoolean();\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            as(TextDisplay.class).setDefaultBackground(value.asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"default_background\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"default_background\", EntityDefaultBackground.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityDirection.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.Fireball;\r\n\r\npublic class EntityDirection implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Fireball;\r\n    }\r\n\r\n    public static EntityDirection getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityDirection((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"direction\"\r\n    };\r\n\r\n    public EntityDirection(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return new LocationTag(getFireball().getDirection()).identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"direction\";\r\n    }\r\n\r\n    public Fireball getFireball() {\r\n        return (Fireball) entity.getBukkitEntity();\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.direction>\r\n        // @returns LocationTag\r\n        // @mechanism EntityTag.direction\r\n        // @group attributes\r\n        // @description\r\n        // Returns the movement/acceleration direction of a fireball entity, as a LocationTag vector.\r\n        // -->\r\n        PropertyParser.registerTag(EntityDirection.class, LocationTag.class, \"direction\", (attribute, object) -> {\r\n            return new LocationTag(object.getFireball().getDirection());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name direction\r\n        // @input LocationTag\r\n        // @description\r\n        // Sets the movement/acceleration direction of a fireball entity, as a LocationTag vector.\r\n        // @tags\r\n        // <EntityTag.direction>\r\n        // -->\r\n        if (mechanism.matches(\"direction\") && mechanism.requireObject(LocationTag.class)) {\r\n            getFireball().setDirection(mechanism.valueAsType(LocationTag.class).toVector());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityDisabledSlots.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.nbt.CustomNBT;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport org.bukkit.entity.ArmorStand;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\n\r\nimport java.util.*;\r\n\r\npublic class EntityDisabledSlots implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof ArmorStand;\r\n    }\r\n\r\n    public static EntityDisabledSlots getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityDisabledSlots((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"disabled_slots_raw\", \"disabled_slots\"\r\n    };\r\n\r\n    public EntityDisabledSlots(EntityTag entity) {\r\n        dentity = entity;\r\n    }\r\n\r\n    EntityTag dentity;\r\n\r\n    public enum Action {\r\n        ALL(0), REMOVE(8), PLACE(16);\r\n\r\n        public final int id;\r\n\r\n        Action(int id) {\r\n            this.id = id;\r\n        }\r\n\r\n        public int getId() {\r\n            return id;\r\n        }\r\n    }\r\n\r\n    public ListTag getDisabledSlots() {\r\n        Map<EquipmentSlot, Set<Action>> map = CustomNBT.getDisabledSlots(dentity.getBukkitEntity());\r\n        ListTag list = new ListTag();\r\n        for (Map.Entry<EquipmentSlot, Set<Action>> entry : map.entrySet()) {\r\n            for (Action action : entry.getValue()) {\r\n                list.add(CoreUtilities.toLowerCase(entry.getKey().name() + \"/\" + action.name()));\r\n            }\r\n        }\r\n        return list;\r\n    }\r\n\r\n    public MapTag getDisabledSlotsMap() {\r\n        Map<EquipmentSlot, Set<Action>> map = CustomNBT.getDisabledSlots(dentity.getBukkitEntity());\r\n        MapTag mapTag = new MapTag();\r\n        for (Map.Entry<EquipmentSlot, Set<Action>> entry : map.entrySet()) {\r\n            ListTag actions = new ListTag();\r\n            for (Action action : entry.getValue()) {\r\n                actions.addObject(new ElementTag(action));\r\n            }\r\n            mapTag.putObject(entry.getKey().name(), actions);\r\n        }\r\n        return mapTag;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        MapTag map = getDisabledSlotsMap();\r\n        return map.isEmpty() ? null : map.identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"disabled_slots\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.disabled_slots>\r\n        // @returns ListTag\r\n        // @mechanism EntityTag.disabled_slots\r\n        // @deprecated Use 'EntityTag.disabled_slots_data'\r\n        // @group properties\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.disabled_slots_data>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityDisabledSlots.class, ObjectTag.class, \"disabled_slots\", (attribute, object) -> {\r\n\r\n            // <--[tag]\r\n            // @attribute <EntityTag.disabled_slots.raw>\r\n            // @returns ElementTag(Number)\r\n            // @mechanism EntityTag.disabled_slots_raw\r\n            // @deprecated Use 'disabled_slots_data'\r\n            // @group properties\r\n            // @description\r\n            // Deprecated in favor of <@link tag EntityTag.disabled_slots_data>.\r\n            // -->\r\n            if (attribute.startsWith(\"raw\", 2)) {\r\n                BukkitImplDeprecations.armorStandRawSlot.warn(attribute.context);\r\n                attribute.fulfill(1);\r\n                return new ElementTag(CustomNBT.getCustomIntNBT(object.dentity.getBukkitEntity(), CustomNBT.KEY_DISABLED_SLOTS));\r\n            }\r\n\r\n            BukkitImplDeprecations.armorStandDisabledSlotsOldFormat.warn(attribute.context);\r\n            return object.getDisabledSlots();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.disabled_slots_data>\r\n        // @returns MapTag\r\n        // @mechanism EntityTag.disabled_slots\r\n        // @group properties\r\n        // @description\r\n        // If the entity is an armor stand, returns its disabled slots as a map of slot names to list of actions.\r\n        // -->\r\n        PropertyParser.registerTag(EntityDisabledSlots.class, MapTag.class, \"disabled_slots_data\", (attribute, object) -> {\r\n            return object.getDisabledSlotsMap();\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name disabled_slots_raw\r\n        // @input ElementTag(Number)\r\n        // @deprecated Use 'disabled_slots'\r\n        // @description\r\n        // Deprecated in favor of <@link mechanism EntityTag.disabled_slots>.\r\n        // @tags\r\n        // <EntityTag.disabled_slots>\r\n        // <EntityTag.disabled_slots.raw>\r\n        // -->\r\n        if (mechanism.matches(\"disabled_slots_raw\") && mechanism.requireInteger()) {\r\n            BukkitImplDeprecations.armorStandRawSlot.warn(mechanism.context);\r\n            CustomNBT.addCustomNBT(dentity.getBukkitEntity(), CustomNBT.KEY_DISABLED_SLOTS, mechanism.getValue().asInt());\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name disabled_slots\r\n        // @input MapTag\r\n        // @description\r\n        // Sets the disabled slots of an armor stand as a map of slot names to list of actions.\r\n        // For example: [HEAD=PLACE|REMOVE;CHEST=PLACE;FEET=ALL]\r\n        // Provide no input to enable all slots.\r\n        // Slots: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/EquipmentSlot.html>\r\n        // Actions: ALL, REMOVE, PLACE\r\n        // NOTE: Minecraft contains a bug where disabling ALL for the HAND slot still allows item removal.\r\n        // To fully disable hand interaction, disable ALL and REMOVE.\r\n        // @tags\r\n        // <EntityTag.disabled_slots_data>\r\n        // -->\r\n        if (mechanism.matches(\"disabled_slots\")) {\r\n            if (!mechanism.hasValue()) {\r\n                CustomNBT.removeCustomNBT(dentity.getBukkitEntity(), CustomNBT.KEY_DISABLED_SLOTS);\r\n                return;\r\n            }\r\n            Map<EquipmentSlot, Set<Action>> map = new HashMap<>();\r\n            if (mechanism.value.canBeType(MapTag.class)) {\r\n                MapTag input = mechanism.valueAsType(MapTag.class);\r\n                for (Map.Entry<StringHolder, ObjectTag> entry : input.entrySet()) {\r\n                    EquipmentSlot slot = new ElementTag(entry.getKey().str).asEnum(EquipmentSlot.class);\r\n\r\n                    if (slot == null) {\r\n                        mechanism.echoError(\"Invalid equipment slot specified: \" + entry.getKey().str);\r\n                        continue;\r\n                    }\r\n                    ListTag actionsInput = entry.getValue().asType(ListTag.class, mechanism.context);\r\n                    Set<Action> actions = new HashSet<>();\r\n                    for (String actionStr : actionsInput) {\r\n                        Action action = new ElementTag(actionStr).asEnum(Action.class);\r\n                        if (action == null) {\r\n                            mechanism.echoError(\"Invalid action specified: \" + actionStr);\r\n                            continue;\r\n                        }\r\n                        actions.add(action);\r\n                    }\r\n                    map.put(slot, actions);\r\n                }\r\n            }\r\n            else {\r\n                BukkitImplDeprecations.armorStandDisabledSlotsOldFormat.warn(mechanism.context);\r\n                ListTag input = mechanism.valueAsType(ListTag.class);\r\n                for (String string : input) {\r\n                    String[] split = string.split(\"/\", 2);\r\n                    EquipmentSlot slot = new ElementTag(split[0]).asEnum(EquipmentSlot.class);\r\n                    Action action = Action.ALL;\r\n                    if (slot == null) {\r\n                        mechanism.echoError(\"Invalid equipment slot specified: \" + split[0]);\r\n                        continue;\r\n                    }\r\n                    if (split.length == 2) {\r\n                        action = new ElementTag(split[1]).asEnum(Action.class);\r\n                        if (action == null) {\r\n                            mechanism.echoError(\"Invalid action specified: \" + split[1]);\r\n                            continue;\r\n                        }\r\n                    }\r\n                    Set<Action> set = map.computeIfAbsent(slot, k -> new HashSet<>());\r\n                    set.add(action);\r\n                }\r\n            }\r\n            CustomNBT.setDisabledSlots(dentity.getBukkitEntity(), map);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityDisplay.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.ItemDisplay;\r\nimport org.bukkit.entity.TextDisplay;\r\n\r\npublic class EntityDisplay extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name display\r\n    // @input ElementTag\r\n    // @synonyms EntityTag.display_transform, EntityTag.text_alignment\r\n    // @description\r\n    // For an item display entity this is the model transform it will display, can be any of <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/ItemDisplay.ItemDisplayTransform.html>.\r\n    // For a text display entity this is its text alignment, can be any of <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/TextDisplay.html>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof ItemDisplay || entity.getBukkitEntity() instanceof TextDisplay;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (getEntity() instanceof ItemDisplay itemDisplay) {\r\n            return new ElementTag(itemDisplay.getItemDisplayTransform());\r\n        }\r\n        return new ElementTag(as(TextDisplay.class).getAlignment());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        if (getEntity() instanceof ItemDisplay) {\r\n            return value.asEnum(ItemDisplay.ItemDisplayTransform.class) == ItemDisplay.ItemDisplayTransform.NONE;\r\n        }\r\n        return value.asEnum(TextDisplay.TextAlignment.class) == TextDisplay.TextAlignment.CENTER;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (getEntity() instanceof ItemDisplay itemDisplay) {\r\n            if (mechanism.requireEnum(ItemDisplay.ItemDisplayTransform.class)) {\r\n                itemDisplay.setItemDisplayTransform(value.asEnum(ItemDisplay.ItemDisplayTransform.class));\r\n            }\r\n        }\r\n        else if (mechanism.requireEnum(TextDisplay.TextAlignment.class)) {\r\n            as(TextDisplay.class).setAlignment(value.asEnum(TextDisplay.TextAlignment.class));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"display\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"display\", EntityDisplay.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityDropsItem.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.EnderSignal;\n\npublic class EntityDropsItem extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name drops_item\n    // @input ElementTag(Boolean)\n    // @description\n    // Whether an eye of ender drops an item when breaking or shatters.\n    // See <@link property EntityTag.item> for controlling an eye's item.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof EnderSignal;\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(as(EnderSignal.class).getDropItem());\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\n        if (mechanism.requireBoolean()) {\n            as(EnderSignal.class).setDropItem(value.asBoolean());\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"drops_item\";\n    }\n\n    public static void register() {\n        autoRegister(\"drops_item\", EntityDropsItem.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityEquipment.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.InventoryTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport net.citizensnpcs.api.trait.trait.Equipment;\nimport org.bukkit.inventory.EquipmentSlot;\nimport org.bukkit.inventory.ItemStack;\n\npublic class EntityEquipment implements Property {\n\n    public static boolean describes(ObjectTag entity) {\n        return entity instanceof EntityTag\n                && ((EntityTag) entity).isLivingEntity();\n    }\n\n    public static EntityEquipment getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        }\n        else {\n            return new EntityEquipment((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledMechs = new String[] {\n            \"equipment\"\n    };\n\n    public EntityEquipment(EntityTag ent) {\n        entity = ent;\n    }\n\n    EntityTag entity;\n\n    @Override\n    public String getPropertyString() {\n        return entity.getEquipment().identify();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"equipment\";\n    }\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <EntityTag.equipment>\n        // @returns ListTag(ItemTag)\n        // @mechanism EntityTag.equipment\n        // @deprecated Use 'EntityTag.equipment_map'.\n        // @group inventory\n        // @description\n        // Deprecated in favor of <@link tag EntityTag.equipment_map>.\n        // -->\n        PropertyParser.registerTag(EntityEquipment.class, ObjectTag.class, \"equipment\", (attribute, object) -> {\n            BukkitImplDeprecations.entityEquipmentListTag.warn();\n            return object.entity.getEquipment();\n        });\n\n        // <--[tag]\n        // @attribute <EntityTag.equipment_map>\n        // @returns MapTag\n        // @mechanism EntityTag.equipment\n        // @group inventory\n        // @description\n        // Returns a MapTag containing the entity's equipment.\n        // Output keys are boots, leggings, chestplate, helmet, and body.\n        // Air items will be left out of the map.\n        // -->\n        PropertyParser.registerTag(EntityEquipment.class, MapTag.class, \"equipment_map\", (attribute, object) -> {\n            MapTag output = new MapTag();\n            org.bukkit.inventory.EntityEquipment equip = object.entity.getLivingEntity().getEquipment();\n            InventoryTag.addToMapIfNonAir(output, \"boots\", equip.getBoots());\n            InventoryTag.addToMapIfNonAir(output, \"leggings\", equip.getLeggings());\n            InventoryTag.addToMapIfNonAir(output, \"chestplate\", equip.getChestplate());\n            InventoryTag.addToMapIfNonAir(output, \"helmet\", equip.getHelmet());\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\n                InventoryTag.addToMapIfNonAir(output, \"body\", equip.getItem(EquipmentSlot.BODY));\n            }\n            return output;\n        });\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name equipment\n        // @input MapTag\n        // @description\n        // Sets the entity's worn equipment.\n        // Input keys are boots, leggings, chestplate, helmet, and body.\n        // @tags\n        // <EntityTag.equipment>\n        // <EntityTag.equipment_map>\n        // -->\n        if (mechanism.matches(\"equipment\") && mechanism.hasValue()) {\n            org.bukkit.inventory.EntityEquipment equip = entity.getLivingEntity().getEquipment();\n            if (mechanism.value.canBeType(MapTag.class)) {\n                MapTag map = mechanism.valueAsType(MapTag.class);\n                ItemTag boots = map.getObjectAs(\"boots\", ItemTag.class, mechanism.context);\n                if (boots != null) {\n                    ItemStack bootsItem = boots.getItemStack();\n                    if (entity.isCitizensNPC()) {\n                        entity.getDenizenNPC().getEquipmentTrait().set(Equipment.EquipmentSlot.BOOTS, bootsItem);\n                    }\n                    else {\n                        equip.setBoots(bootsItem);\n                    }\n                }\n                ItemTag leggings = map.getObjectAs(\"leggings\", ItemTag.class, mechanism.context);\n                if (leggings != null) {\n                    ItemStack leggingsItem = leggings.getItemStack();\n                    if (entity.isCitizensNPC()) {\n                        entity.getDenizenNPC().getEquipmentTrait().set(Equipment.EquipmentSlot.LEGGINGS, leggingsItem);\n                    }\n                    else {\n                        equip.setLeggings(leggingsItem);\n                    }\n                }\n                ItemTag chestplate = map.getObjectAs(\"chestplate\", ItemTag.class, mechanism.context);\n                if (chestplate != null) {\n                    ItemStack chestplateItem = chestplate.getItemStack();\n                    if (entity.isCitizensNPC()) {\n                        entity.getDenizenNPC().getEquipmentTrait().set(Equipment.EquipmentSlot.CHESTPLATE, chestplateItem);\n                    }\n                    else {\n                        equip.setChestplate(chestplateItem);\n                    }\n                }\n                ItemTag helmet = map.getObjectAs(\"helmet\", ItemTag.class, mechanism.context);\n                if (helmet != null) {\n                    ItemStack helmetItem = helmet.getItemStack();\n                    if (entity.isCitizensNPC()) {\n                        entity.getDenizenNPC().getEquipmentTrait().set(Equipment.EquipmentSlot.HELMET, helmetItem);\n                    }\n                    else {\n                        equip.setHelmet(helmetItem);\n                    }\n                }\n                if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\n                    ItemTag body = map.getObjectAs(\"body\", ItemTag.class, mechanism.context);\n                    if (body != null) {\n                        ItemStack bodyItem = body.getItemStack();\n                        if (entity.isCitizensNPC()) {\n                            entity.getDenizenNPC().getEquipmentTrait().set(Equipment.EquipmentSlot.BODY, bodyItem);\n                        }\n                        else {\n                            equip.setItem(EquipmentSlot.BODY, bodyItem);\n                        }\n                    }\n                }\n            }\n            else { // Soft-deprecate: no warn, but long term back-support\n                ListTag list = mechanism.valueAsType(ListTag.class);\n                ItemStack[] stacks = new ItemStack[list.size()];\n                for (int i = 0; i < list.size(); i++) {\n                    stacks[i] = ItemTag.valueOf(list.get(i), mechanism.context).getItemStack();\n                }\n                equip.setArmorContents(stacks);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityEquipmentDropChance.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport org.bukkit.entity.Mob;\r\nimport org.bukkit.inventory.EntityEquipment;\r\n\r\npublic class EntityEquipmentDropChance extends EntityProperty<MapTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name equipment_drop_chance\r\n    // @input MapTag\r\n    // @description\r\n    // Controls the chance of each piece of equipment dropping when the entity dies.\r\n    // A drop chance of 0 will prevent the item from dropping, a drop chance of 1 will always drop the item if killed by a player, and a drop chance of higher than 1 will always drop the item no matter what the entity was killed by.\r\n    // A map of equipment slots to drop chances, with keys \"head\", \"chest\", \"legs\", \"feet\", \"hand\", and \"off_hand\".\r\n    //\r\n    // @tag-example\r\n    // # Use to narrate the drop chances of a zombie's equipment:\r\n    // - narrate <[zombie].equipment_drop_chance>\r\n    //\r\n    // @mechanism-example\r\n    // # Use to prevent a zombie from dropping any of its equipped items, no matter what:\r\n    // - adjust <[zombie]> equipment_drop_chance:[head=0;chest=0;legs=0;feet=0;hand=0;off_hand=0]\r\n    //\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Mob;\r\n    }\r\n\r\n    @Override\r\n    public MapTag getPropertyValue() {\r\n        EntityEquipment equipment = getLivingEntity().getEquipment();\r\n        MapTag map = new MapTag();\r\n        map.putObject(\"head\", new ElementTag(equipment.getHelmetDropChance()));\r\n        map.putObject(\"chest\", new ElementTag(equipment.getChestplateDropChance()));\r\n        map.putObject(\"legs\", new ElementTag(equipment.getLeggingsDropChance()));\r\n        map.putObject(\"feet\", new ElementTag(equipment.getBootsDropChance()));\r\n        map.putObject(\"hand\", new ElementTag(equipment.getItemInMainHandDropChance()));\r\n        map.putObject(\"off_hand\", new ElementTag(equipment.getItemInOffHandDropChance()));\r\n        return map;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(MapTag map, Mechanism mechanism) {\r\n        EntityEquipment equipment = getLivingEntity().getEquipment();\r\n        ElementTag head = map.getElement(\"head\");\r\n        ElementTag chest = map.getElement(\"chest\");\r\n        ElementTag legs = map.getElement(\"legs\");\r\n        ElementTag feet = map.getElement(\"feet\");\r\n        ElementTag hand = map.getElement(\"hand\");\r\n        ElementTag offHand = map.getElement(\"off_hand\");\r\n        if (head != null) {\r\n            equipment.setHelmetDropChance(head.asFloat());\r\n        }\r\n        if (chest != null) {\r\n            equipment.setChestplateDropChance(chest.asFloat());\r\n        }\r\n        if (legs != null) {\r\n            equipment.setLeggingsDropChance(legs.asFloat());\r\n        }\r\n        if (feet != null) {\r\n            equipment.setBootsDropChance(feet.asFloat());\r\n        }\r\n        if (hand != null) {\r\n            equipment.setItemInMainHandDropChance(hand.asFloat());\r\n        }\r\n        if (offHand != null) {\r\n            equipment.setItemInOffHandDropChance(offHand.asFloat());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"equipment_drop_chance\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"equipment_drop_chance\", EntityEquipmentDropChance.class, MapTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityExploredLocations.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport org.bukkit.entity.Sniffer;\r\n\r\npublic class EntityExploredLocations extends EntityProperty<ListTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name explored_locations\r\n    // @input ListTag(LocationTag)\r\n    // @description\r\n    // Controls a sniffer's explored locations.\r\n    // @mechanism\r\n    // Note that if the sniffer is not in the same world as the input LocationTag(s), then the LocationTag(s) will not be added to the list of explored locations.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Sniffer;\r\n    }\r\n\r\n    @Override\r\n    public ListTag getPropertyValue() {\r\n        return new ListTag(as(Sniffer.class).getExploredLocations(), LocationTag::new);\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ListTag param, Mechanism mechanism) {\r\n        as(Sniffer.class).getExploredLocations().forEach(as(Sniffer.class)::removeExploredLocation);\r\n        param.filter(LocationTag.class, mechanism.context).forEach(as(Sniffer.class)::addExploredLocation);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"explored_locations\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"explored_locations\", EntityExploredLocations.class, ListTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityExplosionFire.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Explosive;\r\n\r\npublic class EntityExplosionFire implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Explosive;\r\n    }\r\n\r\n    public static EntityExplosionFire getFrom(ObjectTag entity) {\r\n        return describes(entity) ? new EntityExplosionFire((EntityTag) entity) : null;\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"explosion_fire\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"explosion_fire\"\r\n    };\r\n\r\n    public boolean isIncendiary() {\r\n        return ((Explosive) entity.getBukkitEntity()).isIncendiary();\r\n    }\r\n\r\n    public EntityExplosionFire(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(isIncendiary());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"explosion_fire\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.explosion_fire>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.explosion_fire\r\n        // @group properties\r\n        // @description\r\n        // If this entity is explosive, returns whether its explosion creates fire.\r\n        // -->\r\n        if (attribute.startsWith(\"explosion_fire\")) {\r\n            return new ElementTag(isIncendiary())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name explosion_fire\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // If this entity is explosive, sets whether its explosion creates fire.\r\n        // @tags\r\n        // <EntityTag.explosion_fire>\r\n        // -->\r\n        if (mechanism.matches(\"explosion_fire\") && mechanism.requireBoolean()) {\r\n            ((Explosive) entity.getBukkitEntity()).setIsIncendiary(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityExplosionRadius.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Creeper;\r\nimport org.bukkit.entity.Explosive;\r\n\r\npublic class EntityExplosionRadius implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && (((EntityTag) entity).getBukkitEntity() instanceof Explosive\r\n                || ((EntityTag) entity).getBukkitEntity() instanceof Creeper);\r\n    }\r\n\r\n    public static EntityExplosionRadius getFrom(ObjectTag entity) {\r\n        return describes(entity) ? new EntityExplosionRadius((EntityTag) entity) : null;\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"explosion_radius\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"explosion_radius\"\r\n    };\r\n\r\n    public float getExplosionRadius() {\r\n        if (entity.getBukkitEntity() instanceof Creeper) {\r\n            return ((Creeper) entity.getBukkitEntity()).getExplosionRadius();\r\n        }\r\n        return ((Explosive) entity.getBukkitEntity()).getYield();\r\n    }\r\n\r\n    public EntityExplosionRadius(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(getExplosionRadius());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"explosion_radius\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.explosion_radius>\r\n        // @returns ElementTag(Decimal)\r\n        // @mechanism EntityTag.explosion_radius\r\n        // @group properties\r\n        // @description\r\n        // If this entity can explode, returns its explosion radius.\r\n        // -->\r\n        if (attribute.startsWith(\"explosion_radius\")) {\r\n            return new ElementTag(getExplosionRadius())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name explosion_radius\r\n        // @input ElementTag(Decimal)\r\n        // @description\r\n        // If this entity can explode, sets its explosion radius.\r\n        // @tags\r\n        // <EntityTag.explosion_radius>\r\n        // -->\r\n        if (mechanism.matches(\"explosion_radius\") && mechanism.requireFloat()) {\r\n            if (entity.getBukkitEntity() instanceof Creeper) {\r\n                ((Creeper) entity.getBukkitEntity()).setExplosionRadius(mechanism.getValue().asInt());\r\n            }\r\n            else {\r\n                ((Explosive) entity.getBukkitEntity()).setYield(mechanism.getValue().asFloat());\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityEyeTargetLocation.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport org.bukkit.entity.EnderSignal;\n\npublic class EntityEyeTargetLocation implements Property {\n\n    public static boolean describes(ObjectTag entity) {\n        return entity instanceof EntityTag\n                && ((EntityTag) entity).getBukkitEntity() instanceof EnderSignal;\n    }\n\n    public static EntityEyeTargetLocation getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        }\n        else {\n            return new EntityEyeTargetLocation((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledMechs = new String[] {\n            \"ender_eye_target_location\"\n    };\n\n    public EntityEyeTargetLocation(EntityTag _entity) {\n        entity = _entity;\n    }\n\n    EntityTag entity;\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <EntityTag.ender_eye_target_location>\n        // @returns LocationTag\n        // @mechanism EntityTag.ender_eye_target_location\n        // @group properties\n        // @description\n        // Returns a thrown eye of ender's target location - the location it's moving towards, which in vanilla is a stronghold location.\n        // -->\n        PropertyParser.registerTag(EntityEyeTargetLocation.class, LocationTag.class, \"ender_eye_target_location\", (attribute, entity) -> {\n            return new LocationTag(((EnderSignal) entity.entity.getBukkitEntity()).getTargetLocation());\n        });\n    }\n\n    @Override\n    public String getPropertyString() {\n        return new LocationTag(((EnderSignal) entity.getBukkitEntity()).getTargetLocation()).identify();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"ender_eye_target_location\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name ender_eye_target_location\n        // @input LocationTag\n        // @description\n        // Sets a thrown eye of ender's target location - the location it's moving towards.\n        // @tags\n        // <EntityTag.ender_eye_target_location>\n        // -->\n        if (mechanism.matches(\"ender_eye_target_location\") && mechanism.requireObject(LocationTag.class)) {\n            ((EnderSignal) entity.getBukkitEntity()).setTargetLocation(mechanism.valueAsType(LocationTag.class));\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityFirework.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Firework;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.meta.FireworkMeta;\r\n\r\npublic class EntityFirework implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag entityTag && entityTag.getBukkitEntity() instanceof Firework;\r\n    }\r\n\r\n    public static EntityFirework getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityFirework((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"firework_item\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"firework_item\"\r\n    };\r\n\r\n    public EntityFirework(EntityTag entity) {\r\n        firework = entity;\r\n    }\r\n\r\n    EntityTag firework;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        ItemStack item = new ItemStack(Material.FIREWORK_ROCKET);\r\n        item.setItemMeta(((Firework) firework.getBukkitEntity()).getFireworkMeta());\r\n        return new ItemTag(item).identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"firework_item\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.firework_item>\r\n        // @returns ItemTag\r\n        // @mechanism EntityTag.firework_item\r\n        // @group properties\r\n        // @description\r\n        // If the entity is a firework, returns the firework item used to launch it.\r\n        // -->\r\n        if (attribute.startsWith(\"firework_item\")) {\r\n            ItemStack item = new ItemStack(Material.FIREWORK_ROCKET);\r\n            item.setItemMeta(((Firework) firework.getBukkitEntity()).getFireworkMeta());\r\n            return new ItemTag(item).getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name firework_item\r\n        // @input ItemTag\r\n        // @description\r\n        // Changes the firework effect on this entity, using a firework item.\r\n        // @tags\r\n        // <EntityTag.firework_item>\r\n        // -->\r\n        if (mechanism.matches(\"firework_item\") && mechanism.requireObject(ItemTag.class)) {\r\n            ItemTag item = mechanism.valueAsType(ItemTag.class);\r\n            if (item != null && item.getItemMeta() instanceof FireworkMeta) {\r\n                ((Firework) firework.getBukkitEntity()).setFireworkMeta((FireworkMeta) item.getItemMeta());\r\n            }\r\n            else {\r\n                mechanism.echoError(\"'\" + mechanism.getValue().asString() + \"' is not a valid firework item.\");\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityFireworkLifetime.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.Firework;\r\n\r\npublic class EntityFireworkLifetime implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntity() instanceof Firework;\r\n    }\r\n\r\n    public static EntityFireworkLifetime getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityFireworkLifetime((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"firework_lifetime\"\r\n    };\r\n\r\n    public EntityFireworkLifetime(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public DurationTag getDuration() {\r\n        return new DurationTag((long) NMSHandler.entityHelper.getFireworkLifetime((Firework) entity.getBukkitEntity()));\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getDuration().identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"firework_lifetime\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.firework_lifetime>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.firework_lifetime\r\n        // @group properties\r\n        // @description\r\n        // Returns the duration that a firework will live for (before detonating).\r\n        // -->\r\n        PropertyParser.registerTag(EntityFireworkLifetime.class, DurationTag.class, \"firework_lifetime\", (attribute, object) -> {\r\n            return object.getDuration();\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name firework_lifetime\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the duration that a firework will live for (before detonating).\r\n        // @tags\r\n        // <EntityTag.firework_lifetime>\r\n        // -->\r\n        if (mechanism.matches(\"firework_lifetime\") && mechanism.requireObject(DurationTag.class)) {\r\n            NMSHandler.entityHelper.setFireworkLifetime((Firework) entity.getBukkitEntity(), mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityFixed.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.ItemFrame;\r\n\r\npublic class EntityFixed implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntity() instanceof ItemFrame;\r\n    }\r\n\r\n    public static EntityFixed getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityFixed((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"fixed\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"fixed\"\r\n    };\r\n\r\n    public EntityFixed(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return ((ItemFrame) entity.getBukkitEntity()).isFixed() ? \"true\" : \"false\";\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"fixed\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.fixed>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.fixed\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the item frame is fixed. (Meaning, it can't be altered by players or broken by block obstructions).\r\n        // -->\r\n        if (attribute.startsWith(\"fixed\")) {\r\n            return new ElementTag(((ItemFrame) entity.getBukkitEntity()).isFixed())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name fixed\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether this item frame is fixed. (Meaning, it can't be altered by players or broken by block obstructions).\r\n        // @tags\r\n        // <EntityTag.fixed>\r\n        // -->\r\n        if (mechanism.matches(\"fixed\") && mechanism.requireBoolean()) {\r\n            ((ItemFrame) entity.getBukkitEntity()).setFixed(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityFlags.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.flags.DataPersistenceFlagTracker;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.MapTagFlagTracker;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\n\r\nimport java.util.Collection;\r\n\r\npublic class EntityFlags implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag;\r\n    }\r\n\r\n    public static EntityFlags getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityFlags((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"flag_map\"\r\n    };\r\n\r\n    public EntityFlags(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        AbstractFlagTracker tracker = entity.getFlagTracker();\r\n        if (!(tracker instanceof DataPersistenceFlagTracker)) {\r\n            return null;\r\n        }\r\n        Collection<String> flagNames = tracker.listAllFlags();\r\n        if (flagNames.isEmpty()) {\r\n            return null;\r\n        }\r\n        MapTag flags = new MapTag();\r\n        for (String name : flagNames) {\r\n            flags.putObject(name, tracker.getRootMap(name));\r\n        }\r\n        return flags.toString();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"flag_map\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name flag_map\r\n        // @input MapTag\r\n        // @description\r\n        // Internal setter for the EntityTag flag map.\r\n        // Do not use this in scripts.\r\n        // -->\r\n        if (mechanism.matches(\"flag_map\") && mechanism.requireObject(MapTag.class)) {\r\n            MapTagFlagTracker flags = new MapTagFlagTracker(mechanism.valueAsType(MapTag.class));\r\n            AbstractFlagTracker tracker = entity.getFlagTracker();\r\n            if (!(tracker instanceof DataPersistenceFlagTracker)) {\r\n                return;\r\n            }\r\n            for (String flagName : flags.map.keys()) {\r\n                ((DataPersistenceFlagTracker) tracker).setRootMap(flagName, flags.getRootMap(flagName));\r\n            }\r\n            entity.reapplyTracker(tracker);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityFlower.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Bee;\r\nimport org.bukkit.entity.Entity;\r\n\r\npublic class EntityFlower implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        if (!(entity instanceof EntityTag)) {\r\n            return false;\r\n        }\r\n        Entity bukkitEntity = ((EntityTag) entity).getBukkitEntity();\r\n        return bukkitEntity instanceof Bee;\r\n    }\r\n\r\n    public static EntityFlower getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityFlower((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"flower\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"flower\"\r\n    };\r\n\r\n    public EntityFlower(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public Bee getBee() {\r\n        return (Bee) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (getBee().getFlower() == null) {\r\n            return null;\r\n        }\r\n        return new LocationTag(getBee().getFlower()).identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"flower\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.flower>\r\n        // @returns LocationTag\r\n        // @mechanism EntityTag.flower\r\n        // @group properties\r\n        // @description\r\n        // Returns the location of a bee's flower (if any).\r\n        // -->\r\n        if (attribute.startsWith(\"flower\")) {\r\n            if (getBee().getFlower() == null) {\r\n                return null;\r\n            }\r\n            return new LocationTag(getBee().getFlower())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name flower\r\n        // @input LocationTag\r\n        // @description\r\n        // Changes the location of a bee's flower.\r\n        // Give no input to unset the bee's flower.\r\n        // @tags\r\n        // <EntityTag.flower>\r\n        // -->\r\n        if (mechanism.matches(\"flower\")) {\r\n            if (mechanism.hasValue() && mechanism.requireObject(LocationTag.class)) {\r\n                getBee().setFlower(mechanism.valueAsType(LocationTag.class));\r\n            }\r\n            else {\r\n                getBee().setFlower(null);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityFramed.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport org.bukkit.Material;\nimport org.bukkit.Rotation;\nimport org.bukkit.entity.ItemFrame;\n\npublic class EntityFramed implements Property {\n\n    // TODO: Possibly merge class with EntityItem?\n    public static boolean describes(ObjectTag entity) {\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntity() instanceof ItemFrame;\n    }\n\n    public static EntityFramed getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        }\n        else {\n            return new EntityFramed((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"framed_item_rotation\", \"framed_item\", \"has_framed_item\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"framed\"\n    };\n\n    public EntityFramed(EntityTag item) {\n        item_frame = item;\n    }\n\n    EntityTag item_frame;\n\n    public boolean hasItem() {\n        return getItemFrameEntity().getItem() != null\n                && getItemFrameEntity().getItem().getType() != Material.AIR;\n    }\n\n    public ItemFrame getItemFrameEntity() {\n        return (ItemFrame) item_frame.getBukkitEntity();\n    }\n\n    public void setItem(ItemTag item) {\n        getItemFrameEntity().setItem(item.getItemStack());\n    }\n\n    public ItemTag getItem() {\n        return new ItemTag(getItemFrameEntity().getItem());\n    }\n\n    @Override\n    public String getPropertyString() {\n        if (hasItem()) {\n            return getItem().identify()\n                    + (getItemFrameEntity().getRotation() == Rotation.NONE ? \"\"\n                    : '|' + CoreUtilities.toLowerCase(getItemFrameEntity().getRotation().name()));\n        }\n        else {\n            return null;\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"framed\";\n    }\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.framed_item_rotation>\n        // @returns ElementTag\n        // @mechanism EntityTag.framed\n        // @group properties\n        // @description\n        // If the entity is an item frame, returns the rotation of the item currently framed.\n        // -->\n        if (attribute.startsWith(\"framed_item_rotation\")) {\n            return new ElementTag(CoreUtilities.toLowerCase(getItemFrameEntity().getRotation().name()))\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.framed_item>\n        // @returns ItemTag\n        // @mechanism EntityTag.framed\n        // @group properties\n        // @description\n        // If the entity is an item frame, returns the item currently framed.\n        // -->\n        if (attribute.startsWith(\"framed_item\")) {\n            return getItem()\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.has_framed_item>\n        // @returns ElementTag(Boolean)\n        // @mechanism EntityTag.framed\n        // @group properties\n        // @description\n        // If the entity is an item frame, returns whether the frame has an item in it.\n        // -->\n        if (attribute.startsWith(\"has_framed_item\")) {\n            return new ElementTag(hasItem())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n        return null;\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name framed\n        // @input ItemTag(|ElementTag)\n        // @description\n        // Sets the entity's framed item and optionally the rotation as well.\n        // Valid rotations: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Rotation.html>\n        // For example: framed:diamond_sword|clockwise\n        // @tags\n        // <EntityTag.has_framed_item>\n        // <EntityTag.framed_item>\n        // <EntityTag.framed_item_rotation>\n        // -->\n        if (mechanism.matches(\"framed\")) {\n            ListTag list = mechanism.valueAsType(ListTag.class);\n            if (list.isEmpty()) {\n                mechanism.echoError(\"Missing value for 'framed' mechanism!\");\n                return;\n            }\n            if (new ElementTag(list.get(0)).matchesType(ItemTag.class)) {\n                setItem(new ElementTag(list.get(0)).asType(ItemTag.class, mechanism.context));\n            }\n            else {\n                mechanism.echoError(\"Invalid item '\" + list.get(0) + \"'\");\n            }\n            if (list.size() > 1 && new ElementTag(list.get(1)).matchesEnum(Rotation.class)) {\n                getItemFrameEntity().setRotation(Rotation.valueOf(list.get(1).toUpperCase()));\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityFreezeDuration.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\n\r\npublic class EntityFreezeDuration implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag;\r\n    }\r\n\r\n    public static EntityFreezeDuration getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        return new EntityFreezeDuration((EntityTag) entity);\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"freeze_duration\"\r\n    };\r\n\r\n    public EntityFreezeDuration(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return new DurationTag((long) entity.getBukkitEntity().getFreezeTicks()).identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"freeze_duration\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.max_freeze_duration>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.freeze_duration\r\n        // @group attributes\r\n        // @description\r\n        // Returns the maximum duration an entity can be freezing for (from powdered snow). (When reached, an entity is fully frozen over).\r\n        // -->\r\n        PropertyParser.registerTag(EntityFreezeDuration.class, DurationTag.class, \"max_freeze_duration\", (attribute, entity) -> {\r\n            return new DurationTag((long) entity.entity.getBukkitEntity().getMaxFreezeTicks());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.freeze_duration>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.freeze_duration\r\n        // @group attributes\r\n        // @description\r\n        // Returns the duration an entity has been freezing for (from powdered snow).\r\n        // -->\r\n        PropertyParser.registerTag(EntityFreezeDuration.class, DurationTag.class, \"freeze_duration\", (attribute, entity) -> {\r\n            return new DurationTag((long) entity.entity.getBukkitEntity().getFreezeTicks());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name freeze_duration\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the duration an entity has been freezing for (from powdered snow).\r\n        // @tags\r\n        // <EntityTag.freeze_duration>\r\n        // -->\r\n        if (mechanism.matches(\"freeze_duration\") && mechanism.requireObject(DurationTag.class)) {\r\n            entity.getBukkitEntity().setFreezeTicks(mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityGlowColor.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport org.bukkit.Color;\r\nimport org.bukkit.entity.Display;\r\n\r\npublic class EntityGlowColor extends EntityProperty<ColorTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name glow_color\r\n    // @input ColorTag\r\n    // @description\r\n    // A display entity's glow color override, if any.\r\n    // For the mechanism: provide no input to remove the override.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public ColorTag getPropertyValue() {\r\n        Color glowColorOverride = as(Display.class).getGlowColorOverride();\r\n        return glowColorOverride != null ? BukkitColorExtensions.fromColor(glowColorOverride) : null;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ColorTag value, Mechanism mechanism) {\r\n        as(Display.class).setGlowColorOverride(value != null ? BukkitColorExtensions.getColor(value) : null);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"glow_color\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegisterNullable(\"glow_color\", EntityGlowColor.class, ColorTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityGravity.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport net.citizensnpcs.trait.Gravity;\r\n\r\npublic class EntityGravity implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag;\r\n    }\r\n\r\n    public static EntityGravity getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityGravity((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"gravity\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"gravity\"\r\n    };\r\n\r\n    public EntityGravity(EntityTag entity) {\r\n        dentity = entity;\r\n    }\r\n\r\n    EntityTag dentity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (dentity.getBukkitEntity().hasGravity()) {\r\n            return null;\r\n        }\r\n        else {\r\n            return \"false\";\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"gravity\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.gravity>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.gravity\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the entity has gravity.\r\n        // -->\r\n        if (attribute.startsWith(\"gravity\")) {\r\n            if (dentity.isCitizensNPC()) {\r\n                boolean gravityBlocked = dentity.getDenizenNPC().getCitizen().hasTrait(Gravity.class)\r\n                        && !dentity.getDenizenNPC().getCitizen().getOrAddTrait(Gravity.class).hasGravity();\r\n                return new ElementTag(!gravityBlocked);\r\n            }\r\n            return new ElementTag(dentity.getBukkitEntity().hasGravity())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name gravity\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes the gravity state of an entity.\r\n        // When set false (no gravity), side effects may also occur, eg all movement entirely being blocked.\r\n        // @tags\r\n        // <EntityTag.gravity>\r\n        // -->\r\n        if (mechanism.matches(\"gravity\") && mechanism.requireBoolean()) {\r\n            if (dentity.isCitizensNPC()) {\r\n                dentity.getDenizenNPC().getCitizen().getOrAddTrait(Gravity.class).setHasGravity(mechanism.getValue().asBoolean());\r\n            }\r\n            else {\r\n                dentity.getBukkitEntity().setGravity(mechanism.getValue().asBoolean());\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityHasNectar.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Bee;\r\nimport org.bukkit.entity.Entity;\r\n\r\npublic class EntityHasNectar implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        if (!(entity instanceof EntityTag)) {\r\n            return false;\r\n        }\r\n        Entity bukkitEntity = ((EntityTag) entity).getBukkitEntity();\r\n        return bukkitEntity instanceof Bee;\r\n    }\r\n\r\n    public static EntityHasNectar getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityHasNectar((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"has_nectar\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"has_nectar\"\r\n    };\r\n\r\n    public EntityHasNectar(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public Bee getBee() {\r\n        return (Bee) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getBee().hasNectar() ? \"true\" : \"false\";\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"has_nectar\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.has_nectar>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.has_nectar\r\n        // @group properties\r\n        // @description\r\n        // Returns whether a bee entity has nectar on it.\r\n        // -->\r\n        if (attribute.startsWith(\"has_nectar\")) {\r\n            return new ElementTag(getBee().hasNectar())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name has_nectar\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes whether a bee entity has nectar on it.\r\n        // @tags\r\n        // <EntityTag.has_nectar>\r\n        // -->\r\n        if (mechanism.matches(\"has_nectar\") && mechanism.requireBoolean()) {\r\n            getBee().setHasNectar(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityHasStung.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Bee;\r\nimport org.bukkit.entity.Entity;\r\n\r\npublic class EntityHasStung implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        if (!(entity instanceof EntityTag)) {\r\n            return false;\r\n        }\r\n        Entity bukkitEntity = ((EntityTag) entity).getBukkitEntity();\r\n        return bukkitEntity instanceof Bee;\r\n    }\r\n\r\n    public static EntityHasStung getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityHasStung((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"has_stung\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"has_stung\"\r\n    };\r\n\r\n    public EntityHasStung(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public Bee getBee() {\r\n        return (Bee) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getBee().hasStung() ? \"true\" : \"false\";\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"has_stung\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.has_stung>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.has_stung\r\n        // @group properties\r\n        // @description\r\n        // Returns whether a bee entity has already used its stinger.\r\n        // -->\r\n        if (attribute.startsWith(\"has_stung\")) {\r\n            return new ElementTag(getBee().hasStung())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name has_stung\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes whether a bee entity has already used its stinger.\r\n        // @tags\r\n        // <EntityTag.has_stung>\r\n        // -->\r\n        if (mechanism.matches(\"has_stung\") && mechanism.requireBoolean()) {\r\n            getBee().setHasStung(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityHealth.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.npc.traits.HealthTrait;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\n\nimport java.util.List;\n\npublic class EntityHealth implements Property {\n\n    public static boolean describes(ObjectTag entity) {\n        return entity instanceof EntityTag\n                && ((EntityTag) entity).isLivingEntity();\n    }\n\n    public static EntityHealth getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        }\n        else {\n            return new EntityHealth((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"health\", \"formatted_health\", \"health_max\", \"health_percentage\", \"health_data\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"max_health\", \"health_data\", \"health\"\n    };\n\n    public EntityHealth(EntityTag ent) {\n        entity = ent;\n    }\n\n    EntityTag entity;\n\n    @Override\n    public String getPropertyString() {\n        return CoreUtilities.doubleToString(entity.getLivingEntity().getHealth()) + \"/\" + CoreUtilities.doubleToString(entity.getLivingEntity().getMaxHealth());\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"health_data\";\n    }\n\n    public static ElementTag getHealthFormatted(EntityTag entity, Double maxHealth) {\n        if (maxHealth == null) {\n            maxHealth = entity.getLivingEntity().getMaxHealth();\n        }\n        if ((float) entity.getLivingEntity().getHealth() / maxHealth < .10) {\n            return new ElementTag(\"dying\");\n        }\n        else if ((float) entity.getLivingEntity().getHealth() / maxHealth < .40) {\n            return new ElementTag(\"seriously wounded\");\n        }\n        else if ((float) entity.getLivingEntity().getHealth() / maxHealth < .75) {\n            return new ElementTag(\"injured\");\n        }\n        else if ((float) entity.getLivingEntity().getHealth() / maxHealth < 1) {\n            return new ElementTag(\"scraped\");\n        }\n        else {\n            return new ElementTag(\"healthy\");\n        }\n    }\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.formatted_health>\n        // @returns ElementTag\n        // @mechanism EntityTag.health_data\n        // @group attributes\n        // @description\n        // Returns a formatted value of the player's current health level.\n        // May be 'dying', 'seriously wounded', 'injured', 'scraped', or 'healthy'.\n        // -->\n        if (attribute.startsWith(\"formatted_health\")) {\n            return getHealthFormatted(entity, attribute.hasParam() ? attribute.getDoubleParam() : null);\n        }\n        if (attribute.startsWith(\"health.formatted\")) {\n            BukkitImplDeprecations.entityHealthTags.warn(attribute.context);\n            return getHealthFormatted(entity, attribute.hasContext(2) ? attribute.getDoubleContext(2) : null);\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.health_max>\n        // @returns ElementTag(Decimal)\n        // @mechanism EntityTag.max_health\n        // @group attributes\n        // @description\n        // Returns the maximum health of the entity.\n        // -->\n        if (attribute.startsWith(\"health_max\")) {\n            return new ElementTag(entity.getLivingEntity().getMaxHealth())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n        if (attribute.startsWith(\"health.max\")) {\n            BukkitImplDeprecations.entityHealthTags.warn(attribute.context);\n            return new ElementTag(entity.getLivingEntity().getMaxHealth())\n                    .getObjectAttribute(attribute.fulfill(2));\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.health_percentage>\n        // @returns ElementTag(Decimal)\n        // @mechanism EntityTag.health\n        // @group attributes\n        // @description\n        // Returns the entity's current health as a percentage.\n        // -->\n        if (attribute.startsWith(\"health_percentage\")) {\n            double maxHealth = entity.getLivingEntity().getMaxHealth();\n            if (attribute.hasParam()) {\n                maxHealth = attribute.getIntParam();\n            }\n            return new ElementTag((entity.getLivingEntity().getHealth() / maxHealth) * 100)\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n        if (attribute.startsWith(\"health.percentage\")) {\n            BukkitImplDeprecations.entityHealthTags.warn(attribute.context);\n            double maxHealth = entity.getLivingEntity().getMaxHealth();\n            if (attribute.hasContext(2)) {\n                maxHealth = attribute.getIntContext(2);\n            }\n            return new ElementTag((entity.getLivingEntity().getHealth() / maxHealth) * 100)\n                    .getObjectAttribute(attribute.fulfill(2));\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.health_data>\n        // @returns ElementTag\n        // @mechanism EntityTag.health\n        // @group attributes\n        // @description\n        // Returns the current health data of the entity, in the format of current/max.\n        // -->\n        if (attribute.startsWith(\"health_data\")) {\n            return new ElementTag(getPropertyString())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.health>\n        // @returns ElementTag(Decimal)\n        // @mechanism EntityTag.health\n        // @group attributes\n        // @description\n        // Returns the current health of the entity.\n        // -->\n        if (attribute.startsWith(\"health\")) {\n            return new ElementTag(entity.getLivingEntity().getHealth())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name max_health\n        // @input ElementTag(Decimal)\n        // @description\n        // Sets the maximum health the entity may have.\n        // The entity must be living.\n        // Note to change the current health at the same time as max_health (might be needed when setting max health higher rather than lower),\n        // use <@link mechanism EntityTag.health_data>.\n        // @tags\n        // <EntityTag.health>\n        // <EntityTag.health_max>\n        // -->\n        if (mechanism.matches(\"max_health\") && mechanism.requireDouble()) {\n            if (entity.isCitizensNPC()) {\n                if (entity.getDenizenNPC().getCitizen().hasTrait(HealthTrait.class)) {\n                    entity.getDenizenNPC().getCitizen().getOrAddTrait(HealthTrait.class).setMaxhealth(mechanism.getValue().asInt());\n                }\n                else {\n                    mechanism.echoError(\"NPC doesn't have health trait!\");\n                }\n            }\n            else if (entity.isLivingEntity()) {\n                entity.getLivingEntity().setMaxHealth(mechanism.getValue().asDouble());\n            }\n            else {\n                mechanism.echoError(\"Entity is not alive!\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name health_data\n        // @input ElementTag(Decimal)/ElementTag(Decimal)\n        // @description\n        // Sets the amount of health the entity has, and the maximum health it has.\n        // The entity must be living.\n        // @tags\n        // <EntityTag.health>\n        // <EntityTag.health_max>\n        // -->\n        if (mechanism.matches(\"health_data\")) {\n            if (entity.isLivingEntity()) {\n                List<String> values = CoreUtilities.split(mechanism.getValue().asString(), '/');\n                entity.getLivingEntity().setMaxHealth(Double.parseDouble(values.get(1)));\n                entity.getLivingEntity().setHealth(Double.parseDouble(values.get(0)));\n            }\n            else {\n                mechanism.echoError(\"Entity is not alive!\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name health\n        // @input ElementTag(Decimal)\n        // @description\n        // Sets the amount of health the entity has.\n        // The entity must be living.\n        // @tags\n        // <EntityTag.health>\n        // <EntityTag.health_max>\n        // -->\n        if (mechanism.matches(\"health\") && mechanism.requireDouble()) {\n            if (entity.isLivingEntity()) {\n                entity.getLivingEntity().setHealth(mechanism.getValue().asDouble());\n            }\n            else {\n                mechanism.echoError(\"Entity is not alive!\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityHeight.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Display;\r\nimport org.bukkit.entity.Interaction;\r\n\r\npublic class EntityHeight extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name height\r\n    // @input ElementTag(Decimal)\r\n    // @description\r\n    // For a display entity, this is the height of it's culling box. The box will span from the entity's y to the entity's y + the height.\r\n    // The default value for these is 0, which disables culling entirely.\r\n    // For an interaction entity, this is the height of it's bounding box (the area that can be interacted with).\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display || entity.getBukkitEntity() instanceof Interaction;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (getEntity() instanceof Display display) {\r\n            return new ElementTag(display.getDisplayHeight());\r\n        }\r\n        return new ElementTag(as(Interaction.class).getInteractionHeight());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return value.asFloat() == (getEntity() instanceof Display ? 0f : 1f);\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (!mechanism.requireFloat()) {\r\n            return;\r\n        }\r\n        if (getEntity() instanceof Display display) {\r\n            display.setDisplayHeight(value.asFloat());\r\n            return;\r\n        }\r\n        as(Interaction.class).setInteractionHeight(value.asFloat());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"height\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"height\", EntityHeight.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityHive.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Bee;\r\nimport org.bukkit.entity.Entity;\r\n\r\npublic class EntityHive implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        if (!(entity instanceof EntityTag)) {\r\n            return false;\r\n        }\r\n        Entity bukkitEntity = ((EntityTag) entity).getBukkitEntity();\r\n        return bukkitEntity instanceof Bee;\r\n    }\r\n\r\n    public static EntityHive getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityHive((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"hive\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"hive\"\r\n    };\r\n\r\n    public EntityHive(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public Bee getBee() {\r\n        return (Bee) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (getBee().getHive() == null) {\r\n            return null;\r\n        }\r\n        return new LocationTag(getBee().getHive()).identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"hive\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.hive>\r\n        // @returns LocationTag\r\n        // @mechanism EntityTag.hive\r\n        // @group properties\r\n        // @description\r\n        // Returns the location of a bee's hive (if any).\r\n        // -->\r\n        if (attribute.startsWith(\"hive\")) {\r\n            if (getBee().getHive() == null) {\r\n                return null;\r\n            }\r\n            return new LocationTag(getBee().getHive())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name hive\r\n        // @input LocationTag\r\n        // @description\r\n        // Changes the location of a bee's hive.\r\n        // Give no input to unset the bee's hive.\r\n        // @tags\r\n        // <EntityTag.hive>\r\n        // -->\r\n        if (mechanism.matches(\"hive\")) {\r\n            if (mechanism.hasValue() && mechanism.requireObject(LocationTag.class)) {\r\n                getBee().setHive(mechanism.valueAsType(LocationTag.class));\r\n            }\r\n            else {\r\n                getBee().setHive(null);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityHorns.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.entity.Goat;\r\n\r\npublic class EntityHorns implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Goat;\r\n    }\r\n\r\n    public static EntityHorns getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityHorns((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public EntityHorns(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getHornsList().identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"horns\";\r\n    }\r\n\r\n    public ListTag getHornsList() {\r\n        ListTag result = new ListTag();\r\n        if (getGoat().hasLeftHorn()) {\r\n            result.addObject(new ElementTag(\"left\"));\r\n        }\r\n        if (getGoat().hasRightHorn()) {\r\n            result.addObject(new ElementTag(\"right\"));\r\n        }\r\n        return result;\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.horns>\r\n        // @returns ListTag\r\n        // @mechanism EntityTag.horns\r\n        // @group properties\r\n        // @description\r\n        // Returns a ListTag of a goat's horns. can include \"left\" and \"right\", for the left and right horns.\r\n        // -->\r\n        PropertyParser.registerTag(EntityHorns.class, ListTag.class, \"horns\", (attribute, object) -> {\r\n            return object.getHornsList();\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name horns\r\n        // @input ListTag\r\n        // @description\r\n        // Sets a goat's horns. can include \"left\" and \"right\", for the left and right horns.\r\n        // @tags\r\n        // <EntityTag.horns>\r\n        // -->\r\n        PropertyParser.registerMechanism(EntityHorns.class, ListTag.class, \"horns\", (object, mechanism, input) -> {\r\n            boolean left = false, right = false;\r\n            for (String value : input) {\r\n                String low = CoreUtilities.toLowerCase(value);\r\n                if (low.equals(\"left\")) {\r\n                    left = true;\r\n                }\r\n                else if (low.equals(\"right\")) {\r\n                    right = true;\r\n                }\r\n                else {\r\n                    mechanism.echoError(\"Invalid horn '\" + value + \"': must be 'left' or 'right'.\");\r\n                }\r\n            }\r\n            object.getGoat().setLeftHorn(left);\r\n            object.getGoat().setRightHorn(right);\r\n        });\r\n    }\r\n\r\n    public Goat getGoat() {\r\n        return (Goat) entity.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityImmune.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Hoglin;\r\nimport org.bukkit.entity.PiglinAbstract;\r\n\r\npublic class EntityImmune implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && (((EntityTag) entity).getBukkitEntity() instanceof PiglinAbstract\r\n                || ((EntityTag) entity).getBukkitEntity() instanceof Hoglin);\r\n    }\r\n\r\n    public static EntityImmune getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityImmune((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"immune\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"immune\"\r\n    };\r\n\r\n    public EntityImmune(EntityTag entity) {\r\n        dentity = entity;\r\n    }\r\n\r\n    EntityTag dentity;\r\n\r\n    public boolean isHoglin() {\r\n        return dentity.getBukkitEntity() instanceof Hoglin;\r\n    }\r\n\r\n    public Hoglin getHoglin() {\r\n        return (Hoglin) dentity.getBukkitEntity();\r\n    }\r\n\r\n    public PiglinAbstract getPiglin() {\r\n        return (PiglinAbstract) dentity.getBukkitEntity();\r\n    }\r\n\r\n    public boolean getIsImmune() {\r\n        if (isHoglin()) {\r\n            return getHoglin().isImmuneToZombification();\r\n        }\r\n        return getPiglin().isImmuneToZombification();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getIsImmune() ? \"true\" : \"false\";\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"immune\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.immune>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.immune\r\n        // @group properties\r\n        // @description\r\n        // Returns whether this piglin or hoglin entity is immune to zombification.\r\n        // -->\r\n        if (attribute.startsWith(\"immune\")) {\r\n            return new ElementTag(getIsImmune())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name immune\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether this piglin or hoglin entity is immune to zombification.\r\n        // @tags\r\n        // <EntityTag.immune>\r\n        // -->\r\n        if (mechanism.matches(\"immune\") && mechanism.requireBoolean()) {\r\n            if (isHoglin()) {\r\n                getHoglin().setImmuneToZombification(mechanism.getValue().asBoolean());\r\n            }\r\n            else {\r\n                getPiglin().setImmuneToZombification(mechanism.getValue().asBoolean());\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityInWaterTime.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.Zombie;\r\n\r\npublic class EntityInWaterTime implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntity() instanceof Zombie;\r\n    }\r\n\r\n    public static EntityInWaterTime getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityInWaterTime((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"in_water_duration\"\r\n    };\r\n\r\n    public EntityInWaterTime(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return new DurationTag((long) NMSHandler.entityHelper.getInWaterTime((Zombie) entity.getBukkitEntity())).identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"in_water_duration\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.in_water_duration>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.in_water_duration\r\n        // @group properties\r\n        // @description\r\n        // If the entity is a zombie mob, returns the duration of time the zombie has been in water for.\r\n        // If this value exceeds 600 ticks, the zombie will begin converted to a Drowned mob.\r\n        // See also <@link tag EntityTag.conversion_duration>\r\n        // -->\r\n        PropertyParser.registerTag(EntityInWaterTime.class, DurationTag.class, \"in_water_duration\", (attribute, object) -> {\r\n            return new DurationTag((long) NMSHandler.entityHelper.getInWaterTime((Zombie) object.entity.getBukkitEntity()));\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name in_water_duration\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // If the entity is a zombie mob, sets the duration of time the zombie has been in water for.\r\n        // If this value exceeds 600 ticks, the zombie will begin converted to a Drowned mob.\r\n        // See also <@link mechanism EntityTag.conversion_duration>\r\n        // @tags\r\n        // <EntityTag.in_water_duration>\r\n        // -->\r\n        if (mechanism.matches(\"in_water_duration\") && mechanism.requireObject(DurationTag.class)) {\r\n            NMSHandler.entityHelper.setInWaterTime((Zombie) entity.getBukkitEntity(), mechanism.valueAsType(DurationTag.class).getTicksAsInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityInterpolationDuration.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport org.bukkit.entity.Display;\r\n\r\npublic class EntityInterpolationDuration extends EntityProperty<DurationTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name interpolation_duration\r\n    // @input DurationTag\r\n    // @description\r\n    // The duration a display entity will spend interpolating between interpolated value(s).\r\n    // See also <@link language Display entity interpolation>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public DurationTag getPropertyValue() {\r\n        return new DurationTag((long) as(Display.class).getInterpolationDuration());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(DurationTag value) {\r\n        return value.getTicksAsInt() == 0;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(DurationTag value, Mechanism mechanism) {\r\n        as(Display.class).setInterpolationDuration(value.getTicksAsInt());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"interpolation_duration\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"interpolation_duration\", EntityInterpolationDuration.class, DurationTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityInterpolationStart.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport org.bukkit.entity.Display;\r\n\r\npublic class EntityInterpolationStart extends EntityProperty<DurationTag> {\r\n\r\n    // <--[language]\r\n    // @name Display Entity Interpolation\r\n    // @group Minecraft Logic\r\n    // @description\r\n    // Display entities can interpolate between different properties, providing a smooth transition.\r\n    // Interpolation can be started (immediately or with a delay) using <@link property EntityTag.interpolation_start>, and it's duration can be set using <@link property EntityTag.interpolation_duration>.\r\n    // The following properties can be interpolated: <@link property EntityTag.scale>, <@link property EntityTag.translation>, <@link property EntityTag.shadow_radius>, <@link property EntityTag.shadow_strength>, <@link mechanism EntityTag.opacity>, <@link property EntityTag.left_rotation>, <@link property EntityTag.right_rotation>.\r\n    // -->\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name interpolation_start\r\n    // @input DurationTag\r\n    // @description\r\n    // The delay between a display entity receiving an update to an interpolated value(s) to it starting its interpolation.\r\n    // Interpolation is started whenever this value is set. If no changes were made to the entity in the same tick, it will (visually) redo its last interpolation.\r\n    // See also <@link language Display entity interpolation>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public DurationTag getPropertyValue() {\r\n        return new DurationTag((long) as(Display.class).getInterpolationDelay());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(DurationTag value) {\r\n        return value.getTicksAsInt() == 0;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(DurationTag value, Mechanism mechanism) {\r\n        as(Display.class).setInterpolationDelay(value.getTicksAsInt());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"interpolation_start\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"interpolation_start\", EntityInterpolationStart.class, DurationTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityInventory.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.properties.inventory.InventoryContents;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.InventoryTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.inventory.InventoryHolder;\n\npublic class EntityInventory implements Property {\n\n    public static boolean describes(ObjectTag entity) {\n        return entity instanceof EntityTag\n                && ((EntityTag) entity).getBukkitEntity() instanceof InventoryHolder;\n    }\n\n    public static EntityInventory getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        }\n        else {\n            return new EntityInventory((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"inventory\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"inventory_contents\"\n    };\n\n    public EntityInventory(EntityTag ent) {\n        entity = ent;\n    }\n\n    EntityTag entity;\n\n    @Override\n    public String getPropertyString() {\n        return new InventoryContents(entity.getInventory()).getContents(false).identify();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"inventory_contents\";\n    }\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.inventory>\n        // @returns InventoryTag\n        // @group inventory\n        // @description\n        // Returns the entity's inventory, if it has one.\n        // -->\n        if (attribute.startsWith(\"inventory\")) {\n            InventoryTag inventory = entity.getInventory();\n            if (inventory != null) {\n                return inventory.getObjectAttribute(attribute.fulfill(1));\n            }\n            else {\n                return null;\n            }\n        }\n\n        return null;\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name inventory_contents\n        // @input ListTag(ItemTag)\n        // @description\n        // Clears the entity's inventory and sets it's item list to match the input.\n        // @tags\n        // <EntityTag.inventory>\n        // <InventoryTag.list_contents>\n        // -->\n        if (mechanism.matches(\"inventory_contents\")) {\n            ListTag list = ListTag.valueOf(mechanism.getValue().asString(), mechanism.context);\n            InventoryTag inv = entity.getInventory();\n            inv.clear();\n            int i = 0;\n            for (String str : list) {\n                inv.setSlots(i, ItemTag.valueOf(str, mechanism.context).getItemStack());\n                i++;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityInvulnerable.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\n\r\npublic class EntityInvulnerable implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag;\r\n    }\r\n\r\n    public static EntityInvulnerable getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityInvulnerable((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"invulnerable\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"invulnerable\"\r\n    };\r\n\r\n    public EntityInvulnerable(EntityTag entity) {\r\n        dentity = entity;\r\n    }\r\n\r\n    EntityTag dentity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (!dentity.getBukkitEntity().isInvulnerable()) {\r\n            return null;\r\n        }\r\n        else {\r\n            return \"true\";\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"invulnerable\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.invulnerable>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.invulnerable\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the entity is invulnerable (cannot be damaged).\r\n        // -->\r\n        if (attribute.startsWith(\"invulnerable\")) {\r\n            return new ElementTag(dentity.getBukkitEntity().isInvulnerable())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name invulnerable\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the entity is invulnerable (cannot be damaged).\r\n        // @tags\r\n        // <EntityTag.invulnerable>\r\n        // -->\r\n        if (mechanism.matches(\"invulnerable\") && mechanism.requireBoolean()) {\r\n            dentity.getBukkitEntity().setInvulnerable(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityIsShowingBottom.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.EnderCrystal;\r\n\r\npublic class EntityIsShowingBottom implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag entityTag && entityTag.getBukkitEntity() instanceof EnderCrystal;\r\n    }\r\n\r\n    public static EntityIsShowingBottom getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityIsShowingBottom((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"is_showing_bottom\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"is_showing_bottom\"\r\n    };\r\n\r\n    public EntityIsShowingBottom(EntityTag entity) {\r\n        dentity = entity;\r\n    }\r\n\r\n    EntityTag dentity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (((EnderCrystal) dentity.getBukkitEntity()).isShowingBottom()) {\r\n            return null;\r\n        }\r\n        else {\r\n            return \"false\";\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"is_showing_bottom\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_showing_bottom>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.is_showing_bottom\r\n        // @group properties\r\n        // @description\r\n        // If the entity is an ender crystal, returns whether the ender crystal has its bottom showing.\r\n        // -->\r\n        if (attribute.startsWith(\"is_showing_bottom\")) {\r\n            return new ElementTag(((EnderCrystal) dentity.getBukkitEntity()).isShowingBottom())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name is_showing_bottom\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes the bottom state of an ender crystal.\r\n        // @tags\r\n        // <EntityTag.is_showing_bottom>\r\n        // -->\r\n        if (mechanism.matches(\"is_showing_bottom\") && mechanism.requireBoolean()) {\r\n            ((EnderCrystal) dentity.getBukkitEntity()).setShowingBottom(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityItem.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport org.bukkit.entity.*;\r\n\r\npublic class EntityItem extends EntityProperty<ItemTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name item\r\n    // @input ItemTag\r\n    // @description\r\n    // An entity's item, which can be:\r\n    // - the item represented and displayed by a dropped item.\r\n    // - the item represented by a thrown trident.\r\n    // - a throwable projectile's display item.\r\n    // - an eye-of-ender's item, which is both displayed and dropped.\r\n    // - a fireball's display item.\r\n    // - an item display's display item.\r\n    // - an ominous item spawner's display item.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag object) {\r\n        Entity entity = object.getBukkitEntity();\r\n        return entity instanceof Item\r\n                || entity instanceof Enderman\r\n                || entity instanceof SizedFireball\r\n                || entity instanceof ThrowableProjectile\r\n                || entity instanceof EnderSignal\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && entity instanceof ItemDisplay)\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && entity instanceof OminousItemSpawner);\r\n    }\r\n\r\n    @Override\r\n    public ItemTag getPropertyValue() {\r\n        if (getEntity() instanceof Item item) {\r\n            return new ItemTag(item.getItemStack());\r\n        }\r\n        else if (getEntity() instanceof SizedFireball fireball) {\r\n            return new ItemTag(fireball.getDisplayItem());\r\n        }\r\n        else if (getEntity() instanceof ThrowableProjectile projectile) {\r\n            return new ItemTag(projectile.getItem());\r\n        }\r\n        else if (getEntity() instanceof EnderSignal signal) {\r\n            return new ItemTag(signal.getItem());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getEntity() instanceof ItemDisplay itemDisplay) {\r\n            return new ItemTag(itemDisplay.getItemStack());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getEntity() instanceof OminousItemSpawner ominousItemSpawner) {\r\n            return new ItemTag(ominousItemSpawner.getItem());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ItemTag item, Mechanism mechanism) {\r\n        if (object.isCitizensNPC()) {\r\n            object.getDenizenNPC().getCitizen().data().setPersistent(NPC.Metadata.ITEM_ID, item.getBukkitMaterial().name());\r\n        }\r\n        if (getEntity() instanceof Item droppedItem) {\r\n            droppedItem.setItemStack(item.getItemStack());\r\n        }\r\n        else if (getEntity() instanceof SizedFireball fireball) {\r\n            fireball.setDisplayItem(item.getItemStack());\r\n        }\r\n        else if (getEntity() instanceof ThrowableProjectile projectile) {\r\n            projectile.setItem(item.getItemStack());\r\n        }\r\n        else if (getEntity() instanceof EnderSignal signal) {\r\n            signal.setItem(item.getItemStack());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getEntity() instanceof ItemDisplay itemDisplay) {\r\n            itemDisplay.setItemStack(item.getItemStack());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getEntity() instanceof OminousItemSpawner ominousItemSpawner) {\r\n            ominousItemSpawner.setItem(item.getItemStack());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"item\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"item\", EntityItem.class, ItemTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityItemInHand.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.Material;\r\n\r\npublic class EntityItemInHand implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        if (!(entity instanceof EntityTag)) {\r\n            return false;\r\n        }\r\n        if (!((EntityTag) entity).isLivingEntity()) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    public static EntityItemInHand getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityItemInHand((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"item_in_hand\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"item_in_hand\"\r\n    };\r\n\r\n    public EntityItemInHand(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        ItemTag item = new ItemTag(entity.getLivingEntity().getEquipment().getItemInMainHand());\r\n        if (item.getBukkitMaterial() != Material.AIR) {\r\n            return item.identify();\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"item_in_hand\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.item_in_hand>\r\n        // @returns ItemTag\r\n        // @mechanism EntityTag.item_in_hand\r\n        // @group inventory\r\n        // @description\r\n        // Returns the item the entity is holding, or air if none.\r\n        // -->\r\n        if (attribute.startsWith(\"item_in_hand\")) {\r\n            if (!entity.isSpawnedOrValidForTag()) {\r\n                return null;\r\n            }\r\n            return new ItemTag(entity.getLivingEntity().getEquipment().getItemInMainHand()).getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name item_in_hand\r\n        // @input ItemTag\r\n        // @description\r\n        // Sets the item in the entity's hand.\r\n        // The entity must be living.\r\n        // @tags\r\n        // <EntityTag.item_in_hand>\r\n        // -->\r\n        if (mechanism.matches(\"item_in_hand\")) {\r\n            entity.getLivingEntity().getEquipment().setItemInMainHand(mechanism.valueAsType(ItemTag.class).getItemStack());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityItemInOffHand.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.Material;\r\n\r\npublic class EntityItemInOffHand implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        if (!(entity instanceof EntityTag)) {\r\n            return false;\r\n        }\r\n        if (!((EntityTag) entity).isLivingEntity()) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    public static EntityItemInOffHand getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityItemInOffHand((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"item_in_offhand\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"item_in_offhand\"\r\n    };\r\n\r\n    public EntityItemInOffHand(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        ItemTag item = new ItemTag(entity.getLivingEntity().getEquipment().getItemInOffHand());\r\n        if (item.getBukkitMaterial() != Material.AIR) {\r\n            return item.identify();\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"item_in_offhand\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.item_in_offhand>\r\n        // @returns ItemTag\r\n        // @mechanism EntityTag.item_in_offhand\r\n        // @group inventory\r\n        // @description\r\n        // Returns the item the entity is holding in their off hand, or air if none.\r\n        // -->\r\n        if (attribute.startsWith(\"item_in_offhand\")) {\r\n            if (!entity.isSpawnedOrValidForTag()) {\r\n                return null;\r\n            }\r\n            return new ItemTag(entity.getLivingEntity().getEquipment().getItemInOffHand()).getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name item_in_offhand\r\n        // @input ItemTag\r\n        // @description\r\n        // Sets the item in the entity's offhand.\r\n        // The entity must be living.\r\n        // @tags\r\n        // <EntityTag.item_in_offhand>\r\n        // -->\r\n        if (mechanism.matches(\"item_in_offhand\")) {\r\n            entity.getLivingEntity().getEquipment().setItemInOffHand(mechanism.valueAsType(ItemTag.class).getItemStack());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityJumpStrength.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.AbstractHorse;\r\n\r\npublic class EntityJumpStrength implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag &&\r\n                ((EntityTag) entity).getBukkitEntity() instanceof AbstractHorse;\r\n    }\r\n\r\n    public static EntityJumpStrength getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityJumpStrength((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"jump_strength\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"jump_strength\"\r\n    };\r\n\r\n    public EntityJumpStrength(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(((AbstractHorse) entity.getBukkitEntity()).getJumpStrength());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"jump_strength\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.jump_strength>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism EntityTag.jump_strength\r\n        // @group properties\r\n        // @synonyms EntityTag.horse_jump_height\r\n        // @description\r\n        // Returns the power of a horse's jump.\r\n        // Also applies to horse-like mobs, such as donkeys and mules.\r\n        // -->\r\n        if (attribute.startsWith(\"jump_strength\")) {\r\n            return new ElementTag(((AbstractHorse) entity.getBukkitEntity()).getJumpStrength())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name jump_strength\r\n        // @input ElementTag(Number)\r\n        // @synonyms EntityTag.horse_jump_height\r\n        // @description\r\n        // Sets the power of the horse's jump.\r\n        // Also applies to horse-like mobs, such as donkeys and mules.\r\n        // @tags\r\n        // <EntityTag.jump_strength>\r\n        // -->\r\n        if (mechanism.matches(\"jump_strength\") && mechanism.requireDouble()) {\r\n            ((AbstractHorse) entity.getBukkitEntity()).setJumpStrength(mechanism.getValue().asDouble());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityKnockback.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.entity.AbstractArrow;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\n@Deprecated\r\npublic class EntityKnockback implements Property {\r\n\r\n    public static boolean describes(ObjectTag object) {\r\n        return object instanceof EntityTag entity && entity.getBukkitEntity() instanceof AbstractArrow;\r\n    }\r\n\r\n    public static EntityKnockback getFrom(ObjectTag object) {\r\n        if (!describes(object)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityKnockback((EntityTag) object);\r\n        }\r\n    }\r\n\r\n    public EntityKnockback(EntityTag entity) {\r\n        arrow = entity;\r\n    }\r\n\r\n    EntityTag arrow;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) ? null : String.valueOf(getAbstractArrow().getKnockbackStrength());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"knockback\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.knockback>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism EntityTag.knockback\r\n        // @group properties\r\n        // @description\r\n        // Deprecated in favor of setting the knockback enchantment on the weapon itself on MC 1.21+.\r\n        // @deprecated use the knockback enchantment on the weapon itself on MC 1.21+.\r\n        // -->\r\n        PropertyParser.registerTag(EntityKnockback.class, ElementTag.class, \"knockback\", (attribute, object) -> {\r\n            BukkitImplDeprecations.entityKnockback.warn(attribute.context);\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n                ItemStack is = object.getAbstractArrow().getWeapon();\r\n                return new ElementTag(is == null ? 0 : is.getEnchantmentLevel(Enchantment.KNOCKBACK)); // it is nullable even when it is marked as @NotNull, for example when you spawn an arrow\r\n            }\r\n            else {\r\n                return new ElementTag(object.getAbstractArrow().getKnockbackStrength());\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name knockback\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Deprecated in favor of setting the knockback enchantment on the weapon itself on MC 1.21+.\r\n        // @deprecated use the knockback enchantment on the weapon itself on MC 1.21+.\r\n        // @tags\r\n        // <EntityTag.knockback>\r\n        // -->\r\n        PropertyParser.registerMechanism(EntityKnockback.class, ElementTag.class, \"knockback\", (object, mechanism, input) -> {\r\n            BukkitImplDeprecations.entityKnockback.warn(mechanism.context);\r\n            if (mechanism.requireInteger()) {\r\n                if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n                    ItemStack is = object.getAbstractArrow().getWeapon();\r\n                    if (is == null) { // it is nullable even when it is marked as @NotNull, for example when you spawn an arrow\r\n                        is = new ItemStack(Material.BOW);\r\n                    }\r\n                    is.addUnsafeEnchantment(Enchantment.KNOCKBACK, input.asInt());\r\n                    object.getAbstractArrow().setWeapon(is);\r\n                }\r\n                else {\r\n                    object.getAbstractArrow().setKnockbackStrength(input.asInt());\r\n                }\r\n            }\r\n        });\r\n    }\r\n\r\n    public AbstractArrow getAbstractArrow() {\r\n        return (AbstractArrow) arrow.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityLeftRotation.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.QuaternionTag;\r\nimport org.bukkit.entity.Display;\r\nimport org.bukkit.util.Transformation;\r\nimport org.joml.Quaternionf;\r\n\r\npublic class EntityLeftRotation extends EntityProperty<QuaternionTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name left_rotation\r\n    // @input QuaternionTag\r\n    // @description\r\n    // A display entity's \"left\" rotation.\r\n    // Note that <@link mechanism EntityTag.right_rotation> is also available, but should normally use this instead.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public QuaternionTag getPropertyValue() {\r\n        Quaternionf leftRotation = as(Display.class).getTransformation().getLeftRotation();\r\n        return new QuaternionTag(leftRotation.x(), leftRotation.y(), leftRotation.z(), leftRotation.w());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(QuaternionTag value) {\r\n        return value.x == 0d && value.y == 0d && value.z == 0d && value.w == 1d;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(QuaternionTag value, Mechanism mechanism) {\r\n        Transformation transformation = as(Display.class).getTransformation();\r\n        transformation.getLeftRotation().set((float) value.x, (float) value.y, (float) value.z, (float) value.w);\r\n        as(Display.class).setTransformation(transformation);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"left_rotation\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"left_rotation\", EntityLeftRotation.class, QuaternionTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityLineWidth.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.TextDisplay;\r\n\r\npublic class EntityLineWidth extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name line_width\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // A text display entity's line width, used to split lines (note that newlines can still be added as normal).\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof TextDisplay;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(TextDisplay.class).getLineWidth());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return value.asInt() == 200;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireInteger()) {\r\n            as(TextDisplay.class).setLineWidth(value.asInt());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"line_width\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"line_width\", EntityLineWidth.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityMarker.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.ArmorStand;\r\nimport org.bukkit.entity.EntityType;\r\n\r\npublic class EntityMarker implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntityType() == EntityType.ARMOR_STAND;\r\n    }\r\n\r\n    public static EntityMarker getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityMarker((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"marker\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"marker\"\r\n    };\r\n\r\n    public EntityMarker(EntityTag entity) {\r\n        dentity = entity;\r\n    }\r\n\r\n    EntityTag dentity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (!((ArmorStand) dentity.getBukkitEntity()).isMarker()) {\r\n            return null;\r\n        }\r\n        else {\r\n            return \"true\";\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"marker\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.marker>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.marker\r\n        // @group properties\r\n        // @description\r\n        // If the entity is an armor stand, returns whether the armor stand is a marker.\r\n        // Marker armor stands have a tiny hitbox.\r\n        // -->\r\n        if (attribute.startsWith(\"marker\")) {\r\n            return new ElementTag(((ArmorStand) dentity.getBukkitEntity()).isMarker())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name marker\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes the marker state of an armor stand.\r\n        // @tags\r\n        // <EntityTag.marker>\r\n        // -->\r\n        if (mechanism.matches(\"marker\") && mechanism.requireBoolean()) {\r\n            ((ArmorStand) dentity.getBukkitEntity()).setMarker(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityMaterial.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.entity.BlockDisplay;\r\nimport org.bukkit.entity.Enderman;\r\nimport org.bukkit.entity.Minecart;\r\n\r\npublic class EntityMaterial extends EntityProperty<MaterialTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name material\r\n    // @input MaterialTag\r\n    // @description\r\n    // An entity's associated block material.\r\n    // For endermen, this is the block being held.\r\n    // For minecarts, this is the block being carried.\r\n    // For block displays, this is the block being displayed.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Enderman\r\n                || entity.getBukkitEntity() instanceof Minecart\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && entity.getBukkitEntity() instanceof BlockDisplay);\r\n    }\r\n\r\n    @Override\r\n    public MaterialTag getPropertyValue() {\r\n        BlockData blockData = null;\r\n        if (getEntity() instanceof Enderman enderman) {\r\n            blockData = enderman.getCarriedBlock();\r\n        }\r\n        else if (getEntity() instanceof Minecart minecart) {\r\n            blockData = minecart.getDisplayBlockData();\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            blockData = as(BlockDisplay.class).getBlock();\r\n        }\r\n        return blockData != null ? new MaterialTag(blockData) : new MaterialTag(Material.AIR);\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(MaterialTag value) {\r\n        return value.getMaterial() == Material.AIR;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(MaterialTag value, Mechanism mechanism) {\r\n        if (getEntity() instanceof Enderman enderman) {\r\n            enderman.setCarriedBlock(value.getModernData());\r\n        }\r\n        else if (getEntity() instanceof Minecart minecart) {\r\n            minecart.setDisplayBlockData(value.getModernData());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n            as(BlockDisplay.class).setBlock(value.getModernData());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"material\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"material\", EntityMaterial.class, MaterialTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityMaxFuseTicks.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.entity.Creeper;\nimport org.bukkit.entity.EntityType;\n\npublic class EntityMaxFuseTicks implements Property {\n\n    public static boolean describes(ObjectTag object) {\n        return object instanceof EntityTag && ((EntityTag) object).getBukkitEntityType() == EntityType.CREEPER;\n    }\n\n    public static EntityMaxFuseTicks getFrom(ObjectTag object) {\n        if (!describes(object)) {\n            return null;\n        }\n        else {\n            return new EntityMaxFuseTicks((EntityTag) object);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"max_fuse_ticks\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"max_fuse_ticks\"\n    };\n\n    public EntityMaxFuseTicks(EntityTag entity) {\n        this.entity = entity;\n    }\n\n    EntityTag entity;\n\n    @Override\n    public String getPropertyString() {\n        return String.valueOf(((Creeper) entity.getBukkitEntity()).getMaxFuseTicks());\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"max_fuse_ticks\";\n    }\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.max_fuse_ticks>\n        // @returns ElementTag(Number)\n        // @mechanism EntityTag.max_fuse_ticks\n        // @group properties\n        // @description\n        // Returns the default number of ticks until the creeper explodes when primed (NOT the time remaining if already primed).\n        // -->\n        if (attribute.startsWith(\"max_fuse_ticks\")) {\n            return new ElementTag(((Creeper) entity.getBukkitEntity()).getMaxFuseTicks())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name max_fuse_ticks\n        // @input ElementTag(Number)\n        // @description\n        // Sets the default number of ticks until the creeper explodes when primed (NOT the time remaining if already primed).\n        // @tags\n        // <EntityTag.max_fuse_ticks>\n        // -->\n        if (mechanism.matches(\"max_fuse_ticks\") && mechanism.requireInteger()) {\n            ((Creeper) entity.getBukkitEntity()).setMaxFuseTicks(mechanism.getValue().asInt());\n        }\n\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityMaxTemper.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.AbstractHorse;\n\npublic class EntityMaxTemper extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name max_temper\n    // @input ElementTag(Number)\n    // @description\n    // Controls the upper-bound for a horse-type entity's chance to be tamed.\n    //\n    // When a player mounts an entity, a number between 0 and the entity's max temper is generated.\n    // The entity becomes tamed if this value is less than the entity's temper value.\n    // Otherwise, the player gets bucked and increases the entity's temper by 5.\n    //\n    // Because an entity must have a level to reach before it can be tamed, value must be 1 or higher.\n    // Default value for llamas and trader llamas is 30.\n    // Default value for all other entities is 100.\n    //\n    // To control the entity's current temper, see <@link mechanism EntityTag.temper>.\n    // To automatically tame an entity, see <@link mechanism EntityTag.tame>.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof AbstractHorse;\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(as(AbstractHorse.class).getMaxDomestication());\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        if (mechanism.requireInteger()) {\n            as(AbstractHorse.class).setMaxDomestication(param.asInt());\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"max_temper\";\n    }\n\n    public static void register() {\n        autoRegister(\"max_temper\", EntityMaxTemper.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityOnBack.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.Panda;\n\npublic class EntityOnBack extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name on_back\n    // @input ElementTag(Boolean)\n    // @description\n    // Controls whether a panda is on its back.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof Panda;\n    }\n\n    @Override\n    public boolean isDefaultValue(ElementTag val) {\n        return !val.asBoolean();\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(as(Panda.class).isOnBack());\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        if (mechanism.requireBoolean()) {\n            as(Panda.class).setOnBack(param.asBoolean());\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"on_back\";\n    }\n\n    public static void register() {\n        autoRegister(\"on_back\", EntityOnBack.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityOpacity.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.TextDisplay;\r\n\r\npublic class EntityOpacity extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name opacity\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // A text display entity's text opacity, from 0 to 255.\r\n    // Can be interpolated, see <@link language Display entity interpolation>.\r\n    // Note that there's currently an edge-case/bug where 0-3 are completely opaque, and it only becomes transparent at 4.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof TextDisplay;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(Byte.toUnsignedInt(as(TextDisplay.class).getTextOpacity()));\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return value.asInt() == 255;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireInteger()) {\r\n            int opacity = value.asInt();\r\n            if (opacity < 0 || opacity > 255) {\r\n                mechanism.echoError(\"Invalid opacity specified, must be between 0 and 255.\");\r\n                return;\r\n            }\r\n            as(TextDisplay.class).setTextOpacity((byte) opacity);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"opacity\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"opacity\", EntityOpacity.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPainting.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.Art;\r\nimport org.bukkit.Registry;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Painting;\r\n\r\npublic class EntityPainting implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntityType() == EntityType.PAINTING;\r\n    }\r\n\r\n    public static EntityPainting getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityPainting((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"painting_width\", \"painting_height\", \"painting\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"painting\"\r\n    };\r\n\r\n    public EntityPainting(EntityTag entity) {\r\n        painting = entity;\r\n    }\r\n\r\n    EntityTag painting;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return ((Painting) painting.getBukkitEntity()).getArt().name();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"painting\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.painting_width>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism EntityTag.painting\r\n        // @group properties\r\n        // @description\r\n        // If the entity is a painting, returns its width.\r\n        // -->\r\n        if (attribute.startsWith(\"painting_width\")) {\r\n            return new ElementTag(NMSHandler.entityHelper.getBlockWidth(((Painting) painting.getBukkitEntity()).getArt()))\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.painting_height>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism EntityTag.painting\r\n        // @group properties\r\n        // @description\r\n        // If the entity is a painting, returns its height.\r\n        // -->\r\n        if (attribute.startsWith(\"painting_height\")) {\r\n            return new ElementTag(NMSHandler.entityHelper.getBlockHeight(((Painting) painting.getBukkitEntity()).getArt()))\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.painting>\r\n        // @returns ElementTag\r\n        // @mechanism EntityTag.painting\r\n        // @group properties\r\n        // @description\r\n        // If the entity is a painting, returns what art it shows.\r\n        // See also <@link tag server.art_types>.\r\n        // -->\r\n        if (attribute.startsWith(\"painting\")) {\r\n            return Utilities.enumlikeToElement(((Painting) painting.getBukkitEntity()).getArt())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name painting\r\n        // @input ElementTag\r\n        // @description\r\n        // Changes the art shown by a painting. Valid a types: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Art.html>.\r\n        // @tags\r\n        // <EntityTag.painting>\r\n        // <server.art_types>\r\n        // -->\r\n        if (mechanism.matches(\"painting\") && Utilities.requireEnumlike(mechanism, Art.class)) {\r\n            Art art = Registry.ART.get(Utilities.parseNamespacedKey(mechanism.getValue().asString()));\r\n            if (((Painting) painting.getBukkitEntity()).getArt() != art) {\r\n                ((Painting) painting.getBukkitEntity()).setArt(art, true);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPatrolLeader.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.Raider;\r\n\r\npublic class EntityPatrolLeader implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntity() instanceof Raider;\r\n    }\r\n\r\n    public static EntityPatrolLeader getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityPatrolLeader((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"is_patrol_leader\"\r\n    };\r\n\r\n    public EntityPatrolLeader(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return ((Raider) entity.getBukkitEntity()).isPatrolLeader() ? \"true\" : \"false\";\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"is_patrol_leader\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_patrol_leader>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.is_patrol_leader\r\n        // @group properties\r\n        // @description\r\n        // If the entity is raider mob (like a pillager), returns whether the entity is a patrol leader.\r\n        // -->\r\n        PropertyParser.registerTag(EntityPatrolLeader.class, ElementTag.class, \"is_patrol_leader\", (attribute, object) -> {\r\n            return new ElementTag(((Raider) object.entity.getBukkitEntity()).isPatrolLeader());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name is_patrol_leader\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // If the entity is raider mob (like a pillager), changes whether the entity is a patrol leader.\r\n        // @tags\r\n        // <EntityTag.is_patrol_leader>\r\n        // -->\r\n        if (mechanism.matches(\"is_patrol_leader\") && mechanism.requireBoolean()) {\r\n            ((Raider) entity.getBukkitEntity()).setPatrolLeader(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPatrolTarget.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.entity.Raider;\r\n\r\npublic class EntityPatrolTarget implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntity() instanceof Raider;\r\n    }\r\n\r\n    public static EntityPatrolTarget getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityPatrolTarget((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"patrol_target\"\r\n    };\r\n\r\n    public EntityPatrolTarget(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        Block target = ((Raider) entity.getBukkitEntity()).getPatrolTarget();\r\n        if (target == null) {\r\n            return null;\r\n        }\r\n        return new LocationTag(target.getLocation()).identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"patrol_target\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.patrol_target>\r\n        // @returns LocationTag\r\n        // @mechanism EntityTag.patrol_target\r\n        // @group properties\r\n        // @description\r\n        // If the entity is raider mob (like a pillager), returns whether the entity is allowed to join active raids.\r\n        // -->\r\n        PropertyParser.registerTag(EntityPatrolTarget.class, LocationTag.class, \"patrol_target\", (attribute, object) -> {\r\n            Block target = ((Raider) object.entity.getBukkitEntity()).getPatrolTarget();\r\n            if (target == null) {\r\n                return null;\r\n            }\r\n            return new LocationTag(target.getLocation());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name patrol_target\r\n        // @input LocationTag\r\n        // @description\r\n        // If the entity is raider mob (like a pillager), changes whether the entity is allowed to join active raids.\r\n        // @tags\r\n        // <EntityTag.patrol_target>\r\n        // -->\r\n        if (mechanism.matches(\"patrol_target\")) {\r\n            if (mechanism.hasValue() && mechanism.requireObject(LocationTag.class)) {\r\n                ((Raider) entity.getBukkitEntity()).setPatrolTarget(mechanism.valueAsType(LocationTag.class).getBlock());\r\n            }\r\n            else {\r\n                ((Raider) entity.getBukkitEntity()).setPatrolTarget(null);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPickupStatus.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.AbstractArrow;\r\n\r\npublic class EntityPickupStatus implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntity() instanceof AbstractArrow;\r\n    }\r\n\r\n    public static EntityPickupStatus getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        return new EntityPickupStatus((EntityTag) entity);\r\n    }\r\n\r\n    public static final String[] handledTags = {\r\n            \"pickup_status\"\r\n    };\r\n\r\n    public static final String[] handledMechs = {\r\n            \"pickup_status\"\r\n    };\r\n\r\n    public EntityPickupStatus(EntityTag entity) {\r\n        dentity = entity;\r\n    }\r\n\r\n    EntityTag dentity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return ((AbstractArrow) dentity.getBukkitEntity()).getPickupStatus().name();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"pickup_status\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.pickup_status>\r\n        // @returns ElementTag\r\n        // @mechanism EntityTag.pickup_status\r\n        // @group properties\r\n        // @description\r\n        // If the entity is an arrow or trident, returns the pickup status of the arrow/trident.\r\n        // -->\r\n        if (attribute.startsWith(\"pickup_status\")) {\r\n            return new ElementTag(((AbstractArrow) dentity.getBukkitEntity()).getPickupStatus())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name pickup_status\r\n        // @input ElementTag\r\n        // @description\r\n        // Changes the pickup status of an arrow/trident.\r\n        // Available pickup statuses can be found here: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/AbstractArrow.PickupStatus.html>\r\n        // @tags\r\n        // <EntityTag.pickup_status>\r\n        // -->\r\n        if (mechanism.matches(\"pickup_status\") && mechanism.requireEnum(AbstractArrow.PickupStatus.class)) {\r\n            ((AbstractArrow) dentity.getBukkitEntity()).setPickupStatus(mechanism.getValue().asEnum(AbstractArrow.PickupStatus.class));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPivot.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Display;\r\n\r\npublic class EntityPivot extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name pivot\r\n    // @input ElementTag\r\n    // @synonyms EntityTag.billboard\r\n    // @description\r\n    // A display entity's pivot point/axes, can be any of <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Display.Billboard.html>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(Display.class).getBillboard());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return value.asEnum(Display.Billboard.class) == Display.Billboard.FIXED;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireEnum(Display.Billboard.class)) {\r\n            as(Display.class).setBillboard(value.asEnum(Display.Billboard.class));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"pivot\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"pivot\", EntityPivot.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPlayerCreated.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.IronGolem;\r\n\r\npublic class EntityPlayerCreated implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof IronGolem;\r\n    }\r\n\r\n    public static EntityPlayerCreated getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityPlayerCreated((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"player_created\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"player_created\"\r\n    };\r\n\r\n    public EntityPlayerCreated(EntityTag entity) {\r\n        dentity = entity;\r\n    }\r\n\r\n    EntityTag dentity;\r\n\r\n    public IronGolem getGolem() {\r\n        return (IronGolem) dentity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getGolem().isPlayerCreated() ? \"true\" : \"false\";\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"player_created\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.player_created>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.player_created\r\n        // @group properties\r\n        // @description\r\n        // Returns whether this Iron_Golem was created by a player.\r\n        // -->\r\n        if (attribute.startsWith(\"player_created\")) {\r\n            return new ElementTag(getGolem().isPlayerCreated())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name player_created\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether this Iron_Golem was created by a player.\r\n        // @tags\r\n        // <EntityTag.player_created>\r\n        // -->\r\n        if (mechanism.matches(\"player_created\") && mechanism.requireBoolean()) {\r\n            getGolem().setPlayerCreated(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPlayingDead.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport org.bukkit.entity.Axolotl;\n\npublic class EntityPlayingDead implements Property {\n\n    public static boolean describes(ObjectTag entity) {\n        if (!(entity instanceof EntityTag)) {\n            return false;\n        }\n        return ((EntityTag) entity).getBukkitEntity() instanceof Axolotl;\n    }\n\n    public static EntityPlayingDead getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        } else {\n            return new EntityPlayingDead((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledMechs = new String[] {\n        \"playing_dead\"\n    };\n\n    public EntityPlayingDead(EntityTag _entity) {\n        entity = _entity;\n    }\n\n    EntityTag entity;\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <EntityTag.playing_dead>\n        // @returns ElementTag(Boolean)\n        // @mechanism EntityTag.playing_dead\n        // @group properties\n        // @description\n        // If the entity is an axolotl, returns whether the entity is playing dead.\n        // -->\n        PropertyParser.registerTag(EntityPlayingDead.class, ElementTag.class, \"playing_dead\", (attribute, entity) -> {\n            return new ElementTag(((Axolotl) entity.entity.getBukkitEntity()).isPlayingDead());\n        });\n    }\n\n    @Override\n    public String getPropertyString() {\n        return ((Axolotl) entity.getBukkitEntity()).isPlayingDead() ? \"true\" : null;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"playing_dead\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name playing_dead\n        // @input ElementTag(Boolean)\n        // @description\n        // If the entity is an axolotl, sets whether the entity is playing dead.\n        // This won't be successful unless the entity is unaware of its surroundings. See <@link mechanism EntityTag.is_aware>.\n        // @tags\n        // <EntityTag.playing_dead>\n        // -->\n        if (mechanism.matches(\"playing_dead\") && mechanism.requireBoolean()) {\n            ((Axolotl) entity.getBukkitEntity()).setPlayingDead(mechanism.getValue().asBoolean());\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPotion.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Arrow;\r\nimport org.bukkit.entity.ThrownPotion;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.meta.PotionMeta;\r\n\r\n@Deprecated\r\npublic class EntityPotion extends EntityProperty<ItemTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name potion\r\n    // @input ItemTag\r\n    // @deprecated use 'EntityTag.potion_type' for arrows, and 'EntityTag.item' for splash potions.\r\n    // @description\r\n    // Deprecated in favor of <@link property EntityTag.potion_type> for arrows, and <@link property EntityTag.item> for splash potions.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof ThrownPotion || entity.getBukkitEntity() instanceof Arrow;\r\n    }\r\n\r\n    @Override\r\n    public ItemTag getPropertyValue() {\r\n        if (getEntity() instanceof ThrownPotion thrownPotion) {\r\n            return new ItemTag(thrownPotion.getItem());\r\n        }\r\n        else { // Tipped arrow\r\n            ItemStack refItem = new ItemStack(Material.POTION);\r\n            PotionMeta meta = (PotionMeta) refItem.getItemMeta();\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                meta.setBasePotionType(as(Arrow.class).getBasePotionType());\r\n            }\r\n            else {\r\n                meta.setBasePotionData(as(Arrow.class).getBasePotionData());\r\n            }\r\n            refItem.setItemMeta(meta);\r\n            return new ItemTag(refItem);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public ItemTag getTagValue(Attribute attribute) {\r\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\r\n            return super.getTagValue(attribute);\r\n        }\r\n        if (getEntity() instanceof ThrownPotion) {\r\n            BukkitImplDeprecations.splashPotionItem.warn(attribute.context);\r\n        }\r\n        else {\r\n            BukkitImplDeprecations.arrowBasePotionType.warn(attribute.context);\r\n        }\r\n        return super.getTagValue(attribute);\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ItemTag value, Mechanism mechanism) {\r\n        if (getEntity() instanceof ThrownPotion thrownPotion) {\r\n            thrownPotion.setItem(value.getItemStack());\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                BukkitImplDeprecations.splashPotionItem.warn(mechanism.context);\r\n            }\r\n        }\r\n        else { // Tipped arrow\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                BukkitImplDeprecations.arrowBasePotionType.warn(mechanism.context);\r\n                as(Arrow.class).setBasePotionType(((PotionMeta) value.getItemMeta()).getBasePotionType());\r\n            }\r\n            else {\r\n                as(Arrow.class).setBasePotionData(((PotionMeta) value.getItemMeta()).getBasePotionData());\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"potion\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"potion\", EntityPotion.class, ItemTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPotionEffects.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.properties.item.ItemPotion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport org.bukkit.entity.AreaEffectCloud;\r\nimport org.bukkit.entity.Arrow;\r\nimport org.bukkit.potion.PotionEffect;\r\nimport org.bukkit.potion.PotionEffectType;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Collection;\r\n\r\npublic class EntityPotionEffects implements Property {\r\n\r\n    public static boolean describes(ObjectTag object) {\r\n        if (!(object instanceof EntityTag entity)) {\r\n            return false;\r\n        }\r\n        return entity.isLivingEntity()\r\n                || entity.getBukkitEntity() instanceof Arrow\r\n                || entity.getBukkitEntity() instanceof AreaEffectCloud;\r\n    }\r\n\r\n    public static EntityPotionEffects getFrom(ObjectTag object) {\r\n        if (!describes(object)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityPotionEffects((EntityTag) object);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"potion_effects\"\r\n    };\r\n\r\n    public EntityPotionEffects(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public Collection<PotionEffect> getEffectsList() {\r\n        if (entity.isLivingEntity()) {\r\n            return entity.getLivingEntity().getActivePotionEffects();\r\n        }\r\n        else if (isArrow()) {\r\n            return getArrow().getCustomEffects();\r\n        }\r\n        else {\r\n            return getAreaEffectCloud().getCustomEffects();\r\n        }\r\n    }\r\n\r\n    public ListTag getEffectsListTag(TagContext context) {\r\n        ListTag result = new ListTag();\r\n        for (PotionEffect effect : getEffectsList()) {\r\n            result.add(ItemPotion.effectToLegacyString(effect, context));\r\n        }\r\n        return result;\r\n    }\r\n\r\n    public ListTag getEffectsMapTag(boolean includeDeprecated) {\r\n        ListTag result = new ListTag();\r\n        for (PotionEffect effect : getEffectsList()) {\r\n            result.addObject(ItemPotion.effectToMap(effect, includeDeprecated));\r\n        }\r\n        return result;\r\n    }\r\n\r\n    public boolean isArrow() {\r\n        return entity.getBukkitEntity() instanceof Arrow;\r\n    }\r\n\r\n    public Arrow getArrow() {\r\n        return (Arrow) entity.getBukkitEntity();\r\n    }\r\n\r\n    public AreaEffectCloud getAreaEffectCloud() {\r\n        return (AreaEffectCloud) entity.getBukkitEntity();\r\n    }\r\n\r\n    public String getPropertyString() {\r\n        ListTag effects = getEffectsMapTag(false);\r\n        return effects.isEmpty() ? null : effects.identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"potion_effects\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.list_effects>\r\n        // @returns ListTag\r\n        // @group attribute\r\n        // @mechanism EntityTag.potion_effects\r\n        // @deprecated use 'effects_data' instead\r\n        // @description\r\n        // Deprecated in favor of <@link tag EntityTag.effects_data>\r\n        // -->\r\n        PropertyParser.registerTag(EntityPotionEffects.class, ListTag.class, \"list_effects\", (attribute, object) -> {\r\n            BukkitImplDeprecations.oldPotionEffects.warn(attribute.context);\r\n            return object.getEffectsListTag(attribute.context);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.effects_data>\r\n        // @returns ListTag(MapTag)\r\n        // @group attribute\r\n        // @mechanism EntityTag.potion_effects\r\n        // @description\r\n        // Returns the active potion effects on the entity, or the potion effects an arrow/area effect cloud will apply.\r\n        // The effects returned are a list of maps in <@link language Potion Effect Format>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityPotionEffects.class, ListTag.class, \"effects_data\", (attribute, object) -> {\r\n            return object.getEffectsMapTag(true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.has_effect[<effect>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @group attributes\r\n        // @mechanism EntityTag.potion_effects\r\n        // @description\r\n        // Returns whether the entity has a specified effect, or whether an arrow/area effect cloud will apply a certain effect.\r\n        // If no effect is specified, returns whether the entity has any effect.\r\n        // The effect type must be from <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/potion/PotionEffectType.html>.\r\n        // -->\r\n        PropertyParser.registerTag(EntityPotionEffects.class, ElementTag.class, \"has_effect\", (attribute, object) -> {\r\n            boolean returnElement = false;\r\n            if (attribute.hasParam()) {\r\n                PotionEffectType effectType = PotionEffectType.getByName(attribute.getParam());\r\n                if (effectType == null) {\r\n                    attribute.echoError(\"Invalid effect type specified: \" + attribute.getParam());\r\n                    return null;\r\n                }\r\n                if (object.entity.isLivingEntity()) {\r\n                    returnElement = object.entity.getLivingEntity().hasPotionEffect(effectType);\r\n                }\r\n                else if (object.isArrow()) {\r\n                    returnElement = object.getArrow().hasCustomEffect(effectType);\r\n                }\r\n                else {\r\n                    returnElement = object.getAreaEffectCloud().hasCustomEffect(effectType);\r\n                }\r\n            }\r\n            else if (!object.getEffectsList().isEmpty()) {\r\n                returnElement = true;\r\n            }\r\n            return new ElementTag(returnElement);\r\n        });\r\n    }\r\n\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name potion_effects\r\n        // @input ListTag\r\n        // @description\r\n        // Set the entity's active potion effects, or the potion effects an arrow/area effect cloud will apply.\r\n        // Each item in the list must be a MapTag in <@link language Potion Effect Format>.\r\n        // @tags\r\n        // <EntityTag.effects_data>\r\n        // <EntityTag.has_effect[<effect>]>\r\n        // -->\r\n        if (mechanism.matches(\"potion_effects\")) {\r\n            for (ObjectTag effectObj : CoreUtilities.objectToList(mechanism.value, mechanism.context)) {\r\n                PotionEffect effect;\r\n                if (effectObj.canBeType(MapTag.class)) {\r\n                    MapTag effectMap = effectObj.asType(MapTag.class, mechanism.context);\r\n                    effect = ItemPotion.parseEffect(effectMap, mechanism.context);\r\n                }\r\n                else {\r\n                    String effectStr = effectObj.toString();\r\n                    effect = ItemPotion.parseLegacyEffectString(effectStr, mechanism.context);\r\n                }\r\n                if (effect == null) {\r\n                    mechanism.echoError(\"Invalid potion effect '\" + effectObj + \"'\");\r\n                    continue;\r\n                }\r\n                if (entity.isLivingEntity()) {\r\n                    entity.getLivingEntity().addPotionEffect(effect);\r\n                }\r\n                else if (isArrow()) {\r\n                    getArrow().addCustomEffect(effect, true);\r\n                }\r\n                else {\r\n                    getAreaEffectCloud().addCustomEffect(effect, true);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPotionType.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.AreaEffectCloud;\nimport org.bukkit.entity.Arrow;\nimport org.bukkit.potion.PotionType;\n\npublic class EntityPotionType extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name potion_type\n    // @input ElementTag\n    // @description\n    // Controls an Arrow or Area Effect Cloud's base potion type, if any.\n    // See <@link url https://minecraft.wiki/w/Potion#Item_data> for a list of potion types.\n    // See <@link property EntityTag.potion_effects> to control the potion effects applied.\n    // @mechanism\n    // Specify no input to remove the base potion type.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof Arrow || entity.getBukkitEntity() instanceof AreaEffectCloud;\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        PotionType type = getEntity() instanceof Arrow arrow ? arrow.getBasePotionType() : as(AreaEffectCloud.class).getBasePotionType();\n        return type != null ? new ElementTag(Utilities.namespacedKeyToString(type.getKey()), true) : null;\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\n        PotionType type = null;\n        if (value != null) {\n            if (!Utilities.requireEnumlike(mechanism, PotionType.class)) {\n                return;\n            }\n            type = Utilities.elementToEnumlike(value, PotionType.class);\n        }\n        if (getEntity() instanceof Arrow arrow) {\n            arrow.setBasePotionType(type);\n        }\n        else {\n            as(AreaEffectCloud.class).setBasePotionType(type);\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"potion_type\";\n    }\n\n    public static void register() {\n        autoRegisterNullable(\"potion_type\", EntityPotionType.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityPowered.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Creeper;\r\nimport org.bukkit.entity.EntityType;\r\n\r\npublic class EntityPowered implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntityType() == EntityType.CREEPER;\r\n    }\r\n\r\n    public static EntityPowered getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityPowered((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"powered\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"powered\"\r\n    };\r\n\r\n    public EntityPowered(EntityTag entity) {\r\n        powered = entity;\r\n    }\r\n\r\n    EntityTag powered;\r\n\r\n    public boolean getPowered() {\r\n        return ((Creeper) (powered.getBukkitEntity())).isPowered();\r\n    }\r\n\r\n    public void setPowered(boolean power) {\r\n        if (powered == null) {\r\n            return;\r\n        }\r\n\r\n        ((Creeper) (powered.getBukkitEntity())).setPowered(power);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (!getPowered()) {\r\n            return null;\r\n        }\r\n        else {\r\n            return \"true\";\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"powered\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.powered>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.powered\r\n        // @group properties\r\n        // @description\r\n        // If the entity is a creeper, returns whether the creeper is powered.\r\n        // -->\r\n        if (attribute.startsWith(\"powered\")) {\r\n            return new ElementTag(getPowered())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name powered\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes the powered state of a Creeper.\r\n        // @tags\r\n        // <EntityTag.powered>\r\n        // -->\r\n        if (mechanism.matches(\"powered\") && mechanism.requireBoolean()) {\r\n            setPowered(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityProfession.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.Villager;\nimport org.bukkit.entity.ZombieVillager;\n\npublic class EntityProfession extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name profession\n    // @input ElementTag\n    // @description\n    // Controls the profession of a villager or zombie villager.\n    // For the list of possible professions, refer to <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Villager.Profession.html>\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof Villager\n                || entity.getBukkitEntity() instanceof ZombieVillager;\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        if (getEntity() instanceof Villager villager) {\n            return Utilities.enumlikeToElement(villager.getProfession());\n        }\n        else if (getEntity() instanceof ZombieVillager zombieVillager) {\n            return Utilities.enumlikeToElement(zombieVillager.getVillagerProfession());\n        }\n        return null;\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\n        if (!mechanism.requireEnum(Villager.Profession.class)) {\n            return;\n        }\n        if (getEntity() instanceof Villager villager) {\n            villager.setProfession(value.asEnum(Villager.Profession.class));\n        }\n        else if (getEntity() instanceof ZombieVillager zombieVillager) {\n            zombieVillager.setVillagerProfession(value.asEnum(Villager.Profession.class));\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"profession\";\n    }\n\n    public static void register() {\n        autoRegister(\"profession\", EntityProfession.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityProperty.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.ObjectProperty;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\n\r\npublic abstract class EntityProperty<TData extends ObjectTag> extends ObjectProperty<EntityTag, TData> {\r\n\r\n    public EntityProperty() {\r\n    }\r\n\r\n    public EntityProperty(EntityTag entity) {\r\n        object = entity;\r\n    }\r\n\r\n    public Entity getEntity() {\r\n        return object.getBukkitEntity();\r\n    }\r\n\r\n    public LivingEntity getLivingEntity() {\r\n        return object.getLivingEntity();\r\n    }\r\n\r\n    public EntityType getType() {\r\n        return object.getBukkitEntityType();\r\n    }\r\n\r\n    public <T extends Entity> T as(Class<T> entityClass) {\r\n        return (T) getEntity();\r\n    }\r\n\r\n    public static String getReasonNotDescribed(EntityTag entity) {\r\n        if (entity.getUUID() == null) {\r\n            return \"generic entity-types cannot match any properties, you must spawn an entity to interact with its properties directly.\";\r\n        }\r\n        else if (!entity.isSpawnedOrValidForTag()) {\r\n            return \"that entity is not spawned.\";\r\n        }\r\n        return \"unspecified reason - are you sure this property applies to that EntityType?\";\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityRightRotation.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.QuaternionTag;\r\nimport org.bukkit.entity.Display;\r\nimport org.bukkit.util.Transformation;\r\nimport org.joml.Quaternionf;\r\n\r\npublic class EntityRightRotation extends EntityProperty<QuaternionTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name right_rotation\r\n    // @input QuaternionTag\r\n    // @description\r\n    // A display entity's \"right\" rotation.\r\n    // Should usually use <@link mechanism EntityTag.left_rotation> instead.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public QuaternionTag getPropertyValue() {\r\n        Quaternionf rightRotation = as(Display.class).getTransformation().getRightRotation();\r\n        return new QuaternionTag(rightRotation.x(), rightRotation.y(), rightRotation.z(), rightRotation.w());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(QuaternionTag value) {\r\n        return value.x == 0d && value.y == 0d && value.z == 0d && value.w == 1d;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(QuaternionTag value, Mechanism mechanism) {\r\n        Transformation transformation = as(Display.class).getTransformation();\r\n        transformation.getRightRotation().set((float) value.x, (float) value.y, (float) value.z, (float) value.w);\r\n        as(Display.class).setTransformation(transformation);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"right_rotation\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"right_rotation\", EntityRightRotation.class, QuaternionTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityRiptide.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\n\npublic class EntityRiptide implements Property {\n    public static boolean describes(ObjectTag object) {\n        return object instanceof EntityTag && ((EntityTag) object).isLivingEntity();\n    }\n\n    public static EntityRiptide getFrom(ObjectTag object) {\n        if (!describes(object)) {\n            return null;\n        }\n        else {\n            return new EntityRiptide((EntityTag) object);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"is_using_riptide\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"is_using_riptide\"\n    };\n\n    public EntityRiptide(EntityTag entity) {\n        this.entity = entity;\n    }\n\n    EntityTag entity;\n\n    @Override\n    public String getPropertyString() {\n        return entity.getLivingEntity().isRiptiding() ? \"true\" : null;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"is_using_riptide\";\n    }\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.is_using_riptide>\n        // @returns ElementTag(Boolean)\n        // @mechanism EntityTag.is_using_riptide\n        // @group properties\n        // @description\n        // Returns whether this entity is using the Riptide enchantment.\n        // -->\n        if (attribute.startsWith(\"is_using_riptide\")) {\n            return new ElementTag(entity.getLivingEntity().isRiptiding())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name is_using_riptide\n        // @input ElementTag(Boolean)\n        // @description\n        // Sets whether this entity is using the Riptide enchantment.\n        // @tags\n        // <EntityTag.is_using_riptide>\n        // -->\n        if (mechanism.matches(\"is_using_riptide\") && mechanism.requireBoolean()) {\n            boolean shouldRiptide = mechanism.getValue().asBoolean();\n            if (shouldRiptide != entity.getLivingEntity().isRiptiding()) {\n                NMSHandler.entityHelper.setRiptide(entity.getBukkitEntity(), shouldRiptide);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityRolling.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.Panda;\n\npublic class EntityRolling extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name rolling\n    // @input ElementTag(Boolean)\n    // @description\n    // Controls whether a panda is rolling on the ground.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof Panda;\n    }\n\n    @Override\n    public boolean isDefaultValue(ElementTag val) {\n        return !val.asBoolean();\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(as(Panda.class).isRolling());\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        if (mechanism.requireBoolean()) {\n            as(Panda.class).setRolling(param.asBoolean());\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"rolling\";\n    }\n\n    public static void register() {\n        autoRegister(\"rolling\", EntityRolling.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityRotation.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport org.bukkit.block.BlockFace;\nimport org.bukkit.entity.Hanging;\n\npublic class EntityRotation implements Property {\n\n    public static boolean describes(ObjectTag entity) {\n        if (!(entity instanceof EntityTag)) {\n            return false;\n        }\n        return ((EntityTag) entity).getBukkitEntity() instanceof Hanging;\n    }\n\n    public static EntityRotation getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        }\n        else {\n            return new EntityRotation((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"rotation\", \"rotation_vector\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"rotation\"\n    };\n\n    public EntityRotation(EntityTag entity) {\n        this.entity = entity;\n    }\n\n    EntityTag entity;\n\n    public BlockFace getRotation() {\n        return ((Hanging) entity.getBukkitEntity()).getAttachedFace().getOppositeFace();\n    }\n\n    public void setRotation(BlockFace direction) {\n        ((Hanging) entity.getBukkitEntity()).setFacingDirection(direction, true);\n    }\n\n    @Override\n    public String getPropertyString() {\n        return CoreUtilities.toLowerCase(getRotation().name());\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"rotation\";\n    }\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.rotation_vector>\n        // @returns LocationTag\n        // @mechanism EntityTag.rotation\n        // @group properties\n        // @description\n        // If the entity can have a rotation, returns the entity's rotation as a direction vector.\n        // Currently, only Hanging-type entities can have rotations.\n        // -->\n        if (attribute.startsWith(\"rotation_vector\")) {\n            return new LocationTag(getRotation().getDirection())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.rotation>\n        // @returns ElementTag\n        // @mechanism EntityTag.rotation\n        // @group properties\n        // @description\n        // If the entity can have a rotation, returns the entity's rotation.\n        // Currently, only Hanging-type entities can have rotations.\n        // Value is from <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/BlockFace.html>.\n        // -->\n        if (attribute.startsWith(\"rotation\")) {\n            return new ElementTag(getRotation())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name rotation\n        // @input ElementTag\n        // @description\n        // Changes the entity's rotation.\n        // Currently, only Hanging-type entities can have rotations.\n        // Value must be from <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/BlockFace.html>.\n        // @tags\n        // <EntityTag.rotation>\n        // <EntityTag.rotation_vector>\n        // -->\n        if (mechanism.matches(\"rotation\") && mechanism.requireEnum(BlockFace.class)) {\n            BlockFace face = BlockFace.valueOf(mechanism.getValue().asString().toUpperCase());\n            if (getRotation() != face) {\n                setRotation(face);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityScale.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport org.bukkit.entity.Display;\r\nimport org.bukkit.util.Transformation;\r\nimport org.joml.Vector3f;\r\n\r\npublic class EntityScale extends EntityProperty<LocationTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name scale\r\n    // @input LocationTag\r\n    // @description\r\n    // A display entity's scale, represented as a <@link objecttype LocationTag> vector.\r\n    // Can be interpolated, see <@link language Display entity interpolation>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public LocationTag getPropertyValue() {\r\n        Vector3f scale = as(Display.class).getTransformation().getScale();\r\n        return new LocationTag(null, scale.x(), scale.y(), scale.z());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(LocationTag value) {\r\n        return value.getX() == 1d && value.getY() == 1d && value.getZ() == 1d;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(LocationTag value, Mechanism mechanism) {\r\n        Transformation transformation = as(Display.class).getTransformation();\r\n        transformation.getScale().set(value.getX(), value.getY(), value.getZ());\r\n        as(Display.class).setTransformation(transformation);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"scale\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"scale\", EntityScale.class, LocationTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityScoreboardTags.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\n\r\npublic class EntityScoreboardTags implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag;\r\n    }\r\n\r\n    public static EntityScoreboardTags getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityScoreboardTags((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"scoreboard_tags\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"scoreboard_tags\", \"clear_scoreboard_tags\"\r\n    };\r\n\r\n    public EntityScoreboardTags(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public ListTag getTags() {\r\n        return new ListTag(entity.getBukkitEntity().getScoreboardTags());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        ListTag tags = getTags();\r\n        if (tags.isEmpty()) {\r\n            return null;\r\n        }\r\n        return tags.identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"scoreboard_tags\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.scoreboard_tags>\r\n        // @returns ListTag\r\n        // @mechanism EntityTag.scoreboard_tags\r\n        // @group attributes\r\n        // @description\r\n        // Returns a list of the scoreboard tags on the entity.\r\n        // -->\r\n        if (attribute.startsWith(\"scoreboard_tags\")) {\r\n            return getTags().getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name clear_scoreboard_tags\r\n        // @input None\r\n        // @description\r\n        // Clears the list of the scoreboard tags on the entity.\r\n        // @tags\r\n        // <EntityTag.scoreboard_tags>\r\n        // -->\r\n        if (mechanism.matches(\"clear_scoreboard_tags\")) {\r\n            for (String str : getTags()) {\r\n                entity.getBukkitEntity().removeScoreboardTag(str);\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name scoreboard_tags\r\n        // @input ListTag\r\n        // @description\r\n        // Adds the list of the scoreboard tags to the entity.\r\n        // To clear existing scoreboard tags, use <@link mechanism EntityTag.clear_scoreboard_tags>.\r\n        // @tags\r\n        // <EntityTag.scoreboard_tags>\r\n        // -->\r\n        if (mechanism.matches(\"scoreboard_tags\") && mechanism.hasValue()) {\r\n            ListTag list = mechanism.valueAsType(ListTag.class);\r\n            for (String str : list) {\r\n                entity.getBukkitEntity().addScoreboardTag(str);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySeeThrough.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.TextDisplay;\r\n\r\npublic class EntitySeeThrough extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name see_through\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Whether a text display entity can be seen through blocks.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof TextDisplay;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(TextDisplay.class).isSeeThrough());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return !value.asBoolean();\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            as(TextDisplay.class).setSeeThrough(value.asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"see_through\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"see_through\", EntitySeeThrough.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityShadowRadius.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Display;\r\n\r\npublic class EntityShadowRadius extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name shadow_radius\r\n    // @input ElementTag(Decimal)\r\n    // @description\r\n    // The radius of a display entity's shadow.\r\n    // Can be interpolated, see <@link language Display entity interpolation>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(Display.class).getShadowRadius());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return value.asFloat() == 0f;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireFloat()) {\r\n            as(Display.class).setShadowRadius(value.asFloat());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"shadow_radius\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"shadow_radius\", EntityShadowRadius.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityShadowStrength.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Display;\r\n\r\npublic class EntityShadowStrength extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name shadow_strength\r\n    // @input ElementTag(Decimal)\r\n    // @description\r\n    // The strength of a display entity's shadow.\r\n    // Note that the final opacity will change based on the entity's distance to the block the shadow is on.\r\n    // Can be interpolated, see <@link language Display entity interpolation>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(Display.class).getShadowStrength());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return value.asFloat() == 1f;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireFloat()) {\r\n            as(Display.class).setShadowStrength(value.asFloat());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"shadow_strength\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"shadow_strength\", EntityShadowStrength.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySheared.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport org.bukkit.entity.*;\n\npublic class EntitySheared extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name sheared\n    // @synonyms has_pumpkin_head\n    // @input ElementTag(Boolean)\n    // @description\n    // Controls whether a sheep is sheared, a bogged is harvested, or a snow golem is derped (ie not wearing a pumpkin).\n    // To include drops or for harvesting mushroom cows consider <@link mechanism EntityTag.shear>.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof Sheep\n                || entity.getBukkitEntity() instanceof Snowman\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && entity.getBukkitEntity() instanceof Bogged);\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        if (getEntity() instanceof Sheep sheep) {\n            return new ElementTag(sheep.isSheared());\n        }\n        else if (getEntity() instanceof Snowman snowman) {\n            return new ElementTag(snowman.isDerp());\n        }\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getEntity() instanceof Bogged bogged) {\n            return new ElementTag(bogged.isSheared());\n        }\n        return null;\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        if (!mechanism.requireBoolean()) {\n            return;\n        }\n        if (getEntity() instanceof Sheep sheep) {\n            sheep.setSheared(param.asBoolean());\n        }\n        else if (getEntity() instanceof Snowman snowman) {\n            snowman.setDerp(param.asBoolean());\n        }\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getEntity() instanceof Bogged bogged) {\n            bogged.setSheared(param.asBoolean());\n        }\n    }\n\n\n    @Override\n    public String getPropertyId() {\n        return \"sheared\";\n    }\n\n    public static void register() {\n        autoRegister(\"sheared\", EntitySheared.class, ElementTag.class, false);\n\n        // <--[tag]\n        // @attribute <EntityTag.is_sheared>\n        // @returns ElementTag(Boolean)\n        // @group attributes\n        // @deprecated use 'EntityTag.sheared'\n        // @description\n        // Deprecated in favor of <@link tag EntityTag.sheared>.\n        // -->\n        PropertyParser.registerTag(EntitySheared.class, ElementTag.class, \"is_sheared\", (attribute, prop) -> {\n            BukkitImplDeprecations.entityIsSheared.warn(attribute.context);\n            return prop.getPropertyValue();\n        });\n\n        // <--[tag]\n        // @attribute <EntityTag.has_pumpkin_head>\n        // @returns ElementTag(Boolean)\n        // @mechanism EntityTag.has_pumpkin_head\n        // @group properties\n        // @deprecated use 'EntityTag.sheared'\n        // @description\n        // Deprecated in favor of <@link tag EntityTag.sheared>.\n        // -->\n        PropertyParser.registerTag(EntitySheared.class, ElementTag.class, \"has_pumpkin_head\", (attribute, prop) -> {\n            BukkitImplDeprecations.entityIsSheared.warn(attribute.context);\n            if (!(prop.getEntity() instanceof Snowman)) {\n                return null;\n            }\n            return new ElementTag(!prop.getPropertyValue().asBoolean());\n        });\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name has_pumpkin_head\n        // @input ElementTag(Boolean)\n        // @deprecated use 'EntityTag.sheared'\n        // @description\n        // Deprecated in favor of <@link mechanism EntityTag.sheared>.\n        // @tags\n        // <EntityTag.has_pumpkin_head>\n        // -->\n        PropertyParser.registerMechanism(EntitySheared.class, ElementTag.class, \"has_pumpkin_head\", (prop, mechanism, input) -> {\n            BukkitImplDeprecations.entityIsSheared.warn(mechanism.context);\n            if (!(prop.getEntity() instanceof Snowman)) {\n                return;\n            }\n            prop.setPropertyValue(new ElementTag(!input.asBoolean()), mechanism);\n        });\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityShivering.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.Strider;\r\n\r\npublic class EntityShivering implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Strider;\r\n    }\r\n\r\n    public static EntityShivering getFrom(ObjectTag _entity) {\r\n        if (!describes(_entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityShivering((EntityTag) _entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"shivering\"\r\n    };\r\n\r\n    public EntityShivering(EntityTag _entity) {\r\n        entity = _entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.shivering>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.shivering\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the strider is shivering.\r\n        // -->\r\n        PropertyParser.registerTag(EntityShivering.class, ElementTag.class, \"shivering\", (attribute, object) -> {\r\n            return new ElementTag(object.getStrider().isShivering());\r\n        });\r\n    }\r\n\r\n    public Strider getStrider() {\r\n        return (Strider) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(getStrider().isShivering());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"shivering\";\r\n    }\r\n\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name shivering\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the strider is shivering.\r\n        // @tags\r\n        // <EntityTag.shivering>\r\n        // -->\r\n        if (mechanism.matches(\"shivering\") && mechanism.requireBoolean()) {\r\n            getStrider().setShivering(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityShotAtAngle.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.Firework;\r\n\r\npublic class EntityShotAtAngle implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntity() instanceof Firework;\r\n    }\r\n\r\n    public static EntityShotAtAngle getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityShotAtAngle((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"shot_at_angle\"\r\n    };\r\n\r\n    public EntityShotAtAngle(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public Firework getFirework() {\r\n        return (Firework) entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getFirework().isShotAtAngle() ? \"true\" : \"false\";\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"shot_at_angle\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.shot_at_angle>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.shot_at_angle\r\n        // @group properties\r\n        // @description\r\n        // Returns true if the Firework entity is 'shot at angle', meaning it should render facing the direction it's moving. If false, will be angled straight up.\r\n        // -->\r\n        PropertyParser.registerTag(EntityShotAtAngle.class, ElementTag.class, \"shot_at_angle\", (attribute, object) -> {\r\n            return new ElementTag(object.getFirework().isShotAtAngle());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name shot_at_angle\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Set to true if the Firework entity should be 'shot at angle', meaning it should render facing the direction it's moving. If false, will be angled straight up.\r\n        // @tags\r\n        // <EntityTag.shot_at_angle>\r\n        // -->\r\n        if (mechanism.matches(\"shot_at_angle\") && mechanism.requireBoolean()) {\r\n            getFirework().setShotAtAngle(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityShulkerPeek.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Shulker;\r\n\r\npublic class EntityShulkerPeek implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag &&\r\n                ((EntityTag) entity).getBukkitEntity() instanceof Shulker;\r\n    }\r\n\r\n    public static EntityShulkerPeek getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityShulkerPeek((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"shulker_peek\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"shulker_peek\"\r\n    };\r\n\r\n    public EntityShulkerPeek(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(getPeek());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"shulker_peek\";\r\n    }\r\n\r\n    public int getPeek() {\r\n        return (int) (((Shulker) entity.getBukkitEntity()).getPeek() * 100);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.shulker_peek>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism EntityTag.shulker_peek\r\n        // @group properties\r\n        // @description\r\n        // Returns the peek value of a shulker box (where 0 is fully closed, 100 is fully open).\r\n        // -->\r\n        if (attribute.startsWith(\"shulker_peek\")) {\r\n            return new ElementTag(getPeek()).getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name shulker_peek\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the peek value of a shulker box (where 0 is fully closed, 100 is fully open).\r\n        // @tags\r\n        // <EntityTag.shulker_peek>\r\n        // -->\r\n        if (mechanism.matches(\"shulker_peek\") && mechanism.requireInteger()) {\r\n            ((Shulker) entity.getBukkitEntity()).setPeek(mechanism.getValue().asFloat() / 100);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySilent.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\n\r\npublic class EntitySilent implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag;\r\n    }\r\n\r\n    public static EntitySilent getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntitySilent((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"silent\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"silent\"\r\n    };\r\n\r\n    public EntitySilent(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return entity.getBukkitEntity().isSilent() ? \"true\" : null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"silent\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.silent>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.silent\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity is silent (Plays no sounds).\r\n        // -->\r\n        if (attribute.startsWith(\"silent\")) {\r\n            return new ElementTag(entity.getBukkitEntity().isSilent())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name silent\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether this entity is silent (Plays no sounds).\r\n        // If you set a player as silent, it may also prevent the player from *hearing* sound.\r\n        // @tags\r\n        // <EntityTag.silent>\r\n        // -->\r\n        if (mechanism.matches(\"silent\") && mechanism.requireBoolean()) {\r\n            entity.getBukkitEntity().setSilent(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySitting.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Sittable;\r\n\r\npublic class EntitySitting implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Sittable;\r\n    }\r\n\r\n    public static EntitySitting getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntitySitting((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"sitting\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"sitting\"\r\n    };\r\n\r\n    public EntitySitting(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (((Sittable) entity.getBukkitEntity()).isSitting()) {\r\n            return \"true\";\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"sitting\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.sitting>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.sitting\r\n        // @group properties\r\n        // @description\r\n        // If the entity is a wolf, cat, or parrot, returns whether the animal is sitting.\r\n        // -->\r\n        if (attribute.startsWith(\"sitting\")) {\r\n            return new ElementTag(((Sittable) entity.getBukkitEntity()).isSitting())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name sitting\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes the sitting state of a wolf, cat, or parrot.\r\n        // @tags\r\n        // <EntityTag.sitting>\r\n        // -->\r\n        if (mechanism.matches(\"sitting\") && mechanism.requireBoolean()) {\r\n            ((Sittable) entity.getBukkitEntity()).setSitting(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySize.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.Phantom;\r\nimport org.bukkit.entity.PufferFish;\r\nimport org.bukkit.entity.Slime;\r\n\r\npublic class EntitySize implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag &&\r\n                (((EntityTag) entity).getBukkitEntity() instanceof Slime\r\n                || ((EntityTag) entity).getBukkitEntity() instanceof Phantom\r\n                || ((EntityTag) entity).getBukkitEntity() instanceof PufferFish);\r\n    }\r\n\r\n    public static EntitySize getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntitySize((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public EntitySize(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public int getSize() {\r\n        if (isSlime()) {\r\n            return getSlime().getSize();\r\n        }\r\n        else if (isPhantom()) {\r\n            return getPhantom().getSize();\r\n        }\r\n        else {\r\n            return getPufferFish().getPuffState();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(getSize());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"size\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.size>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism EntityTag.size\r\n        // @group properties\r\n        // @description\r\n        // Returns the size of a slime-type entity or a Phantom (1-120).\r\n        // If the entity is a PufferFish it returns the puff state (0-3).\r\n        // -->\r\n        PropertyParser.registerTag(EntitySize.class, ElementTag.class, \"size\", (attribute, object) -> {\r\n            return new ElementTag(object.getSize());\r\n        });\r\n\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name size\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the size of a slime-type entity or a Phantom (1-120).\r\n        // If the entity is a PufferFish it sets the puff state (0-3).\r\n        // @tags\r\n        // <EntityTag.size>\r\n        // -->\r\n        PropertyParser.registerMechanism(EntitySize.class, ElementTag.class, \"size\", (object, mechanism, input) -> {\r\n            if (mechanism.requireInteger()) {\r\n                if (object.isSlime()) {\r\n                    object.getSlime().setSize(input.asInt());\r\n                }\r\n                else if (object.isPhantom()) {\r\n                    object.getPhantom().setSize(input.asInt());\r\n                }\r\n                else {\r\n                    object.getPufferFish().setPuffState(input.asInt());\r\n                }\r\n            }\r\n        });\r\n    }\r\n\r\n    public boolean isSlime() {\r\n        return entity.getBukkitEntity() instanceof Slime;\r\n    }\r\n\r\n    public boolean isPhantom() {\r\n        return entity.getBukkitEntity() instanceof Phantom;\r\n    }\r\n\r\n    public Slime getSlime() {\r\n        return (Slime) entity.getBukkitEntity();\r\n    }\r\n\r\n    public Phantom getPhantom() {\r\n        return (Phantom) entity.getBukkitEntity();\r\n    }\r\n\r\n    public PufferFish getPufferFish() {\r\n        return (PufferFish) entity.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySmall.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.ArmorStand;\r\n\r\npublic class EntitySmall implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag &&\r\n                ((EntityTag) entity).getBukkitEntity() instanceof ArmorStand;\r\n    }\r\n\r\n    public static EntitySmall getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntitySmall((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"is_small\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"is_small\"\r\n    };\r\n\r\n    public EntitySmall(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (((ArmorStand) entity.getBukkitEntity()).isSmall()) {\r\n            return \"true\";\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"is_small\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_small>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.is_small\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the armor stand is small.\r\n        // -->\r\n        if (attribute.startsWith(\"is_small\")) {\r\n            return new ElementTag(((ArmorStand) entity.getBukkitEntity()).isSmall())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name is_small\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the armor stand is small.\r\n        // @tags\r\n        // <EntityTag.is_small>\r\n        // -->\r\n        if (mechanism.matches(\"is_small\") && mechanism.requireBoolean()) {\r\n            ((ArmorStand) entity.getBukkitEntity()).setSmall(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySneezing.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.Panda;\n\npublic class EntitySneezing extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name sneezing\n    // @input ElementTag(Boolean)\n    // @description\n    // Controls whether a panda is sneezing.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof Panda;\n    }\n\n    @Override\n    public boolean isDefaultValue(ElementTag val) {\n        return !val.asBoolean();\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(as(Panda.class).isSneezing());\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        if (mechanism.requireBoolean()) {\n            as(Panda.class).setSneezing(param.asBoolean());\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"sneezing\";\n    }\n\n    public static void register() {\n        autoRegister(\"sneezing\", EntitySneezing.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySpeed.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.entity.Boat;\nimport org.bukkit.entity.Minecart;\n\npublic class EntitySpeed implements Property {\n\n    public static boolean describes(ObjectTag entity) {\n        if (!(entity instanceof EntityTag)) {\n            return false;\n        }\n        EntityTag ent = (EntityTag) entity;\n        if (ent.isLivingEntity()) {\n            return true;\n        }\n        return ent.getBukkitEntity() instanceof Boat || ent.getBukkitEntity() instanceof Minecart;\n    }\n\n    public static EntitySpeed getFrom(ObjectTag entity) {\n        if (!describes(entity)) {\n            return null;\n        }\n        else {\n            return new EntitySpeed((EntityTag) entity);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"speed\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"speed\"\n    };\n\n    public EntitySpeed(EntityTag ent) {\n        entity = ent;\n    }\n\n    EntityTag entity;\n\n    @Override\n    public String getPropertyString() {\n        return getSpeed().asString();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"speed\";\n    }\n\n    public ElementTag getSpeed() {\n        if (entity.isLivingEntity()) {\n            return new ElementTag(entity.getLivingEntity().getAttribute(EntityHelper.ATTRIBUTE_MOVEMENT_SPEED).getBaseValue());\n        }\n        else {\n            if (entity.getBukkitEntity() instanceof Boat) {\n                return new ElementTag(((Boat) entity.getBukkitEntity()).getMaxSpeed());\n            }\n            else if (entity.getBukkitEntity() instanceof Minecart) {\n                return new ElementTag(((Minecart) entity.getBukkitEntity()).getMaxSpeed());\n            }\n        }\n        return new ElementTag(0.0);\n    }\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <EntityTag.speed>\n        // @returns ElementTag(Decimal)\n        // @mechanism EntityTag.speed\n        // @group attributes\n        // @description\n        // Returns how fast the entity can move.\n        // Compatible with minecarts, boats, and living entities.\n        // -->\n        if (attribute.startsWith(\"speed\")) {\n            return getSpeed().getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object EntityTag\n        // @name speed\n        // @input ElementTag(Decimal)\n        // @description\n        // Sets how fast the entity can move.\n        // Compatible with minecarts, boats, and living entities.\n        // @tags\n        // <EntityTag.speed>\n        // -->\n        if (mechanism.matches(\"speed\") && mechanism.requireDouble()) {\n            double value = mechanism.getValue().asDouble();\n            if (entity.isLivingEntity()) {\n                entity.getLivingEntity().getAttribute(EntityHelper.ATTRIBUTE_MOVEMENT_SPEED).setBaseValue(value);\n            }\n            else {\n                if (entity.getBukkitEntity() instanceof Boat) {\n                    ((Boat) entity.getBukkitEntity()).setMaxSpeed(value);\n                }\n                else if (entity.getBukkitEntity() instanceof Minecart) {\n                    ((Minecart) entity.getBukkitEntity()).setMaxSpeed(value);\n                }\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntitySpell.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport net.citizensnpcs.trait.versioned.SpellcasterTrait;\r\nimport org.bukkit.entity.Spellcaster;\r\n\r\npublic class EntitySpell extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name spell\r\n    // @input ElementTag\r\n    // @description\r\n    // Controls the spell that an Illager entity (such as an Evoker or Illusioner) should cast.\r\n    // Valid spells are: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Spellcaster.Spell.html>\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Spellcaster;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(Spellcaster.class).getSpell());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (!mechanism.requireEnum(Spellcaster.Spell.class)) {\r\n            return;\r\n        }\r\n        Spellcaster.Spell spell = param.asEnum(Spellcaster.Spell.class);\r\n        if (object.isCitizensNPC()) {\r\n            object.getDenizenNPC().getCitizen().getOrAddTrait(SpellcasterTrait.class).setSpell(spell);\r\n        }\r\n        as(Spellcaster.class).setSpell(spell);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"spell\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"spell\", EntitySpell.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityState.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.Armadillo;\n\npublic class EntityState extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name state\n    // @input ElementTag\n    // @description\n    // Controls the current state of an armadillo.\n    // Valid states are IDLE, ROLLING, SCARED, and UNROLLING.\n    // The entity may roll or unroll due to normal vanilla conditions. If this is not desired, disable <@link property EntityTag.has_ai>.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n          return entity.getBukkitEntity() instanceof Armadillo;\n    }\n\n    public enum ArmadilloState {\n        IDLE, ROLLING, SCARED, UNROLLING\n    }\n\n    @Override\n    public boolean isDefaultValue(ElementTag val) {\n        return val.asEnum(ArmadilloState.class).equals(ArmadilloState.IDLE);\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(NMSHandler.entityHelper.getArmadilloState(as(Armadillo.class)));\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        if (mechanism.requireEnum(ArmadilloState.class)) {\n            NMSHandler.entityHelper.setArmadilloState(as(Armadillo.class), param.asEnum(ArmadilloState.class));\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"state\";\n    }\n\n    public static void register() {\n        autoRegister(\"state\", EntityState.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityStepHeight.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\n\r\n@Deprecated(forRemoval = true)\r\npublic class EntityStepHeight extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name step_height\r\n    // @input ElementTag(Decimal)\r\n    // @deprecated Use the step height attribute on MC 1.20+.\r\n    // @description\r\n    // Deprecated in favor of the step height attribute on MC 1.20+, see <@link language Attribute Modifiers>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(NMSHandler.entityHelper.getStepHeight(getEntity()));\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getTagValue(Attribute attribute) {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            BukkitImplDeprecations.entityStepHeight.warn(attribute.context);\r\n        }\r\n        return super.getTagValue(attribute);\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            BukkitImplDeprecations.entityStepHeight.warn(mechanism.context);\r\n        }\r\n        if (mechanism.requireFloat()) {\r\n            NMSHandler.entityHelper.setStepHeight(getEntity(), param.asFloat());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"step_height\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"step_height\", EntityStepHeight.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityStrength.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.Llama;\r\n\r\npublic class EntityStrength implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag && ((EntityTag) entity).getBukkitEntity() instanceof Llama;\r\n    }\r\n\r\n    public static EntityStrength getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityStrength((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"strength\"\r\n    };\r\n\r\n    public EntityStrength(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public ElementTag getStrength() {\r\n        return new ElementTag(((Llama) entity.getBukkitEntity()).getStrength());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getStrength().asString();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"strength\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.strength>\r\n        // @returns ElementTag\r\n        // @mechanism EntityTag.strength\r\n        // @group properties\r\n        // @description\r\n        // Returns the strength of a Llama. A llama's inventory contains (strength times three) slots.\r\n        // Can be from 1 to 5 (inclusive).\r\n        // -->\r\n        PropertyParser.registerTag(EntityStrength.class, ElementTag.class, \"strength\", (attribute, object) -> {\r\n            return object.getStrength();\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name strength\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets the strength of a Llama. A llama's inventory contains (strength times three) slots.\r\n        // Can be from 1 to 5 (inclusive).\r\n        // @tags\r\n        // <EntityTag.strength>\r\n        // -->\r\n        if (mechanism.matches(\"strength\") && mechanism.requireInteger()) {\r\n            ((Llama) entity.getBukkitEntity()).setStrength(mechanism.getValue().asInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityTame.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.OfflinePlayer;\r\nimport org.bukkit.entity.Tameable;\r\n\r\npublic class EntityTame implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag &&\r\n                ((EntityTag) entity).getBukkitEntity() instanceof Tameable;\r\n    }\r\n\r\n    public static EntityTame getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityTame((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"is_tamed\", \"get_owner\", \"owner\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"tame\", \"owner\"\r\n    };\r\n\r\n    public EntityTame(EntityTag tame) {\r\n        entity = tame;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        if (((Tameable) entity.getBukkitEntity()).isTamed()) {\r\n            OfflinePlayer owner = (OfflinePlayer) ((Tameable) entity.getBukkitEntity()).getOwner();\r\n            if (owner == null) {\r\n                return \"true\";\r\n            }\r\n            else {\r\n                return \"true|\" + owner.getUniqueId();\r\n            }\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"tame\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.is_tamed>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.tame\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the entity has been tamed.\r\n        // -->\r\n        if (attribute.startsWith(\"is_tamed\")) {\r\n            return new ElementTag(((Tameable) entity.getBukkitEntity()).isTamed())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.owner>\r\n        // @returns PlayerTag\r\n        // @mechanism EntityTag.owner\r\n        // @group properties\r\n        // @description\r\n        // Returns the owner of a tamed entity.\r\n        // -->\r\n        if (attribute.startsWith(\"owner\") || attribute.startsWith(\"get_owner\")) {\r\n            if (((Tameable) entity.getBukkitEntity()).isTamed()) {\r\n                OfflinePlayer tamer = (OfflinePlayer) ((Tameable) entity.getBukkitEntity()).getOwner();\r\n                if (tamer == null) {\r\n                    return null;\r\n                }\r\n                return new PlayerTag(tamer)\r\n                        .getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n            else {\r\n                return null;\r\n            }\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name tame\r\n        // @input ElementTag(Boolean)(|PlayerTag)\r\n        // @description\r\n        // Sets whether the entity has been tamed.\r\n        // Also available: <@link mechanism EntityTag.owner>\r\n        // @tags\r\n        // <EntityTag.is_tamed>\r\n        // <EntityTag.tameable>\r\n        // -->\r\n        if (mechanism.matches(\"tame\")) {\r\n            ListTag list = mechanism.valueAsType(ListTag.class);\r\n            if (list.isEmpty()) {\r\n                mechanism.echoError(\"Missing value for 'tame' mechanism!\");\r\n                return;\r\n            }\r\n            if (new ElementTag(list.get(0)).isBoolean()) {\r\n                ((Tameable) entity.getBukkitEntity()).setTamed(mechanism.getValue().asBoolean());\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Invalid boolean value!\");\r\n            }\r\n            if (list.size() > 1 && new ElementTag(list.get(1)).matchesType(PlayerTag.class)) {\r\n                ((Tameable) entity.getBukkitEntity()).setOwner(new ElementTag(list.get(1)).asType(PlayerTag.class, mechanism.context).getOfflinePlayer());\r\n            }\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name owner\r\n        // @input PlayerTag\r\n        // @description\r\n        // Sets the entity's owner. Use with no input to make it not have an owner.\r\n        // Also available: <@link mechanism EntityTag.tame>\r\n        // @tags\r\n        // <EntityTag.is_tamed>\r\n        // <EntityTag.tameable>\r\n        // <EntityTag.owner>\r\n        // -->\r\n        if (mechanism.matches(\"owner\")) {\r\n            if (mechanism.hasValue() && mechanism.requireObject(PlayerTag.class)) {\r\n                ((Tameable) entity.getBukkitEntity()).setOwner(mechanism.valueAsType(PlayerTag.class).getOfflinePlayer());\r\n            }\r\n            else {\r\n                ((Tameable) entity.getBukkitEntity()).setOwner(null);\r\n            }\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityTeleportDuration.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport org.bukkit.entity.Display;\r\n\r\npublic class EntityTeleportDuration extends EntityProperty<DurationTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name teleport_duration\r\n    // @input DurationTag\r\n    // @description\r\n    // The duration a display entity will spend teleporting between positions.\r\n    // See also <@link language Display entity interpolation>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public DurationTag getPropertyValue() {\r\n        return new DurationTag((long) as(Display.class).getTeleportDuration());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(DurationTag value) {\r\n        return value.getTicksAsInt() == 0;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(DurationTag value, Mechanism mechanism) {\r\n        as(Display.class).setTeleportDuration(value.getTicksAsInt());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"teleport_duration\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"teleport_duration\", EntityTeleportDuration.class, DurationTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityTemper.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.AbstractHorse;\n\npublic class EntityTemper extends EntityProperty<ElementTag> {\n\n    // <--[property]\n    // @object EntityTag\n    // @name temper\n    // @input ElementTag(Number)\n    // @description\n    // Controls the temper of a horse-type entity.\n    // A value of 0 indicates that no action has been done to try to domesticate this entity.\n    //\n    // When a player mounts an entity, a number between 0 and the entity's max temper is generated.\n    // The entity becomes tamed if this value is less than the entity's temper value.\n    // Otherwise, the player gets bucked and increases the entity's temper by 5.\n    // Temper can also be increased by feeding the entity.\n    // - Apples, sugar, and wheat increase temper by 3.\n    // - Golden carrots increase temper by 5.\n    // - Golden apples increase temper by 10.\n    //\n    // To control the entity's max temper, see <@link mechanism EntityTag.max_temper>.\n    // To automatically tame an entity, see <@link mechanism EntityTag.tame>.\n    // -->\n\n    public static boolean describes(EntityTag entity) {\n        return entity.getBukkitEntity() instanceof AbstractHorse;\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        return new ElementTag(as(AbstractHorse.class).getDomestication());\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\n        if (mechanism.requireInteger()) {\n            as(AbstractHorse.class).setDomestication(param.asInt());\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"temper\";\n    }\n\n    public static void register() {\n        autoRegister(\"temper\", EntityTemper.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityText.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.TextDisplay;\r\n\r\npublic class EntityText extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name text\r\n    // @input ElementTag\r\n    // @description\r\n    // A text display entity's text, supports new lines.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof TextDisplay;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(PaperAPITools.instance.getText(as(TextDisplay.class)), true);\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return value.asString().isEmpty();\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        PaperAPITools.instance.setText(as(TextDisplay.class), value.asString());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"text\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"text\", EntityText.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityTextShadowed.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.TextDisplay;\r\n\r\npublic class EntityTextShadowed extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name text_shadowed\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Whether a text display entity's text has a shadow.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof TextDisplay;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(TextDisplay.class).isShadowed());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return !value.asBoolean();\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            as(TextDisplay.class).setShadowed(value.asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"text_shadowed\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"text_shadowed\", EntityTextShadowed.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityTrades.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.inventory.Merchant;\r\nimport org.bukkit.inventory.MerchantRecipe;\r\n\r\nimport java.util.ArrayList;\r\n\r\npublic class EntityTrades implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag\r\n                && ((EntityTag) entity).getBukkitEntity() instanceof Merchant;\r\n    }\r\n\r\n    public static EntityTrades getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        return new EntityTrades((EntityTag) entity);\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"trades\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"trades\"\r\n    };\r\n\r\n    public ListTag getTradeRecipes() {\r\n        ArrayList<TradeTag> recipes = new ArrayList<>();\r\n        for (MerchantRecipe recipe : ((Merchant) entity.getBukkitEntity()).getRecipes()) {\r\n            recipes.add(new TradeTag(recipe).duplicate());\r\n        }\r\n        return new ListTag(recipes);\r\n    }\r\n\r\n    public EntityTag entity;\r\n\r\n    public EntityTrades(EntityTag entity) {\r\n        this.entity = entity;\r\n    }\r\n\r\n    public String getPropertyString() {\r\n        return getTradeRecipes().identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"trades\";\r\n    }\r\n\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.trades>\r\n        // @returns ListTag(TradeTag)\r\n        // @mechanism EntityTag.trades\r\n        // @description\r\n        // Returns a list of the Villager's trade recipes.\r\n        // -->\r\n        if (attribute.startsWith(\"trades\")) {\r\n            return getTradeRecipes().getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name trades\r\n        // @input ListTag(TradeTag)\r\n        // @description\r\n        // Sets the trades that the entity will offer.\r\n        // @tags\r\n        // <EntityTag.trades>\r\n        // -->\r\n        if (mechanism.matches(\"trades\")) {\r\n            ArrayList<MerchantRecipe> recipes = new ArrayList<>();\r\n            for (TradeTag recipe : mechanism.valueAsType(ListTag.class).filter(TradeTag.class, mechanism.context)) {\r\n                recipes.add(recipe.getRecipe());\r\n            }\r\n            ((Merchant) entity.getBukkitEntity()).setRecipes(recipes);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityTranslation.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport org.bukkit.entity.Display;\r\nimport org.bukkit.util.Transformation;\r\nimport org.joml.Vector3f;\r\n\r\npublic class EntityTranslation extends EntityProperty<LocationTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name translation\r\n    // @input LocationTag\r\n    // @description\r\n    // A display entity's translation, represented as a <@link objecttype LocationTag> vector.\r\n    // Can be interpolated, see <@link language Display entity interpolation>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public LocationTag getPropertyValue() {\r\n        Vector3f translation = as(Display.class).getTransformation().getTranslation();\r\n        return new LocationTag(null, translation.x(), translation.y(), translation.z());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(LocationTag value) {\r\n        return value.getX() == 0d && value.getY() == 0d && value.getZ() == 0d;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(LocationTag value, Mechanism mechanism) {\r\n        Transformation transformation = as(Display.class).getTransformation();\r\n        transformation.getTranslation().set(value.getX(), value.getY(), value.getZ());\r\n        as(Display.class).setTransformation(transformation);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"translation\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"translation\", EntityTranslation.class, LocationTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityTrapTime.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.SkeletonHorse;\r\n\r\npublic class EntityTrapTime implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag ent &&\r\n                (ent.getBukkitEntity() instanceof SkeletonHorse);\r\n    }\r\n\r\n    public static EntityTrapTime getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityTrapTime((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public EntityTrapTime(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return new DurationTag((long) getSkeletonHorse().getTrapTime()).identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"trap_time\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.trap_time>\r\n        // @returns DurationTag\r\n        // @mechanism EntityTag.trap_time\r\n        // @group properties\r\n        // @description\r\n        // Returns the skeleton horse's trap time in ticks.\r\n        // Trap time will go up every tick for as long as the horse is trapped (see <@link tag EntityTag.trapped>).\r\n        // A trapped horse will despawn after it reaches 18000 ticks (15 minutes).\r\n        // -->\r\n        PropertyParser.registerTag(EntityTrapTime.class, DurationTag.class, \"trap_time\", (attribute, object) -> {\r\n            return new DurationTag((long) object.getSkeletonHorse().getTrapTime());\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name trap_time\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the skeleton horse's trap time.\r\n        // Trap time will go up every tick for as long as the horse is trapped (see <@link tag EntityTag.trapped>).\r\n        // A trap time greater than 18000 ticks (15 minutes) will despawn the horse on the next tick.\r\n        // @tags\r\n        // <EntityTag.trapped>\r\n        // -->\r\n        PropertyParser.registerMechanism(EntityTrapTime.class, DurationTag.class, \"trap_time\", (object, mechanism, duration) -> {\r\n            object.getSkeletonHorse().setTrapTime(duration.getTicksAsInt());\r\n        });\r\n    }\r\n\r\n    public SkeletonHorse getSkeletonHorse() {\r\n        return (SkeletonHorse) entity.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityTrapped.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.SkeletonHorse;\r\n\r\npublic class EntityTrapped implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag ent &&\r\n                (ent.getBukkitEntity() instanceof SkeletonHorse);\r\n    }\r\n\r\n    public static EntityTrapped getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityTrapped((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public EntityTrapped(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(((SkeletonHorse) entity.getBukkitEntity()).isTrapped());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"trapped\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.trapped>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.trapped\r\n        // @group properties\r\n        // @description\r\n        // Returns whether the skeleton horse is trapped.\r\n        // A trapped skeleton horse will trigger the skeleton horse trap when the player is within 10 blocks of it.\r\n        // -->\r\n        PropertyParser.registerTag(EntityTrapped.class, ElementTag.class, \"trapped\", (attribute, object) -> {\r\n            return new ElementTag(object.getSkeletonHorse().isTrapped());\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name trapped\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the skeleton horse is trapped.\r\n        // A trapped skeleton horse will trigger the skeleton horse trap when the player is within 10 blocks of it.\r\n        // @tags\r\n        // <EntityTag.trapped>\r\n        // -->\r\n        PropertyParser.registerMechanism(EntityTrapped.class, ElementTag.class, \"trapped\", (object, mechanism, input) -> {\r\n            if (!mechanism.requireBoolean()) {\r\n                return;\r\n            }\r\n            object.getSkeletonHorse().setTrapped(input.asBoolean());\r\n        });\r\n    }\r\n\r\n    public SkeletonHorse getSkeletonHorse() {\r\n        return (SkeletonHorse) entity.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityVariant.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.utilities.PaperAPITools;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport org.bukkit.entity.*;\n\nimport java.lang.invoke.MethodHandle;\n\npublic class EntityVariant extends EntityProperty<ElementTag> {\n\n    // TODO: once the plugin.yml API version is 1.21, replace with direct method calls (see https://github.com/DenizenScript/Denizen/pull/2727)\n    public static final MethodHandle COW_GET_VARIANT, COW_SET_VARIANT;\n\n    static {\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\n            Class<?> cowClass = ReflectionHelper.getClassOrThrow(\"org.bukkit.entity.Cow\");\n            COW_GET_VARIANT = ReflectionHelper.getMethodHandle(cowClass, \"getVariant\");\n            COW_SET_VARIANT = ReflectionHelper.getMethodHandle(cowClass, \"setVariant\", Cow.Variant.class);\n        }\n        else {\n            COW_GET_VARIANT = null;\n            COW_SET_VARIANT = null;\n        }\n    }\n\n    // <--[property]\n    // @object EntityTag\n    // @name variant\n    // @input ElementTag\n    // @description\n    // Controls which variant a chicken, copper golem, cow, pig, wolf, or zombie nautilus is.\n    // A list of valid chicken variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Chicken.Variant.html>.\n    // A list of valid copper golem variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/CopperGolem.CopperWeatherState.html>.\n    // A list of valid cow variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Cow.Variant.html>.\n    // A list of valid pig variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Pig.Variant.html>.\n    // A list of valid wolf variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Wolf.Variant.html>.\n    // A list of valid zombie nautilus variants can be found at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/ZombieNautilus.Variant.html>.\n    // -->\n\n    public static boolean describes(EntityTag entityTag) {\n        Entity entity = entityTag.getBukkitEntity();\n        return entity instanceof Wolf\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && (entity instanceof Chicken\n                                                                        || entity instanceof CopperGolem\n                                                                        || entity instanceof Cow\n                                                                        || entity instanceof Pig\n                                                                        || entity instanceof ZombieNautilus));\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        if (getEntity() instanceof Wolf wolf) {\n            return new ElementTag(Utilities.namespacedKeyToString(wolf.getVariant().getKey()), true);\n        }\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\n            if (getEntity() instanceof Chicken chicken) {\n                return new ElementTag(Utilities.namespacedKeyToString(chicken.getVariant().getKey()), true);\n            }\n            else if (getEntity() instanceof CopperGolem copperGolem) {\n                return new ElementTag(PaperAPITools.instance.getCopperGolemState(copperGolem), true);\n            }\n            else if (COW_GET_VARIANT != null && getEntity() instanceof Cow cow) {\n                try {\n                    return new ElementTag(Utilities.namespacedKeyToString(((Cow.Variant) COW_GET_VARIANT.invoke(cow)).getKey()), true);\n                }\n                catch (Throwable e) {\n                    Debug.echoError(e);\n                    return null;\n                }\n            }\n            else if (getEntity() instanceof Pig pig) {\n                return new ElementTag(Utilities.namespacedKeyToString(pig.getVariant().getKey()), true);\n            }\n            else if (getEntity() instanceof ZombieNautilus zombieNautilus) {\n                return new ElementTag(Utilities.namespacedKeyToString(zombieNautilus.getVariant().getKey()), true);\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag variant, Mechanism mechanism) {\n        if (getEntity() instanceof Wolf wolf) {\n            Wolf.Variant wolfVariant = Utilities.elementToRequiredEnumLike(variant, Wolf.Variant.class, mechanism);\n            if (wolfVariant != null) {\n                wolf.setVariant(wolfVariant);\n            }\n        }\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\n            if (getEntity() instanceof Chicken chicken) {\n                Chicken.Variant chickenVariant = Utilities.elementToRequiredEnumLike(variant, Chicken.Variant.class, mechanism);\n                if (chickenVariant != null) {\n                    chicken.setVariant(chickenVariant);\n                }\n            }\n            else if (getEntity() instanceof CopperGolem copperGolem) {\n                PaperAPITools.instance.setCopperGolemState(variant, copperGolem, mechanism);\n            }\n            else if (COW_SET_VARIANT != null && getEntity() instanceof Cow cow) {\n                Cow.Variant cowVariant = Utilities.elementToRequiredEnumLike(variant, Cow.Variant.class, mechanism);\n                if (cowVariant != null) {\n                    try {\n                        COW_SET_VARIANT.invoke(cow, cowVariant);\n                    }\n                    catch (Throwable e) {\n                        Debug.echoError(e);\n                    }\n                }\n            }\n            else if (getEntity() instanceof Pig pig) {\n                Pig.Variant pigVariant = Utilities.elementToRequiredEnumLike(variant, Pig.Variant.class, mechanism);\n                if (pigVariant != null) {\n                    pig.setVariant(pigVariant);\n                }\n            }\n            else if (getEntity() instanceof ZombieNautilus zombieNautilus) {\n                ZombieNautilus.Variant zombieNautilusVariant = Utilities.elementToRequiredEnumLike(variant, ZombieNautilus.Variant.class, mechanism);\n                if (zombieNautilusVariant != null) {\n                    zombieNautilus.setVariant(zombieNautilusVariant);\n                }\n            }\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"variant\";\n    }\n\n    public static void register() {\n        autoRegister(\"variant\", EntityVariant.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityViewRange.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Display;\r\n\r\npublic class EntityViewRange extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name view_range\r\n    // @input ElementTag(Decimal)\r\n    // @description\r\n    // A display entity's view range, how far away from a player will it still be visible to them.\r\n    // Note that the final value used depends on client settings such as entity distance scaling, and is multiplied by 64 client-side.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(Display.class).getViewRange());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return value.asFloat() == 1f;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireFloat()) {\r\n            as(Display.class).setViewRange(value.asFloat());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"view_range\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"view_range\", EntityViewRange.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityVillagerExperience.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Villager;\r\n\r\npublic class EntityVillagerExperience implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag &&\r\n                (((EntityTag) entity).getBukkitEntity() instanceof Villager);\r\n    }\r\n\r\n    public static EntityVillagerExperience getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityVillagerExperience((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"villager_experience\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"villager_experience\"\r\n    };\r\n\r\n    public EntityVillagerExperience(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(((Villager) entity.getBukkitEntity()).getVillagerExperience());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"villager_experience\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.villager_experience>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism EntityTag.villager_experience\r\n        // @group properties\r\n        // @description\r\n        // Returns the experience amount of a villager.\r\n        // -->\r\n        if (attribute.startsWith(\"villager_experience\")) {\r\n            return new ElementTag(((Villager) entity.getBukkitEntity()).getVillagerExperience())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name villager_experience\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the experience amount of a villager.\r\n        // @tags\r\n        // <EntityTag.villager_experience>\r\n        // -->\r\n        if (mechanism.matches(\"villager_experience\") && mechanism.requireInteger()) {\r\n            ((Villager) entity.getBukkitEntity()).setVillagerExperience(mechanism.getValue().asInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityVillagerLevel.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.entity.Villager;\r\n\r\npublic class EntityVillagerLevel implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag &&\r\n                (((EntityTag) entity).getBukkitEntity() instanceof Villager);\r\n    }\r\n\r\n    public static EntityVillagerLevel getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityVillagerLevel((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"villager_level\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"villager_level\"\r\n    };\r\n\r\n    public EntityVillagerLevel(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(((Villager) entity.getBukkitEntity()).getVillagerLevel());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"villager_level\";\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.villager_level>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism EntityTag.villager_level\r\n        // @group properties\r\n        // @description\r\n        // Returns the level of a villager.\r\n        // -->\r\n        if (attribute.startsWith(\"villager_level\")) {\r\n            return new ElementTag(((Villager) entity.getBukkitEntity()).getVillagerLevel())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name villager_level\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the level of a villager.\r\n        // @tags\r\n        // <EntityTag.villager_level>\r\n        // -->\r\n        if (mechanism.matches(\"villager_level\") && mechanism.requireInteger()) {\r\n            ((Villager) entity.getBukkitEntity()).setVillagerLevel(mechanism.getValue().asInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityVisible.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.npc.traits.InvisibleTrait;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.entity.ArmorStand;\r\nimport org.bukkit.entity.ItemFrame;\r\n\r\npublic class EntityVisible implements Property {\r\n\r\n    public static boolean describes(ObjectTag entity) {\r\n        return entity instanceof EntityTag &&\r\n                (((EntityTag) entity).getBukkitEntity() instanceof ArmorStand\r\n                || ((EntityTag) entity).getBukkitEntity() instanceof ItemFrame\r\n                || ((EntityTag) entity).isLivingEntity());\r\n    }\r\n\r\n    public static EntityVisible getFrom(ObjectTag entity) {\r\n        if (!describes(entity)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new EntityVisible((EntityTag) entity);\r\n        }\r\n    }\r\n\r\n    public EntityVisible(EntityTag ent) {\r\n        entity = ent;\r\n    }\r\n\r\n    EntityTag entity;\r\n\r\n    public boolean isVisible() {\r\n        if (isArmorStand()) {\r\n            return getArmorStand().isVisible();\r\n        }\r\n        else if (isItemFrame()) {\r\n            return getItemFrame().isVisible();\r\n        }\r\n        else {\r\n            return !entity.getLivingEntity().isInvisible();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return isVisible() ? null : \"false\";\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"visible\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <EntityTag.visible>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism EntityTag.visible\r\n        // @group attributes\r\n        // @description\r\n        // Returns whether the entity is visible.\r\n        // Supports armor stands, item frames, and living entities.\r\n        // -->\r\n        PropertyParser.registerTag(EntityVisible.class, ElementTag.class, \"visible\", (attribute, object) -> {\r\n            return new ElementTag(object.isVisible());\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object EntityTag\r\n        // @name visible\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether the entity is visible.\r\n        // Supports armor stands, item frames, and living entities.\r\n        // @tags\r\n        // <EntityTag.visible>\r\n        // -->\r\n        PropertyParser.registerMechanism(EntityVisible.class, ElementTag.class, \"visible\", (object, mechanism, input) -> {\r\n            if (mechanism.requireBoolean()) {\r\n                if (object.entity.isCitizensNPC()) {\r\n                    InvisibleTrait.setInvisible(object.entity.getLivingEntity(), object.entity.getDenizenNPC().getCitizen(), !input.asBoolean());\r\n                }\r\n                else if (object.isArmorStand()) {\r\n                    object.getArmorStand().setVisible(input.asBoolean());\r\n                }\r\n                else if (object.isItemFrame()) {\r\n                    object.getItemFrame().setVisible(input.asBoolean());\r\n                }\r\n                else {\r\n                    object.entity.getLivingEntity().setInvisible(!input.asBoolean());\r\n                }\r\n            }\r\n        });\r\n    }\r\n\r\n    public boolean isArmorStand() {\r\n        return entity.getBukkitEntity() instanceof ArmorStand;\r\n    }\r\n\r\n    public boolean isItemFrame() {\r\n        return entity.getBukkitEntity() instanceof ItemFrame;\r\n    }\r\n\r\n    public ArmorStand getArmorStand() {\r\n        return (ArmorStand) entity.getBukkitEntity();\r\n    }\r\n\r\n    public ItemFrame getItemFrame() {\r\n        return (ItemFrame) entity.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityVisualFire.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\n\r\npublic class EntityVisualFire extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name visual_fire\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Whether an entity has a fake fire effect. For actual fire, see <@link command burn> and <@link tag EntityTag.on_fire>.\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getEntity().isVisualFire());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return !value.asBoolean();\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            getEntity().setVisualFire(value.asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"visual_fire\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"visual_fire\", EntityVisualFire.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityWidth.java",
    "content": "package com.denizenscript.denizen.objects.properties.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.entity.Display;\r\nimport org.bukkit.entity.Interaction;\r\n\r\npublic class EntityWidth extends EntityProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object EntityTag\r\n    // @name width\r\n    // @input ElementTag(Decimal)\r\n    // @description\r\n    // For a display entity, this is the width of it's culling box. The box will span half the width in every direction from the entity's position.\r\n    // The default value for these is 0, which disables culling entirely.\r\n    // For an interaction entity, this is the width of it's bounding box (the area that can be interacted with).\r\n    // -->\r\n\r\n    public static boolean describes(EntityTag entity) {\r\n        return entity.getBukkitEntity() instanceof Display || entity.getBukkitEntity() instanceof Interaction;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (getEntity() instanceof Display display) {\r\n            return new ElementTag(display.getDisplayWidth());\r\n        }\r\n        return new ElementTag(as(Interaction.class).getInteractionWidth());\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag value) {\r\n        return value.asFloat() == (getEntity() instanceof Display ? 0f : 1f);\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (!mechanism.requireFloat()) {\r\n            return;\r\n        }\r\n        if (getEntity() instanceof Display display) {\r\n            display.setDisplayWidth(value.asFloat());\r\n            return;\r\n        }\r\n        as(Interaction.class).setInteractionWidth(value.asFloat());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"width\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"width\", EntityWidth.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/inventory/InventoryContents.java",
    "content": "package com.denizenscript.denizen.objects.properties.inventory;\r\n\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.properties.ObjectProperty;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic class InventoryContents extends ObjectProperty<InventoryTag, ListTag> {\r\n\r\n    public static boolean describes(InventoryTag inventory) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ListTag getPropertyValue() {\r\n        if (!object.isGeneric() && !object.isSaving) {\r\n            return null;\r\n        }\r\n        ListTag contents = getContents(false);\r\n        if (contents == null || contents.isEmpty()) {\r\n            return null;\r\n        }\r\n        return contents;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ListTag list, Mechanism mechanism) {\r\n        if (object.isGeneric() || !mechanism.isProperty) {\r\n            object.setContents(list, mechanism.context);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"contents\";\r\n    }\r\n\r\n    public InventoryContents(InventoryTag inventory) {\r\n        object = inventory;\r\n    }\r\n\r\n    public ListTag getContents(boolean simple) {\r\n        if (object.getInventory() == null) {\r\n            return null;\r\n        }\r\n        int lastNonAir = -1;\r\n        ListTag contents = new ListTag();\r\n        for (ItemStack item : object.getInventory().getContents()) {\r\n            if (item != null && item.getType() != Material.AIR) {\r\n                lastNonAir = contents.size();\r\n                if (simple) {\r\n                    contents.add(new ItemTag(item).identifySimple());\r\n                }\r\n                else {\r\n                    contents.addObject(new ItemTag(item));\r\n                }\r\n            }\r\n            else {\r\n                contents.addObject(new ItemTag(Material.AIR));\r\n            }\r\n        }\r\n        lastNonAir++;\r\n        while (contents.size() > lastNonAir) {\r\n            contents.remove(lastNonAir);\r\n        }\r\n        return contents;\r\n    }\r\n\r\n    public ListTag getContentsWithLore(String lore, boolean simple) {\r\n        if (object.getInventory() == null) {\r\n            return null;\r\n        }\r\n        ListTag contents = new ListTag();\r\n        lore = ChatColor.stripColor(lore);\r\n        for (ItemStack item : object.getInventory().getContents()) {\r\n            if (item != null && item.getType() != Material.AIR) {\r\n                if (item.hasItemMeta() && item.getItemMeta().hasLore()) {\r\n                    for (String line : item.getItemMeta().getLore()) {\r\n                        // Add the item to the list if it contains the lore specified in\r\n                        // the context\r\n                        if (ChatColor.stripColor(line).equalsIgnoreCase(lore)) {\r\n                            if (simple) {\r\n                                contents.add(new ItemTag(item).identifySimple());\r\n                            }\r\n                            else {\r\n                                contents.addObject(new ItemTag(item));\r\n                            }\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        return contents;\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.map_slots>\r\n        // @returns MapTag\r\n        // @group properties\r\n        // @description\r\n        // Returns a map of inventory slots to the items in those slots (excludes air).\r\n        // -->\r\n        PropertyParser.registerTag(InventoryContents.class, MapTag.class, \"map_slots\", (attribute, prop) -> {\r\n            MapTag map = new MapTag();\r\n            ItemStack[] items = prop.object.getContents();\r\n            for (int i = 0; i < items.length; i++) {\r\n                if (items[i] == null || items[i].getType() == Material.AIR) {\r\n                    continue;\r\n                }\r\n                map.putObject(String.valueOf(i + 1), new ItemTag(items[i]));\r\n            }\r\n            return map;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.list_contents>\r\n        // @returns ListTag(ItemTag)\r\n        // @group properties\r\n        // @mechanism InventoryTag.contents\r\n        // @description\r\n        // Returns a list of all items in the inventory.\r\n        // -->\r\n        PropertyParser.registerTag(InventoryContents.class, ListTag.class, \"list_contents\", (attribute, prop) -> {\r\n\r\n            // <--[tag]\r\n            // @attribute <InventoryTag.list_contents.simple>\r\n            // @returns ListTag(ItemTag)\r\n            // @group properties\r\n            // @mechanism InventoryTag.contents\r\n            // @description\r\n            // Returns a list of all items in the inventory, without item properties.\r\n            // -->\r\n            if (attribute.startsWith(\"simple\", 2)) {\r\n                attribute.fulfill(1);\r\n                return prop.getContents(true);\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <InventoryTag.list_contents.with_lore[<element>]>\r\n            // @returns ListTag(ItemTag)\r\n            // @group properties\r\n            // @mechanism InventoryTag.contents\r\n            // @description\r\n            // Returns a list of all items in the inventory with the specified\r\n            // lore. Color codes are ignored.\r\n            // -->\r\n            if (attribute.startsWith(\"with_lore\", 2)) {\r\n                attribute.fulfill(1);\r\n                // Must specify lore to check\r\n                if (!attribute.hasParam()) {\r\n                    return null;\r\n                }\r\n                String lore = attribute.getParam();\r\n                attribute.fulfill(1);\r\n\r\n                // <--[tag]\r\n                // @attribute <InventoryTag.list_contents.with_lore[<element>].simple>\r\n                // @returns ListTag(ItemTag)\r\n                // @group properties\r\n                // @mechanism InventoryTag.contents\r\n                // @description\r\n                // Returns a list of all items in the inventory with the specified\r\n                // lore, without item properties. Color codes are ignored.\r\n                // -->\r\n                if (attribute.startsWith(\"simple\", 2)) {\r\n                    attribute.fulfill(1);\r\n                    return prop.getContentsWithLore(lore, true);\r\n                }\r\n\r\n                return prop.getContentsWithLore(lore, false);\r\n            }\r\n\r\n            return prop.getContents(false);\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object InventoryTag\r\n        // @name contents\r\n        // @input ListTag(ItemTag)\r\n        // @description\r\n        // Sets the contents of the inventory.\r\n        // @tags\r\n        // <InventoryTag.list_contents>\r\n        // <InventoryTag.list_contents.simple>\r\n        // <InventoryTag.list_contents.with_lore[<lore>]>\r\n        // <InventoryTag.list_contents.with_lore[<lore>].simple>\r\n        // -->\r\n        PropertyParser.registerMechanism(InventoryContents.class, ListTag.class, \"contents\", (prop, mechanism, param) -> {\r\n            prop.setPropertyValue(param, mechanism);\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/inventory/InventoryHolder.java",
    "content": "package com.denizenscript.denizen.objects.properties.inventory;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.objects.properties.ObjectProperty;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.event.inventory.InventoryType;\r\n\r\npublic class InventoryHolder extends ObjectProperty<InventoryTag, ObjectTag> {\r\n\r\n    public static boolean describes(InventoryTag inventory) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getPropertyValue() {\r\n        ObjectTag holder = object.getIdHolder();\r\n        if (holder == null || (object.getIdType().equals(\"generic\") && object.getInventoryType() == InventoryType.CHEST)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return holder;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ObjectTag param, Mechanism mechanism) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"holder\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.id_holder>\r\n        // @returns ObjectTag\r\n        // @group properties\r\n        // @description\r\n        // Returns Denizen's holder ID for this inventory. (player object, location object, etc.)\r\n        // -->\r\n        PropertyParser.registerTag(InventoryHolder.class, ObjectTag.class, \"id_holder\", (attribute, prop) -> {\r\n            return prop.object.getIdHolder();\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <InventoryTag.script>\r\n        // @returns ScriptTag\r\n        // @group properties\r\n        // @description\r\n        // Returns the script that this inventory came from (if any).\r\n        // -->\r\n        PropertyParser.registerTag(InventoryHolder.class, ScriptTag.class, \"script\", (attribute, prop) -> {\r\n            ObjectTag holder = prop.object.getIdHolder();\r\n            if (holder instanceof ScriptTag) {\r\n                return ((ScriptTag) holder).validate();\r\n            }\r\n            return null;\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/inventory/InventorySize.java",
    "content": "package com.denizenscript.denizen.objects.properties.inventory;\r\n\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.ObjectProperty;\r\nimport org.bukkit.event.inventory.InventoryType;\r\n\r\npublic class InventorySize extends ObjectProperty<InventoryTag, ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object InventoryTag\r\n    // @name size\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls the size of the inventory.\r\n    // Note that the mechanism can only be set for \"generic\" chest inventories.\r\n    // -->\r\n\r\n    public static boolean describes(ObjectTag inventory) {\r\n        return true;\r\n    }\r\n\r\n    public int getSize() {\r\n        if (object.getInventory() == null) {\r\n            return 0;\r\n        }\r\n        return object.getInventory().getSize();\r\n    }\r\n\r\n    public void setSize(int size) {\r\n        object.setSize(size);\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag size) {\r\n        return !(getSize() > 0 && (object.getIdType().equals(\"generic\") || object.getIdType().equals(\"script\")) && object.getInventoryType() == InventoryType.CHEST);\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getSize());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        if (object.getIdType().equals(\"generic\") || object.getIdType().equals(\"script\")) {\r\n            setSize(param.asInt());\r\n        }\r\n        else {\r\n            mechanism.echoError(\"Inventories of type '\" + object.getIdType() + \"' cannot have their size changed!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"size\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"size\", InventorySize.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/inventory/InventoryTitle.java",
    "content": "package com.denizenscript.denizen.objects.properties.inventory;\r\n\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.InventoryScriptHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.ObjectProperty;\r\nimport org.bukkit.event.inventory.InventoryType;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic class InventoryTitle extends ObjectProperty<InventoryTag, ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object InventoryTag\r\n    // @name title\r\n    // @input ElementTag\r\n    // @description\r\n    // Controls the title of the inventory.\r\n    // Note that the mechanism can only be set for \"generic\" inventories.\r\n    // -->\r\n\r\n    public static boolean describes(InventoryTag inventory) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ElementTag title) {\r\n        return !object.isGeneric() && !object.isSaving;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (object.getInventory() != null) {\r\n            String title = PaperAPITools.instance.getTitle(object.getInventory());\r\n            if (title != null) {\r\n                if (!title.startsWith(\"container.\")) {\r\n                    return new ElementTag(title, true);\r\n                }\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag param, Mechanism mechanism) {\r\n        InventoryTag inventory = object;\r\n        if (!inventory.isGeneric() && !inventory.isUnique()) {\r\n            mechanism.echoError(\"Cannot set a title on a non-generic inventory.\");\r\n            return;\r\n        }\r\n        String title = param.asString();\r\n        if (InventoryScriptHelper.isPersonalSpecialInv(inventory.getInventory())) {\r\n            inventory.customTitle = title;\r\n            return;\r\n        }\r\n        if (inventory.getInventory() != null && PaperAPITools.instance.getTitle(inventory.getInventory()).equals(title)) {\r\n            return;\r\n        }\r\n        inventory.uniquifier = null;\r\n        if (inventory.getInventory() == null) {\r\n            inventory.setInventory(PaperAPITools.instance.createInventory(null, InventoryTag.maxSlots, title));\r\n            InventoryTag.trackTemporaryInventory(inventory);\r\n            return;\r\n        }\r\n        ItemStack[] contents = inventory.getContents();\r\n        if (inventory.getInventory().getType() == InventoryType.CHEST) {\r\n            inventory.setInventory(PaperAPITools.instance.createInventory(null, inventory.getSize(), title));\r\n        }\r\n        else {\r\n            inventory.setInventory(PaperAPITools.instance.createInventory(null, inventory.getInventory().getType(), title));\r\n        }\r\n        inventory.setContents(contents);\r\n        InventoryTag.trackTemporaryInventory(inventory);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"title\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"title\", InventoryTitle.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/inventory/InventoryTrades.java",
    "content": "package com.denizenscript.denizen.objects.properties.inventory;\r\n\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.ObjectProperty;\r\nimport org.bukkit.inventory.MerchantInventory;\r\nimport org.bukkit.inventory.MerchantRecipe;\r\n\r\nimport java.util.ArrayList;\r\n\r\npublic class InventoryTrades extends ObjectProperty<InventoryTag, ListTag> {\r\n\r\n    // <--[property]\r\n    // @object InventoryTag\r\n    // @name trades\r\n    // @input ListTag(TradeTag)\r\n    // @description\r\n    // Controls the trade recipe list for a merchant inventory.\r\n    // -->\r\n\r\n    public static boolean describes(InventoryTag inventory) {\r\n        return inventory.getInventory() instanceof MerchantInventory;\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(ListTag list) {\r\n        return list.isEmpty();\r\n    }\r\n\r\n    @Override\r\n    public ListTag getPropertyValue() {\r\n        ArrayList<TradeTag> recipes = new ArrayList<>();\r\n        for (MerchantRecipe recipe : ((MerchantInventory) object.getInventory()).getMerchant().getRecipes()) {\r\n            recipes.add(new TradeTag(recipe).duplicate());\r\n        }\r\n        return new ListTag(recipes);\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ListTag list, Mechanism mechanism) {\r\n        ArrayList<MerchantRecipe> recipes = new ArrayList<>();\r\n        for (TradeTag recipe : list.filter(TradeTag.class, mechanism.context)) {\r\n            recipes.add(recipe.getRecipe());\r\n        }\r\n        ((MerchantInventory) object.getInventory()).getMerchant().setRecipes(recipes);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"trades\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"trades\", InventoryTrades.class, ListTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/inventory/InventoryUniquifier.java",
    "content": "package com.denizenscript.denizen.objects.properties.inventory;\r\n\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.ObjectProperty;\r\n\r\npublic class InventoryUniquifier extends ObjectProperty<InventoryTag, ElementTag> {\r\n\r\n    public static boolean describes(InventoryTag inventory) {\r\n        return inventory.isGeneric();\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (object.uniquifier == null || object.isSaving) {\r\n            return null;\r\n        }\r\n        return new ElementTag(object.uniquifier);\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"uniquifier\";\r\n    }\r\n\r\n    public static void register() {\r\n        // Intentionally no tags.\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemArmorPose.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.kyori.adventure.nbt.BinaryTagTypes;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.kyori.adventure.nbt.FloatBinaryTag;\r\nimport net.kyori.adventure.nbt.ListBinaryTag;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.List;\r\n\r\npublic class ItemArmorPose extends ItemProperty<MapTag> {\r\n\r\n    // <--[property]\r\n    // @object ItemTag\r\n    // @name armor_pose\r\n    // @input MapTag\r\n    // @description\r\n    // Controls the pose of this armor stand item.\r\n    // Allowed keys: head, body, left_arm, right_arm, left_leg, right_leg\r\n    // -->\r\n\r\n    public static boolean describes(ItemTag item) {\r\n        return item.getBukkitMaterial() == Material.ARMOR_STAND;\r\n    }\r\n\r\n    @Override\r\n    public MapTag getPropertyValue() {\r\n        CompoundBinaryTag entityNbt = NMSHandler.itemHelper.getEntityData(getItemStack());\r\n        if (entityNbt == null) {\r\n            return null;\r\n        }\r\n        CompoundBinaryTag pose = entityNbt.getCompound(\"Pose\", null);\r\n        if (pose == null) {\r\n            return null;\r\n        }\r\n        MapTag result = new MapTag();\r\n        procPart(pose, \"Head\", \"head\", result);\r\n        procPart(pose, \"Body\", \"body\", result);\r\n        procPart(pose, \"LeftArm\", \"left_arm\", result);\r\n        procPart(pose, \"RightArm\", \"right_arm\", result);\r\n        procPart(pose, \"LeftLeg\", \"left_leg\", result);\r\n        procPart(pose, \"RightLeg\", \"right_leg\", result);\r\n        return result;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(MapTag param, Mechanism mechanism) {\r\n        CompoundBinaryTag entityNbt = NMSHandler.itemHelper.getEntityData(getItemStack());\r\n        if (mechanism.hasValue()) {\r\n            if (entityNbt == null) {\r\n                entityNbt = CompoundBinaryTag.empty();\r\n            }\r\n            CompoundBinaryTag.Builder poseBuilder = CompoundBinaryTag.builder();\r\n            procMechKey(mechanism, poseBuilder, \"Head\", \"head\", param);\r\n            procMechKey(mechanism, poseBuilder, \"Body\", \"body\", param);\r\n            procMechKey(mechanism, poseBuilder, \"LeftArm\", \"left_arm\", param);\r\n            procMechKey(mechanism, poseBuilder, \"RightArm\", \"right_arm\", param);\r\n            procMechKey(mechanism, poseBuilder, \"LeftLeg\", \"left_leg\", param);\r\n            procMechKey(mechanism, poseBuilder, \"RightLeg\", \"right_leg\", param);\r\n            CompoundBinaryTag pose = poseBuilder.build();\r\n            if (pose.isEmpty()) {\r\n                entityNbt = entityNbt.remove(\"Pose\");\r\n            }\r\n            else {\r\n                entityNbt = entityNbt.put(\"Pose\", pose);\r\n            }\r\n        }\r\n        else {\r\n            if (entityNbt == null) {\r\n                return;\r\n            }\r\n            if (!entityNbt.contains(\"Pose\", BinaryTagTypes.COMPOUND)) {\r\n                return;\r\n            }\r\n            entityNbt = entityNbt.remove(\"Pose\");\r\n        }\r\n        ItemStack result = NMSHandler.itemHelper.setEntityData(getItemStack(), entityNbt, EntityType.ARMOR_STAND);\r\n        setItemStack(result);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"armor_pose\";\r\n    }\r\n\r\n    public static void procPart(CompoundBinaryTag pose, String nmsName, String denizenName, MapTag result) {\r\n        ListBinaryTag list = pose.getList(nmsName, BinaryTagTypes.FLOAT, null);\r\n        if (list == null || list.size() != 3) {\r\n            return;\r\n        }\r\n        String combined = list.getFloat(0) + \",\" + list.getFloat(1) + \",\" + list.getFloat(2);\r\n        result.putObject(denizenName, new ElementTag(combined));\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"armor_pose\", ItemArmorPose.class, MapTag.class, false);\r\n    }\r\n\r\n    public static void procMechKey(Mechanism mech, CompoundBinaryTag.Builder poseBuilder, String nmsName, String denizenName, MapTag input) {\r\n        ObjectTag value = input.getObject(denizenName);\r\n        if (value == null) {\r\n            return;\r\n        }\r\n        List<String> raw = CoreUtilities.split(value.toString(), ',');\r\n        if (raw.size() != 3) {\r\n            mech.echoError(\"Invalid pose piece '\" + value + \"'\");\r\n            return;\r\n        }\r\n        ListBinaryTag.Builder<FloatBinaryTag> listBuilder = ListBinaryTag.builder(BinaryTagTypes.FLOAT);\r\n        for (int i = 0; i < 3; i++) {\r\n            listBuilder.add(FloatBinaryTag.floatBinaryTag(Float.parseFloat(raw.get(i))));\r\n        }\r\n        poseBuilder.put(nmsName, listBuilder.build());\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemAttributeModifiers.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.properties.entity.EntityAttributeModifiers;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\nimport com.google.common.collect.LinkedHashMultimap;\nimport com.google.common.collect.Multimap;\nimport org.bukkit.NamespacedKey;\nimport org.bukkit.attribute.AttributeModifier;\nimport org.bukkit.inventory.EquipmentSlot;\nimport org.bukkit.inventory.meta.ItemMeta;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.UUID;\n\npublic class ItemAttributeModifiers extends ItemProperty<MapTag> {\n\n    // <--[property]\n    // @object ItemTag\n    // @name attribute_modifiers\n    // @input MapTag\n    // @description\n    // Controls the attribute modifiers of an item, with key as the attribute name and value as a list of modifiers,\n    // where each modifier is a MapTag containing keys 'name', 'amount', 'slot', 'operation', and 'id'.\n    // For use as a mechanism, this is a SET operation, meaning pre-existing modifiers are removed.\n    // For format details, refer to <@link language attribute modifiers>.\n    // -->\n\n    public static boolean describes(ItemTag item) {\n        return true;\n    }\n\n    @Override\n    public boolean isDefaultValue(MapTag map) {\n        return map.isEmpty();\n    }\n\n    @Override\n    public MapTag getPropertyValue() {\n        ItemMeta meta = getItemMeta();\n        if (meta == null) {\n            return null;\n        }\n        Multimap<org.bukkit.attribute.Attribute, AttributeModifier> metaMap = meta.getAttributeModifiers();\n        return getAttributeModifiersFor(metaMap);\n    }\n\n    @Override\n    public void setPropertyValue(MapTag param, Mechanism mechanism) {\n        Multimap<org.bukkit.attribute.Attribute, AttributeModifier> metaMap = LinkedHashMultimap.create();\n        for (Map.Entry<StringHolder, ObjectTag> mapEntry : param.entrySet()) {\n            org.bukkit.attribute.Attribute attr = org.bukkit.attribute.Attribute.valueOf(mapEntry.getKey().str.toUpperCase());\n            for (ObjectTag listValue : CoreUtilities.objectToList(mapEntry.getValue(), mechanism.context)) {\n                metaMap.put(attr, EntityAttributeModifiers.modiferForMap(attr, (MapTag) listValue, mechanism.context));\n            }\n        }\n        ItemMeta meta = getItemMeta();\n        meta.setAttributeModifiers(metaMap);\n        setItemMeta(meta);\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"attribute_modifiers\";\n    }\n\n    public static MapTag getAttributeModifiersFor(Multimap<org.bukkit.attribute.Attribute, AttributeModifier> metaMap) {\n        MapTag map = new MapTag();\n        if (metaMap == null) {\n            return map;\n        }\n        for (org.bukkit.attribute.Attribute attribute : metaMap.keys()) {\n            Collection<AttributeModifier> modifiers = metaMap.get(attribute);\n            if (modifiers.isEmpty()) {\n                continue;\n            }\n            ListTag subList = new ListTag();\n            for (AttributeModifier modifier : modifiers) {\n                subList.addObject(EntityAttributeModifiers.mapify(modifier));\n            }\n            map.putObject(attribute.name(), subList);\n        }\n        return map;\n    }\n\n    public static void register() {\n        autoRegister(\"attribute_modifiers\", ItemAttributeModifiers.class, MapTag.class, false);\n\n        // <--[tag]\n        // @attribute <ItemTag.default_attribute_modifiers[<slot>]>\n        // @returns MapTag\n        // @group properties\n        // @description\n        // Returns a map of all default attribute modifiers on the item based purely on its material type, for the given slot,\n        // in the same format as <@link tag ItemTag.attribute_modifiers>\n        // Slot must be one of: HAND, OFF_HAND, FEET, LEGS, CHEST, or HEAD\n        // -->\n        PropertyParser.registerTag(ItemAttributeModifiers.class, MapTag.class, \"default_attribute_modifiers\", (attribute, prop) -> {\n            if (!attribute.hasParam() || !NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\n                return null;\n            }\n            EquipmentSlot slot = attribute.getParamElement().asEnum(EquipmentSlot.class);\n            if (slot == null) {\n                attribute.echoError(\"Invalid slot specified: \" + attribute.getParam());\n                return null;\n            }\n            return getAttributeModifiersFor(prop.getMaterial().getDefaultAttributeModifiers(slot));\n        });\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name add_attribute_modifiers\n        // @input MapTag\n        // @description\n        // Adds attribute modifiers to an item without altering existing modifiers.\n        // For input format details, refer to <@link language attribute modifiers>.\n        // @tags\n        // <ItemTag.attribute_modifiers>\n        // -->\n        PropertyParser.registerMechanism(ItemAttributeModifiers.class, MapTag.class, \"add_attribute_modifiers\", (prop, mechanism, param) -> {\n            ItemMeta meta = prop.getItemMeta();\n            for (Map.Entry<StringHolder, ObjectTag> subValue : param.entrySet()) {\n                org.bukkit.attribute.Attribute attr = org.bukkit.attribute.Attribute.valueOf(subValue.getKey().str.toUpperCase());\n                for (ObjectTag listValue : CoreUtilities.objectToList(subValue.getValue(), mechanism.context)) {\n                    meta.addAttributeModifier(attr, EntityAttributeModifiers.modiferForMap(attr, (MapTag) listValue, mechanism.context));\n                }\n            }\n            prop.setItemMeta(meta);\n        });\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name remove_attribute_modifiers\n        // @input ListTag\n        // @description\n        // Removes attribute modifiers from an item. Specify a list of attribute names or modifier keys (UUIDs on versions below MC 1.21) as input.\n        // See also <@link language attribute modifiers>.\n        // @tags\n        // <ItemTag.attribute_modifiers>\n        // -->\n        PropertyParser.registerMechanism(ItemAttributeModifiers.class, ListTag.class, \"remove_attribute_modifiers\", (prop, mechanism, param) -> {\n            ItemMeta meta = prop.getItemMeta();\n            ArrayList<String> inputList = new ArrayList<>(param);\n            for (String toRemove : new ArrayList<>(inputList)) {\n                if (Utilities.matchesEnumlike(new ElementTag(toRemove), org.bukkit.attribute.Attribute.class)) {\n                    inputList.remove(toRemove);\n                    org.bukkit.attribute.Attribute attr = org.bukkit.attribute.Attribute.valueOf(toRemove.toUpperCase());\n                    meta.removeAttributeModifier(attr);\n                }\n            }\n            for (String toRemove : inputList) {\n                UUID id = null;\n                NamespacedKey key = null;\n                boolean is1_21 = NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21);\n                if (is1_21) {\n                    key = Utilities.parseNamespacedKey(toRemove);\n                }\n                else {\n                    id = UUID.fromString(toRemove);\n                }\n                Multimap<org.bukkit.attribute.Attribute, AttributeModifier> metaMap = meta.getAttributeModifiers();\n                for (org.bukkit.attribute.Attribute attribute : metaMap.keys()) {\n                    for (AttributeModifier modifer : metaMap.get(attribute)) {\n                        if (is1_21 ? modifer.getKey().equals(key) : modifer.getUniqueId().equals(id)) {\n                            meta.removeAttributeModifier(attribute, modifer);\n                            break;\n                        }\n                    }\n                }\n            }\n            prop.setItemMeta(meta);\n        });\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemAttributeNBT.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.tags.core.EscapeTagUtil;\nimport com.denizenscript.denizencore.utilities.AsciiMatcher;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport org.bukkit.Material;\nimport org.bukkit.NamespacedKey;\nimport org.bukkit.Registry;\nimport org.bukkit.attribute.AttributeModifier;\nimport org.bukkit.inventory.EquipmentSlot;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.inventory.meta.ItemMeta;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.UUID;\n\n@Deprecated\npublic class ItemAttributeNBT implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag;\n    }\n\n    public static ItemAttributeNBT getFrom(ObjectTag item) {\n        if (!describes(item)) {\n            return null;\n        }\n        else {\n            return new ItemAttributeNBT((ItemTag) item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"nbt_attributes\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"nbt_attributes\"\n    };\n\n    public ItemAttributeNBT(ItemTag item) {\n        this.item = item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        if (attribute.startsWith(\"nbt_attributes\")) {\n            BukkitImplDeprecations.legacyAttributeProperties.warn(attribute.context);\n            return getList().getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    public ListTag getList() {\n        ItemStack itemStack = item.getItemStack();\n        ItemMeta meta = itemStack.getItemMeta();\n        ListTag list = new ListTag();\n        if (meta == null || !meta.hasAttributeModifiers()) {\n            return list;\n        }\n        for (Map.Entry<org.bukkit.attribute.Attribute, AttributeModifier> entry : meta.getAttributeModifiers().entries()) {\n            AttributeModifier modifier = entry.getValue();\n            String slotName = toLegacyName(modifier.getSlot() != null ? modifier.getSlot() : EquipmentSlot.HAND);\n            list.add(EscapeTagUtil.escape(entry.getKey().getKey().getKey()) + \"/\" + EscapeTagUtil.escape(slotName) + \"/\" + modifier.getOperation().ordinal() + \"/\" + modifier.getAmount());\n        }\n        return list;\n    }\n\n    @Override\n    public String getPropertyString() {\n        return null;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"nbt_attributes\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        if (mechanism.matches(\"nbt_attributes\")) {\n            BukkitImplDeprecations.legacyAttributeProperties.warn(mechanism.context);\n            if (item.getMaterial().getMaterial() == Material.AIR) {\n                mechanism.echoError(\"Cannot apply NBT to AIR!\");\n                return;\n            }\n            ListTag list = mechanism.valueAsType(ListTag.class);\n            ItemStack itemStack = item.getItemStack();\n            ItemMeta meta = itemStack.getItemMeta();\n            if (meta.hasAttributeModifiers()) {\n                meta.getAttributeModifiers().keySet().forEach(meta::removeAttributeModifier);\n            }\n            for (String string : list) {\n                String[] split = string.split(\"/\");\n                if (split.length != 4) {\n                    mechanism.echoError(\"Invalid nbt_attributes input: must have 4 values per attribute.\");\n                    continue;\n                }\n                String attribute = fixAttributeName1_16(EscapeTagUtil.unEscape(split[0]));\n                String slot = EscapeTagUtil.unEscape(split[1]);\n                int op = new ElementTag(split[2]).asInt();\n                double amt = new ElementTag(split[3]).asDouble();\n                long uuidhelp = uuidChoice(itemStack);\n                int attribsSize = meta.hasAttributeModifiers() ? meta.getAttributeModifiers().values().size() : 0;\n                UUID fullUuid = new UUID(uuidhelp + 88512 + attribsSize, uuidhelp * 2 + 1250025L + attribsSize);\n                meta.addAttributeModifier(Registry.ATTRIBUTE.get(NamespacedKey.minecraft(attribute)), new AttributeModifier(fullUuid, attribute, amt, AttributeModifier.Operation.values()[op], fromLegacyName(slot)));\n            }\n            itemStack.setItemMeta(meta);\n            item.setItemStack(itemStack);\n        }\n    }\n\n    public static final AsciiMatcher uppercaseMatcher = new AsciiMatcher(AsciiMatcher.LETTERS_UPPER);\n\n    public static final HashMap<String, String> attributeNameUpdates = new HashMap<>();\n\n    static {\n        attributeNameUpdates.put(\"generic.maxHealth\", \"generic.max_health\");\n        attributeNameUpdates.put(\"generic.followRange\", \"generic.follow_range\");\n        attributeNameUpdates.put(\"generic.knockbackResistance\", \"generic.knockback_resistance\");\n        attributeNameUpdates.put(\"generic.movementSpeed\", \"generic.movement_speed\");\n        attributeNameUpdates.put(\"generic.flyingSpeed\", \"generic.flying_speed\");\n        attributeNameUpdates.put(\"generic.attackDamage\", \"generic.attack_damage\");\n        attributeNameUpdates.put(\"generic.attackKnockback\", \"generic.attack_knockback\");\n        attributeNameUpdates.put(\"generic.attackSpeed\", \"generic.attack_speed\");\n        attributeNameUpdates.put(\"generic.armorToughness\", \"generic.armor_toughness\");\n    }\n\n    public static String fixAttributeName1_16(String input) {\n        if (!uppercaseMatcher.containsAnyMatch(input)) {\n            return input;\n        }\n        String replacement = attributeNameUpdates.get(input);\n        if (replacement != null) {\n            return replacement;\n        }\n        return CoreUtilities.toLowerCase(input);\n    }\n\n    public static long uuidChoice(ItemStack its) {\n        String mat = CoreUtilities.toLowerCase(its.getType().name());\n        if (mat.contains(\"boots\")) {\n            return 1000;\n        }\n        else if (mat.contains(\"legging\")) {\n            return 100000;\n        }\n        else if (mat.contains(\"helmet\")) {\n            return 10000000;\n        }\n        else if (mat.contains(\"chestp\")) {\n            return 1000000000;\n        }\n        else {\n            return 1;\n        }\n    }\n\n    private static EquipmentSlot fromLegacyName(String name) {\n        return switch (name) {\n            case \"mainhand\" -> EquipmentSlot.HAND;\n            case \"offhand\" -> EquipmentSlot.OFF_HAND;\n            default -> {\n                EquipmentSlot slot = ElementTag.asEnum(EquipmentSlot.class, name);\n                yield slot != null ? slot : EquipmentSlot.HAND;\n            }\n        };\n    }\n\n    private static String toLegacyName(EquipmentSlot slot) {\n        return switch (slot) {\n            case HAND -> \"mainhand\";\n            case OFF_HAND -> \"offhand\";\n            default -> CoreUtilities.toLowerCase(slot.name());\n        };\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemBaseColor.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.DyeColor;\r\nimport org.bukkit.Material;\r\n\r\npublic class ItemBaseColor extends ItemProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object ItemTag\r\n    // @name base_color\r\n    // @input ElementTag\r\n    // @description\r\n    // Controls the base color of a shield.\r\n    // For the list of possible colors, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/DyeColor.html>.\r\n    // Give no input with a shield to remove the base color (and any patterns).\r\n    // Tag returns null if there is no base color or patterns.\r\n    // -->\r\n\r\n    public static boolean describes(ItemTag item) {\r\n        return item.getBukkitMaterial() == Material.SHIELD;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        DyeColor color = NMSHandler.itemHelper.getShieldColor(getItemStack());\r\n        return color != null ? new ElementTag(color) : null;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (value != null && !mechanism.requireEnum(DyeColor.class)) {\r\n            return;\r\n        }\r\n        setItemStack(NMSHandler.itemHelper.setShieldColor(getItemStack(), value != null ? value.asEnum(DyeColor.class) : null));\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"base_color\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegisterNullable(\"base_color\", ItemBaseColor.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemBlockMaterial.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.MaterialTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.inventory.meta.BlockDataMeta;\n\npublic class ItemBlockMaterial implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && ((ItemTag) item).getItemMeta() instanceof BlockDataMeta;\n    }\n\n    public static ItemBlockMaterial getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemBlockMaterial((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"block_material\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"block_material\"\n    };\n\n    public ItemBlockMaterial(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.block_material>\n        // @returns MaterialTag\n        // @mechanism ItemTag.block_material\n        // @group properties\n        // @description\n        // Returns the material for an item with complex-block-data attached.\n        // -->\n        if (attribute.startsWith(\"block_material\")) {\n            BlockDataMeta meta = (BlockDataMeta) item.getItemMeta();\n            if (meta.hasBlockData()) {\n                return new MaterialTag(meta.getBlockData(item.getBukkitMaterial()))\n                        .getObjectAttribute(attribute.fulfill(1));\n            }\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        BlockDataMeta meta = (BlockDataMeta) item.getItemMeta();\n        if (meta.hasBlockData()) {\n            return new MaterialTag(meta.getBlockData(item.getBukkitMaterial())).identify();\n        }\n        return null;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"block_material\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name block_material\n        // @input MaterialTag\n        // @description\n        // Attaches complex-block-data from a material to an item.\n        // @tags\n        // <ItemTag.block_material>\n        // -->\n        if (mechanism.matches(\"block_material\") && mechanism.requireObject(MaterialTag.class)) {\n            BlockDataMeta meta = (BlockDataMeta) item.getItemMeta();\n            MaterialTag mat = mechanism.valueAsType(MaterialTag.class);\n            meta.setBlockData(mat.getModernData());\n            item.setItemMeta(meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemBook.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport com.denizenscript.denizencore.tags.core.EscapeTagUtil;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport org.bukkit.Material;\nimport org.bukkit.inventory.meta.BookMeta;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class ItemBook implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && (((ItemTag) item).getBukkitMaterial() == Material.WRITTEN_BOOK\n                || ((ItemTag) item).getBukkitMaterial() == Material.WRITABLE_BOOK);\n    }\n\n    public static ItemBook getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemBook((ItemTag) _item);\n        }\n    }\n\n    public ItemBook(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n\n    @Override\n    public String getPropertyString() {\n        return getBookMap().identify();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"book\";\n    }\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <ItemTag.book_author>\n        // @returns ElementTag\n        // @mechanism ItemTag.book_author\n        // @group properties\n        // @description\n        // Returns the author of the book.\n        // -->\n        PropertyParser.registerTag(ItemBook.class, ElementTag.class, \"book_author\", (attribute, object) -> {\n            BookMeta bookMeta = object.getBookMeta();\n            return bookMeta.hasAuthor() ? new ElementTag(bookMeta.getAuthor(), true) : null;\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.book_title>\n        // @returns ElementTag\n        // @mechanism ItemTag.book_title\n        // @group properties\n        // @description\n        // Returns the title of the book.\n        // -->\n        PropertyParser.registerTag(ItemBook.class, ElementTag.class, \"book_title\", (attribute, object) -> {\n            BookMeta bookMeta = object.getBookMeta();\n            return bookMeta.hasTitle() ? new ElementTag(bookMeta.getTitle()) : null;\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.book_pages>\n        // @returns ListTag\n        // @mechanism ItemTag.book_pages\n        // @group properties\n        // @description\n        // Returns the plain-text pages of the book as a ListTag.\n        // -->\n        PropertyParser.registerTag(ItemBook.class, ListTag.class, \"book_pages\", (attribute, object) -> {\n            List<BaseComponent[]> pages = object.getBookMeta().spigot().getPages();\n            ListTag pageList = new ListTag(pages.size());\n            for (BaseComponent[] page : pages) {\n                pageList.addObject(new ElementTag(FormattedTextHelper.stringify(page), true));\n            }\n            return pageList;\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.book_map>\n        // @returns MapTag\n        // @mechanism ItemTag.book\n        // @group properties\n        // @description\n        // Returns a MapTag of data about the book, with keys \"pages\" (a ListTag), and when available, \"author\" and \"title\".\n        // -->\n        PropertyParser.registerTag(ItemBook.class, MapTag.class, \"book_map\", (attribute, object) -> {\n            return object.getBookMap();\n        });\n\n\n        PropertyParser.registerTag(ItemBook.class, ObjectTag.class, \"book\", (attribute, object) -> {\n            BukkitImplDeprecations.itemBookTags.warn(attribute.context);\n            BookMeta bookMeta = object.getBookMeta();\n            if (object.isWrittenBook()) {\n                if (attribute.startsWith(\"author\", 2)) {\n                    attribute.fulfill(1);\n                    return new ElementTag(bookMeta.getAuthor());\n                }\n                if (attribute.startsWith(\"title\", 2)) {\n                    attribute.fulfill(1);\n                    return new ElementTag(bookMeta.getTitle());\n                }\n            }\n            if (attribute.startsWith(\"page_count\", 2)) {\n                attribute.fulfill(1);\n                return new ElementTag(bookMeta.getPageCount());\n            }\n            if ((attribute.startsWith(\"page\", 2) || attribute.startsWith(\"get_page\", 2)) && attribute.hasContext(2)) {\n                attribute.fulfill(1);\n                return new ElementTag(FormattedTextHelper.stringify(bookMeta.spigot().getPage(attribute.getIntParam())));\n            }\n            if (attribute.startsWith(\"pages\", 2)) {\n                attribute.fulfill(1);\n                ListTag output = new ListTag();\n                for (BaseComponent[] page : bookMeta.spigot().getPages()) {\n                    output.add(FormattedTextHelper.stringify(page));\n                }\n                return output;\n            }\n            String output = object.getOutputString();\n            if (output == null) {\n                output = \"null\";\n            }\n            return new ElementTag(output);\n        });\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name book_pages\n        // @input ListTag\n        // @description\n        // Changes the plain-text pages of a book item.\n        // @tags\n        // <ItemTag.book_pages>\n        // -->\n        PropertyParser.registerMechanism(ItemBook.class, ListTag.class, \"book_pages\", (object, mechanism, input) -> {\n            BookMeta bookMeta = object.getBookMeta();\n            List<BaseComponent[]> newPages = new ArrayList<>(input.size());\n            for (String page : input) {\n                newPages.add(FormattedTextHelper.parse(page, ChatColor.BLACK));\n            }\n            bookMeta.spigot().setPages(newPages);\n            object.item.setItemMeta(bookMeta);\n        });\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name book_author\n        // @input ElementTag\n        // @description\n        // Changes the author of a book item.\n        // @tags\n        // <ItemTag.book_author>\n        // -->\n        PropertyParser.registerMechanism(ItemBook.class, ElementTag.class, \"book_author\", (object, mechanism, input) -> {\n            if (!object.isWrittenBook()) {\n                mechanism.echoError(\"Only 'written_book' items can have an author!\");\n                return;\n            }\n            BookMeta bookMeta = object.getBookMeta();\n            bookMeta.setAuthor(input.asString());\n            object.item.setItemMeta(bookMeta);\n        });\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name book_title\n        // @input ElementTag\n        // @description\n        // Changes the title of a book item.\n        // @tags\n        // <ItemTag.book_title>\n        // -->\n        PropertyParser.registerMechanism(ItemBook.class, ElementTag.class, \"book_title\", (object, mechanism, input) -> {\n            if (!object.isWrittenBook()) {\n                mechanism.echoError(\"Only 'written_book' items can have a title!\");\n                return;\n            }\n            BookMeta bookMeta = object.getBookMeta();\n            bookMeta.setTitle(input.asString());\n            object.item.setItemMeta(bookMeta);\n        });\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name book\n        // @input MapTag\n        // @description\n        // Changes the information on a book item.\n        // Can have keys \"pages\" (a ListTag), \"title\", and \"author\", all optional.\n        // @tags\n        // <ItemTag.is_book>\n        // <ItemTag.book_title>\n        // <ItemTag.book_author>\n        // <ItemTag.book_pages>\n        // -->\n        PropertyParser.registerMechanism(ItemBook.class, ObjectTag.class, \"book\", (object, mechanism, input) -> {\n            BookMeta bookMeta = object.getBookMeta();\n            if (input.canBeType(MapTag.class)) {\n                MapTag bookMap = input.asType(MapTag.class, mechanism.context);\n                if (bookMap == null) {\n                    mechanism.echoError(\"Invalid book map specified: \" + input);\n                    return;\n                }\n                ElementTag author = bookMap.getElement(\"author\");\n                ElementTag title = bookMap.getElement(\"title\");\n                if (author != null) {\n                    if (!object.isWrittenBook()) {\n                        mechanism.echoError(\"Only 'written_book' items can have an author!\");\n                        return;\n                    }\n                    bookMeta.setAuthor(author.asString());\n                }\n                if (title != null) {\n                    if (!object.isWrittenBook()) {\n                        mechanism.echoError(\"Only 'written_book' items can have a title!\");\n                        return;\n                    }\n                    bookMeta.setTitle(title.asString());\n                }\n                ListTag pages = bookMap.getObjectAs(\"pages\", ListTag.class, mechanism.context);\n                if (pages != null) {\n                    List<BaseComponent[]> newPages = new ArrayList<>(pages.size());\n                    for (String page : pages) {\n                        newPages.add(FormattedTextHelper.parse(page, ChatColor.BLACK));\n                    }\n                    bookMeta.spigot().setPages(newPages);\n                }\n                object.item.setItemMeta(bookMeta);\n                return;\n            }\n            ListTag data = input.asType(ListTag.class, mechanism.context);\n            if (data.size() < 1) {\n                mechanism.echoError(\"Invalid book input!\");\n                return;\n            }\n            if (data.size() < 2) {\n                // Nothing to do, but not necessarily invalid.\n                return;\n            }\n            if (data.size() > 4 && data.get(0).equalsIgnoreCase(\"author\")\n                    && data.get(2).equalsIgnoreCase(\"title\")) {\n                if (!object.isWrittenBook()) {\n                    mechanism.echoError(\"Only 'written_book' items can have a title or author!\");\n                }\n                else {\n                    bookMeta.setAuthor(EscapeTagUtil.unEscape(data.get(1)));\n                    bookMeta.setTitle(EscapeTagUtil.unEscape(data.get(3)));\n                    for (int i = 0; i < 4; i++) {\n                        data.removeObject(0); // No .removeRange?\n                    }\n                }\n            }\n            if (data.get(0).equalsIgnoreCase(\"raw_pages\")) {\n                List<BaseComponent[]> newPages = new ArrayList<>(data.size());\n                for (int i = 1; i < data.size(); i++) {\n                    newPages.add(FormattedTextHelper.parseJson(EscapeTagUtil.unEscape(data.get(i))));\n                }\n                bookMeta.spigot().setPages(newPages);\n            }\n            else if (data.get(0).equalsIgnoreCase(\"pages\")) {\n                List<BaseComponent[]> newPages = new ArrayList<>(data.size());\n                for (int i = 1; i < data.size(); i++) {\n                    newPages.add(FormattedTextHelper.parse(EscapeTagUtil.unEscape(data.get(i)), ChatColor.BLACK));\n                }\n                bookMeta.spigot().setPages(newPages);\n            }\n            else {\n                mechanism.echoError(\"Invalid book input!\");\n            }\n            object.item.setItemMeta(bookMeta);\n        });\n    }\n\n    public MapTag getBookMap() {\n        MapTag bookMap = new MapTag();\n        BookMeta bookMeta = getBookMeta();\n        if (bookMeta.hasAuthor()) {\n            bookMap.putObject(\"author\", new ElementTag(bookMeta.getAuthor(), true));\n        }\n        if (bookMeta.hasTitle()) {\n            bookMap.putObject(\"title\", new ElementTag(bookMeta.getTitle(), true));\n        }\n        if (bookMeta.hasPages()) {\n            List<BaseComponent[]> pages = bookMeta.spigot().getPages();\n            ListTag pageList = new ListTag(pages.size());\n            for (BaseComponent[] page : pages) {\n                pageList.addObject(new ElementTag(FormattedTextHelper.stringify(page), true));\n            }\n            bookMap.putObject(\"pages\", pageList);\n        }\n        return bookMap;\n    }\n\n    public boolean isWrittenBook() {\n        return item.getBukkitMaterial() == Material.WRITTEN_BOOK;\n    }\n\n    public BookMeta getBookMeta() {\n        return (BookMeta) item.getItemMeta();\n    }\n\n    @Deprecated\n    public String getOutputString() {\n        StringBuilder output = new StringBuilder(128);\n        BookMeta bookMeta = getBookMeta();\n        if (isWrittenBook() && bookMeta.hasAuthor() && bookMeta.hasTitle()) {\n            output.append(\"author|\").append(EscapeTagUtil.escape(bookMeta.getAuthor()))\n                    .append(\"|title|\").append(EscapeTagUtil.escape(bookMeta.getTitle())).append(\"|\");\n        }\n        output.append(\"pages|\");\n        if (bookMeta.hasPages()) {\n            for (BaseComponent[] page : bookMeta.spigot().getPages()) {\n                output.append(EscapeTagUtil.escape(FormattedTextHelper.stringify(page))).append(\"|\");\n            }\n        }\n        return output.substring(0, output.length() - 1);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemBookGeneration.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.Material;\nimport org.bukkit.inventory.meta.BookMeta;\n\npublic class ItemBookGeneration implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag && ((ItemTag) item).getBukkitMaterial() == Material.WRITTEN_BOOK;\n    }\n\n    public static ItemBookGeneration getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemBookGeneration((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"book_generation\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"book_generation\"\n    };\n\n    public ItemBookGeneration(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.book_generation>\n        // @returns ListTag\n        // @mechanism ItemTag.book_generation\n        // @group properties\n        // @description\n        // Returns the generation of the book (if any), as ORIGINAL, COPY_OF_ORIGINAL, COPY_OF_COPY, or TATTERED.\n        // -->\n        if (attribute.startsWith(\"book_generation\")) {\n            BookMeta meta = (BookMeta) item.getItemMeta();\n            if (!meta.hasGeneration()) {\n                return null;\n            }\n            return new ElementTag(meta.getGeneration()).getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        BookMeta meta = (BookMeta) item.getItemMeta();\n        if (!meta.hasGeneration()) {\n            return null;\n        }\n        return meta.getGeneration().name();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"book_generation\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name book_generation\n        // @input ListTag\n        // @description\n        // Sets the generation of the book (if any), as ORIGINAL, COPY_OF_ORIGINAL, COPY_OF_COPY, or TATTERED.\n        // @tags\n        // <ItemTag.book_generation>\n        // -->\n        if (mechanism.matches(\"book_generation\") && mechanism.requireEnum(BookMeta.Generation.class)) {\n            BookMeta meta = (BookMeta) item.getItemMeta();\n            meta.setGeneration(BookMeta.Generation.valueOf(mechanism.getValue().asString().toUpperCase()));\n            item.setItemMeta(meta);\n        }\n\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemCanDestroy.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.List;\r\nimport java.util.stream.Collectors;\r\n\r\n// TODO: this should be deprecated in favor of a new property that properly represents the underlying data\r\npublic class ItemCanDestroy implements Property {\r\n\r\n    public static boolean describes(ObjectTag item) {\r\n        return item instanceof ItemTag;\r\n    }\r\n\r\n    public static ItemCanDestroy getFrom(ObjectTag item) {\r\n        if (!describes(item)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new ItemCanDestroy((ItemTag) item);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"can_destroy\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"can_destroy\"\r\n    };\r\n\r\n    public ItemCanDestroy(ItemTag item) {\r\n        this.item = item;\r\n    }\r\n\r\n    ItemTag item;\r\n\r\n    public ListTag getMaterials() {\r\n        ItemStack itemStack = item.getItemStack();\r\n        List<Material> materials = NMSHandler.itemHelper.getCanBreak(itemStack);\r\n        if (materials != null && !materials.isEmpty()) {\r\n            ListTag list = new ListTag();\r\n            for (Material material : materials) {\r\n                list.addObject(new MaterialTag(material));\r\n            }\r\n            return list;\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.can_destroy>\r\n        // @returns ListTag(MaterialTag)\r\n        // @group properties\r\n        // @mechanism ItemTag.can_destroy\r\n        // @description\r\n        // Returns a list of materials this item can destroy while in adventure mode, if any.\r\n        // -->\r\n        if (attribute.startsWith(\"can_destroy\")) {\r\n            ListTag materials = getMaterials();\r\n            if (materials != null) {\r\n                return materials.getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        ListTag materials = getMaterials();\r\n        return materials != null ? materials.identify() : null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"can_destroy\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name can_destroy\r\n        // @input ListTag(MaterialTag)\r\n        // @description\r\n        // Sets the materials this item can destroy while in adventure mode.\r\n        // Leave empty to remove this property.\r\n        // @tags\r\n        // <ItemTag.can_destroy>\r\n        // -->\r\n        if (mechanism.matches(\"can_destroy\")) {\r\n            if (item.getMaterial().getMaterial() == Material.AIR) {\r\n                mechanism.echoError(\"Cannot apply NBT to AIR!\");\r\n                return;\r\n            }\r\n\r\n            ItemStack itemStack = item.getItemStack();\r\n\r\n            if (mechanism.hasValue()) {\r\n                List<Material> materials = mechanism.valueAsType(ListTag.class).filter(MaterialTag.class, mechanism.context)\r\n                        .stream().map(MaterialTag::getMaterial).collect(Collectors.toList());\r\n                itemStack = NMSHandler.itemHelper.setCanBreak(itemStack, materials);\r\n            }\r\n            else {\r\n                itemStack = NMSHandler.itemHelper.setCanBreak(itemStack, null);\r\n            }\r\n\r\n            item.setItemStack(itemStack);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemCanPlaceOn.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.List;\r\nimport java.util.stream.Collectors;\r\n\r\n// TODO: this should be deprecated in favor of a new property that properly represents the underlying data\r\npublic class ItemCanPlaceOn implements Property {\r\n\r\n    public static boolean describes(ObjectTag item) {\r\n        return item instanceof ItemTag;\r\n    }\r\n\r\n    public static ItemCanPlaceOn getFrom(ObjectTag item) {\r\n        if (!describes(item)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new ItemCanPlaceOn((ItemTag) item);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"can_place_on\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"can_place_on\"\r\n    };\r\n\r\n    public ItemCanPlaceOn(ItemTag item) {\r\n        this.item = item;\r\n    }\r\n\r\n    ItemTag item;\r\n\r\n    public ListTag getMaterials() {\r\n        ItemStack itemStack = item.getItemStack();\r\n        List<Material> materials = NMSHandler.itemHelper.getCanPlaceOn(itemStack);\r\n        if (materials != null && !materials.isEmpty()) {\r\n            ListTag list = new ListTag();\r\n            for (Material material : materials) {\r\n                list.addObject(new MaterialTag(material));\r\n            }\r\n            return list;\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.can_place_on>\r\n        // @returns ListTag(MaterialTag)\r\n        // @group properties\r\n        // @mechanism ItemTag.can_place_on\r\n        // @description\r\n        // Returns a list of materials this item can be placed on while in adventure mode, if any.\r\n        // -->\r\n        if (attribute.startsWith(\"can_place_on\")) {\r\n            ListTag materials = getMaterials();\r\n            if (materials != null) {\r\n                return materials.getObjectAttribute(attribute.fulfill(1));\r\n            }\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        ListTag materials = getMaterials();\r\n        return materials != null ? materials.identify() : null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"can_place_on\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name can_place_on\r\n        // @input ListTag(MaterialTag)\r\n        // @description\r\n        // Sets the materials this item can be placed on while in adventure mode.\r\n        // Leave empty to remove this property.\r\n        // @tags\r\n        // <ItemTag.can_place_on>\r\n        // -->\r\n        if (mechanism.matches(\"can_place_on\")) {\r\n            if (item.getMaterial().getMaterial() == Material.AIR) {\r\n                mechanism.echoError(\"Cannot apply NBT to AIR!\");\r\n                return;\r\n            }\r\n\r\n            ItemStack itemStack = item.getItemStack();\r\n\r\n            if (mechanism.hasValue()) {\r\n                List<Material> materials = mechanism.valueAsType(ListTag.class).filter(MaterialTag.class, mechanism.context)\r\n                        .stream().map(MaterialTag::getMaterial).collect(Collectors.toList());\r\n                itemStack = NMSHandler.itemHelper.setCanPlaceOn(itemStack, materials);\r\n            }\r\n            else {\r\n                itemStack = NMSHandler.itemHelper.setCanPlaceOn(itemStack, null);\r\n            }\r\n\r\n            item.setItemStack(itemStack);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemChargedProjectile.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.meta.CrossbowMeta;\r\n\r\npublic class ItemChargedProjectile implements Property {\r\n\r\n    public static boolean describes(ObjectTag item) {\r\n        return item instanceof ItemTag\r\n                && ((ItemTag) item).getBukkitMaterial() == Material.CROSSBOW;\r\n    }\r\n\r\n    public static ItemChargedProjectile getFrom(ObjectTag item) {\r\n        if (!describes(item)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new ItemChargedProjectile((ItemTag) item);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"charged_projectiles\", \"is_charged\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"charged_projectiles\", \"add_charged_projectile\", \"remove_charged_projectiles\"\r\n    };\r\n\r\n    public ItemChargedProjectile(ItemTag _item) {\r\n        item = _item;\r\n    }\r\n\r\n    ItemTag item;\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.charged_projectiles>\r\n        // @returns ListTag(ItemTag)\r\n        // @mechanism ItemTag.charged_projectiles\r\n        // @group properties\r\n        // @description\r\n        // Returns a list of charged projectile items on this crossbow.\r\n        // -->\r\n        if (attribute.startsWith(\"charged_projectiles\")) {\r\n            return getChargedProjectiles()\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.is_charged>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism ItemTag.charged_projectiles\r\n        // @group properties\r\n        // @description\r\n        // Returns whether this crossbow is charged.\r\n        // -->\r\n        if (attribute.startsWith(\"is_charged\")) {\r\n            return new ElementTag(((CrossbowMeta) item.getItemMeta()).hasChargedProjectiles())\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    public ListTag getChargedProjectiles() {\r\n        CrossbowMeta meta = (CrossbowMeta) item.getItemMeta();\r\n        ListTag list = new ListTag();\r\n        if (!meta.hasChargedProjectiles()) {\r\n            return list;\r\n        }\r\n\r\n        for (ItemStack projectile : meta.getChargedProjectiles()) {\r\n            list.addObject(new ItemTag(projectile));\r\n        }\r\n        return list;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        ListTag projectiles = getChargedProjectiles();\r\n        return projectiles.size() > 0 ? projectiles.identify() : null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"charged_projectiles\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name charged_projectiles\r\n        // @input ListTag(ItemTag)\r\n        // @description\r\n        // Sets the charged projectile items on this crossbow. Charged projectiles may only be arrows and fireworks.\r\n        // @tags\r\n        // <ItemTag.charged_projectiles>\r\n        // <ItemTag.is_charged>\r\n        // -->\r\n        if (mechanism.matches(\"charged_projectiles\")) {\r\n            CrossbowMeta meta = (CrossbowMeta) item.getItemMeta();\r\n            meta.setChargedProjectiles(null);\r\n            for (ItemTag projectile : mechanism.valueAsType(ListTag.class).filter(ItemTag.class, mechanism.context)) {\r\n                try {\r\n                    meta.addChargedProjectile(projectile.getItemStack());\r\n                }\r\n                catch (IllegalArgumentException e) {\r\n                    mechanism.echoError(\"Charged crossbow projectiles may only be arrows or fireworks!\");\r\n                }\r\n            }\r\n            item.setItemMeta(meta);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name add_charged_projectile\r\n        // @input ItemTag\r\n        // @description\r\n        // Adds a new charged projectile item on this crossbow. Charged projectiles may only be arrows and fireworks.\r\n        // @tags\r\n        // <ItemTag.charged_projectiles>\r\n        // <ItemTag.is_charged>\r\n        // -->\r\n        if (mechanism.matches(\"add_charged_projectile\") && mechanism.requireObject(ItemTag.class)) {\r\n            CrossbowMeta meta = (CrossbowMeta) item.getItemMeta();\r\n            try {\r\n                meta.addChargedProjectile(mechanism.valueAsType(ItemTag.class).getItemStack());\r\n            }\r\n            catch (IllegalArgumentException e) {\r\n                mechanism.echoError(\"Charged crossbow projectiles may only be arrows or fireworks!\");\r\n            }\r\n            item.setItemMeta(meta);\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name remove_charged_projectiles\r\n        // @input None\r\n        // @description\r\n        // Removes all charged projectiles from this crossbow.\r\n        // @tags\r\n        // <ItemTag.charged_projectiles>\r\n        // <ItemTag.is_charged>\r\n        // -->\r\n        if (mechanism.matches(\"remove_charged_projectiles\")) {\r\n            CrossbowMeta meta = (CrossbowMeta) item.getItemMeta();\r\n            meta.setChargedProjectiles(null);\r\n            item.setItemMeta(meta);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemColor.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ColorTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport org.bukkit.Color;\nimport org.bukkit.inventory.meta.LeatherArmorMeta;\nimport org.bukkit.inventory.meta.MapMeta;\nimport org.bukkit.inventory.meta.PotionMeta;\n\npublic class ItemColor extends ItemProperty<ColorTag> {\n\n    // <--[property]\n    // @object ItemTag\n    // @name color\n    // @input ColorTag\n    // @description\n    // Controls the color of a leather armor, potion, filled map, or tipped arrow item.\n    // @tag\n    // For potions, will return a white <@link objecttype ColorTag> if the given item doesn't have a color.\n    // For maps, will return null if the given item doesn't have a color.\n    // -->\n\n    public static boolean describes(ItemTag item) {\n        return item.getItemMeta() instanceof LeatherArmorMeta\n                || item.getItemMeta() instanceof MapMeta\n                || item.getItemMeta() instanceof PotionMeta;\n    }\n\n    @Override\n    public ColorTag getPropertyValue() {\n        if (getItemMeta() instanceof LeatherArmorMeta leatherArmorMeta) {\n            return BukkitColorExtensions.fromColor(leatherArmorMeta.getColor());\n        }\n        if (getItemMeta() instanceof MapMeta mapMeta) {\n            if (!mapMeta.hasColor()) {\n                return null;\n            }\n            return BukkitColorExtensions.fromColor(mapMeta.getColor());\n        }\n        if (getItemMeta() instanceof PotionMeta potionMeta) {\n            if (!potionMeta.hasColor()) {\n                return null;\n            }\n            return BukkitColorExtensions.fromColor(potionMeta.getColor());\n        }\n        return null;\n    }\n\n    @Override\n    public void setPropertyValue(ColorTag color, Mechanism mechanism) {\n        if (getItemMeta() instanceof LeatherArmorMeta leatherArmorMeta) {\n            leatherArmorMeta.setColor(BukkitColorExtensions.getColor(color));\n            setItemMeta(leatherArmorMeta);\n            return;\n        }\n        if (getItemMeta() instanceof MapMeta mapMeta) {\n            mapMeta.setColor(BukkitColorExtensions.getColor(color));\n            setItemMeta(mapMeta);\n            return;\n        }\n        editMeta(PotionMeta.class, meta -> meta.setColor(BukkitColorExtensions.getColor(color)));\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"color\";\n    }\n\n    public static void register() {\n        PropertyParser.registerTag(ItemColor.class, ColorTag.class, \"color\", (attribute, item) -> {\n            if (item.getItemMeta() instanceof PotionMeta potionMeta && !potionMeta.hasColor()) {\n                return BukkitColorExtensions.fromColor(Color.WHITE);\n            }\n            else {\n                return item.getPropertyValue();\n            }\n        }, \"dye_color\");\n\n        PropertyParser.registerMechanism(ItemColor.class, ColorTag.class, \"color\", (item, mechanism, color) -> {\n            item.setPropertyValue(color, mechanism);\n        }, \"dye\", \"dye_color\");\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemComponentsPatch.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\nimport org.bukkit.Keyed;\nimport org.bukkit.Material;\nimport org.bukkit.entity.EntityType;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class ItemComponentsPatch extends ItemProperty<MapTag> {\n\n    // <--[property]\n    // @object ItemTag\n    // @name components_patch\n    // @input MapTag\n    // @description\n    // Controls the item's internal component patch. That is, the changes in components on top of the item type's default components.\n    // The map is in <@link language Raw NBT Encoding> format.\n    // This is mainly intended for item data persistence, and scripts should prefer using proper item properties instead of setting raw data directly.\n    // If you're trying to control custom data (such as data set by other plugins), use <@link property ItemTag.custom_data>.\n    // @tag\n    // Note that this is just data that isn't already controlled by other ItemTag properties, see <@link tag ItemTag.full_components_patch> for the complete component patch.\n    // @warning\n    // Due to this being a direct representation of internal data, compatibility for script usage across versions is not guaranteed.\n    // -->\n\n    public static final String DATA_VERSION_KEY = \"denizen:__data_version\";\n    public static final PerIdPropertyDataRemover ENTITY_DATA_REMOVER = new PerIdPropertyDataRemover(\"minecraft:entity_data\");\n    public static final PerIdPropertyDataRemover BLOCK_ENTITY_DATA_REMOVER = new PerIdPropertyDataRemover(\"minecraft:block_entity_data\");\n    public static final StringHolder INSTRUMENT_COMPONENT = new StringHolder(\"minecraft:instrument\");\n    public static final Set<String> propertyHandledComponents = new HashSet<>();\n\n    public static void registerHandledComponent(String component) {\n        propertyHandledComponents.add(\"minecraft:\" + component);\n    }\n\n    static {\n        ENTITY_DATA_REMOVER.registerRemoval(EntityType.ITEM_FRAME, \"Invisible\");\n        ENTITY_DATA_REMOVER.registerRemoval(EntityType.ARMOR_STAND, \"Pose\", \"Small\", \"NoBasePlate\", \"Marker\", \"Invisible\", \"ShowArms\");\n        BLOCK_ENTITY_DATA_REMOVER.registerRemoval(\"minecraft:sign\", \"front_text\", \"back_text\", \"is_waxed\");\n        BLOCK_ENTITY_DATA_REMOVER.registerRemoval(\"minecraft:hanging_sign\", \"front_text\", \"back_text\", \"is_waxed\");\n        BLOCK_ENTITY_DATA_REMOVER.registerRemoval(\"minecraft:spawner\",\n                \"SpawnCount\", \"Delay\", \"MinSpawnDelay\", \"MaxSpawnDelay\", \"MaxNearbyEntities\", \"RequiredPlayerRange\", \"SpawnRange\"\n//                , \"SpawnData\", \"SpawnPotentials\" TODO: needs proper property support\n        );\n    }\n\n    public static boolean describes(ItemTag item) {\n        return item.getBukkitMaterial() != Material.AIR;\n    }\n\n    @Override\n    public MapTag getPropertyValue() {\n        MapTag rawComponents = NMSHandler.itemHelper.getRawComponentsPatch(getItemStack(), true);\n        if (rawComponents.isEmpty()) {\n            return rawComponents;\n        }\n        ENTITY_DATA_REMOVER.removeFrom(rawComponents);\n        BLOCK_ENTITY_DATA_REMOVER.removeFrom(rawComponents);\n        rawComponents.map.computeIfPresent(INSTRUMENT_COMPONENT, (key, value) -> value instanceof ElementTag ? null : value);\n        if (rawComponents.size() == 1) { // Just the data version\n            return new MapTag();\n        }\n        return rawComponents;\n    }\n\n    @Override\n    public boolean isDefaultValue(MapTag value) {\n        return value.isEmpty();\n    }\n\n    @Override\n    public void setPropertyValue(MapTag value, Mechanism mechanism) {\n        ElementTag dataVersionInput = value.getElement(DATA_VERSION_KEY);\n        int dataVersion;\n        if (dataVersionInput == null) {\n            dataVersion = Integer.MAX_VALUE;\n        }\n        else if (!dataVersionInput.isInt()) {\n            mechanism.echoError(\"Invalid data version '\" + dataVersionInput + \"' specified: must be a valid non-decimal number.\");\n            return;\n        }\n        else {\n            dataVersion = dataVersionInput.asInt();\n            value.remove(DATA_VERSION_KEY);\n        }\n        setItemStack(NMSHandler.itemHelper.setRawComponentsPatch(getItemStack(), value, dataVersion, mechanism::echoError));\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"components_patch\";\n    }\n\n    public static void register() {\n        autoRegister(\"components_patch\", ItemComponentsPatch.class, MapTag.class, false);\n\n        // <--[tag]\n        // @attribute <ItemTag.full_components_patch>\n        // @returns MapTag\n        // @description\n        // Returns the item's entire internal component patch (see <@link tag ItemTag.components_patch>).\n        // @warning\n        // Due to this being a direct representation of internal data, compatibility for script usage across versions is not guaranteed.\n        // -->\n        PropertyParser.registerTag(ItemComponentsPatch.class, MapTag.class, \"full_components_patch\", (attribute, property) -> {\n            return NMSHandler.itemHelper.getRawComponentsPatch(property.getItemStack(), false);\n        });\n    }\n\n    public record PerIdPropertyDataRemover(StringHolder propertyId, Map<String, Set<StringHolder>> removalsPerId) {\n\n        public static final StringHolder ID_STRING_HOLDER = new StringHolder(\"id\");\n\n        public PerIdPropertyDataRemover(String propertyId) {\n            this(new StringHolder(propertyId), new HashMap<>());\n        }\n\n        public void registerRemoval(Keyed type, String... keys) {\n            registerRemoval(type.getKey().toString(), keys);\n        }\n\n        public void registerRemoval(String id, String... keys) {\n            Set<StringHolder> toRemove = new HashSet<>(keys.length + 1);\n            toRemove.add(ID_STRING_HOLDER);\n            for (String key : keys) {\n                toRemove.add(new StringHolder(key));\n            }\n            removalsPerId.put(\"string:\" + id, toRemove);\n        }\n\n        public void removeFrom(MapTag rawComponents) {\n            rawComponents.map.computeIfPresent(propertyId, (key, rawValue) -> {\n                MapTag value = (MapTag) rawValue;\n                Set<StringHolder> toRemove = removalsPerId.get(value.getObject(ID_STRING_HOLDER).toString());\n                if (toRemove != null && toRemove.size() >= value.size() && toRemove.containsAll(value.keySet())) {\n                    return null;\n                }\n                return rawValue;\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemCustomData.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.kyori.adventure.nbt.BinaryTag;\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\n\npublic class ItemCustomData extends ItemProperty<MapTag> {\n\n    // <--[property]\n    // @object ItemTag\n    // @name custom_data\n    // @input MapTag\n    // @description\n    // Controls an item's custom NBT data, if any.\n    // The map is in NBT format, see <@link language Raw NBT Encoding>.\n    // This does not include any normal vanilla data (enchantments, lore, etc.), just extra custom data.\n    // This is useful for integrating with items from external systems (such as custom items from plugins), but item flags should be preferred otherwise.\n    // @mechanism\n    // Provide no input to clear custom data.\n    // @tag-example\n    // # Use to check if an item has custom data from another plugin.\n    // - if <[item].custom_data.get[external_plugin_data].if_null[null]> == external_custom_item:\n    //   - narrate \"You are using an item from an external plugin!\"\n    // -->\n\n    // Custom data added by Denizen\n    public static final String[] DENIZEN_DATA = new String[] { \"Denizen Item Script\", \"DenizenItemScript\", \"Denizen NBT\", \"Denizen\" };\n\n    public static boolean describes(ItemTag item) {\n        return !item.getBukkitMaterial().isAir();\n    }\n\n    public ItemCustomData(ItemTag item) {\n        this.object = item;\n    }\n\n    @Override\n    public MapTag getPropertyValue() {\n        CompoundBinaryTag customData = NMSHandler.itemHelper.getCustomData(getItemStack());\n        if (customData == null) {\n            return null;\n        }\n        if (customData.isEmpty()) {\n            return new MapTag();\n        }\n        MapTag dataMap = (MapTag) ItemRawNBT.nbtTagToObject(customData);\n        for (String denizenKey : DENIZEN_DATA) {\n            dataMap.remove(denizenKey);\n        }\n        return dataMap.isEmpty() ? null : dataMap;\n    }\n\n    @Override\n    public void setPropertyValue(MapTag value, Mechanism mechanism) {\n        if (value == null) {\n            setItemStack(NMSHandler.itemHelper.setCustomData(getItemStack(), addDenizenKeys(null)));\n            return;\n        }\n        CompoundBinaryTag customData;\n        try {\n            customData = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(value, mechanism.context, \"(data)\");\n        }\n        catch (Exception ex) {\n            mechanism.echoError(\"Invalid custom data specified:\");\n            Debug.echoError(ex);\n            return;\n        }\n        if (customData == null) {\n            mechanism.echoError(\"Invalid custom data specified.\");\n            return;\n        }\n        setItemStack(NMSHandler.itemHelper.setCustomData(getItemStack(), addDenizenKeys(customData)));\n    }\n\n    private CompoundBinaryTag addDenizenKeys(CompoundBinaryTag tag) {\n        CompoundBinaryTag currentData = NMSHandler.itemHelper.getCustomData(getItemStack());\n        if (currentData == null || currentData.isEmpty()) {\n            return tag;\n        }\n        CompoundBinaryTag.Builder tagBuilder = null;\n        for (String denizenKey : DENIZEN_DATA) {\n            BinaryTag denizenValue = currentData.get(denizenKey);\n            if (denizenValue != null) {\n                if (tagBuilder == null) {\n                    // TODO: adventure-nbt: compound tag builder\n                    tagBuilder = tag != null ? CompoundBinaryTag.builder().put(tag) : CompoundBinaryTag.builder();\n                }\n                tagBuilder.put(denizenKey, denizenValue);\n            }\n        }\n        return tagBuilder != null ? tagBuilder.build() : tag;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"custom_data\";\n    }\n\n    public static void register() {\n        autoRegisterNullable(\"custom_data\", ItemCustomData.class, MapTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemCustomModel.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport org.bukkit.inventory.meta.ItemMeta;\n\npublic class ItemCustomModel extends ItemProperty<ElementTag> {\n\n    // <--[property]\n    // @object ItemTag\n    // @name custom_model_data\n    // @input ElementTag(Number)\n    // @description\n    // Controls the custom model data ID number of the item.\n    // Use with no input to remove the custom model data.\n    // Prefer <@link property ItemTag.item_model> on MC 1.21+.\n    // See also <@link tag ItemTag.has_custom_model_data>\n    // -->\n    public static boolean describes(ItemTag item) {\n        return !item.getBukkitMaterial().isAir();\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        if (getItemMeta().hasCustomModelData()) {\n            return new ElementTag(getItemMeta().getCustomModelData());\n        }\n        return null;\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\n        ItemMeta meta = getItemMeta();\n        if (mechanism.hasValue() && mechanism.requireInteger()) {\n            meta.setCustomModelData(value.asInt());\n        }\n        else {\n            meta.setCustomModelData(null);\n        }\n        setItemMeta(meta);\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"custom_model_data\";\n    }\n\n    public static void register() {\n        autoRegisterNullable(\"custom_model_data\", ItemCustomModel.class, ElementTag.class, false);\n\n        // <--[tag]\n        // @attribute <ItemTag.has_custom_model_data>\n        // @returns ElementTag(Boolean)\n        // @mechanism ItemTag.custom_model_data\n        // @group properties\n        // @description\n        // Returns whether the item has a custom model data ID number set on it.\n        // Prefer <@link property ItemTag.item_model> on MC 1.21+.\n        // See also <@link tag ItemTag.custom_model_data>.\n        // -->\n        PropertyParser.registerTag(ItemCustomModel.class, ElementTag.class, \"has_custom_model_data\", (attribute, prop) -> {\n            return new ElementTag(prop.getItemMeta().hasCustomModelData());\n        });\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemDisplayname.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.tags.core.EscapeTagUtil;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport org.bukkit.ChatColor;\nimport org.bukkit.inventory.meta.ItemMeta;\n\npublic class ItemDisplayname implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        // Technically, all items can have a display name\n        return item instanceof ItemTag;\n    }\n\n    public static ItemDisplayname getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemDisplayname((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"display\", \"has_display\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"display\", \"display_name\"\n    };\n\n    public ItemDisplayname(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    public boolean hasDisplayName() {\n        return item.getItemMeta() != null && item.getItemMeta().hasDisplayName();\n    }\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.display>\n        // @returns ElementTag\n        // @mechanism ItemTag.display\n        // @synonyms ItemTag.display_name\n        // @group properties\n        // @description\n        // Returns the display name of the item, as set by plugin or an anvil.\n        // -->\n        if (attribute.startsWith(\"display\")) {\n            if (hasDisplayName()) {\n                return new ElementTag(NMSHandler.itemHelper.getDisplayName(item), true)\n                        .getObjectAttribute(attribute.fulfill(1));\n            }\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.has_display>\n        // @returns ElementTag(Boolean)\n        // @mechanism ItemTag.display\n        // @group properties\n        // @description\n        // Returns whether the item has a custom set display name.\n        // -->\n        if (attribute.startsWith(\"has_display\")) {\n            return new ElementTag(hasDisplayName())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        if (hasDisplayName()) {\n            String res = NMSHandler.itemHelper.getDisplayName(item);\n            if (res.isEmpty()) { // Special case: persist empty strings as a single empty color code so it's not ignored\n                return ChatColor.WHITE.toString();\n            }\n            return res;\n        }\n        else {\n            return null;\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"display\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name display\n        // @input ElementTag\n        // @synonyms ItemTag.display_name\n        // @description\n        // Changes the item's display name.\n        // Give no input to remove the item's display name.\n        // @tags\n        // <ItemTag.display>\n        // -->\n        if (mechanism.matches(\"display\")) {\n            if (!mechanism.hasValue()) {\n                ItemMeta meta = item.getItemMeta();\n                meta.setDisplayName(null);\n                item.setItemMeta(meta);\n            }\n            else {\n                NMSHandler.itemHelper.setDisplayName(item, mechanism.getValue().asString());\n            }\n        }\n        else if (mechanism.matches(\"display_name\")) {\n            BukkitImplDeprecations.itemDisplayNameMechanism.warn(mechanism.context);\n            ItemMeta meta = item.getItemMeta();\n            meta.setDisplayName(mechanism.hasValue() ? CoreUtilities.clearNBSPs(EscapeTagUtil.unEscape(mechanism.getValue().asString())) : null);\n            item.setItemMeta(meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemDurability.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.inventory.meta.Damageable;\n\npublic class ItemDurability implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && ((ItemTag) item).isRepairable();\n    }\n\n    public static ItemDurability getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemDurability((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"durability\", \"max_durability\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"durability\"\n    };\n\n    public ItemDurability(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.durability>\n        // @returns ElementTag(Number)\n        // @mechanism ItemTag.durability\n        // @group properties\n        // @description\n        // Returns the current durability (number of uses) on the item.\n        // -->\n        if (attribute.startsWith(\"durability\")) {\n            return new ElementTag(((Damageable) item.getItemMeta()).getDamage())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.max_durability>\n        // @returns ElementTag(Number)\n        // @group properties\n        // @description\n        // Returns the maximum durability (number of uses) of this item.\n        // For use with <@link tag ItemTag.durability> and <@link mechanism ItemTag.durability>.\n        // -->\n        if (attribute.startsWith(\"max_durability\")) {\n            return new ElementTag(item.getMaterial().getMaterial().getMaxDurability())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        int durability = ((Damageable) item.getItemMeta()).getDamage();\n        if (durability != 0) {\n            return String.valueOf(durability);\n        }\n        else {\n            return null;\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"durability\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name durability\n        // @input ElementTag(Number)\n        // @description\n        // Changes the durability of damageable items.\n        // @tags\n        // <ItemTag.durability>\n        // <ItemTag.max_durability>\n        // <ItemTag.repairable>\n        // -->\n        if (mechanism.matches(\"durability\") && mechanism.requireInteger()) {\n            item.setDurability((short) mechanism.getValue().asInt());\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemEnchantments.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.EnchantmentTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\nimport org.bukkit.Material;\nimport org.bukkit.enchantments.Enchantment;\nimport org.bukkit.inventory.meta.EnchantmentStorageMeta;\n\nimport java.util.*;\n\npublic class ItemEnchantments implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        // Technically, all items can hold enchants.\n        return item instanceof ItemTag;\n    }\n\n    public static ItemEnchantments getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemEnchantments((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"is_enchanted\", \"enchantments\", \"enchantment_map\", \"enchantment_types\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"remove_enchantments\", \"enchantments\"\n    };\n\n    public ItemEnchantments(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.is_enchanted>\n        // @returns ElementTag(Boolean)\n        // @mechanism ItemTag.enchantments\n        // @group properties\n        // @description\n        // Returns whether the item has any enchantments.\n        // -->\n        if (attribute.startsWith(\"is_enchanted\")) {\n            return new ElementTag(getEnchantments().size() > 0)\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        if (attribute.startsWith(\"enchantments.with_levels\")) {\n            BukkitImplDeprecations.itemEnchantmentTags.warn(attribute.context);\n            Set<Map.Entry<Enchantment, Integer>> enchantments = getEnchantments();\n            ListTag enchants = new ListTag();\n            for (Map.Entry<Enchantment, Integer> enchantment : enchantments) {\n                enchants.add(new EnchantmentTag(enchantment.getKey()).getCleanName() + \",\" + enchantment.getValue());\n            }\n            return enchants.getObjectAttribute(attribute.fulfill(2));\n        }\n        if (attribute.startsWith(\"enchantments.levels\")) {\n            BukkitImplDeprecations.itemEnchantmentTags.warn(attribute.context);\n            Set<Map.Entry<Enchantment, Integer>> enchantments = getEnchantments();\n            ListTag enchants = new ListTag();\n            for (Map.Entry<Enchantment, Integer> enchantment : enchantments) {\n                enchants.add(String.valueOf(enchantment.getValue()));\n            }\n            return enchants.getObjectAttribute(attribute.fulfill(2));\n        }\n        if (attribute.startsWith(\"enchantments.level\") && attribute.hasContext(2)) {\n            BukkitImplDeprecations.itemEnchantmentTags.warn(attribute.context);\n            Set<Map.Entry<Enchantment, Integer>> enchantments = getEnchantments();\n            if (enchantments.size() > 0) {\n                for (Map.Entry<Enchantment, Integer> enchantment : enchantments) {\n                    if (enchantment.getKey().getName().equalsIgnoreCase(attribute.getContext(2))\n                            || new EnchantmentTag(enchantment.getKey()).getCleanName().equalsIgnoreCase(attribute.getContext(2))) {\n                        return new ElementTag(enchantment.getValue())\n                                .getObjectAttribute(attribute.fulfill(2));\n                    }\n                }\n            }\n            return new ElementTag(0)\n                    .getObjectAttribute(attribute.fulfill(2));\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.enchantment_types>\n        // @returns ListTag(EnchantmentTag)\n        // @mechanism ItemTag.enchantments\n        // @group properties\n        // @description\n        // Returns a list of the types of enchantments on the item.\n        // -->\n        if (attribute.startsWith(\"enchantment_types\")) {\n            Set<Map.Entry<Enchantment, Integer>> enchantments = getEnchantments();\n            ListTag enchants = new ListTag();\n            for (Map.Entry<Enchantment, Integer> enchantment : enchantments) {\n                enchants.addObject(new EnchantmentTag(enchantment.getKey()));\n            }\n            return enchants.getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.enchantments>\n        // @returns ListTag\n        // @mechanism ItemTag.enchantments\n        // @group properties\n        // @deprecated Use 'enchantment_types' or 'enchantment_map'\n        // @description\n        // Deprecated in favor of <@link tag ItemTag.enchantment_types> or <@link tag ItemTag.enchantment_map>\n        // -->\n        if (attribute.startsWith(\"enchantments\")) {\n            BukkitImplDeprecations.itemEnchantmentsLegacy.warn(attribute.context);\n            Set<Map.Entry<Enchantment, Integer>> enchantments = getEnchantments();\n            ListTag enchants = new ListTag();\n            for (Map.Entry<Enchantment, Integer> enchantment : enchantments) {\n                enchants.add(new EnchantmentTag(enchantment.getKey()).getCleanName());\n            }\n            return enchants.getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.enchantment_map>\n        // @returns MapTag\n        // @mechanism ItemTag.enchantments\n        // @group properties\n        // @description\n        // Returns a map of enchantments on the item.\n        // Map keys are enchantment names (like \"sharpness\"), and values are the level (as a number).\n        // -->\n        if (attribute.startsWith(\"enchantment_map\")) {\n            return getEnchantmentMap().getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    public MapTag getEnchantmentMap() {\n        MapTag enchants = new MapTag();\n        for (Map.Entry<Enchantment, Integer> enchantment : getEnchantments()) {\n            enchants.putObject(new EnchantmentTag(enchantment.getKey()).getCleanName(), new ElementTag(enchantment.getValue()));\n        }\n        return enchants;\n    }\n\n    public Set<Map.Entry<Enchantment, Integer>> getEnchantments() {\n        if (item.getItemStack().getEnchantments().size() > 0) {\n            return item.getItemStack().getEnchantments().entrySet();\n        }\n        else if (item.getItemMeta() instanceof EnchantmentStorageMeta) {\n            return ((EnchantmentStorageMeta) item.getItemMeta()).getStoredEnchants().entrySet();\n        }\n        return new HashSet<>();\n    }\n\n    @Override\n    public String getPropertyString() {\n        MapTag map = getEnchantmentMap();\n        if (map.isEmpty()) {\n            return null;\n        }\n        return map.toString();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"enchantments\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name remove_enchantments\n        // @input ListTag\n        // @description\n        // Removes the specified enchantments from the item (as a list of EnchantmentTags).\n        // Give no value input to remove all enchantments.\n        // @tags\n        // <ItemTag.enchantment_types>\n        // <ItemTag.enchantment_map>\n        // -->\n        if (mechanism.matches(\"remove_enchantments\")) {\n            if (mechanism.hasValue()) {\n                List<EnchantmentTag> toRemove = mechanism.valueAsType(ListTag.class).filter(EnchantmentTag.class, mechanism.context);\n                if (item.getBukkitMaterial() == Material.ENCHANTED_BOOK) {\n                    EnchantmentStorageMeta meta = (EnchantmentStorageMeta) item.getItemMeta();\n                    for (EnchantmentTag ench : toRemove) {\n                        meta.removeStoredEnchant(ench.enchantment);\n                    }\n                    item.setItemMeta(meta);\n\n                }\n                else {\n                    for (EnchantmentTag ench : toRemove) {\n                        item.getItemStack().removeEnchantment(ench.enchantment);\n                    }\n                    item.resetCache();\n                }\n            }\n            else {\n                if (item.getBukkitMaterial() == Material.ENCHANTED_BOOK) {\n                    EnchantmentStorageMeta meta = (EnchantmentStorageMeta) item.getItemMeta();\n                    for (Enchantment ench : meta.getStoredEnchants().keySet()) {\n                        meta.removeStoredEnchant(ench);\n                    }\n                    item.setItemMeta(meta);\n                }\n                else {\n                    for (Enchantment ench : item.getItemStack().getEnchantments().keySet()) {\n                        item.getItemStack().removeEnchantment(ench);\n                    }\n                    item.resetCache();\n                }\n            }\n        }\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name enchantments\n        // @input MapTag\n        // @description\n        // Sets the item's enchantments as a map of EnchantmentTags or enchantment names to level.\n        // For example: - inventory adjust slot:hand enchantments:sharpness=1\n        // Does not remove existing enchantments, for that use <@link mechanism ItemTag.remove_enchantments>\n        // @tags\n        // <ItemTag.enchantment_map>\n        // -->\n        if (mechanism.matches(\"enchantments\")) {\n            String val = mechanism.getValue().asString();\n            if (val.startsWith(\"map@\") || val.startsWith(\"[\") || (val.contains(\"=\") && !val.contains(\",\"))) {\n                MapTag map = mechanism.valueAsType(MapTag.class);\n                for (Map.Entry<StringHolder, ObjectTag> enchantments : map.entrySet()) {\n                    Enchantment ench = EnchantmentTag.valueOf(enchantments.getKey().low, mechanism.context).enchantment;\n                    int level = enchantments.getValue().asElement().asInt();\n                    if (ench != null) {\n                        if (item.getBukkitMaterial() == Material.ENCHANTED_BOOK) {\n                            EnchantmentStorageMeta meta = (EnchantmentStorageMeta) item.getItemMeta();\n                            meta.addStoredEnchant(ench, level, true);\n                            item.setItemMeta(meta);\n                        }\n                        else {\n                            item.getItemStack().addUnsafeEnchantment(ench, level);\n                            item.resetCache();\n                        }\n                    }\n                    else {\n                        mechanism.echoError(\"Unknown enchantment '\" + enchantments.getKey().str + \"'\");\n                    }\n                }\n            }\n            else {\n                for (String enchant : mechanism.valueAsType(ListTag.class)) {\n                    if (!enchant.contains(\",\")) {\n                        mechanism.echoError(\"Invalid enchantment format, use name,level|...\");\n                    }\n                    else {\n                        String[] data = enchant.split(\",\", 2);\n                        try {\n                            Enchantment ench = EnchantmentTag.valueOf(data[0], mechanism.context).enchantment;\n                            if (ench != null) {\n                                if (item.getBukkitMaterial() == Material.ENCHANTED_BOOK) {\n                                    EnchantmentStorageMeta meta = (EnchantmentStorageMeta) item.getItemMeta();\n                                    meta.addStoredEnchant(ench, Integer.parseInt(data[1]), true);\n                                    item.setItemMeta(meta);\n                                }\n                                else {\n                                    item.getItemStack().addUnsafeEnchantment(ench, Integer.parseInt(data[1]));\n                                    item.resetCache();\n                                }\n                            }\n                            else {\n                                mechanism.echoError(\"Unknown enchantment '\" + data[0] + \"'\");\n                            }\n                        }\n                        catch (NullPointerException e) {\n                            mechanism.echoError(\"Unknown enchantment '\" + data[0] + \"'\");\n                        }\n                        catch (NumberFormatException ex) {\n                            mechanism.echoError(\"Cannot apply enchantment '\" + data[0] + \"': '\" + data[1] + \"' is not a valid integer!\");\n                            if (CoreConfiguration.debugVerbose) {\n                                Debug.echoError(ex);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemFirework.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\nimport com.denizenscript.denizencore.objects.core.ColorTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport org.bukkit.Color;\nimport org.bukkit.FireworkEffect;\nimport org.bukkit.inventory.meta.FireworkEffectMeta;\nimport org.bukkit.inventory.meta.FireworkMeta;\nimport org.bukkit.inventory.meta.ItemMeta;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\npublic class ItemFirework implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && ((((ItemTag) item).getItemMeta() instanceof FireworkMeta)\n                || (((ItemTag) item).getItemMeta() instanceof FireworkEffectMeta));\n    }\n\n    public static ItemFirework getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemFirework((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledMechs = new String[] {\n            \"firework\"\n    };\n\n    public ItemFirework(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    public ListTag getFireworkData() {\n        List<FireworkEffect> effects;\n        ListTag list = new ListTag();\n        if (item.getItemMeta() instanceof FireworkMeta) {\n            effects = ((FireworkMeta) item.getItemMeta()).getEffects();\n            int power = ((FireworkMeta) item.getItemMeta()).getPower();\n            if (power != 0) {\n                list.add(String.valueOf(power));\n            }\n        }\n        else {\n            effects = Collections.singletonList(((FireworkEffectMeta) item.getItemMeta()).getEffect());\n        }\n        if (effects != null) {\n            for (FireworkEffect effect : effects) {\n                if (effect == null) {\n                    continue;\n                }\n                Color ColOne = effect.getColors() != null && effect.getColors().size() > 0 ? effect.getColors().get(0) : Color.BLUE;\n                Color ColTwo = effect.getFadeColors() != null && effect.getFadeColors().size() > 0 ? effect.getFadeColors().get(0) : ColOne;\n                list.add(effect.hasTrail() + \",\" + effect.hasFlicker() + \",\" + effect.getType().name() + \",\" +\n                        ColOne.getRed() + \",\" + ColOne.getGreen() + \",\" + ColOne.getBlue() + \",\" +\n                        ColTwo.getRed() + \",\" + ColTwo.getGreen() + \",\" + ColTwo.getBlue());\n            }\n        }\n        return list;\n    }\n\n    public ListTag getFireworkDataMap() {\n        List<FireworkEffect> effects;\n        ListTag list = new ListTag();\n        if (item.getItemMeta() instanceof FireworkMeta) {\n            effects = ((FireworkMeta) item.getItemMeta()).getEffects();\n        }\n        else {\n            effects = Collections.singletonList(((FireworkEffectMeta) item.getItemMeta()).getEffect());\n        }\n        if (effects != null) {\n            for (FireworkEffect effect : effects) {\n                if (effect == null) {\n                    continue;\n                }\n                ListTag colors = new ListTag(effect.getColors().stream().map(BukkitColorExtensions::fromColor).collect(Collectors.toList()));\n                ListTag fadeColors = new ListTag(effect.getFadeColors().stream().map(BukkitColorExtensions::fromColor).collect(Collectors.toList()));\n                if (colors.isEmpty()) {\n                    colors.addObject(BukkitColorExtensions.fromColor(Color.BLUE));\n                }\n                if (fadeColors.isEmpty()) {\n                    fadeColors.addObject(colors.getObject(0));\n                }\n                MapTag effectMap = new MapTag();\n                effectMap.putObject(\"trail\", new ElementTag(effect.hasTrail()));\n                effectMap.putObject(\"flicker\", new ElementTag(effect.hasFlicker()));\n                effectMap.putObject(\"type\", new ElementTag(effect.getType()));\n                effectMap.putObject(\"color\", colors.size() == 1 ? colors.getObject(0) : colors);\n                effectMap.putObject(\"fade_color\", fadeColors.size() == 1 ? fadeColors.getObject(0) : fadeColors);\n                list.addObject(effectMap);\n            }\n        }\n        return list;\n    }\n\n    public int getPower() {\n        return item.getItemMeta() instanceof FireworkMeta ? ((FireworkMeta) item.getItemMeta()).getPower() : 0;\n    }\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <ItemTag.firework>\n        // @returns ListTag\n        // @group properties\n        // @mechanism ItemTag.firework\n        // @description\n        // Returns the firework's property value as a list, matching the non-MapTag format of the mechanism.\n        // Consider instead using <@link tag ItemTag.firework_data>\n        // -->\n        PropertyParser.registerTag(ItemFirework.class, ListTag.class, \"firework\", (attribute, object) -> {\n            return object.getFireworkData();\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.firework_data>\n        // @returns ListTag\n        // @group properties\n        // @mechanism ItemTag.firework\n        // @description\n        // Returns the firework's property value as a ListTag of MapTags, matching the MapTag format of the mechanism.\n        // -->\n        PropertyParser.registerTag(ItemFirework.class, ListTag.class, \"firework_data\", (attribute, object) -> {\n            return object.getFireworkDataMap();\n        });\n    }\n\n    @Override\n    public String getPropertyString() {\n        ListTag data = getFireworkDataMap();\n        return data.size() > 0 ? data.identify() : null;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"firework\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name firework\n        // @input ListTag\n        // @description\n        // Sets the firework's settings.\n        // Each item in the list can be any of the following:\n        // 1: Comma-separated effect data in the format: TRAIL,FLICKER,TYPE,RED,GREEN,BLUE,RED,GREEN,BLUE\n        // For example: true,false,BALL,255,0,0,0,255,0 would create a trailing ball firework that fades from red to green.\n        // 2: A MapTag, with \"type\", \"color\", \"fade_color\", \"trail\", and \"flicker\" keys.\n        // For example: [type=ball;color=red;fade_color=green;trail=true;flicker=false]\n        // 3: A single number, to set the power.\n        // Types: ball, ball_large, star, burst, or creeper\n        // \"color\" and \"fade_color\" may be a list of colors.\n        // Note that this is an add operation, provide no input to clear all effects.\n        // @tags\n        // <ItemTag.firework>\n        // <ItemTag.firework_data>\n        // -->\n        if (mechanism.matches(\"firework\")) {\n            ItemMeta meta = item.getItemMeta();\n            if (!mechanism.hasValue()) {\n                if (meta instanceof FireworkMeta) {\n                    ((FireworkMeta) meta).clearEffects();\n                }\n                else {\n                    ((FireworkEffectMeta) meta).setEffect(null);\n                }\n            }\n            else {\n                Collection<ObjectTag> list = CoreUtilities.objectToList(mechanism.getValue(), mechanism.context);\n                for (ObjectTag object : list) {\n                    if (object.canBeType(MapTag.class)) {\n                        MapTag effectMap = object.asType(MapTag.class, mechanism.context);\n                        FireworkEffect.Builder builder = FireworkEffect.builder();\n                        ElementTag type = effectMap.getElement(\"type\");\n                        ObjectTag color = effectMap.getObject(\"color\");\n                        ObjectTag fadeColor = effectMap.getObject(\"fade_color\");\n                        ElementTag trail = effectMap.getElement(\"trail\", \"false\");\n                        ElementTag flicker = effectMap.getElement(\"flicker\", \"false\");\n                        builder.trail(trail.asBoolean());\n                        builder.flicker(flicker.asBoolean());\n                        if (type != null) {\n                            if (type.matchesEnum(FireworkEffect.Type.class)) {\n                                builder.with(type.asEnum(FireworkEffect.Type.class));\n                            }\n                            else {\n                                mechanism.echoError(\"Invalid firework type '\" + type.asString() + \"'\");\n                            }\n                        }\n                        List<Color> colors = Collections.singletonList(Color.BLACK);\n                        if (color != null) {\n                            colors = color.asType(ListTag.class, mechanism.context).filter(ColorTag.class, mechanism.context).stream().map(BukkitColorExtensions::getColor).collect(Collectors.toList());\n                        }\n                        builder.withColor(colors);\n                        if (fadeColor != null) {\n                            List<Color> fadeColors = fadeColor.asType(ListTag.class, mechanism.context).filter(ColorTag.class, mechanism.context).stream().map(BukkitColorExtensions::getColor).collect(Collectors.toList());\n                            builder.withFade(fadeColors);\n                        }\n                        FireworkEffect built = builder.build();\n                        if (meta instanceof FireworkMeta) {\n                            ((FireworkMeta) meta).addEffect(built);\n                        }\n                        else {\n                            ((FireworkEffectMeta) meta).setEffect(built);\n                        }\n                    }\n                    else {\n                        String effect = object.toString();\n                        String[] data = effect.split(\",\");\n                        if (data.length == 9) {\n                            FireworkEffect.Builder builder = FireworkEffect.builder();\n                            builder.trail(new ElementTag(data[0]).asBoolean());\n                            builder.flicker(new ElementTag(data[1]).asBoolean());\n                            if (new ElementTag(data[2]).matchesEnum(FireworkEffect.Type.class)) {\n                                builder.with(FireworkEffect.Type.valueOf(data[2].toUpperCase()));\n                            }\n                            else {\n                                mechanism.echoError(\"Invalid firework type '\" + data[2] + \"'\");\n                            }\n                            builder.withColor(Color.fromRGB(new ElementTag(data[3]).asInt(),\n                                    new ElementTag(data[4]).asInt(),\n                                    new ElementTag(data[5]).asInt()));\n                            builder.withFade(Color.fromRGB(new ElementTag(data[6]).asInt(),\n                                    new ElementTag(data[7]).asInt(),\n                                    new ElementTag(data[8]).asInt()));\n\n                            FireworkEffect built = builder.build();\n                            if (meta instanceof FireworkMeta) {\n                                ((FireworkMeta) meta).addEffect(built);\n                            }\n                            else {\n                                ((FireworkEffectMeta) meta).setEffect(built);\n                            }\n                        }\n                        else if (data.length == 1) {\n                            if (meta instanceof FireworkMeta) {\n                                ((FireworkMeta) meta).setPower(new ElementTag(data[0]).asInt());\n                            }\n                            else {\n                                mechanism.echoError(\"Cannot set the power of a firework effect!\");\n                            }\n                        }\n                        else {\n                            mechanism.echoError(\"Invalid firework data '\" + effect + \"'\");\n                        }\n                    }\n                }\n            }\n            item.setItemMeta(meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemFireworkPower.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.inventory.meta.FireworkMeta;\r\n\r\npublic class ItemFireworkPower extends ItemProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object ItemTag\r\n    // @name firework_power\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls the firework's power.\r\n    // Power primarily affects how high the firework flies, with each level of power corresponding to approximately half a second of additional flight time.\r\n    // -->\r\n\r\n    public static boolean describes(ItemTag item) {\r\n        return item.getItemMeta() instanceof FireworkMeta;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(as(FireworkMeta.class).getPower());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"firework_power\";\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireInteger()) {\r\n            editMeta(FireworkMeta.class, fireworkMeta -> fireworkMeta.setPower(value.asInt()));\r\n        }\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"firework_power\", ItemFireworkPower.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemFlags.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.MapTagFlagTracker;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.core.TimeTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.scripts.commands.core.FlagCommand;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.utilities.data.DataAction;\r\nimport com.denizenscript.denizencore.utilities.data.DataActionHelper;\r\n\r\npublic class ItemFlags implements Property {\r\n\r\n    public static boolean describes(ObjectTag item) {\r\n        return item instanceof ItemTag;\r\n    }\r\n\r\n    public static ItemFlags getFrom(ObjectTag item) {\r\n        if (!describes(item)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new ItemFlags((ItemTag) item);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"with_flag\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"flag\", \"flag_map\"\r\n    };\r\n\r\n    public ItemFlags(ItemTag item) {\r\n        this.item = item;\r\n    }\r\n\r\n    ItemTag item;\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.with_flag[<flag_set_action>]>\r\n        // @returns ItemTag\r\n        // @mechanism ItemTag.flag\r\n        // @group properties\r\n        // @description\r\n        // Returns a copy of the item with the specified flag data action applied to it.\r\n        // For example: <[item].with_flag[myflagname]>, or <[item].with_flag[myflag:myvalue]>, or <[item].with_flag[mycounter:+:<[amount]>]>\r\n        // -->\r\n        if (attribute.startsWith(\"with_flag\")) {\r\n            ItemTag item = new ItemTag(this.item.getItemStack().clone());\r\n            FlagCommand.FlagActionProvider provider = new FlagCommand.FlagActionProvider();\r\n            provider.tracker = item.getFlagTracker();\r\n            DataAction action = DataActionHelper.parse(provider, attribute.getParam(), attribute.context);\r\n\r\n            // <--[tag]\r\n            // @attribute <ItemTag.with_flag[<flag_set_action>].duration[<expire_duration>]>\r\n            // @returns ItemTag\r\n            // @mechanism ItemTag.flag\r\n            // @group properties\r\n            // @description\r\n            // Returns a copy of the item with the specified flag data action (and the specified expiration duration) applied to it.\r\n            // For example: <[item].with_flag[myflagname].duration[5m]>\r\n            // -->\r\n            if (attribute.startsWith(\"duration\", 2)) {\r\n                provider.expiration = new TimeTag(TimeTag.now().millis() + attribute.getContextObject(2).asType(DurationTag.class, attribute.context).getMillis());\r\n                attribute.fulfill(1);\r\n            }\r\n            action.execute(attribute.context);\r\n            item.reapplyTracker(provider.tracker);\r\n            return item\r\n                    .getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        AbstractFlagTracker tracker = item.getFlagTracker();\r\n        if (tracker instanceof MapTagFlagTracker && ((MapTagFlagTracker) tracker).map.isEmpty()) {\r\n            return null;\r\n        }\r\n        return tracker.toString();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"flag_map\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name flag_map\r\n        // @input MapTag\r\n        // @deprecated Internal-usage only.\r\n        // @description\r\n        // Internal-usage direct re-setter for the item's full raw flag data.\r\n        // -->\r\n        if (mechanism.matches(\"flag_map\") && mechanism.requireObject(MapTag.class)) {\r\n            item.reapplyTracker(new MapTagFlagTracker(mechanism.valueAsType(MapTag.class)));\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name flag\r\n        // @input ObjectTag\r\n        // @description\r\n        // Modifies a flag on this item, using syntax similar to <@link command flag>.\r\n        // For example, 'flag:myflagname:!' will remove flag 'myflagname', or 'flag:myflagname:3' sets flag 'myflagname' to value '3'.\r\n        // @tags\r\n        // <FlaggableObject.flag[<flag_name>]>\r\n        // <FlaggableObject.has_flag[<flag_name>]>\r\n        // <FlaggableObject.flag_expiration[<flag_name>]>\r\n        // <FlaggableObject.list_flags>\r\n        // -->\r\n        if (mechanism.matches(\"flag\")) {\r\n            FlagCommand.FlagActionProvider provider = new FlagCommand.FlagActionProvider();\r\n            provider.tracker = item.getFlagTracker();\r\n            DataAction action = DataActionHelper.parse(provider, mechanism.getValue().asString(), mechanism.context);\r\n            action.execute(mechanism.context);\r\n            item.reapplyTracker(provider.tracker);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemFrameInvisible.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.EntityType;\r\n\r\npublic class ItemFrameInvisible implements Property {\r\n\r\n    public static boolean describes(ObjectTag object) {\r\n        return object instanceof ItemTag &&\r\n                (((ItemTag) object).getBukkitMaterial() == Material.ITEM_FRAME\r\n                || ((ItemTag) object).getBukkitMaterial() == Material.GLOW_ITEM_FRAME);\r\n    }\r\n\r\n    public static ItemFrameInvisible getFrom(ObjectTag object) {\r\n        if (!describes(object)) {\r\n            return null;\r\n        }\r\n        return new ItemFrameInvisible((ItemTag) object);\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"invisible\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"invisible\"\r\n    };\r\n\r\n    public ItemFrameInvisible(ItemTag item) {\r\n        this.item = item;\r\n    }\r\n\r\n    ItemTag item;\r\n\r\n    public boolean isInvisible() {\r\n        CompoundBinaryTag entityNbt = NMSHandler.itemHelper.getEntityData(item.getItemStack());\r\n        if (entityNbt == null) {\r\n            return false;\r\n        }\r\n        byte b = entityNbt.getByte(\"Invisible\");\r\n        return b == 1;\r\n    }\r\n\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.invisible>\r\n        // @returns ElementTag(Boolean)\r\n        // @group properties\r\n        // @mechanism ItemTag.invisible\r\n        // @description\r\n        // Returns whether an Item_Frame item will be invisible when placed.\r\n        // -->\r\n        if (attribute.startsWith(\"invisible\")) {\r\n            return new ElementTag(isInvisible()).getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    public String getPropertyString() {\r\n        if (isInvisible()) {\r\n            return \"true\";\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"invisible\";\r\n    }\r\n\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name invisible\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes whether an Item_Frame item will be invisible when placed.\r\n        // @tags\r\n        // <ItemTag.invisible>\r\n        // -->\r\n        if (mechanism.matches(\"invisible\") && mechanism.requireBoolean()) {\r\n            CompoundBinaryTag entityNbt = NMSHandler.itemHelper.getEntityData(item.getItemStack());\r\n            boolean invisible = mechanism.getValue().asBoolean();\r\n            if (!invisible && entityNbt == null) {\r\n                return;\r\n            }\r\n            if (invisible) {\r\n                entityNbt = ItemRawNBT.compoundOrEmpty(entityNbt).putByte(\"Invisible\", (byte) 1);\r\n            }\r\n            else {\r\n                entityNbt = entityNbt.remove(\"Invisible\");\r\n            }\r\n            item.setItemStack(NMSHandler.itemHelper.setEntityData(item.getItemStack(), entityNbt, item.getBukkitMaterial() == Material.ITEM_FRAME ? EntityType.ITEM_FRAME : EntityType.GLOW_ITEM_FRAME));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemHidden.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.Material;\nimport org.bukkit.inventory.ItemFlag;\nimport org.bukkit.inventory.meta.ItemMeta;\n\npublic class ItemHidden implements Property {\n\n    // TODO once 1.20 is the minimum supported version, can directly reference the enum\n    public static final ItemFlag HIDE_ITEM_DATA_FLAG = ItemFlag.valueOf(\"HIDE_POTION_EFFECTS\");\n\n    public static boolean describes(ObjectTag item) {\n        // All items can have hides\n        return item instanceof ItemTag && ((ItemTag) item).getBukkitMaterial() != Material.AIR;\n    }\n\n    public static ItemHidden getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemHidden((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"flags\", \"hides\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"flags\", \"hides\"\n    };\n\n    public ItemHidden(ItemTag _item) {\n        item = _item;\n    }\n\n    @Deprecated\n    public ListTag flags() {\n        ListTag output = new ListTag();\n        for (ItemFlag flag : item.getItemMeta().getItemFlags()) {\n            output.add(flag.name());\n        }\n        return output;\n    }\n\n    public ListTag hides() {\n        ListTag output = new ListTag();\n        if (item.getItemMeta() == null) {\n            return output;\n        }\n        for (ItemFlag flag : item.getItemMeta().getItemFlags()) {\n            if (flag == HIDE_ITEM_DATA_FLAG) {\n                output.add(\"ITEM_DATA\");\n            }\n            else {\n                output.add(flag.name().substring(\"HIDE_\".length()));\n            }\n        }\n        return output;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.hides>\n        // @returns ListTag\n        // @mechanism ItemTag.hides\n        // @group properties\n        // @description\n        // Returns a list of item data types to be hidden from view on this item.\n        // Valid hide types include: ATTRIBUTES, DESTROYS, ENCHANTS, PLACED_ON, ITEM_DATA, UNBREAKABLE, and DYE\n        // ITEM_DATA hides potion effects, banner patterns, etc.\n        // -->\n        if (attribute.startsWith(\"hides\")) {\n            return hides().getObjectAttribute(attribute.fulfill(1));\n        }\n        if (attribute.startsWith(\"flags\")) {\n            BukkitImplDeprecations.itemFlagsProperty.warn(attribute.context);\n            return flags().getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        ListTag hidden = hides();\n        if (hidden.size() > 0) {\n            if (hidden.size() == ItemFlag.values().length) {\n                return \"ALL\";\n            }\n            return hidden.identify();\n        }\n        else {\n            return null;\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"hides\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name hides\n        // @input ListTag\n        // @description\n        // Sets the item's list of data types to hide.\n        // Valid hide types include: ATTRIBUTES, DESTROYS, ENCHANTS, PLACED_ON, ITEM_DATA, UNBREAKABLE, DYE, or ALL.\n        // ITEM_DATA hides potion effects, banner patterns, etc.\n        // Use \"ALL\" to automatically hide all hideable item data.\n        // @tags\n        // <ItemTag.hides>\n        // -->\n        if (mechanism.matches(\"flags\") || mechanism.matches(\"hides\")) {\n            if (mechanism.matches(\"flags\")) {\n                BukkitImplDeprecations.itemFlagsProperty.warn(mechanism.context);\n            }\n            ItemMeta meta = item.getItemMeta();\n            meta.removeItemFlags(ItemFlag.values());\n            ListTag new_hides = mechanism.valueAsType(ListTag.class);\n            for (String str : new_hides) {\n                str = str.toUpperCase();\n                if (!str.startsWith(\"HIDE_\")) {\n                    str = \"HIDE_\" + str;\n                }\n                if (str.equals(\"HIDE_ALL\")) {\n                    meta.addItemFlags(ItemFlag.values());\n                }\n                else if (str.equals(\"HIDE_ITEM_DATA\")) {\n                    meta.addItemFlags(HIDE_ITEM_DATA_FLAG);\n                }\n                else {\n                    meta.addItemFlags(ItemFlag.valueOf(str));\n                }\n            }\n            item.setItemMeta(meta);\n        }\n\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemInstrument.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.MusicInstrument;\r\nimport org.bukkit.inventory.meta.MusicInstrumentMeta;\r\n\r\npublic class ItemInstrument extends ItemProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object ItemTag\r\n    // @name instrument\r\n    // @input ElementTag\r\n    // @description\r\n    // A goat horn's instrument, if any.\r\n    // Goat horns will default to playing \"ponder_goat_horn\" when the instrument is unset, although this is effectively random and shouldn't be relied on.\r\n    // Valid instruments are: admire_goat_horn, call_goat_horn, dream_goat_horn, feel_goat_horn, ponder_goat_horn, seek_goat_horn, sing_goat_horn, yearn_goat_horn.\r\n    // For the mechanism: provide no input to unset the instrument.\r\n    // @example\r\n    // # This can narrate: \"This horn has the ponder_goat_horn instrument!\"\r\n    // - narrate \"This horn has the <player.item_in_hand.instrument> instrument!\"\r\n    // @example\r\n    // # Forces the player's held item to play seek_goat_horn instead of whatever it played before.\r\n    // # Would break if the player isn't holding a goat horn.\r\n    // - inventory adjust slot:hand instrument:seek_goat_horn\r\n    // -->\r\n\r\n    public static boolean describes(ItemTag item) {\r\n        return item.getItemMeta() instanceof MusicInstrumentMeta;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        MusicInstrument instrument = ((MusicInstrumentMeta) getItemMeta()).getInstrument();\r\n        if (instrument != null) {\r\n            return new ElementTag(Utilities.namespacedKeyToString(instrument.getKey()));\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        MusicInstrument instrument = value != null ? MusicInstrument.getByKey(Utilities.parseNamespacedKey(value.asString())) : null;\r\n        if (value != null && instrument == null) {\r\n            mechanism.echoError(\"Invalid instrument: \" + value);\r\n            return;\r\n        }\r\n        editMeta(MusicInstrumentMeta.class, meta -> meta.setInstrument(instrument));\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"instrument\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegisterNullable(\"instrument\", ItemInstrument.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemInventoryContents.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.properties.inventory.InventoryContents;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.Conversion;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.block.Chest;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryHolder;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.meta.BlockStateMeta;\r\nimport org.bukkit.inventory.meta.BundleMeta;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class ItemInventoryContents extends ItemProperty<ListTag> {\r\n\r\n    // <--[property]\r\n    // @object ItemTag\r\n    // @name inventory_contents\r\n    // @input ListTag(ItemTag)\r\n    // @description\r\n    // A container item's inventory contents.\r\n    // -->\r\n\r\n    public static boolean describes(ItemTag item) {\r\n        return (item.getItemMeta() instanceof BlockStateMeta blockStateMeta && blockStateMeta.getBlockState() instanceof InventoryHolder)\r\n                || item.getItemMeta() instanceof BundleMeta;\r\n    }\r\n\r\n    public InventoryTag getItemInventory() {\r\n        InventoryHolder holder = (InventoryHolder) as(BlockStateMeta.class).getBlockState();\r\n        Inventory inv = getInventoryFor(holder);\r\n        return InventoryTag.mirrorBukkitInventory(inv);\r\n    }\r\n\r\n    public static Inventory getInventoryFor(InventoryHolder holder) {\r\n        return holder instanceof Chest chest ? chest.getBlockInventory() : holder.getInventory();\r\n    }\r\n\r\n    @Override\r\n    public ListTag getPropertyValue() {\r\n        if (getItemMeta() instanceof BlockStateMeta blockStateMeta) {\r\n            if (!blockStateMeta.hasBlockState()) {\r\n                return null;\r\n            }\r\n            return new InventoryContents(getItemInventory()).getContents(false);\r\n        }\r\n        return new ListTag(as(BundleMeta.class).getItems(), item -> item != null && item.getType() != Material.AIR, ItemTag::new);\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ListTag value, Mechanism mechanism) {\r\n        List<ItemStack> items = new ArrayList<>(value.size());\r\n        for (ItemTag item : value.filter(ItemTag.class, mechanism.context)) {\r\n            items.add(item.getItemStack());\r\n        }\r\n        if (getItemMeta() instanceof BlockStateMeta blockStateMeta) {\r\n            BlockState state = blockStateMeta.getBlockState();\r\n            Inventory inventory = getInventoryFor((InventoryHolder) state);\r\n            if (items.size() > inventory.getSize()) {\r\n                mechanism.echoError(\"Input list is too large: must be \" + inventory.getSize() + \" or less.\");\r\n                return;\r\n            }\r\n            inventory.setContents(items.toArray(new ItemStack[0]));\r\n            blockStateMeta.setBlockState(state);\r\n            setItemMeta(blockStateMeta);\r\n        }\r\n        else {\r\n            editMeta(BundleMeta.class, bundleMeta -> bundleMeta.setItems(items));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"inventory_contents\";\r\n    }\r\n\r\n    public static void register() {\r\n        PropertyParser.registerTag(ItemInventoryContents.class, ListTag.class, \"inventory_contents\", (attribute, prop) -> {\r\n            if (prop.getItemMeta() instanceof BlockStateMeta blockStateMeta && !blockStateMeta.hasBlockState()) {\r\n                return new ListTag();\r\n            }\r\n            return prop.getPropertyValue();\r\n        });\r\n        PropertyParser.registerMechanism(ItemInventoryContents.class, ListTag.class, \"inventory_contents\", (prop, mechanism, input) -> {\r\n            prop.setPropertyValue(input, mechanism);\r\n        });\r\n\r\n        PropertyParser.registerTag(ItemInventoryContents.class, InventoryTag.class, \"inventory\", (attribute, prop) -> {\r\n            BukkitImplDeprecations.itemInventoryTag.warn(attribute.context);\r\n            return prop.getItemInventory();\r\n        });\r\n        PropertyParser.registerMechanism(ItemInventoryContents.class, InventoryTag.class, \"inventory\", (prop, mechanism, input) -> {\r\n            BukkitImplDeprecations.itemInventoryTag.warn(mechanism.context);\r\n            Argument argument = new Argument(\"\");\r\n            argument.unsetValue();\r\n            argument.object = mechanism.getValue();\r\n            Map.Entry<Integer, InventoryTag> inventoryPair = Conversion.getInventory(argument, mechanism.context);\r\n            if (inventoryPair == null || inventoryPair.getValue().getInventory() == null) {\r\n                return;\r\n            }\r\n            ListTag items = new InventoryContents(inventoryPair.getValue()).getContents(false);\r\n            ItemStack[] itemArray = new ItemStack[items.size()];\r\n            for (int i = 0; i < itemArray.length; i++) {\r\n                itemArray[i] = ((ItemTag) items.objectForms.get(i)).getItemStack().clone();\r\n            }\r\n            if (prop.getItemMeta() instanceof BlockStateMeta blockStateMeta) {\r\n                InventoryHolder invHolder = (InventoryHolder) blockStateMeta.getBlockState();\r\n                if (items.size() > getInventoryFor(invHolder).getSize()) {\r\n                    mechanism.echoError(\"Invalid inventory mechanism input size; expected \" + getInventoryFor(invHolder).getSize() + \" or less.\");\r\n                    return;\r\n                }\r\n                getInventoryFor(invHolder).setContents(itemArray);\r\n                blockStateMeta.setBlockState((BlockState) invHolder);\r\n                prop.setItemMeta(blockStateMeta);\r\n            }\r\n            else {\r\n                prop.editMeta(BundleMeta.class, bundleMeta -> bundleMeta.setItems(Arrays.asList(itemArray)));\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemKnowledgeBookRecipes.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.Material;\nimport org.bukkit.NamespacedKey;\nimport org.bukkit.inventory.meta.KnowledgeBookMeta;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class ItemKnowledgeBookRecipes implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag && ((ItemTag) item).getBukkitMaterial() == Material.KNOWLEDGE_BOOK;\n    }\n\n    public static ItemKnowledgeBookRecipes getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemKnowledgeBookRecipes((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"knowledge_book_recipes\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"knowledge_book_recipes\"\n    };\n\n    public ItemKnowledgeBookRecipes(ItemTag _item) {\n        item = _item;\n    }\n\n    public ListTag recipeList() {\n        ListTag output = new ListTag();\n        if (item.getItemMeta() instanceof KnowledgeBookMeta) {\n            for (NamespacedKey key : ((KnowledgeBookMeta) item.getItemMeta()).getRecipes()) {\n                output.add(key.toString());\n            }\n        }\n        return output;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.knowledge_book_recipes>\n        // @returns ListTag\n        // @mechanism ItemTag.knowledge_book_recipes\n        // @group properties\n        // @description\n        // Returns a recipes unlocked by this knowledge book. Recipes are in the Namespace:Key format, for example \"minecraft:gold_nugget\".\n        // These keys are not necessarily 1:1 with material names, as seen in the example \"minecraft:gold_ingot_from_nuggets\".\n        // -->\n        if (attribute.startsWith(\"knowledge_book_recipes\")) {\n            return recipeList().getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        ListTag recipes = recipeList();\n        if (recipes.size() > 0) {\n            return recipes.identify();\n        }\n        else {\n            return null;\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"knowledge_book_recipes\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name knowledge_book_recipes\n        // @input ListTag\n        // @description\n        // Sets the item's knowledge book recipe list, in the Namespace:Key format.\n        // @tags\n        // <ItemTag.knowledge_book_recipes>\n        // -->\n        if (mechanism.matches(\"knowledge_book_recipes\")) {\n            KnowledgeBookMeta meta = (KnowledgeBookMeta) item.getItemMeta();\n            List<NamespacedKey> recipes = new ArrayList<>();\n            ListTag newRecipes = mechanism.valueAsType(ListTag.class);\n            for (String str : newRecipes) {\n                recipes.add(Utilities.parseNamespacedKey(str));\n            }\n            meta.setRecipes(recipes);\n            item.setItemMeta(meta);\n        }\n\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemLock.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.block.Lockable;\r\nimport org.bukkit.inventory.meta.BlockStateMeta;\r\n\r\npublic class ItemLock implements Property {\r\n\r\n    public static boolean describes(ObjectTag item) {\r\n        return item instanceof ItemTag\r\n                && ((ItemTag) item).getItemMeta() instanceof BlockStateMeta\r\n                && ((BlockStateMeta) ((ItemTag) item).getItemMeta()).getBlockState() instanceof Lockable;\r\n    }\r\n\r\n    public static ItemLock getFrom(ObjectTag _item) {\r\n        if (!describes(_item)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new ItemLock((ItemTag) _item);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"lock\", \"is_locked\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"lock\"\r\n    };\r\n\r\n    public String getItemLock() {\r\n        return ((Lockable) ((BlockStateMeta) item.getItemMeta()).getBlockState()).getLock();\r\n    }\r\n\r\n    public boolean isLocked() {\r\n        return ((Lockable) ((BlockStateMeta) item.getItemMeta()).getBlockState()).isLocked();\r\n    }\r\n\r\n    public ItemLock(ItemTag _item) {\r\n        item = _item;\r\n    }\r\n\r\n    ItemTag item;\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.lock>\r\n        // @returns ElementTag\r\n        // @mechanism ItemTag.lock\r\n        // @group properties\r\n        // @description\r\n        // Returns the lock password of this item.\r\n        // -->\r\n        if (attribute.startsWith(\"lock\")) {\r\n            if (!isLocked()) {\r\n                return null;\r\n            }\r\n            return new ElementTag(getItemLock(), true).getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.is_locked>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism ItemTag.lock\r\n        // @group properties\r\n        // @description\r\n        // Returns whether this item has a lock password.\r\n        // -->\r\n        if (attribute.startsWith(\"is_locked\")) {\r\n            return new ElementTag(isLocked()).getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return isLocked() ? getItemLock() : null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"lock\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name lock\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the item's lock password.\r\n        // Locked blocks can only be opened while holding an item with the name of the lock.\r\n        // @tags\r\n        // <ItemTag.lock>\r\n        // <ItemTag.is_locked>\r\n        // <ItemTag.is_lockable>\r\n        // -->\r\n        if (mechanism.matches(\"lock\")) {\r\n            BlockStateMeta bsm = ((BlockStateMeta) item.getItemMeta());\r\n            Lockable lockable = (Lockable) bsm.getBlockState();\r\n            lockable.setLock(mechanism.hasValue() ? mechanism.getValue().asString() : null);\r\n            bsm.setBlockState((BlockState) lockable);\r\n            item.setItemMeta(bsm);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemLodestoneLocation.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.Location;\nimport org.bukkit.Material;\nimport org.bukkit.inventory.meta.CompassMeta;\n\npublic class ItemLodestoneLocation implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && (((ItemTag) item).getBukkitMaterial() == Material.COMPASS);\n    }\n\n    public static ItemLodestoneLocation getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemLodestoneLocation((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"lodestone_location\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"lodestone_location\"\n    };\n\n    public ItemLodestoneLocation(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.lodestone_location>\n        // @returns LocationTag\n        // @group properties\n        // @mechanism ItemTag.lodestone_location\n        // @description\n        // Returns the lodestone location this compass is pointing at (if any).\n        // See also <@link tag ItemTag.lodestone_tracked>\n        // -->\n        if (attribute.startsWith(\"lodestone_location\")) {\n            LocationTag target = getTarget();\n            if (target == null) {\n                return null;\n            }\n            return target.getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    public LocationTag getTarget() {\n        CompassMeta meta = (CompassMeta) item.getItemMeta();\n        Location loc = meta.getLodestone();\n        if (loc == null || loc.getWorld() == null) {\n            return null;\n        }\n        return new LocationTag(meta.getLodestone());\n    }\n\n    @Override\n    public String getPropertyString() {\n        LocationTag target = getTarget();\n        if (target == null) {\n            return null;\n        }\n        return target.identify();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"lodestone_location\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name lodestone_location\n        // @input LocationTag\n        // @description\n        // Changes the lodestone location this compass is pointing at.\n        // See also <@link mechanism ItemTag.lodestone_tracked>\n        // Give no input to unset.\n        // @tags\n        // <ItemTag.lodestone_location>\n        // -->\n        if (mechanism.matches(\"lodestone_location\")) {\n            CompassMeta meta = (CompassMeta) item.getItemMeta();\n            if (mechanism.hasValue() && mechanism.requireObject(LocationTag.class)) {\n                LocationTag loc = mechanism.valueAsType(LocationTag.class).clone();\n                if (loc.getWorldName() != null && loc.getWorld() == null) {\n                    return; // This edge case is handled by RawNBT\n                }\n                meta.setLodestone(loc);\n            }\n            else {\n                meta.setLodestone(null);\n            }\n            item.setItemMeta(meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemLodestoneTracked.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.Material;\nimport org.bukkit.inventory.meta.CompassMeta;\n\npublic class ItemLodestoneTracked implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && (((ItemTag) item).getBukkitMaterial() == Material.COMPASS);\n    }\n\n    public static ItemLodestoneTracked getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemLodestoneTracked((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"lodestone_tracked\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"lodestone_tracked\"\n    };\n\n    public ItemLodestoneTracked(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.lodestone_tracked>\n        // @returns ElementTag(Boolean)\n        // @group properties\n        // @mechanism ItemTag.lodestone_tracked\n        // @description\n        // Returns whether the compass will track a lodestone. If \"true\", the compass will only work if there's a lodestone at the target location.\n        // See also <@link tag ItemTag.lodestone_location>\n        // -->\n        if (attribute.startsWith(\"lodestone_tracked\")) {\n            CompassMeta meta = (CompassMeta) item.getItemMeta();\n            return new ElementTag(meta.isLodestoneTracked()).getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        CompassMeta meta = (CompassMeta) item.getItemMeta();\n        return meta.isLodestoneTracked() ? \"true\" : \"false\";\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"lodestone_tracked\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name lodestone_tracked\n        // @input ElementTag(Boolean)\n        // @description\n        // Changes whether the compass will track a lodestone. If \"true\", the compass will only work if there's a lodestone at the target location.\n        // See also <@link mechanism ItemTag.lodestone_location>\n        // @tags\n        // <ItemTag.lodestone_tracked>\n        // -->\n        if (mechanism.matches(\"lodestone_tracked\") && mechanism.requireBoolean()) {\n            CompassMeta meta = (CompassMeta) item.getItemMeta();\n            meta.setLodestoneTracked(mechanism.getValue().asBoolean());\n            item.setItemMeta(meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemLore.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.tags.core.EscapeTagUtil;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\n\nimport java.util.List;\n\npublic class ItemLore implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        // Technically, all items can hold lore\n        return item instanceof ItemTag;\n    }\n\n    public static ItemLore getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemLore((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"lore\", \"has_lore\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"lore\"\n    };\n\n    public boolean hasLore() {\n        return item.getItemMeta() != null && item.getItemMeta().hasLore();\n    }\n\n    public ItemLore(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.lore>\n        // @returns ListTag\n        // @mechanism ItemTag.lore\n        // @group properties\n        // @description\n        // Returns lore as a ListTag.\n        // -->\n        if (attribute.startsWith(\"lore\")) {\n            if (hasLore()) {\n                return getLoreList().getObjectAttribute(attribute.fulfill(1));\n            }\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.has_lore>\n        // @returns ElementTag(Boolean)\n        // @mechanism ItemTag.lore\n        // @group properties\n        // @description\n        // Returns whether the item has lore set on it.\n        // -->\n        if (attribute.startsWith(\"has_lore\")) {\n            return new ElementTag(hasLore())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    public ListTag getLoreList() {\n        List<String> lore = NMSHandler.itemHelper.getLore(item);\n        if (lore == null) {\n            return null;\n        }\n        return new ListTag(lore, true);\n    }\n\n    @Override\n    public String getPropertyString() {\n        if (hasLore()) {\n            ListTag output = getLoreList();\n            return (output.size() == 0) ? null : output.identify();\n        }\n        else {\n            return null;\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"lore\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name lore\n        // @input ListTag\n        // @description\n        // Sets the item's lore.\n        // @tags\n        // <ItemTag.lore>\n        // -->\n        if (mechanism.matches(\"lore\")) {\n            ListTag lore = mechanism.valueAsType(ListTag.class);\n            CoreUtilities.fixNewLinesToListSeparation(lore);\n            for (int i = 0; i < lore.size(); i++) {\n                String loreLine = lore.get(i);\n                if (lore.wasLegacy) {\n                    loreLine = EscapeTagUtil.unEscape(loreLine);\n                }\n                lore.set(i, CoreUtilities.clearNBSPs(loreLine));\n            }\n            NMSHandler.itemHelper.setLore(item, lore);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemMap.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport org.bukkit.Material;\nimport org.bukkit.inventory.meta.MapMeta;\nimport org.bukkit.map.MapView;\n\nimport java.util.List;\n\npublic class ItemMap implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && (((ItemTag) item).getBukkitMaterial() == Material.FILLED_MAP);\n    }\n\n    public static ItemMap getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemMap((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledMechs = new String[] {\n            \"map\", \"full_render\", \"map_locked\", \"map_center\"\n    };\n\n    public ItemMap(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <ItemTag.map>\n        // @returns ElementTag(Number)\n        // @group properties\n        // @mechanism ItemTag.map\n        // @description\n        // Returns the ID number of the map item's map.\n        // -->\n        PropertyParser.registerTag(ItemMap.class, ElementTag.class, \"map\", (attribute, object) -> {\n            if (!object.hasMapId()) {\n                return null;\n            }\n            return new ElementTag(object.getMapId());\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.map_scale>\n        // @returns ElementTag(Number)\n        // @group properties\n        // @mechanism ItemTag.map\n        // @description\n        // Returns the scale of the map, from 0 (smallest) to 4 (largest).\n        // -->\n        PropertyParser.registerTag(ItemMap.class, ElementTag.class, \"map_scale\", (attribute, object) -> {\n            if (!object.hasMapId()) {\n                return null;\n            }\n            MapMeta map = object.getMapMeta();\n            if (!map.hasMapView()) {\n                return null;\n            }\n            return new ElementTag(map.getMapView().getScale().getValue());\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.map_locked>\n        // @returns ElementTag(Boolean)\n        // @group properties\n        // @mechanism ItemTag.map_locked\n        // @description\n        // Returns whether maps with the same ID as this map are locked.\n        // -->\n        PropertyParser.registerTag(ItemMap.class, ElementTag.class, \"map_locked\", (attribute, object) -> {\n            if (!object.hasMapId()) {\n                return null;\n            }\n            MapMeta map = object.getMapMeta();\n            if (!map.hasMapView()) {\n                return null;\n            }\n            return new ElementTag(map.getMapView().isLocked());\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.map_center>\n        // @returns LocationTag\n        // @group properties\n        // @mechanism ItemTag.map_center\n        // @description\n        // Returns the center location on the map's display.\n        // Note that there is no Y value (it's always 0), only X, Z, and a World.\n        // -->\n        PropertyParser.registerTag(ItemMap.class, LocationTag.class, \"map_center\", (attribute, object) -> {\n            if (!object.hasMapId()) {\n                return null;\n            }\n            MapMeta map = object.getMapMeta();\n            if (!map.hasMapView()) {\n                return null;\n            }\n            MapView mapView = map.getMapView();\n            return new LocationTag(mapView.getWorld(), mapView.getCenterX(), 0, mapView.getCenterZ());\n        });\n    }\n\n    public MapMeta getMapMeta() {\n        return (MapMeta) item.getItemMeta();\n    }\n\n    public boolean hasMapId() {\n        return getMapMeta().hasMapId();\n    }\n\n    public int getMapId() {\n        MapMeta map = getMapMeta();\n        return map.hasMapId() ? map.getMapId() : 0;\n    }\n\n    public void setMapId(int id) {\n        MapMeta map = getMapMeta();\n        map.setMapId(id);\n        item.setItemMeta(map);\n    }\n\n    @Override\n    public String getPropertyString() {\n        return hasMapId() ? String.valueOf(getMapId()) : null;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"map\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name map\n        // @input ElementTag(Number)\n        // @description\n        // Changes what map ID number a map item uses.\n        // @tags\n        // <ItemTag.map>\n        // <ItemTag.map_scale>\n        // -->\n        if (mechanism.matches(\"map\") && mechanism.requireInteger()) {\n            setMapId(mechanism.getValue().asInt());\n        }\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name full_render\n        // @input ElementTag\n        // @description\n        // Fully renders all or part of a map item's view of the world.\n        // Be warned that this can run very slowly on large maps.\n        // Input can be nothing to render the full map, or a comma separated set of integers to render part of the map, in format x1,z1,x2,z2.\n        // Input numbers are pixel indices within the map image - so, any integer from 0 to 128.\n        // The input for a full map render would be 0,0,128,128.\n        //\n        // @example\n        // # Use to render sections slowly (to reduce server impact):\n        // - repeat 16 as:x:\n        //     - adjust <item[filled_map[map=4]]> full_render:<[x].sub[1].mul[8]>,0,<[x].mul[8]>,128\n        //     - wait 2t\n        // @tags\n        // <ItemTag.map>\n        // <ItemTag.map_scale>\n        // -->\n        if (mechanism.matches(\"full_render\")) {\n            int xMin = 0, zMin = 0, xMax = 128, zMax = 128;\n            if (mechanism.hasValue()) {\n                List<String> input = CoreUtilities.split(mechanism.getValue().asString(), ',');\n                if (input.size() != 4) {\n                    mechanism.echoError(\"Invalid input to 'full_render' - must be a set of 4 comma separated integers.\");\n                    return;\n                }\n                try {\n                    xMin = Math.max(Integer.parseInt(input.get(0)), 0);\n                    zMin = Math.max(Integer.parseInt(input.get(1)), 0);\n                    xMax = Math.min(Integer.parseInt(input.get(2)), 128);\n                    zMax = Math.min(Integer.parseInt(input.get(3)), 128);\n                }\n                catch (NumberFormatException ex) {\n                    mechanism.echoError(\"Invalid input to 'full_render' - found comma separated list of 4 values, but not all values are integers: \" + ex.getMessage());\n                }\n            }\n            boolean worked = NMSHandler.itemHelper.renderEntireMap(getMapId(), xMin, zMin, xMax, zMax);\n            if (!worked) {\n                mechanism.echoError(\"Cannot render map: ID doesn't exist. Has the map never been displayed?\");\n            }\n        }\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name map_locked\n        // @input ElementTag(Boolean)\n        // @description\n        // Changes whether the map is currently locked.\n        // Note that this applies globally to all map items with the same ID.\n        // @tags\n        // <ItemTag.map>\n        // <ItemTag.map_locked>\n        // -->\n        if (mechanism.matches(\"map_locked\") && mechanism.requireBoolean()) {\n            MapMeta meta = getMapMeta();\n            if (!meta.hasMapView()) {\n                mechanism.echoError(\"Map is not loaded/rendered.\");\n                return;\n            }\n            meta.getMapView().setLocked(mechanism.getValue().asBoolean());\n        }\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name map_center\n        // @input LocationTag\n        // @description\n        // Sets the map's center location (the location in the middle of the map's display).\n        // @tags\n        // <ItemTag.map_center>\n        // -->\n        if (mechanism.matches(\"map_center\") && mechanism.requireObject(LocationTag.class)) {\n            LocationTag loc = mechanism.valueAsType(LocationTag.class);\n            MapMeta meta = getMapMeta();\n            if (!meta.hasMapView()) {\n                mechanism.echoError(\"Map is not loaded/rendered.\");\n                return;\n            }\n            MapView mapView = meta.getMapView();\n            mapView.setCenterX(loc.getBlockX());\n            mapView.setCenterZ(loc.getBlockZ());\n            if (loc.getWorld() != null) {\n                mapView.setWorld(loc.getWorld());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemNBT.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.utilities.nbt.CustomNBT;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.tags.core.EscapeTagUtil;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport org.bukkit.Material;\nimport org.bukkit.inventory.ItemStack;\n\nimport java.util.List;\n\npublic class ItemNBT implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag;\n    }\n\n    public static ItemNBT getFrom(ObjectTag item) {\n        if (!describes(item)) {\n            return null;\n        }\n        else {\n            return new ItemNBT((ItemTag) item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"has_nbt\", \"nbt_keys\", \"nbt\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"remove_nbt\", \"nbt\"\n    };\n\n    public ItemNBT(ItemTag item) {\n        this.item = item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        if (attribute.startsWith(\"has_nbt\")) {\n            BukkitImplDeprecations.itemNbt.warn(attribute.context);\n            return new ElementTag(CustomNBT.hasCustomNBT(item.getItemStack(), attribute.getParam(), CustomNBT.KEY_DENIZEN))\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        if (attribute.startsWith(\"nbt_keys\")) {\n            BukkitImplDeprecations.itemNbt.warn(attribute.context);\n            return new ListTag(CustomNBT.listNBT(item.getItemStack(), CustomNBT.KEY_DENIZEN))\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        if (attribute.matches(\"nbt\")) {\n            BukkitImplDeprecations.itemNbt.warn(attribute.context);\n            if (!attribute.hasParam()) {\n                ListTag list = getNBTDataList();\n                if (list == null) {\n                    return null;\n                }\n                return list.getObjectAttribute(attribute.fulfill(1));\n            }\n            String res = CustomNBT.getCustomNBT(item.getItemStack(), attribute.getParam(), CustomNBT.KEY_DENIZEN);\n            if (res == null) {\n                return null;\n            }\n            return new ElementTag(res)\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    public ListTag getNBTDataList() {\n        ItemStack itemStack = item.getItemStack();\n        List<String> nbtKeys = CustomNBT.listNBT(itemStack, CustomNBT.KEY_DENIZEN);\n        if (nbtKeys != null && !nbtKeys.isEmpty()) {\n            ListTag list = new ListTag();\n            for (String key : nbtKeys) {\n                list.add(EscapeTagUtil.escape(key) + \"/\" + EscapeTagUtil.escape(CustomNBT.getCustomNBT(itemStack, key, CustomNBT.KEY_DENIZEN)));\n            }\n            return list;\n        }\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        ListTag list = getNBTDataList();\n        if (list == null) {\n            return null;\n        }\n        return list.identify();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"nbt\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        if (mechanism.matches(\"remove_nbt\")) {\n            BukkitImplDeprecations.itemNbt.warn(mechanism.context);\n            if (item.getMaterial().getMaterial() == Material.AIR) {\n                mechanism.echoError(\"Cannot apply NBT to AIR!\");\n                return;\n            }\n            ItemStack itemStack = item.getItemStack();\n            List<String> list;\n            if (mechanism.hasValue()) {\n                list = mechanism.valueAsType(ListTag.class);\n            }\n            else {\n                list = CustomNBT.listNBT(itemStack, CustomNBT.KEY_DENIZEN);\n            }\n            for (String string : list) {\n                itemStack = CustomNBT.removeCustomNBT(itemStack, string, CustomNBT.KEY_DENIZEN);\n            }\n            item.setItemStack(itemStack);\n        }\n\n        if (mechanism.matches(\"nbt\")) {\n            BukkitImplDeprecations.itemNbt.warn(mechanism.context);\n            if (item.getMaterial().getMaterial() == Material.AIR) {\n                mechanism.echoError(\"Cannot apply NBT to AIR!\");\n                return;\n            }\n            ListTag list = mechanism.valueAsType(ListTag.class);\n            ItemStack itemStack = item.getItemStack();\n            for (String string : list) {\n                String[] split = string.split(\"/\", 2);\n                itemStack = CustomNBT.addCustomNBT(itemStack, EscapeTagUtil.unEscape(split[0]), EscapeTagUtil.unEscape(split[1]), CustomNBT.KEY_DENIZEN);\n            }\n            item.setItemStack(itemStack);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemPatterns.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.DyeColor;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Banner;\r\nimport org.bukkit.block.banner.Pattern;\r\nimport org.bukkit.block.banner.PatternType;\r\nimport org.bukkit.inventory.meta.BannerMeta;\r\nimport org.bukkit.inventory.meta.BlockStateMeta;\r\nimport org.bukkit.inventory.meta.ItemMeta;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class ItemPatterns implements Property {\r\n\r\n    public static boolean isBannerOrShield(Material material) {\r\n        return material == Material.SHIELD || material.name().endsWith(\"_BANNER\");\r\n    }\r\n\r\n    public static boolean describes(ObjectTag item) {\r\n        if (item instanceof ItemTag) {\r\n            Material material = ((ItemTag) item).getBukkitMaterial();\r\n            return isBannerOrShield(material);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static ItemPatterns getFrom(ObjectTag item) {\r\n        if (!describes(item)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new ItemPatterns((ItemTag) item);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"patterns\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"patterns\"\r\n    };\r\n\r\n    public ItemPatterns(ItemTag item) {\r\n        this.item = item;\r\n    }\r\n\r\n    ItemTag item;\r\n\r\n    public ListTag listPatterns() {\r\n        ListTag list = new ListTag();\r\n        for (Pattern pattern : getPatterns()) {\r\n            list.add(pattern.getColor().name() + \"/\" + pattern.getPattern().name());\r\n        }\r\n        return list;\r\n    }\r\n\r\n    public List<Pattern> getPatterns() {\r\n        ItemMeta itemMeta = item.getItemMeta();\r\n        if (itemMeta instanceof BannerMeta) {\r\n            return ((BannerMeta) itemMeta).getPatterns();\r\n        }\r\n        else if (itemMeta instanceof BlockStateMeta) {\r\n            return ((Banner) ((BlockStateMeta) itemMeta).getBlockState()).getPatterns();\r\n        }\r\n        else {\r\n            // ...???\r\n            return new ArrayList<>();\r\n        }\r\n    }\r\n\r\n    public void setPatterns(List<Pattern> patterns) {\r\n        ItemMeta itemMeta = item.getItemMeta();\r\n        if (itemMeta instanceof BannerMeta) {\r\n            ((BannerMeta) itemMeta).setPatterns(patterns);\r\n        }\r\n        else if (itemMeta instanceof BlockStateMeta) {\r\n            try {\r\n                Banner banner = (Banner) ((BlockStateMeta) itemMeta).getBlockState();\r\n                banner.setPatterns(patterns);\r\n                banner.update();\r\n                ((BlockStateMeta) itemMeta).setBlockState(banner);\r\n            }\r\n            catch (Exception ex) {\r\n                Debug.echoError(\"Banner setPatterns failed!\");\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        else {\r\n            // ...???\r\n        }\r\n        item.setItemMeta(itemMeta);\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.patterns>\r\n        // @returns ListTag\r\n        // @group properties\r\n        // @mechanism ItemTag.patterns\r\n        // @description\r\n        // Lists a banner's patterns in the form \"COLOR/PATTERN|COLOR/PATTERN\" etc.\r\n        // For the list of possible colors, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/DyeColor.html>.\r\n        // For the list of possible patterns, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/banner/PatternType.html>.\r\n        // -->\r\n        if (attribute.startsWith(\"patterns\")) {\r\n            return listPatterns().getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        ListTag list = listPatterns();\r\n        if (list.isEmpty()) {\r\n            return null;\r\n        }\r\n        return list.identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"patterns\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name patterns\r\n        // @input ListTag\r\n        // @description\r\n        // Changes the patterns of a banner. Input must be in the form\r\n        // \"COLOR/PATTERN|COLOR/PATTERN\" etc.\r\n        // For the list of possible colors, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/DyeColor.html>.\r\n        // For the list of possible patterns, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/banner/PatternType.html>.\r\n        // @tags\r\n        // <ItemTag.patterns>\r\n        // <server.pattern_types>\r\n        // -->\r\n        if (mechanism.matches(\"patterns\")) {\r\n            List<Pattern> patterns = new ArrayList<>();\r\n            ListTag list = mechanism.valueAsType(ListTag.class);\r\n            List<String> split;\r\n            for (String string : list) {\r\n                try {\r\n                    split = CoreUtilities.split(string, '/', 2);\r\n                    patterns.add(new Pattern(DyeColor.valueOf(split.get(0).toUpperCase()),\r\n                            PatternType.valueOf(split.get(1).toUpperCase())));\r\n                }\r\n                catch (Exception e) {\r\n                    Debug.echoError(\"Could not apply pattern to banner: \" + string);\r\n                }\r\n            }\r\n            setPatterns(patterns);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemPotion.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.*;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.Registry;\r\nimport org.bukkit.inventory.meta.ItemMeta;\r\nimport org.bukkit.inventory.meta.PotionMeta;\r\nimport org.bukkit.inventory.meta.SuspiciousStewMeta;\r\nimport org.bukkit.potion.PotionData;\r\nimport org.bukkit.potion.PotionEffect;\r\nimport org.bukkit.potion.PotionEffectType;\r\nimport org.bukkit.potion.PotionType;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class ItemPotion extends ItemProperty<ObjectTag> {\r\n\r\n    // <--[language]\r\n    // @name Potion Effect Format\r\n    // @group Minecraft Logic\r\n    // @description\r\n    // Potion effects (be it on an entity, given by a potion, etc.) are represented in Denizen as <@link ObjectType MapTag>s with the following keys:\r\n    // - effect: the effect type given by the effect, see <@link url https://minecraft.wiki/w/Effect#Descriptions>.\r\n    // - amplifier: the number to increase the effect level by, usually controls how powerful its effects are (optional for input, defaults to 0 which is level 1).\r\n    // - duration (<@link ObjectType DurationTag>): how long the effect should last (optional for input, defaults to 0s).\r\n    // - ambient: a boolean (true/false) for whether the effect's particles should be more translucent and less intrusive, like effects given by a beacon (optional for input, defaults to true).\r\n    // - particles: a boolean (true/false) for whether the effect should display particles (optional for input, defaults to true).\r\n    // - icon: a boolean (true/false) for whether the effect should have an icon on a player's HUD when applied (optional for input, defaults to false).\r\n    //\r\n    // For example, [effect=speed;amplifier=2;duration=10s;ambient=false;particles=true;icon=true] would be a level 3 speed effect that lasts 10 seconds, with (normal) particles and an icon.\r\n    // -->\r\n    \r\n    public static boolean describes(ItemTag item) {\r\n        return item.getItemMeta() instanceof PotionMeta || item.getItemMeta() instanceof SuspiciousStewMeta;\r\n    }\r\n\r\n    public static MapTag effectToMap(PotionEffect effect, boolean includeDeprecated) {\r\n        MapTag map = new MapTag();\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            map.putObject(\"effect\", new ElementTag(Utilities.namespacedKeyToString(effect.getType().getKey()), true));\r\n        }\r\n        else {\r\n            includeDeprecated = true;\r\n        }\r\n        map.putObject(\"amplifier\", new ElementTag(effect.getAmplifier()));\r\n        map.putObject(\"duration\", new DurationTag((long) effect.getDuration()));\r\n        map.putObject(\"ambient\", new ElementTag(effect.isAmbient()));\r\n        map.putObject(\"particles\", new ElementTag(effect.hasParticles()));\r\n        map.putObject(\"icon\", new ElementTag(effect.hasIcon()));\r\n        // TODO: deprecate this\r\n        if (includeDeprecated) {\r\n            map.putObject(\"type\", new ElementTag(effect.getType().getName(), true));\r\n        }\r\n        return map;\r\n    }\r\n\r\n    public ListTag getMapTagData(boolean includeExtras) {\r\n        List<PotionEffect> potionEffects = getCustomEffects();\r\n        ListTag result = new ListTag(potionEffects.size() + 1);\r\n        if (getItemMeta() instanceof PotionMeta potionMeta) {\r\n            MapTag base = new MapTag();\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                if (potionMeta.hasBasePotionType()) {\r\n                    base.putObject(\"base_type\", new ElementTag(Utilities.namespacedKeyToString(potionMeta.getBasePotionType().getKey()), true));\r\n                }\r\n                if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && PaperAPITools.instance.hasCustomName(potionMeta)) {\r\n                    base.putObject(\"translation_id\", new ElementTag(potionMeta.getCustomName(), true));\r\n                }\r\n            }\r\n            else {\r\n                includeExtras = true;\r\n            }\r\n            if (includeExtras) { // TODO: Eventually remove these 4\r\n                LegacyPotionData data = getLegacyBasePotionData();\r\n                base.putObject(\"type\", new ElementTag(data.type(), true));\r\n                base.putObject(\"upgraded\", new ElementTag(data.upgraded()));\r\n                base.putObject(\"extended\", new ElementTag(data.extended()));\r\n                if (potionMeta.hasColor()) {\r\n                    base.putObject(\"color\", BukkitColorExtensions.fromColor(potionMeta.getColor()));\r\n                }\r\n            }\r\n            result.addObject(base);\r\n        }\r\n        for (PotionEffect potionEffect : potionEffects) {\r\n            result.addObject(effectToMap(potionEffect, includeExtras));\r\n        }\r\n        return result;\r\n    }\r\n\r\n    public static PotionEffect parseEffect(MapTag effectMap, TagContext context) {\r\n        PotionEffectType type;\r\n        DurationTag duration = new DurationTag(0);\r\n        int amplifier = 0;\r\n        boolean ambient = true;\r\n        boolean particles = true;\r\n        boolean icon = false;\r\n        ElementTag effectInput = effectMap.getElement(\"effect\");\r\n        ElementTag typeInput = null;\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && effectInput != null) {\r\n            type = Registry.EFFECT.get(Utilities.parseNamespacedKey(effectInput.asString()));\r\n        }\r\n        else if ((typeInput = effectMap.getElement(\"type\")) != null) {\r\n            type = PotionEffectType.getByName(typeInput.asString());\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                BukkitImplDeprecations.oldPotionEffectType.warn(context);\r\n            }\r\n        }\r\n        else {\r\n            if (context.showErrors()) {\r\n                Debug.echoError(\"Invalid potion effect: effect type is required.\");\r\n            }\r\n            return null;\r\n        }\r\n        if (type == null) {\r\n            if (context.showErrors()) {\r\n                Debug.echoError(\"Invalid potion effect type '\" + (effectInput != null ? effectInput : typeInput) + \"' specified: effect type is required.\");\r\n            }\r\n            return null;\r\n        }\r\n        if (effectMap.containsKey(\"amplifier\")) {\r\n            ElementTag amplifierElement = effectMap.getElement(\"amplifier\");\r\n            if (amplifierElement.isInt()) {\r\n                amplifier = amplifierElement.asInt();\r\n            }\r\n            else if (context.showErrors()) {\r\n                Debug.echoError(\"Invalid amplifier '\" + amplifierElement + \"': must be an integer.\");\r\n            }\r\n        }\r\n        if (effectMap.containsKey(\"duration\")) {\r\n            ObjectTag durationObj = effectMap.getObject(\"duration\");\r\n            if (durationObj.canBeType(DurationTag.class)) {\r\n                duration = durationObj.asType(DurationTag.class, context);\r\n            }\r\n            else if (context.showErrors()) {\r\n                Debug.echoError(\"Invalid duration '\" + durationObj + \"': must be a valid DurationTag\");\r\n            }\r\n        }\r\n        if (effectMap.containsKey(\"ambient\")) {\r\n            ElementTag ambientElement = effectMap.getElement(\"ambient\");\r\n            if (ambientElement.isBoolean()) {\r\n                ambient = ambientElement.asBoolean();\r\n            }\r\n            else if (context.showErrors()) {\r\n                Debug.echoError(\"Invalid ambient state '\" + ambientElement + \"': must be a boolean.\");\r\n            }\r\n        }\r\n        if (effectMap.containsKey(\"particles\")) {\r\n            ElementTag particlesElement = effectMap.getElement(\"particles\");\r\n            if (particlesElement.isBoolean()) {\r\n                particles = particlesElement.asBoolean();\r\n            }\r\n            else if (context.showErrors()) {\r\n                Debug.echoError(\"Invalid particles state '\" + particlesElement + \"': must be a boolean.\");\r\n            }\r\n        }\r\n        if (effectMap.containsKey(\"icon\")) {\r\n            ElementTag iconElement = effectMap.getElement(\"icon\");\r\n            if (iconElement.isBoolean()) {\r\n                icon = iconElement.asBoolean();\r\n            }\r\n            else if (context.showErrors()) {\r\n                Debug.echoError(\"Invalid icon state '\" + iconElement + \"': must be a boolean.\");\r\n            }\r\n        }\r\n        return new PotionEffect(type, duration.getTicksAsInt(), amplifier, ambient, particles, icon);\r\n    }\r\n\r\n    public List<PotionEffect> getCustomEffects() {\r\n        if (getItemMeta() instanceof SuspiciousStewMeta suspiciousStewMeta) {\r\n            return suspiciousStewMeta.getCustomEffects();\r\n        }\r\n        return as(PotionMeta.class).getCustomEffects();\r\n    }\r\n\r\n    @Override\r\n    public ListTag getPropertyValue() {\r\n        return getMapTagData(false);\r\n    }\r\n\r\n    private boolean applyBasePotionData(PotionMeta potionMeta, ObjectTag baseData, Mechanism mechanism) {\r\n        if (!baseData.canBeType(MapTag.class)) {\r\n            return applyLegacyStringBasePotionData(baseData.toString(), potionMeta, mechanism);\r\n        }\r\n        MapTag baseDataMap = baseData.asType(MapTag.class, mechanism.context);\r\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19) || baseDataMap.containsKey(\"type\")) {\r\n            return applyLegacyMapBasePotionData(baseDataMap, potionMeta, mechanism);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            ElementTag translationId = baseDataMap.getElement(\"translation_id\");\r\n            potionMeta.setCustomName(translationId != null ? translationId.asString() : null);\r\n        }\r\n        ElementTag baseTypeElement = baseDataMap.getElement(\"base_type\");\r\n        if (baseTypeElement == null) {\r\n            potionMeta.setBasePotionType(null);\r\n            return false;\r\n        }\r\n        PotionType baseType = Utilities.elementToEnumlike(baseTypeElement, PotionType.class);\r\n        if (baseType == null) {\r\n            mechanism.echoError(\"Invalid base potion type '\" + baseTypeElement + \"' specified.\");\r\n            return true;\r\n        }\r\n        potionMeta.setBasePotionType(baseType);\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ObjectTag value, Mechanism mechanism) {\r\n        List<ObjectTag> data = new ArrayList<>(CoreUtilities.objectToList(value, mechanism.context));\r\n        ItemMeta meta = getItemMeta();\r\n        if (meta instanceof PotionMeta potionMeta) {\r\n            if (applyBasePotionData(potionMeta, data.remove(0), mechanism)) {\r\n                return;\r\n            }\r\n            potionMeta.clearCustomEffects();\r\n        }\r\n        else {\r\n            ((SuspiciousStewMeta) meta).clearCustomEffects();\r\n        }\r\n        for (ObjectTag effectObj : data) {\r\n            PotionEffect effect;\r\n            if (effectObj.canBeType(MapTag.class)) {\r\n                effect = parseEffect(effectObj.asType(MapTag.class, mechanism.context), mechanism.context);\r\n            }\r\n            else {\r\n                effect = parseLegacyEffectString(effectObj.toString(), mechanism.context);\r\n            }\r\n            if (effect == null) {\r\n                mechanism.echoError(\"Invalid potion effect '\" + effectObj + \"'\");\r\n                continue;\r\n            }\r\n            if (meta instanceof PotionMeta potionMeta) {\r\n                potionMeta.addCustomEffect(effect, false);\r\n            }\r\n            else {\r\n                ((SuspiciousStewMeta) meta).addCustomEffect(effect, false);\r\n            }\r\n        }\r\n        setItemMeta(meta);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"potion_effects\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.effects_data>\r\n        // @returns ListTag(MapTag)\r\n        // @mechanism ItemTag.potion_effects\r\n        // @group properties\r\n        // @description\r\n        // Returns a list of all potion effects on this item, in the same format as the MapTag input to the mechanism.\r\n        // This applies to Potion items, Tipped Arrow items, and Suspicious Stews.\r\n        // Note that for potions or tipped arrows (not suspicious stew) the first value in the list is the potion's base type.\r\n        // All subsequent entries are potion effects in <@link language Potion Effect Format>.\r\n        // -->\r\n        PropertyParser.registerTag(ItemPotion.class, ListTag.class, \"effects_data\", (attribute, prop) -> {\r\n            return prop.getMapTagData(true);\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name potion_effects\r\n        // @input ListTag\r\n        // @description\r\n        // Sets the item's potion effect(s).\r\n        // This applies to Potion items, Tipped Arrow items, and Suspicious Stews.\r\n        //\r\n        // For potions or tipped arrows (not suspicious stew), the first item in the list must be a MapTag with keys:\r\n        // \"base_type\" - from <@link url https://minecraft.wiki/w/Potion#Item_data> (optional, becomes an uncraftable potion when unset).\r\n        // \"translation_id\" - controls the translation key used for the default item display name. The translation key used is \"item.minecraft.<item type>.effect.<id>\" (optional).\r\n        //\r\n        // For example: [base_type=strong_swiftness]\r\n        // This example produces an item labeled as \"Potion of Swiftness - Speed II (1:30)\"\r\n        //\r\n        // Each following item in the list are potion effects, which must be a MapTag in <@link language Potion Effect Format>.\r\n        //\r\n        // A very short full default potion item would be: potion[potion_effects=[base_type=regeneration]\r\n        // A (relatively) short full potion item would be: potion[potion_effects=<list[[base_type=regeneration]|[effect=speed;duration=10s]]>]\r\n        // (Note the list constructor to force data format interpretation, as potion formats can be given multiple ways and the system will get confused without a constructor)\r\n        //\r\n        // @tags\r\n        // <ItemTag.effects_data>\r\n        // <server.potion_types>\r\n        // <server.potion_effect_types>\r\n        // -->\r\n        PropertyParser.registerMechanism(ItemPotion.class, ObjectTag.class, \"potion_effects\", (prop, mechanism, input) -> {\r\n            prop.setPropertyValue(input, mechanism);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.has_potion_effect>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism ItemTag.potion_effects\r\n        // @description\r\n        // Returns whether the item (potion, tipped arrow, or suspicious stew) has a potion effect.\r\n        // -->\r\n        PropertyParser.registerTag(ItemPotion.class, ElementTag.class, \"has_potion_effect\", (attribute, object) -> {\r\n            return new ElementTag(object.getItemMeta() instanceof SuspiciousStewMeta suspiciousStewMeta ? suspiciousStewMeta.hasCustomEffects() : object.as(PotionMeta.class).hasCustomEffects());\r\n        });\r\n\r\n        /*\r\n        ==============================\r\n        = Deprecated/legacy features =\r\n        ==============================\r\n        */\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.potion_base_type>\r\n        // @returns ElementTag\r\n        // @mechanism ItemTag.potion_effects\r\n        // @group properties\r\n        // @deprecated use 'effects_data.first.get[base_type]' instead\r\n        // @description\r\n        // Deprecated in favor of <@link tag ItemTag.effects_data>\r\n        // -->\r\n        PropertyParser.registerTag(ItemPotion.class, ElementTag.class, \"potion_base_type\", (attribute, object) -> {\r\n            if (!(object.getItemMeta() instanceof PotionMeta)) {\r\n                attribute.echoError(\"This item does not have a base potion type.\");\r\n                return null;\r\n            }\r\n            BukkitImplDeprecations.oldPotionEffects.warn(attribute.context);\r\n            return new ElementTag(object.getLegacyBasePotionData().type(), true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.potion_base>\r\n        // @returns ElementTag\r\n        // @group attribute\r\n        // @mechanism ItemTag.potion_effects\r\n        // @deprecated use 'effects_data' instead\r\n        // @description\r\n        // Deprecated in favor of <@link tag ItemTag.effects_data>\r\n        // -->\r\n        PropertyParser.registerTag(ItemPotion.class, ElementTag.class, \"potion_base\", (attribute, object) -> {\r\n            if (!(object.getItemMeta() instanceof PotionMeta potionMeta)) {\r\n                attribute.echoError(\"This item does not have a base potion type.\");\r\n                return null;\r\n            }\r\n            BukkitImplDeprecations.oldPotionEffects.warn(attribute.context);\r\n            LegacyPotionData data = object.getLegacyBasePotionData();\r\n            return new ElementTag(data.type() + \",\" + (data.upgraded() ? 2 : 1)\r\n                    + \",\" + data.extended() + \",\" + (object.getMaterial() == Material.SPLASH_POTION)\r\n                    + (potionMeta.hasColor() ? \",\" + BukkitColorExtensions.fromColor(potionMeta.getColor()).identify() : \"\"));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.potion_effects>\r\n        // @returns ListTag\r\n        // @group attribute\r\n        // @mechanism ItemTag.potion_effects\r\n        // @deprecated use 'effects_data' instead\r\n        // @description\r\n        // Deprecated in favor of <@link tag ItemTag.effects_data>\r\n        // -->\r\n        PropertyParser.registerTag(ItemPotion.class, ListTag.class, \"potion_effects\", (attribute, object) -> {\r\n            ListTag result = new ListTag();\r\n            for (PotionEffect pot : object.getCustomEffects()) {\r\n                result.add(effectToLegacyString(pot, attribute.context));\r\n            }\r\n            return result;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.potion_effect[<#>]>\r\n        // @returns ElementTag\r\n        // @group attribute\r\n        // @mechanism ItemTag.potion_effects\r\n        // @deprecated use 'effects_data' instead\r\n        // @description\r\n        // Deprecated in favor of <@link tag ItemTag.effects_data>\r\n        // -->\r\n        PropertyParser.registerTag(ItemPotion.class, ElementTag.class, \"potion_effect\", (attribute, object) -> {\r\n            BukkitImplDeprecations.oldPotionEffects.warn(attribute.context);\r\n            int potN = attribute.hasParam() ? attribute.getIntParam() - 1 : 0;\r\n            if (potN < 0 || potN > object.getCustomEffects().size()) {\r\n                return null;\r\n            }\r\n            if (attribute.startsWith(\"is_splash\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getMaterial() == Material.SPLASH_POTION);\r\n            }\r\n            if (attribute.startsWith(\"is_extended\", 2)) {\r\n                attribute.fulfill(1);\r\n                if (!(object.getItemMeta() instanceof PotionMeta)) {\r\n                    return null;\r\n                }\r\n                return new ElementTag(object.getLegacyBasePotionData().extended());\r\n            }\r\n            if (attribute.startsWith(\"level\", 2)) {\r\n                attribute.fulfill(1);\r\n                if (!(object.getItemMeta() instanceof PotionMeta)) {\r\n                    return null;\r\n                }\r\n                return new ElementTag(object.getLegacyBasePotionData().upgraded() ? 2 : 1);\r\n            }\r\n            if (attribute.startsWith(\"is_ambient\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getCustomEffects().get(potN).isAmbient());\r\n            }\r\n            if (attribute.startsWith(\"icon\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getCustomEffects().get(potN).hasIcon());\r\n            }\r\n            if (attribute.startsWith(\"has_particles\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getCustomEffects().get(potN).hasParticles());\r\n            }\r\n            if (attribute.startsWith(\"duration\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getCustomEffects().get(potN).getDuration());\r\n            }\r\n            if (attribute.startsWith(\"amplifier\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getCustomEffects().get(potN).getAmplifier());\r\n            }\r\n            if (attribute.startsWith(\"type\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(object.getCustomEffects().get(potN).getType().getName());\r\n            }\r\n            if (attribute.startsWith(\"data\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(0);\r\n            }\r\n            if (!(object.getItemMeta() instanceof PotionMeta)) {\r\n                return null;\r\n            }\r\n            LegacyPotionData data = object.getLegacyBasePotionData();\r\n            return new ElementTag(data.type() + \",\" + (data.upgraded() ? 2 : 1)\r\n                    + \",\" + data.extended() + \",\" + (object.getMaterial() == Material.SPLASH_POTION));\r\n\r\n        });\r\n    }\r\n\r\n    private static boolean applyLegacyMapBasePotionData(MapTag input, PotionMeta potionMeta, Mechanism mechanism) {\r\n        if (!input.containsKey(\"type\")) {\r\n            mechanism.echoError(\"Must specify a base potion type.\");\r\n            return true;\r\n        }\r\n        ElementTag typeElement = input.getElement(\"type\");\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && typeElement.asLowerString().equals(\"uncraftable\")) {\r\n            potionMeta.setBasePotionType(null);\r\n            return false;\r\n        }\r\n        PotionType type = Utilities.elementToEnumlike(typeElement, PotionType.class);\r\n        if (type == null) {\r\n            mechanism.echoError(\"Invalid base potion type '\" + typeElement + \"': type is required\");\r\n            return true;\r\n        }\r\n        boolean upgraded = false;\r\n        boolean extended = false;\r\n        if (input.containsKey(\"upgraded\")) {\r\n            ElementTag upgradedElement = input.getElement(\"upgraded\");\r\n            if (upgradedElement.isBoolean()) {\r\n                upgraded = upgradedElement.asBoolean();\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Invalid upgraded state '\" + upgradedElement + \"': must be a boolean\");\r\n            }\r\n        }\r\n        if (input.containsKey(\"extended\")) {\r\n            ElementTag extendedElement = input.getElement(\"extended\");\r\n            if (extendedElement.isBoolean()) {\r\n                extended = extendedElement.asBoolean();\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Invalid extended state '\" + extendedElement + \"': must be a boolean\");\r\n            }\r\n        }\r\n        ColorTag color = null;\r\n        if (input.containsKey(\"color\")) {\r\n            ObjectTag colorObj = input.getObject(\"color\");\r\n            if (colorObj.canBeType(ColorTag.class)) {\r\n                color = colorObj.asType(ColorTag.class, mechanism.context);\r\n            }\r\n            else {\r\n                mechanism.echoError(\"Invalid color '\" + colorObj + \"': must be a valid ColorTag\");\r\n            }\r\n        }\r\n        applyLegacyBasePotionData(potionMeta, type, upgraded, extended, color, mechanism);\r\n        return false;\r\n    }\r\n\r\n    private static boolean applyLegacyStringBasePotionData(String input, PotionMeta potionMeta, Mechanism mechanism) {\r\n        String[] d1 = input.split(\",\");\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && CoreUtilities.equalsIgnoreCase(d1[0], \"uncraftable\")) {\r\n            potionMeta.setBasePotionType(null);\r\n            return false;\r\n        }\r\n        PotionType type;\r\n        try {\r\n            type = PotionType.valueOf(d1[0].toUpperCase());\r\n        }\r\n        catch (IllegalArgumentException ex) {\r\n            mechanism.echoError(\"Invalid base potion type '\" + d1[0] + \"': type is required\");\r\n            return true;\r\n        }\r\n        boolean upgraded = CoreUtilities.equalsIgnoreCase(d1[1], \"true\");\r\n        boolean extended = CoreUtilities.equalsIgnoreCase(d1[2], \"true\");\r\n        ColorTag color = null;\r\n        if (d1.length > 3) {\r\n            ColorTag temp = ColorTag.valueOf(d1[3].replace(\"&comma\", \",\"), mechanism.context);\r\n            if (temp == null) {\r\n                mechanism.echoError(\"Invalid color '\" + d1[3] + \"': must be a valid ColorTag\");\r\n            }\r\n            else {\r\n                color = temp;\r\n            }\r\n        }\r\n        applyLegacyBasePotionData(potionMeta, type, upgraded, extended, color, mechanism);\r\n        return false;\r\n    }\r\n\r\n    private static void applyLegacyBasePotionData(PotionMeta potionMeta, PotionType type, boolean upgraded, boolean extended, ColorTag color, Mechanism mechanism) {\r\n        if (upgraded && !type.isUpgradeable()) {\r\n            mechanism.echoError(\"Cannot upgrade potion of type '\" + type.name() + \"'\");\r\n            upgraded = false;\r\n        }\r\n        if (extended && !type.isExtendable()) {\r\n            mechanism.echoError(\"Cannot extend potion of type '\" + type.name() + \"'\");\r\n            extended = false;\r\n        }\r\n        if (upgraded && extended) {\r\n            mechanism.echoError(\"Cannot both upgrade and extend a potion\");\r\n            extended = false;\r\n        }\r\n        potionMeta.setBasePotionData(new PotionData(type, extended, upgraded));\r\n        if (color != null) {\r\n            potionMeta.setColor(BukkitColorExtensions.getColor(color));\r\n        }\r\n    }\r\n\r\n    public static String effectToLegacyString(PotionEffect effect, TagContext context) {\r\n        BukkitImplDeprecations.oldPotionEffects.warn(context);\r\n        return effect.getType().getName() + \",\" +\r\n                effect.getAmplifier() + \",\" +\r\n                effect.getDuration() + \",\" +\r\n                effect.isAmbient() + \",\" +\r\n                effect.hasParticles() + \",\" +\r\n                effect.hasIcon();\r\n    }\r\n\r\n    public static PotionEffect parseLegacyEffectString(String str, TagContext context) {\r\n        String[] d2 = str.split(\",\");\r\n        PotionEffectType type;\r\n        try {\r\n            type = PotionEffectType.getByName(d2[0].toUpperCase());\r\n        }\r\n        catch (IllegalArgumentException ex) {\r\n            if (context.showErrors()) {\r\n                Debug.echoError(\"Invalid potion effect type '\" + d2[0] + \"'\");\r\n            }\r\n            return null;\r\n        }\r\n        if (d2.length < 3) {\r\n            return null;\r\n        }\r\n        // NOTE: amplifier and duration are swapped around in the input format\r\n        // as compared to the PotionEffect constructor!\r\n        int duration = new ElementTag(d2[2]).asInt();\r\n        int amplifier = new ElementTag(d2[1]).asInt();\r\n        boolean ambient = true;\r\n        boolean particles = true;\r\n        if (d2.length > 3) {\r\n            ambient = new ElementTag(d2[3]).asBoolean();\r\n            particles = new ElementTag(d2[4]).asBoolean();\r\n        }\r\n        boolean icon = false;\r\n        if (d2.length > 5) {\r\n            ElementTag check = new ElementTag(d2[5]);\r\n            if (check.isBoolean()) {\r\n                icon = check.asBoolean();\r\n            }\r\n        }\r\n        return new PotionEffect(type, duration, amplifier, ambient, particles, icon);\r\n    }\r\n\r\n    @Deprecated(forRemoval = true)\r\n    public record LegacyPotionData(String type, boolean extended, boolean upgraded) {\r\n        public static final LegacyPotionData DEFAULT_DATA = new LegacyPotionData(\"UNCRAFTABLE\", false, false);\r\n\r\n        public LegacyPotionData(PotionData data) {\r\n            this(data.getType().name(), data.isExtended(), data.isUpgraded());\r\n        }\r\n    }\r\n\r\n    @Deprecated(forRemoval = true)\r\n    public LegacyPotionData getLegacyBasePotionData() {\r\n        PotionData data = as(PotionMeta.class).getBasePotionData();\r\n        return data != null ? new LegacyPotionData(data) : LegacyPotionData.DEFAULT_DATA;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemProperty.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.ObjectProperty;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.meta.ItemMeta;\r\n\r\nimport java.util.function.Consumer;\r\n\r\npublic abstract class ItemProperty<TData extends ObjectTag> extends ObjectProperty<ItemTag, TData> {\r\n\r\n    public MaterialTag getMaterialTag() {\r\n        return object.getMaterial();\r\n    }\r\n\r\n    public Material getMaterial() {\r\n        return object.getBukkitMaterial();\r\n    }\r\n\r\n    public ItemStack getItemStack() {\r\n        return object.getItemStack();\r\n    }\r\n\r\n    public ItemMeta getItemMeta() {\r\n        return object.getItemMeta();\r\n    }\r\n\r\n    public void setItemStack(ItemStack item) {\r\n        object.setItemStack(item);\r\n    }\r\n\r\n    public void setItemMeta(ItemMeta meta) {\r\n        object.setItemMeta(meta);\r\n    }\r\n\r\n    public <T extends ItemMeta> T as(Class<T> metaType) {\r\n        return (T) getItemMeta();\r\n    }\r\n\r\n    public <T extends ItemMeta> void editMeta(Class<T> metaType, Consumer<T> editor) {\r\n        T meta = (T) getItemMeta();\r\n        editor.accept(meta);\r\n        setItemMeta(meta);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemQuantity.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\n\npublic class ItemQuantity implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        // all items can have a quantity\n        return item instanceof ItemTag;\n    }\n\n    public static ItemQuantity getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemQuantity((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"quantity\", \"qty\", \"max_stack\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"quantity\"\n    };\n\n    public ItemQuantity(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.quantity>\n        // @returns ElementTag(Number)\n        // @mechanism ItemTag.quantity\n        // @group properties\n        // @description\n        // Returns the number of items in the ItemTag's itemstack.\n        // -->\n        if (attribute.startsWith(\"qty\")) {\n            BukkitImplDeprecations.qtyTags.warn(attribute.context);\n            return new ElementTag(item.getItemStack().getAmount())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n        if (attribute.startsWith(\"quantity\")) {\n            return new ElementTag(item.getItemStack().getAmount())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.max_stack>\n        // @returns ElementTag(Number)\n        // @group properties\n        // @description\n        // Returns the max number of this item possible in a single stack of this type.\n        // For use with <@link tag ItemTag.quantity> and <@link mechanism ItemTag.quantity>.\n        // -->\n        if (attribute.startsWith(\"max_stack\")) {\n            return new ElementTag(item.getItemStack().getMaxStackSize())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        if (item.getItemStack().getAmount() > 1) {\n            return String.valueOf(item.getItemStack().getAmount());\n        }\n        else {\n            return null;\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"quantity\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name quantity\n        // @input ElementTag(Number)\n        // @description\n        // Changes the number of items in this stack.\n        // @tags\n        // <ItemTag.quantity>\n        // <ItemTag.max_stack>\n        // -->\n        if (mechanism.matches(\"quantity\") && mechanism.requireInteger()) {\n            item.setAmount(mechanism.getValue().asInt());\n        }\n\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemRawNBT.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\nimport net.kyori.adventure.nbt.*;\nimport org.bukkit.Material;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class ItemRawNBT extends ItemProperty<MapTag> {\n\n    public static boolean describes(ItemTag item) {\n        // All items can have raw NBT\n        return item.getBukkitMaterial() != Material.AIR;\n    }\n\n    public ItemRawNBT(ItemTag item) {\n        this.object = item;\n    }\n\n    public static String[] defaultNbtKeys = new String[] {\n        // Denizen\n        \"Denizen Item Script\", \"DenizenItemScript\", \"Denizen NBT\", \"Denizen\",\n        // General\n        \"Damage\", \"Unbreakable\", \"CanDestroy\", \"CustomModelData\", \"trim\",\n        // Display data\n        \"display\", \"HideFlags\",\n        // Block\n        \"CanPlaceOn\", \"BlockEntityTag\", \"BlockStateTag\",\n        // Enchanting\n        \"Enchantments\", \"StoredEnchantments\", \"RepairCost\",\n        // Attributes\n        \"AttributeModifiers\",\n        // Potions\n        \"CustomPotionEffects\", \"Potion\", \"CustomPotionColor\",\n        // Crossbow specific\n        \"ChargedProjectiles\", \"Charged\",\n        // Book specific\n        \"resolved\", \"generation\", \"author\", \"title\", \"pages\",\n        // Player Head specific\n        \"SkullOwner\",\n        // Firework specific\n        \"Explosion\", \"Fireworks\",\n        //\"EntityTag\", // Special handling\n        // Bucket specific\n        //\"BucketVariantTag\", // Temporarily sent through as raw due to lack of property coverage\n        // Map specific\n        \"map\", \"map_scale_direction\",\n        //\"Decorations\", // Temporarily sent through due to apparent usage in certain vanilla cases not covered by properties\n        // Stew specific\n        \"Effects\",\n        // Lodestone compass specific\n        //\"LodestoneDimension\", \"LodestonePos\", // Temporarily sent through due to \"Dimension\" inconsistency, and compatibility with unloaded worlds\n        \"LodestoneTracked\",\n        // Bundle specific\n        \"Items\",\n        // Goat Horn specific\n        \"instrument\"\n    };\n\n    public MapTag getNonDefaultNBTMap() {\n        MapTag result = getFullNBTMap();\n        for (String key : defaultNbtKeys) {\n            result.remove(key);\n        }\n        if (getMaterial() == Material.ITEM_FRAME) {\n            MapTag entityMap = (MapTag) result.getObject(\"EntityTag\");\n            if (entityMap != null) {\n                entityMap.putObject(\"Invisible\", null);\n                if (entityMap.isEmpty()) {\n                    result.putObject(\"EntityTag\", null);\n                }\n            }\n        }\n        if (getMaterial() == Material.ARMOR_STAND) {\n            MapTag entityMap = (MapTag) result.getObject(\"EntityTag\");\n            if (entityMap != null) {\n                entityMap.putObject(\"Pose\", null);\n                entityMap.putObject(\"Small\", null);\n                entityMap.putObject(\"NoBasePlate\", null);\n                entityMap.putObject(\"Marker\", null);\n                entityMap.putObject(\"Invisible\", null);\n                entityMap.putObject(\"ShowArms\", null);\n                if (entityMap.isEmpty()) {\n                    result.putObject(\"EntityTag\", null);\n                }\n            }\n        }\n        return result;\n    }\n\n    public MapTag getFullNBTMap() {\n        return (MapTag) nbtTagToObject(NMSHandler.itemHelper.getNbtData(getItemStack()));\n    }\n\n    // <--[language]\n    // @name Raw NBT Encoding\n    // @group Useful Lists\n    // @description\n    // Several things in Minecraft use NBT to store data, such as items and entities.\n    // For the sake of inter-compatibility, a special standard format is used in Denizen to preserve data types.\n    // This system exists in Denizen primarily for the sake of compatibility with external plugins/systems.\n    // It should not be used in any scripts that don't rely on data from external plugins.\n    //\n    // NBT Tags are encoded as follows:\n    // CompoundTag: (a fully formed MapTag)\n    // ListTag: list:(NBT type-code):(a fully formed ListTag)\n    // ByteArrayTag: byte_array:(a pipe-separated list of numbers)\n    // IntArrayTag: int_array:(a pipe-separated list of numbers)\n    // ByteTag: byte:(#)\n    // ShortTag: short:(#)\n    // IntTag: int:(#)\n    // LongTag: long:(#)\n    // FloatTag: float:(#)\n    // DoubleTag: double:(#)\n    // StringTag: string:(text here)\n    // EndTag: end\n    //\n    // -->\n\n    public static final BinaryTagType<?>[] BY_ID;\n    public static final boolean HAS_NBT_LIST_TYPES = NMSHandler.getVersion().isAtMost(NMSVersion.v1_20);\n    public static final TagStringIO SNBT_PARSER = TagStringIO.builder().emitHeterogeneousLists(HAS_NBT_LIST_TYPES).acceptHeterogeneousLists(!HAS_NBT_LIST_TYPES).build();\n\n    static {\n        // TODO: adventure-nbt: get type by id\n        List<BinaryTagType<?>> allTypes = ReflectionHelper.getFieldValue(BinaryTagType.class, \"TYPES\", null);\n        BY_ID = new BinaryTagType[allTypes.size()];\n        for (BinaryTagType<?> type : allTypes) {\n            BY_ID[type.id()] = type;\n        }\n    }\n\n    public static CompoundBinaryTag compoundOrEmpty(CompoundBinaryTag compoundTag) {\n        return compoundTag != null ? compoundTag : CompoundBinaryTag.empty();\n    }\n\n    public static BinaryTag convertObjectToNbt(ObjectTag inputObject, TagContext context, String path) {\n        if (inputObject.canBeType(MapTag.class)) {\n            MapTag map = inputObject.asType(MapTag.class, context);\n            CompoundBinaryTag.Builder resultBuilder = CompoundBinaryTag.builder(map.size());\n            for (Map.Entry<StringHolder, ObjectTag> entry : map.entrySet()) {\n                try {\n                    resultBuilder.put(entry.getKey().str, convertObjectToNbt(entry.getValue(), context, path + \".\" + entry.getKey().str));\n                }\n                catch (Exception ex) {\n                    Debug.echoError(\"Object NBT interpretation failed for key '\" + path + \".\" + entry.getKey().str + \"'.\");\n                    Debug.echoError(ex);\n                    return null;\n                }\n            }\n            return resultBuilder.build();\n        }\n        else if (!HAS_NBT_LIST_TYPES && inputObject.shouldBeType(ListTag.class)) {\n            ListTag list = inputObject.asType(ListTag.class, context);\n            ListBinaryTag.Builder<BinaryTag> resultBuilder = ListBinaryTag.heterogeneousListBinaryTag(list.size());\n            for (int i = 0; i < list.size(); i++) {\n                try {\n                    resultBuilder.add(convertObjectToNbt(list.getObject(i), context, path + '[' + i + ']'));\n                }\n                catch (Exception ex) {\n                    Debug.echoError(\"Object NBT interpretation failed for list key '\" + path + \"' at index \" + i + '.');\n                    Debug.echoError(ex);\n                    return null;\n                }\n            }\n            return resultBuilder.build();\n        }\n        String input = inputObject.identify();\n        if (input.equals(\"end\")) {\n            return EndBinaryTag.endBinaryTag();\n        }\n        int colonIndex = input.indexOf(':');\n        if (colonIndex == -1) {\n            Debug.echoError(\"Object NBT interpretation failed for key '\" + path + \"': missing object type.\");\n            return null;\n        }\n        String type = input.substring(0, colonIndex), value = input.substring(colonIndex + 1);\n        return switch (type) {\n            case \"list\" -> {\n                int nextColonIndex = value.indexOf(':');\n                int typeCode = Integer.parseInt(value.substring(0, nextColonIndex));\n                String listValue = value.substring(nextColonIndex + 1);\n                List<BinaryTag> result = new ArrayList<>();\n                ListTag listTag = ListTag.valueOf(listValue, context);\n                for (int i = 0; i < listTag.size(); i++) {\n                    try {\n                        result.add(convertObjectToNbt(listTag.getObject(i), context, path + '[' + i + ']'));\n                    }\n                    catch (Exception ex) {\n                        Debug.echoError(\"Object NBT interpretation failed for list key '\" + path + \"' at index \" + i + '.');\n                        Debug.echoError(ex);\n                        yield null;\n                    }\n                }\n                yield ListBinaryTag.listBinaryTag(BY_ID[typeCode], result);\n            }\n            case \"byte_array\" -> {\n                ListTag numberStrings = ListTag.valueOf(value, context);\n                byte[] result = new byte[numberStrings.size()];\n                for (int i = 0; i < result.length; i++) {\n                    result[i] = Byte.parseByte(numberStrings.get(i));\n                }\n                yield ByteArrayBinaryTag.byteArrayBinaryTag(result);\n            }\n            case \"int_array\" -> {\n                ListTag numberStrings = ListTag.valueOf(value, context);\n                int[] result = new int[numberStrings.size()];\n                for (int i = 0; i < result.length; i++) {\n                    result[i] = Integer.parseInt(numberStrings.get(i));\n                }\n                yield IntArrayBinaryTag.intArrayBinaryTag(result);\n            }\n            case \"long_array\" -> {\n                ListTag numberStrings = ListTag.valueOf(value, context);\n                long[] result = new long[numberStrings.size()];\n                for (int i = 0; i < result.length; i++) {\n                    result[i] = Long.parseLong(numberStrings.get(i));\n                }\n                yield LongArrayBinaryTag.longArrayBinaryTag(result);\n            }\n            case \"byte\" -> ByteBinaryTag.byteBinaryTag(Byte.parseByte(value));\n            case \"short\" -> ShortBinaryTag.shortBinaryTag(Short.parseShort(value));\n            case \"int\" -> IntBinaryTag.intBinaryTag(Integer.parseInt(value));\n            case \"long\" -> LongBinaryTag.longBinaryTag(Long.parseLong(value));\n            case \"float\" -> FloatBinaryTag.floatBinaryTag(Float.parseFloat(value));\n            case \"double\" -> DoubleBinaryTag.doubleBinaryTag(Double.parseDouble(value));\n            case \"string\" -> StringBinaryTag.stringBinaryTag(value);\n            default -> {\n                if (context == null || context.showErrors()) {\n                    Debug.echoError(\"Unknown raw NBT value: \" + inputObject);\n                }\n                yield null;\n            }\n        };\n    }\n\n    public static ObjectTag nbtTagToObject(BinaryTag tag) {\n        return nbtTagToObject(tag, false);\n    }\n\n    public static ObjectTag nbtTagToObject(BinaryTag tag, boolean unwrapLists) {\n        if (tag instanceof CompoundBinaryTag compoundTag) {\n            MapTag result = new MapTag();\n            for (Map.Entry<String, ? extends BinaryTag> entry : compoundTag) {\n                result.putObject(entry.getKey(), nbtTagToObject(entry.getValue(), unwrapLists));\n            }\n            return result;\n        }\n        else if (tag instanceof ListBinaryTag listTag) {\n            if (!HAS_NBT_LIST_TYPES && unwrapLists) {\n                listTag = listTag.unwrapHeterogeneity();\n            }\n            ListTag result = new ListTag(listTag.size());\n            for (BinaryTag entry : listTag) {\n                result.addObject(nbtTagToObject(entry, unwrapLists));\n            }\n            return HAS_NBT_LIST_TYPES ? new ElementTag(\"list:\" + listTag.elementType().id() + ':' + result.identify()) : result;\n        }\n        else if (tag instanceof ByteArrayBinaryTag byteArrayTag) {\n            byte[] data = byteArrayTag.value();\n            StringBuilder output = new StringBuilder(data.length * 4);\n            for (byte value : data) {\n                output.append(value).append('|');\n            }\n            return new ElementTag(\"byte_array:\" + output);\n        }\n        else if (tag instanceof IntArrayBinaryTag intArrayTag) {\n            int[] data = intArrayTag.value();\n            StringBuilder output = new StringBuilder(data.length * 4);\n            for (int value : data) {\n                output.append(value).append('|');\n            }\n            return new ElementTag(\"int_array:\" + output);\n        }\n        else if (tag instanceof LongArrayBinaryTag longArrayTag) {\n            long[] data = longArrayTag.value();\n            StringBuilder output = new StringBuilder(data.length * 4);\n            for (long value : data) {\n                output.append(value).append('|');\n            }\n            return new ElementTag(\"long_array:\" + output);\n        }\n        else if (tag instanceof ByteBinaryTag byteTag) {\n            return new ElementTag(\"byte:\" + byteTag.value());\n        }\n        else if (tag instanceof ShortBinaryTag shortTag) {\n            return new ElementTag(\"short:\" + shortTag.value());\n        }\n        else if (tag instanceof IntBinaryTag intTag) {\n            return new ElementTag(\"int:\" + intTag.value());\n        }\n        else if (tag instanceof LongBinaryTag longTag) {\n            return new ElementTag(\"long:\" + longTag.value());\n        }\n        else if (tag instanceof FloatBinaryTag floatTag) {\n            return new ElementTag(\"float:\" + floatTag.value());\n        }\n        else if (tag instanceof DoubleBinaryTag doubleTag) {\n            return new ElementTag(\"double:\" + doubleTag.value());\n        }\n        else if (tag instanceof StringBinaryTag stringTag) {\n            return new ElementTag(\"string:\" + stringTag.value());\n        }\n        else  if (tag instanceof EndBinaryTag) {\n            return new ElementTag(\"end\");\n        }\n        throw new IllegalStateException(\"Unrecognized API tag of type '\" + tag.type() + \"': \" + tag);\n    }\n\n    @Override\n    public MapTag getPropertyValue() {\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\n            MapTag nonDefaultNBT = getNonDefaultNBTMap();\n            return nonDefaultNBT.isEmpty() ? null : nonDefaultNBT;\n        }\n        return null;\n    }\n\n    @Override\n    public void setPropertyValue(MapTag value, Mechanism mechanism) {\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\n            setFullNBT(object, value, mechanism.context, true);\n            return;\n        }\n        BukkitImplDeprecations.oldNbtProperty.warn(mechanism.context);\n        CompoundBinaryTag oldNbtData;\n        try {\n            oldNbtData = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(value, mechanism.context, \"(item)\");\n        }\n        catch (Exception ex) {\n            mechanism.echoError(\"Invalid NBT data specified:\");\n            Debug.echoError(ex);\n            return;\n        }\n        if (oldNbtData == null) {\n            mechanism.echoError(\"Invalid NBT data specified.\");\n            return;\n        }\n        setItemStack(NMSHandler.itemHelper.setPartialOldNbt(getItemStack(), oldNbtData));\n    }\n\n    public static void register() {\n\n        // <--[tag]\n        // @attribute <ItemTag.raw_nbt>\n        // @returns MapTag\n        // @mechanism ItemTag.raw_nbt\n        // @deprecated use 'ItemTag.custom_data'\n        // @description\n        // Returns a map of all non-default raw NBT on this item.\n        // Refer to format details at <@link language Raw NBT Encoding>.\n        // Deprecated in favor of <@link tag ItemTag.custom_data> on MC 1.20+.\n        // -->\n        PropertyParser.registerTag(ItemRawNBT.class, MapTag.class, \"raw_nbt\", (attribute, prop) -> {\n            if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\n                return prop.getPropertyValue();\n            }\n            BukkitImplDeprecations.oldNbtProperty.warn(attribute.context);\n            return new ItemCustomData(prop.object).getPropertyValue();\n        });\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name raw_nbt\n        // @input MapTag\n        // @deprecated use 'ItemTag.custom_data'\n        // @description\n        // Sets the given map of raw NBT keys onto this item.\n        // Note that the input format must be strictly perfect.\n        // Refer to <@link language Raw NBT Encoding> for explanation of the input format.\n        // Deprecated in favor of <@link property ItemTag.custom_data> on MC 1.20+.\n        // @tags\n        // <ItemTag.raw_nbt>\n        // <ItemTag.all_raw_nbt>\n        // -->\n        PropertyParser.registerMechanism(ItemRawNBT.class, MapTag.class, \"raw_nbt\", (prop, mechanism, value) -> {\n            prop.setPropertyValue(value, mechanism);\n        });\n\n        // <--[tag]\n        // @attribute <ItemTag.all_raw_nbt>\n        // @returns MapTag\n        // @mechanism ItemTag.raw_nbt\n        // @group properties\n        // @description\n        // Returns a map of all raw NBT on this item, including default values.\n        // Refer to format details at <@link language Raw NBT Encoding>.\n        // -->\n        PropertyParser.registerTag(ItemRawNBT.class, MapTag.class, \"all_raw_nbt\", (attribute, prop) -> {\n            return prop.getFullNBTMap();\n        });\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"raw_nbt\";\n    }\n\n    public void setFullNBT(ItemTag item, MapTag input, TagContext context, boolean retainOld) {\n        CompoundBinaryTag currentTag = retainOld ? NMSHandler.itemHelper.getNbtData(item.getItemStack()) : null;\n        // TODO: adventure-nbt: compound to builder\n        CompoundBinaryTag.Builder compoundTagBuilder = currentTag != null ? CompoundBinaryTag.builder().put(currentTag) : CompoundBinaryTag.builder();\n        for (Map.Entry<StringHolder, ObjectTag> entry : input.entrySet()) {\n            try {\n                BinaryTag tag = convertObjectToNbt(entry.getValue(), context, \"(item).\");\n                if (tag != null) {\n                    compoundTagBuilder.put(entry.getKey().str, tag);\n                }\n            }\n            catch (Exception ex) {\n                Debug.echoError(\"Raw_Nbt input failed for root key '\" + entry.getKey().str + \"'.\");\n                Debug.echoError(ex);\n                return;\n            }\n        }\n        item.setItemStack(NMSHandler.itemHelper.setNbtData(item.getItemStack(), compoundTagBuilder.build()));\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemRepairCost.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.inventory.meta.ItemMeta;\nimport org.bukkit.inventory.meta.Repairable;\n\npublic class ItemRepairCost implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && ((ItemTag) item).getItemMeta() instanceof Repairable;\n    }\n\n    public static ItemRepairCost getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemRepairCost((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n           \"repair_cost\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"repair_cost\"\n    };\n\n    public ItemRepairCost(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.repair_cost>\n        // @returns ElementTag(Number)\n        // @mechanism ItemTag.repair_cost\n        // @group properties\n        // @description\n        // Returns the current repair cost (on an anvil) for this item.\n        // Note that zero indicates no repair cost.\n        // -->\n        if (attribute.startsWith(\"repair_cost\")) {\n            return new ElementTag(((Repairable) item.getItemMeta()).getRepairCost())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        int cost = ((Repairable) item.getItemMeta()).getRepairCost();\n        if (cost != 0) {\n            return String.valueOf(cost);\n        }\n        else {\n            return null;\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"repair_cost\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name repair_cost\n        // @input ElementTag(Number)\n        // @description\n        // Changes the repair cost (on an anvil) of the item.\n        // @tags\n        // <ItemTag.repair_cost>\n        // -->\n        if (mechanism.matches(\"repair_cost\") && mechanism.requireInteger()) {\n            Repairable meta = ((Repairable) item.getItemMeta());\n            meta.setRepairCost(mechanism.getValue().asInt());\n            item.setItemMeta((ItemMeta) meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemScript.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptContainer;\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\n\npublic class ItemScript implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        // All items can have a script\n        return item instanceof ItemTag;\n    }\n\n    public static ItemScript getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemScript((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"has_script\", \"scriptname\", \"script\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"script\"\n    };\n\n    public ItemScript(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        if (attribute.startsWith(\"has_script\")) {\n            BukkitImplDeprecations.hasScriptTags.warn(attribute.context);\n            return new ElementTag(item.isItemscript())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.scriptname>\n        // @returns ElementTag\n        // @deprecated use \".script.name\" instead.\n        // @group data\n        // @description\n        // Use \".script.name\" instead.\n        // This deprecated tag may be useful for debugging items when item scripts may have been deleted.\n        // -->\n        if (attribute.startsWith(\"scriptname\")) {\n            BukkitImplDeprecations.hasScriptTags.warn(attribute.context);\n            if (item.isItemscript()) {\n                return new ElementTag(item.getScriptName())\n                        .getObjectAttribute(attribute.fulfill(1));\n            }\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.script>\n        // @returns ScriptTag\n        // @group scripts\n        // @description\n        // Returns the script of the item if it was created by an item script.\n        // -->\n        if (attribute.startsWith(\"script\")) {\n            ItemScriptContainer container = ItemScriptHelper.getItemScriptContainer(item.getItemStack());\n            if (container != null) {\n                return new ScriptTag(container)\n                        .getObjectAttribute(attribute.fulfill(1));\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        return item.getScriptName();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"script\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // Undocumented as meant for internal usage.\n\n        if (mechanism.matches(\"script\") && mechanism.hasValue()) {\n            ScriptTag script = mechanism.valueAsType(ScriptTag.class);\n            if (script == null) {\n                mechanism.echoError(\"Invalid item script '\" + mechanism.getValue().asString() + \"' - doesn't exist. Applying anyway. Resultant item may be corrupt.\");\n                item.setItemScriptName(mechanism.getValue().asString());\n            }\n            else if (script.getContainer() instanceof ItemScriptContainer) {\n                item.setItemScript((ItemScriptContainer) script.getContainer());\n            }\n            else {\n                mechanism.echoError(\"Script '\" + script.getName() + \"' is not an item script (but was specified as one).\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSignContents.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.block.Sign;\r\nimport org.bukkit.inventory.meta.BlockStateMeta;\r\n\r\nimport java.util.Arrays;\r\n\r\npublic class ItemSignContents implements Property {\r\n\r\n    public static boolean describes(ObjectTag item) {\r\n        return item instanceof ItemTag\r\n                && ((ItemTag) item).getItemMeta() instanceof BlockStateMeta\r\n                && ((BlockStateMeta) ((ItemTag) item).getItemMeta()).getBlockState() instanceof Sign;\r\n    }\r\n\r\n    public static ItemSignContents getFrom(ObjectTag _item) {\r\n        if (!describes(_item)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new ItemSignContents((ItemTag) _item);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"sign_contents\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"sign_contents\"\r\n    };\r\n\r\n    public ListTag getSignContents() {\r\n        return new ListTag(Arrays.asList(PaperAPITools.instance.getSignLines((Sign) ((BlockStateMeta) item.getItemMeta()).getBlockState())), true);\r\n    }\r\n\r\n    public ItemSignContents(ItemTag _item) {\r\n        item = _item;\r\n    }\r\n\r\n    ItemTag item;\r\n\r\n    @Override\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.sign_contents>\r\n        // @returns ListTag\r\n        // @mechanism ItemTag.sign_contents\r\n        // @group properties\r\n        // @description\r\n        // Returns a list of lines on a sign item.\r\n        // -->\r\n        if (attribute.startsWith(\"sign_contents\")) {\r\n            return getSignContents().getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        for (String line : getSignContents()) {\r\n            if (line.length() > 0) {\r\n                return getSignContents().identify();\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"sign_contents\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name sign_contents\r\n        // @input ListTag\r\n        // @description\r\n        // Sets the contents of a sign item.\r\n        // @tags\r\n        // <ItemTag.sign_contents>\r\n        // -->\r\n        if (mechanism.matches(\"sign_contents\")) {\r\n            BlockStateMeta bsm = ((BlockStateMeta) item.getItemMeta());\r\n            Sign sign = (Sign) bsm.getBlockState();\r\n            for (int i = 0; i < 4; i++) {\r\n                PaperAPITools.instance.setSignLine(sign, i, \"\");\r\n            }\r\n            ListTag list = mechanism.valueAsType(ListTag.class);\r\n            CoreUtilities.fixNewLinesToListSeparation(list);\r\n            if (list.size() > 4) {\r\n                Debug.echoError(\"Sign can only hold four lines!\");\r\n            }\r\n            else {\r\n                for (int i = 0; i < list.size(); i++) {\r\n                    PaperAPITools.instance.setSignLine(sign, i, list.get(i));\r\n                }\r\n            }\r\n            bsm.setBlockState(sign);\r\n            item.setItemMeta(bsm);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSignIsWaxed.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.Tag;\nimport org.bukkit.block.Sign;\nimport org.bukkit.inventory.meta.BlockStateMeta;\n\npublic class ItemSignIsWaxed extends ItemProperty<ElementTag> {\n\n    // <--[property]\n    // @object ItemTag\n    // @name is_waxed\n    // @input ElementTag(Boolean)\n    // @description\n    // Controls whether a sign item is waxed (cannot be edited once placed).\n    // -->\n\n    public static boolean describes(ItemTag item) {\n        return Tag.ALL_SIGNS.isTagged(item.getBukkitMaterial());\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        BlockStateMeta stateMeta = as(BlockStateMeta.class);\n        if (!stateMeta.hasBlockState()) {\n            return null;\n        }\n        return new ElementTag(((Sign) stateMeta.getBlockState()).isWaxed());\n    }\n\n    @Override\n    public ElementTag getTagValue(Attribute attribute) {\n        ElementTag value = getPropertyValue();\n        return value == null ? new ElementTag(false) : value;\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\n        if (!mechanism.requireBoolean()) {\n            return;\n        }\n        editMeta(BlockStateMeta.class, stateMeta -> {\n            Sign sign = (Sign) stateMeta.getBlockState();\n            sign.setWaxed(value.asBoolean());\n            stateMeta.setBlockState(sign);\n        });\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"is_waxed\";\n    }\n\n    public static void register() {\n        autoRegister(\"is_waxed\", ItemSignIsWaxed.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSkullskin.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.utilities.Settings;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport org.bukkit.inventory.meta.SkullMeta;\n\nimport java.util.UUID;\n\npublic class ItemSkullskin implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && ((ItemTag) item).getItemMeta() instanceof SkullMeta;\n    }\n\n    public static ItemSkullskin getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemSkullskin((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"skin\", \"has_skin\", \"skull_skin\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"skull_skin\"\n    };\n\n    public ItemSkullskin(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.skull_skin>\n        // @returns ElementTag\n        // @mechanism ItemTag.skull_skin\n        // @group properties\n        // @description\n        // Returns the UUID of the player whose skin a skull item uses.\n        // Note: Item must be a 'player_head' with a skin.\n        // In format: UUID|Texture|Name.\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\n        // -->\n        if (attribute.startsWith(\"skull_skin\")) {\n            String skin = getPropertyString();\n            if (skin == null) {\n                return null;\n            }\n            return new ElementTag(skin).getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.skin>\n        // @returns ElementTag\n        // @mechanism ItemTag.skull_skin\n        // @group properties\n        // @description\n        // Returns the UUID of the player whose skin a skull item uses.\n        // Note: Item must be a 'player_head' with a skin.\n        // In format: UUID|Texture|Name.\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\n        // -->\n        if (attribute.startsWith(\"skin\")) {\n            String skin = getPropertyString();\n            if (skin != null) {\n                attribute = attribute.fulfill(1);\n\n                if (attribute.startsWith(\"full\")) {\n                    BukkitImplDeprecations.itemSkinFullTag.warn(attribute.context);\n                    return new ElementTag(skin).getObjectAttribute(attribute.fulfill(1));\n                }\n                return new ElementTag(CoreUtilities.split(skin, '|').get(0)).getObjectAttribute(attribute);\n            }\n            else {\n                attribute.echoError(\"This skull item does not have a skin set!\");\n            }\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.has_skin>\n        // @returns ElementTag(Boolean)\n        // @mechanism ItemTag.skull_skin\n        // @group properties\n        // @description\n        // Returns whether the item has a custom skin set.\n        // (Only for 'player_head's)\n        // -->\n        if (attribute.startsWith(\"has_skin\")) {\n            return new ElementTag(getPropertyString() != null)\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        PlayerProfile playerProfile = NMSHandler.itemHelper.getSkullSkin(item.getItemStack());\n        if (playerProfile != null) {\n            String name = playerProfile.getName();\n            UUID uuid = playerProfile.getUniqueId();\n            return (uuid != null ? uuid : name)\n                    + (playerProfile.hasTexture() ? \"|\" + playerProfile.getTexture() +\n                    (uuid != null && name != null ? \"|\" + name : \"\") : \"\");\n        }\n        return null;\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"skull_skin\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name skull_skin\n        // @input ElementTag(|ElementTag(|ElementTag))\n        // @description\n        // Sets the player skin on a player_head.\n        // A head should have a Texture blob, the player's UUID, and the player's Name.\n        // The most-correct input is UUID|Texture|Name.\n        // You can alternately input Name|Texture|UUID and the order will be automatically corrected.\n        // You can alternately input just a UUID, or just a Name, or just UUID|Texture, or just Name|Texture, and the missing data will be downloaded.\n        // You can alternately input just a Texture and the remaining data will be filled as name \"null\" and UUID \"000-000\". Doing this may cause side effects with Minecraft internals or external plugins, use with caution.\n        // See also <@link language Player Entity Skins (Skin Blobs)>.\n        // @tags\n        // <ItemTag.skull_skin>\n        // <ItemTag.skin>\n        // <ItemTag.has_skin>\n        // -->\n        if (mechanism.matches(\"skull_skin\")) {\n            ListTag list = mechanism.valueAsType(ListTag.class);\n            String idString = list.get(0);\n            String texture = null;\n            if (list.size() == 1 && idString.length() > 64) {\n                texture = idString;\n                idString = null;\n            }\n            if (list.size() > 1) {\n                texture = list.get(1);\n            }\n            PlayerProfile profile;\n            if (idString == null) {\n                profile = new PlayerProfile(\"null\", new UUID(0, 0), texture);\n            }\n            else if (idString.length() < 3 && list.size() == 2) {\n                profile = new PlayerProfile(idString, new UUID(0, 0), texture);\n            }\n            else {\n                if (CoreUtilities.contains(idString, '-')) {\n                    UUID uuid = UUID.fromString(idString);\n                    String name = null;\n                    if (list.size() > 2) {\n                        name = list.get(2);\n                    }\n                    profile = new PlayerProfile(name, uuid, texture);\n                }\n                else {\n                    profile = new PlayerProfile(idString, Settings.nullifySkullSkinIds ? new UUID(0, 0) : null, texture);\n                }\n            }\n            if (texture == null || profile.getUniqueId() == null) { // Load if needed\n                profile = NMSHandler.instance.fillPlayerProfile(profile);\n            }\n            if (texture != null) {\n                profile.setTexture(texture);\n            }\n            if (profile.getTexture() == null) {\n                return; // Can't set a skull skin to nothing.\n            }\n            item.setItemStack(NMSHandler.itemHelper.setSkullSkin(item.getItemStack(), profile));\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSpawnerCount.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.block.CreatureSpawner;\nimport org.bukkit.inventory.meta.BlockStateMeta;\n\npublic class ItemSpawnerCount implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && ((ItemTag) item).getItemMeta() instanceof BlockStateMeta\n                && ((BlockStateMeta) ((ItemTag) item).getItemMeta()).getBlockState() instanceof CreatureSpawner;\n    }\n\n    public static ItemSpawnerCount getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemSpawnerCount((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"spawner_count\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"spawner_count\"\n    };\n\n    public ItemSpawnerCount(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.spawner_count>\n        // @returns ElementTag(Number)\n        // @mechanism ItemTag.spawner_count\n        // @group properties\n        // @description\n        // Returns the spawn count for a spawner block item.\n        // -->\n        if (attribute.startsWith(\"spawner_count\")) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            return new ElementTag(state.getSpawnCount())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n        CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n        return String.valueOf(state.getSpawnCount());\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"spawner_count\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name spawner_count\n        // @input ElementTag(Number)\n        // @description\n        // Sets the spawn count of a spawner block item.\n        // @tags\n        // <ItemTag.spawner_count>\n        // -->\n        if (mechanism.matches(\"spawner_count\") && mechanism.requireInteger()) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            state.setSpawnCount(mechanism.getValue().asInt());\n            meta.setBlockState(state);\n            item.setItemMeta(meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSpawnerDelay.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.block.CreatureSpawner;\nimport org.bukkit.inventory.meta.BlockStateMeta;\n\npublic class ItemSpawnerDelay implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && ((ItemTag) item).getItemMeta() instanceof BlockStateMeta\n                && ((BlockStateMeta) ((ItemTag) item).getItemMeta()).getBlockState() instanceof CreatureSpawner;\n    }\n\n    public static ItemSpawnerDelay getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemSpawnerDelay((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"spawner_spawn_delay\", \"spawner_minimum_spawn_delay\", \"spawner_maximum_spawn_delay\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"spawner_delay_data\"\n    };\n\n    public ItemSpawnerDelay(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.spawner_spawn_delay>\n        // @returns ElementTag(Number)\n        // @mechanism ItemTag.spawner_delay_data\n        // @group properties\n        // @description\n        // Returns the current spawn delay for a spawner block item.\n        // This changes over time between <@link tag ItemTag.spawner_minimum_spawn_delay> and <@link tag ItemTag.spawner_maximum_spawn_delay>.\n        // -->\n        if (attribute.startsWith(\"spawner_spawn_delay\")) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            return new ElementTag(state.getDelay())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.spawner_minimum_spawn_delay>\n        // @returns ElementTag(Number)\n        // @mechanism ItemTag.spawner_delay_data\n        // @group properties\n        // @description\n        // Returns the minimum spawn delay for a spawner block item.\n        // -->\n        if (attribute.startsWith(\"spawner_minimum_spawn_delay\")) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            return new ElementTag(state.getMinSpawnDelay())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.spawner_maximum_spawn_delay>\n        // @returns ElementTag(Number)\n        // @mechanism ItemTag.spawner_delay_data\n        // @group properties\n        // @description\n        // Returns the maximum spawn delay for a spawner block item.\n        // -->\n        if (attribute.startsWith(\"spawner_maximum_spawn_delay\")) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            return new ElementTag(state.getMaxSpawnDelay())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n        CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n        return state.getDelay() + \"|\" + state.getMinSpawnDelay() + \"|\" + state.getMaxSpawnDelay();\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"spawner_delay_data\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name spawner_delay_data\n        // @input ListTag\n        // @description\n        // Sets the current spawn delay, minimum spawn delay, and maximum spawn delay of a mob spawner block item.\n        // For example, -1|200|800\n        // @tags\n        // <ItemTag.spawner_spawn_delay>\n        // <ItemTag.spawner_minimum_spawn_delay>\n        // <ItemTag.spawner_maximum_spawn_delay>\n        // -->\n        if (mechanism.matches(\"spawner_delay_data\")) {\n            ListTag list = mechanism.valueAsType(ListTag.class);\n            if (list.size() < 3) {\n                return;\n            }\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            state.setDelay(Integer.parseInt(list.get(0)));\n            int minDelay = Integer.parseInt(list.get(1));\n            int maxDelay = Integer.parseInt(list.get(2));\n            // Minecraft won't set the limits if the new max would be lower than the current min\n            // or new min would be higher than the current max\n            if (minDelay > state.getMaxSpawnDelay()) {\n                state.setMaxSpawnDelay(maxDelay);\n                state.setMinSpawnDelay(minDelay);\n            } else {\n                state.setMinSpawnDelay(minDelay);\n                state.setMaxSpawnDelay(maxDelay);\n            }\n            meta.setBlockState(state);\n            item.setItemMeta(meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSpawnerMaxNearbyEntities.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.block.CreatureSpawner;\nimport org.bukkit.inventory.meta.BlockStateMeta;\n\npublic class ItemSpawnerMaxNearbyEntities implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && ((ItemTag) item).getItemMeta() instanceof BlockStateMeta\n                && ((BlockStateMeta) ((ItemTag) item).getItemMeta()).getBlockState() instanceof CreatureSpawner;\n    }\n\n    public static ItemSpawnerMaxNearbyEntities getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemSpawnerMaxNearbyEntities((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"spawner_max_nearby_entities\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"spawner_max_nearby_entities\"\n    };\n\n    public ItemSpawnerMaxNearbyEntities(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.spawner_max_nearby_entities>\n        // @returns ElementTag(Number)\n        // @mechanism ItemTag.spawner_max_nearby_entities\n        // @group properties\n        // @description\n        // Returns the maximum nearby entities for a spawner block item.\n        // -->\n        if (attribute.startsWith(\"spawner_max_nearby_entities\")) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            return new ElementTag(state.getMaxNearbyEntities())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n        CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n        return String.valueOf(state.getMaxNearbyEntities());\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"spawner_max_nearby_entities\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name spawner_max_nearby_entities\n        // @input ElementTag(Number)\n        // @description\n        // Sets the maximum nearby entities of a spawner block item.\n        // @tags\n        // <ItemTag.spawner_max_nearby_entities>\n        // -->\n        if (mechanism.matches(\"spawner_max_nearby_entities\") && mechanism.requireInteger()) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            state.setMaxNearbyEntities(mechanism.getValue().asInt());\n            meta.setBlockState(state);\n            item.setItemMeta(meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSpawnerPlayerRange.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.block.CreatureSpawner;\nimport org.bukkit.inventory.meta.BlockStateMeta;\n\npublic class ItemSpawnerPlayerRange implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && ((ItemTag) item).getItemMeta() instanceof BlockStateMeta\n                && ((BlockStateMeta) ((ItemTag) item).getItemMeta()).getBlockState() instanceof CreatureSpawner;\n    }\n\n    public static ItemSpawnerPlayerRange getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemSpawnerPlayerRange((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"spawner_player_range\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"spawner_player_range\"\n    };\n\n    public ItemSpawnerPlayerRange(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.spawner_player_range>\n        // @returns ElementTag(Number)\n        // @mechanism ItemTag.spawner_player_range\n        // @group properties\n        // @description\n        // Returns the maximum player range for a spawner block item (ie how close a player must be for this spawner to be active).\n        // -->\n        if (attribute.startsWith(\"spawner_player_range\")) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            return new ElementTag(state.getRequiredPlayerRange())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n        CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n        return String.valueOf(state.getRequiredPlayerRange());\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"spawner_player_range\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name spawner_player_range\n        // @input ElementTag(Number)\n        // @description\n        // Sets the maximum player range of a spawner block item.\n        // @tags\n        // <ItemTag.spawner_player_range>\n        // -->\n        if (mechanism.matches(\"spawner_player_range\") && mechanism.requireInteger()) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            state.setRequiredPlayerRange(mechanism.getValue().asInt());\n            meta.setBlockState(state);\n            item.setItemMeta(meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSpawnerRange.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.properties.Property;\nimport com.denizenscript.denizencore.tags.Attribute;\nimport org.bukkit.block.CreatureSpawner;\nimport org.bukkit.inventory.meta.BlockStateMeta;\n\npublic class ItemSpawnerRange implements Property {\n\n    public static boolean describes(ObjectTag item) {\n        return item instanceof ItemTag\n                && ((ItemTag) item).getItemMeta() instanceof BlockStateMeta\n                && ((BlockStateMeta) ((ItemTag) item).getItemMeta()).getBlockState() instanceof CreatureSpawner;\n    }\n\n    public static ItemSpawnerRange getFrom(ObjectTag _item) {\n        if (!describes(_item)) {\n            return null;\n        }\n        else {\n            return new ItemSpawnerRange((ItemTag) _item);\n        }\n    }\n\n    public static final String[] handledTags = new String[] {\n            \"spawner_range\"\n    };\n\n    public static final String[] handledMechs = new String[] {\n            \"spawner_range\"\n    };\n\n    public ItemSpawnerRange(ItemTag _item) {\n        item = _item;\n    }\n\n    ItemTag item;\n\n    @Override\n    public ObjectTag getObjectAttribute(Attribute attribute) {\n\n        if (attribute == null) {\n            return null;\n        }\n\n        // <--[tag]\n        // @attribute <ItemTag.spawner_range>\n        // @returns ElementTag(Number)\n        // @mechanism ItemTag.spawner_range\n        // @group properties\n        // @description\n        // Returns the spawn range for a spawner block item (the radius mobs will spawn in).\n        // -->\n        if (attribute.startsWith(\"spawner_range\")) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            return new ElementTag(state.getSpawnRange())\n                    .getObjectAttribute(attribute.fulfill(1));\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getPropertyString() {\n        BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n        CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n        return String.valueOf(state.getSpawnRange());\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"spawner_range\";\n    }\n\n    @Override\n    public void adjust(Mechanism mechanism) {\n\n        // <--[mechanism]\n        // @object ItemTag\n        // @name spawner_range\n        // @input ElementTag(Number)\n        // @description\n        // Sets the spawn range of a spawner block item (the radius mobs will spawn in).\n        // @tags\n        // <ItemTag.spawner_range>\n        // -->\n        if (mechanism.matches(\"spawner_range\") && mechanism.requireInteger()) {\n            BlockStateMeta meta = (BlockStateMeta) item.getItemMeta();\n            CreatureSpawner state = (CreatureSpawner) meta.getBlockState();\n            state.setSpawnRange(mechanism.getValue().asInt());\n            meta.setBlockState(state);\n            item.setItemMeta(meta);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemSpawnerType.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport org.bukkit.block.CreatureSpawner;\nimport org.bukkit.entity.EntityType;\nimport org.bukkit.inventory.meta.BlockStateMeta;\n\npublic class ItemSpawnerType extends ItemProperty<EntityTag> {\n\n    // <--[property]\n    // @object ItemTag\n    // @name spawner_type\n    // @input EntityTag\n    // @description\n    // The entity type a spawner item will spawn, if any.\n    // For the mechanism: provide no input to unset the type.\n    // Note that the type can only be unset on 1.20 and above.\n    // -->\n\n    public static boolean describes(ItemTag item) {\n        return item.getItemMeta() instanceof BlockStateMeta meta && meta.getBlockState() instanceof CreatureSpawner;\n    }\n\n    @Override\n    public EntityTag getPropertyValue() {\n        EntityType spawnedType = ((CreatureSpawner) ((BlockStateMeta) getItemMeta()).getBlockState()).getSpawnedType();\n        return spawnedType != null ? new EntityTag(spawnedType) : null;\n    }\n\n    @Override\n    public void setPropertyValue(EntityTag entity, Mechanism mechanism) {\n        if (entity == null && NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\n            mechanism.echoError(\"must have input of type 'EntityTag', but none was given.\");\n            return;\n        }\n        editMeta(BlockStateMeta.class, meta -> {\n            CreatureSpawner spawner = (CreatureSpawner) meta.getBlockState();\n            spawner.setSpawnedType(entity != null ? entity.getBukkitEntityType() : null);\n            meta.setBlockState(spawner);\n        });\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"spawner_type\";\n    }\n\n    public static void register() {\n        autoRegisterNullable(\"spawner_type\", ItemSpawnerType.class, EntityTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemTrim.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport org.bukkit.Registry;\r\nimport org.bukkit.inventory.meta.ArmorMeta;\r\nimport org.bukkit.inventory.meta.trim.ArmorTrim;\r\nimport org.bukkit.inventory.meta.trim.TrimMaterial;\r\nimport org.bukkit.inventory.meta.trim.TrimPattern;\r\n\r\npublic class ItemTrim extends ItemProperty<MapTag> {\r\n\r\n    // <--[property]\r\n    // @object ItemTag\r\n    // @name trim\r\n    // @input MapTag\r\n    // @description\r\n    // An armor item's trim.\r\n    // Allowed keys: material, pattern.\r\n    // Valid material values can be found here: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/meta/trim/TrimMaterial.html>\r\n    // Valid pattern values can be found here: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/meta/trim/TrimPattern.html>\r\n    // Valid values also include ones added by datapacks, plugins, etc. as a namespaced key.\r\n    // For the mechanism, if an item already has a trim, you can omit either material or pattern to keep the original data while also changing the other option.\r\n    // For example, if you only want to change the pattern and not the material, you can omit the material, and it will use the already existing material.\r\n    // @mechanism\r\n    // To remove the trim provide no input.\r\n    // -->\r\n\r\n    public static boolean describes(ItemTag item) {\r\n        return item.getItemMeta() instanceof ArmorMeta;\r\n    }\r\n\r\n    @Override\r\n    public MapTag getPropertyValue() {\r\n        ArmorTrim currentTrim = ((ArmorMeta) getItemMeta()).getTrim();\r\n        if (currentTrim == null) {\r\n            return null;\r\n        }\r\n        MapTag map = new MapTag();\r\n        map.putObject(\"material\", new ElementTag(Utilities.namespacedKeyToString(currentTrim.getMaterial().getKey()), true));\r\n        map.putObject(\"pattern\", new ElementTag(Utilities.namespacedKeyToString(currentTrim.getPattern().getKey()), true));\r\n        return map;\r\n    }\r\n\r\n    @Override\r\n    public boolean isDefaultValue(MapTag map) {\r\n        return map.isEmpty();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"trim\";\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(MapTag map, Mechanism mechanism) {\r\n        ArmorMeta meta = (ArmorMeta) getItemMeta();\r\n        ArmorTrim newTrim;\r\n        if (map == null) {\r\n            newTrim = null;\r\n        }\r\n        else {\r\n            ElementTag mat = map.getElement(\"material\");\r\n            ElementTag pat = map.getElement(\"pattern\");\r\n            ArmorTrim currentTrim = meta.getTrim();\r\n            if (mat == null && currentTrim == null) {\r\n                mechanism.echoError(\"The armor piece must have a material already if you want to omit it!\");\r\n                return;\r\n            }\r\n            if (pat == null && currentTrim == null) {\r\n                mechanism.echoError(\"The armor piece must have a pattern already if you want to omit it!\");\r\n                return;\r\n            }\r\n            TrimMaterial material = mat == null ? currentTrim.getMaterial() : Registry.TRIM_MATERIAL.get(Utilities.parseNamespacedKey(mat.asString()));\r\n            TrimPattern pattern = pat == null ? currentTrim.getPattern() : Registry.TRIM_PATTERN.get(Utilities.parseNamespacedKey(pat.asString()));\r\n            newTrim = new ArmorTrim(material, pattern);\r\n        }\r\n        meta.setTrim(newTrim);\r\n        setItemMeta(meta);\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegisterNullable(\"trim\", ItemTrim.class, MapTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/item/ItemUnbreakable.java",
    "content": "package com.denizenscript.denizen.objects.properties.item;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport org.bukkit.inventory.meta.ItemMeta;\r\n\r\npublic class ItemUnbreakable implements Property {\r\n\r\n    public static boolean describes(ObjectTag object) {\r\n        return object instanceof ItemTag;\r\n    }\r\n\r\n    public static ItemUnbreakable getFrom(ObjectTag object) {\r\n        if (!describes(object)) {\r\n            return null;\r\n        }\r\n        return new ItemUnbreakable((ItemTag) object);\r\n    }\r\n\r\n    public static final String[] handledTags = new String[] {\r\n            \"unbreakable\"\r\n    };\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"unbreakable\"\r\n    };\r\n\r\n    public ItemUnbreakable(ItemTag item) {\r\n        this.item = item;\r\n    }\r\n\r\n    ItemTag item;\r\n\r\n    public ObjectTag getObjectAttribute(Attribute attribute) {\r\n\r\n        if (attribute == null) {\r\n            return null;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <ItemTag.unbreakable>\r\n        // @returns ElementTag(Boolean)\r\n        // @group properties\r\n        // @mechanism ItemTag.unbreakable\r\n        // @description\r\n        // Returns whether an item has the unbreakable flag.\r\n        // -->\r\n        if (attribute.startsWith(\"unbreakable\")) {\r\n            return new ElementTag(getPropertyString() != null).getObjectAttribute(attribute.fulfill(1));\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    public String getPropertyString() {\r\n        return (item.getItemMeta() != null && item.getItemMeta().isUnbreakable()) ? \"true\" : null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"unbreakable\";\r\n    }\r\n\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object ItemTag\r\n        // @name unbreakable\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Changes whether an item has the unbreakable item flag.\r\n        // @tags\r\n        // <ItemTag.unbreakable>\r\n        // -->\r\n        if (mechanism.matches(\"unbreakable\") && mechanism.requireBoolean()) {\r\n            ItemMeta meta = item.getItemMeta();\r\n            meta.setUnbreakable(mechanism.getValue().asBoolean());\r\n            item.setItemMeta(meta);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialAge.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.exceptions.Unreachable;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.Ageable;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.Hatchable;\r\nimport org.bukkit.block.data.type.Sapling;\r\nimport org.bukkit.block.data.type.TurtleEgg;\r\n\r\npublic class MaterialAge extends MaterialProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object MaterialTag\r\n    // @name age\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls an ageable material's current age. This includes plant growth.\r\n    // See also <@link tag MaterialTag.maximum_age>.\r\n    // -->\r\n\r\n    public static boolean describes(MaterialTag material) {\r\n        BlockData data = material.getModernData();\r\n        return data instanceof Ageable\r\n                || data instanceof TurtleEgg\r\n                || data instanceof Sapling\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && data instanceof Hatchable);\r\n    }\r\n\r\n    public MaterialAge(MaterialTag material) { // NOTE: BlockGrowsScriptEvent needs this available\r\n        super(material);\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getCurrent());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        if (!mechanism.requireInteger()) {\r\n            return;\r\n        }\r\n        int age = val.asInt();\r\n        if (age < 0 || age > getMax()) {\r\n            mechanism.echoError(\"Age value '\" + age + \"' is not valid. Must be between 0 and \" + getMax() + \" for material '\" + object.name() + \"'.\");\r\n            return;\r\n        }\r\n        BlockData data = getBlockData();\r\n        if (data instanceof TurtleEgg turtle) {\r\n            turtle.setHatch(age);\r\n        }\r\n        else if (data instanceof Sapling sapling) {\r\n            sapling.setStage(age);\r\n        }\r\n        else if (data instanceof Ageable ageable) {\r\n            ageable.setAge(age);\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && data instanceof Hatchable hatchable) {\r\n            hatchable.setHatch(age);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"age\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.maximum_age>\r\n        // @returns ElementTag(Number)\r\n        // @group properties\r\n        // @description\r\n        // Returns the maximum age for an ageable material. This includes plant growth.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialAge.class, ElementTag.class, \"maximum_age\", (attribute, prop) -> {\r\n            return new ElementTag(prop.getMax());\r\n        }, \"maximum_plant_growth\");\r\n\r\n        autoRegister(\"age\", MaterialAge.class, ElementTag.class, true, \"plant_growth\");\r\n    }\r\n\r\n    public int getCurrent() {\r\n        BlockData data = getBlockData();\r\n        if (data instanceof TurtleEgg turtle) {\r\n            return turtle.getHatch();\r\n        }\r\n        else if (data instanceof Sapling sapling) {\r\n            return sapling.getStage();\r\n        }\r\n        else if (data instanceof Ageable age) {\r\n            return age.getAge();\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && data instanceof Hatchable hatchable) {\r\n            return hatchable.getHatch();\r\n        }\r\n        throw new Unreachable();\r\n    }\r\n\r\n    public int getMax() {\r\n        BlockData data = getBlockData();\r\n        if (data instanceof TurtleEgg turtle) {\r\n            return turtle.getMaximumHatch();\r\n        }\r\n        else if (data instanceof Sapling sapling) {\r\n            return sapling.getMaximumStage();\r\n        }\r\n        else if (data instanceof Ageable age) {\r\n            return age.getMaximumAge();\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && data instanceof Hatchable hatchable) {\r\n            return hatchable.getMaximumHatch();\r\n        }\r\n        throw new Unreachable();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialAttached.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.exceptions.Unreachable;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.block.data.Attachable;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.Hangable;\r\nimport org.bukkit.block.data.type.Gate;\r\nimport org.bukkit.block.data.type.Lantern;\r\n\r\npublic class MaterialAttached extends MaterialProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object MaterialTag\r\n    // @name attached\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Controls whether a material is attached.\r\n    // For a lantern, this sets whether it is hanging from the ceiling.\r\n    // For a gate, this sets whether it is lowered to attach to a wall block.\r\n    // For a mangrove_propagule, this sets whether it is hanging from the block above it.\r\n    // For a tripwire, this sets whether a tripwire hook or string forms a complete tripwire circuit and is ready to trigger.\r\n    // Updating the property on a tripwire hook will change the texture to indicate a connected string, but will not have any effect when used on the tripwire string itself.\r\n    // It may however still be used to check whether the string forms a circuit.\r\n    // For hanging signs, this affects signs hanging below a block and changes whether the chains are vertical (false) or diagonal (true).\r\n    // -->\r\n\r\n    public static boolean describes(MaterialTag material) {\r\n        BlockData data = material.getModernData();\r\n        return data instanceof Gate || data instanceof Lantern || data instanceof Attachable\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && data instanceof Hangable);\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(isAttached());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        if (!mechanism.requireBoolean()) {\r\n            return;\r\n        }\r\n        boolean attach = val.asBoolean();\r\n        BlockData data = getBlockData();\r\n        if (data instanceof Gate gate) {\r\n            gate.setInWall(attach);\r\n        }\r\n        else if (data instanceof Lantern lantern) { // TODO: remove once 1.19 is the minimum - Lantern extends Hangable\r\n            lantern.setHanging(attach);\r\n        }\r\n        else if (data instanceof Attachable attachable) {\r\n            attachable.setAttached(attach);\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && data instanceof Hangable hangable) {\r\n            hangable.setHanging(attach);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"attached\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"attached\", MaterialAttached.class, ElementTag.class, true, \"attached_to_wall\");\r\n    }\r\n\r\n    public boolean isAttached() {\r\n        BlockData data = getBlockData();\r\n        if (data instanceof Gate gate) {\r\n            return gate.isInWall();\r\n        }\r\n        else if (data instanceof Lantern lantern) { // TODO: remove once 1.19 is the minimum - Lantern extends Hangable\r\n            return lantern.isHanging(); // This is explicitly Hangable.isHanging, yet somehow it still works pre-1.19, rare moment of Java being nice about that stuff\r\n        }\r\n        else if (data instanceof Attachable attachable) {\r\n            return attachable.isAttached();\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && data instanceof Hangable hangable) {\r\n            return hangable.isHanging();\r\n        }\r\n        throw new Unreachable();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialAttachmentFace.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.block.data.Directional;\r\nimport org.bukkit.block.data.FaceAttachable;\r\nimport org.bukkit.block.data.type.Bell;\r\n\r\npublic class MaterialAttachmentFace extends MaterialProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object MaterialTag\r\n    // @name attachment_face\r\n    // @input ElementTag\r\n    // @description\r\n    // Controls the current attach direction for attachable materials such as switches, grindstones, and bells.\r\n    // For bell values, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/data/type/Bell.Attachment.html>\r\n    // For all other supported type values, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/data/FaceAttachable.AttachedFace.html>\r\n    // -->\r\n\r\n    public static boolean describes(MaterialTag material) {\r\n        return material.getModernData() instanceof FaceAttachable || material.getModernData() instanceof Bell;\r\n    }\r\n\r\n    public MaterialAttachmentFace(MaterialTag material) {\r\n        super(material);\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (getBlockData() instanceof FaceAttachable attachable) {\r\n            return new ElementTag(attachable.getAttachedFace());\r\n        }\r\n        else if (getBlockData() instanceof Bell bell) {\r\n            return new ElementTag(bell.getAttachment());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"attachment_face\";\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (getBlockData() instanceof FaceAttachable attachable) {\r\n            if (mechanism.requireEnum(FaceAttachable.AttachedFace.class)) {\r\n                attachable.setAttachedFace(value.asEnum(FaceAttachable.AttachedFace.class));\r\n            }\r\n        }\r\n        else if (getBlockData() instanceof Bell bell) {\r\n            if (mechanism.requireEnum(Bell.Attachment.class)) {\r\n                bell.setAttachment(value.asEnum(Bell.Attachment.class));\r\n            }\r\n        }\r\n    }\r\n\r\n    public BlockFace getAttachedTo() {\r\n        if (getBlockData() instanceof FaceAttachable attachable) {\r\n            return switch (attachable.getAttachedFace()) {\r\n                case WALL -> getBlockData() instanceof Directional directional ? directional.getFacing().getOppositeFace() : BlockFace.SELF;\r\n                case FLOOR -> BlockFace.DOWN;\r\n                case CEILING -> BlockFace.UP;\r\n            };\r\n        }\r\n        else if (getBlockData() instanceof Bell bell) {\r\n            return switch (bell.getAttachment()) {\r\n                case SINGLE_WALL, DOUBLE_WALL -> ((Directional) getBlockData()).getFacing();\r\n                case FLOOR -> BlockFace.DOWN;\r\n                case CEILING -> BlockFace.UP;\r\n            };\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"attachment_face\", MaterialAttachmentFace.class, ElementTag.class, false, \"switch_face\");\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialBlockType.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.exceptions.Unreachable;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.type.*;\r\n\r\npublic class MaterialBlockType extends MaterialProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object MaterialTag\r\n    // @name type\r\n    // @input ElementTag\r\n    // @description\r\n    // Controls the current type of the block.\r\n    // For slabs, input is TOP, BOTTOM, or DOUBLE.\r\n    // For piston_heads, input is NORMAL or STICKY.\r\n    // For campfires, input is NORMAL or SIGNAL.\r\n    // For pointed dripstone, input is BASE, FRUSTUM, MIDDLE, TIP, or TIP_MERGE.\r\n    // For cave vines, input is NORMAL or BERRIES.\r\n    // For scaffolding, input is NORMAL or BOTTOM.\r\n    // -->\r\n\r\n    public static boolean describes(MaterialTag material) {\r\n        BlockData data = material.getModernData();\r\n        return data instanceof Slab || data instanceof TechnicalPiston || data instanceof Campfire\r\n                || data instanceof Scaffolding || data instanceof PointedDripstone || data instanceof CaveVinesPlant;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getType());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        BlockData data = getBlockData();\r\n        if (data instanceof Slab slab && mechanism.requireEnum(Slab.Type.class)) {\r\n            slab.setType(val.asEnum(Slab.Type.class));\r\n        }\r\n        else if (data instanceof TechnicalPiston piston && mechanism.requireEnum(TechnicalPiston.Type.class)) {\r\n            piston.setType(val.asEnum(TechnicalPiston.Type.class));\r\n        }\r\n        else if (data instanceof Campfire campfire) {\r\n            campfire.setSignalFire(CoreUtilities.equalsIgnoreCase(mechanism.getValue().asString(), \"signal\"));\r\n        }\r\n        else if (data instanceof PointedDripstone dripstone && mechanism.requireEnum(PointedDripstone.Thickness.class)) {\r\n            dripstone.setThickness(val.asEnum(PointedDripstone.Thickness.class));\r\n        }\r\n        else if (data instanceof CaveVinesPlant vines) {\r\n            vines.setBerries(CoreUtilities.equalsIgnoreCase(val.asString(), \"berries\"));\r\n        }\r\n        else if (data instanceof Scaffolding scaffolding) {\r\n            scaffolding.setBottom(CoreUtilities.equalsIgnoreCase(val.asString(), \"bottom\"));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"type\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"type\", MaterialBlockType.class, ElementTag.class, true, \"slab_type\");\r\n    }\r\n\r\n    public String getType() {\r\n        BlockData data = getBlockData();\r\n        if (data instanceof Slab slab) {\r\n            return slab.getType().name();\r\n        }\r\n        else if (data instanceof TechnicalPiston piston) {\r\n            return piston.getType().name();\r\n        }\r\n        else if (data instanceof Campfire campfire) {\r\n            return campfire.isSignalFire()  ? \"SIGNAL\" : \"NORMAL\";\r\n        }\r\n        else if (data instanceof PointedDripstone dripstone) {\r\n            return dripstone.getThickness().name();\r\n        }\r\n        else if (data instanceof CaveVinesPlant vines) {\r\n            return vines.isBerries() ? \"BERRIES\" : \"NORMAL\";\r\n        }\r\n        else if (data instanceof Scaffolding scaffolding) {\r\n            return scaffolding.isBottom() ? \"BOTTOM\" : \"NORMAL\";\r\n        }\r\n        throw new Unreachable();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialBrewingStand.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.type.BrewingStand;\r\n\r\npublic class MaterialBrewingStand implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof BrewingStand;\r\n    }\r\n\r\n    public static MaterialBrewingStand getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialBrewingStand((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"bottles\"\r\n    };\r\n\r\n    public MaterialBrewingStand(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.bottles>\r\n        // @returns ListTag\r\n        // @mechanism MaterialTag.bottles\r\n        // @group properties\r\n        // @description\r\n        // Returns a list of booleans that represent whether a slot in a brewing stand has a bottle.\r\n        // Under current implementation this always returns exactly 3 values, like \"true|false|true\".\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialBrewingStand.class, ListTag.class, \"bottles\", (attribute, material) -> {\r\n            return material.getBottleBooleans();\r\n        });\r\n    }\r\n\r\n    public BrewingStand getBrewingStand() {\r\n        return (BrewingStand) material.getModernData();\r\n    }\r\n\r\n    public int getMaxBottles() {\r\n        return getBrewingStand().getMaximumBottles();\r\n    }\r\n\r\n    public ListTag getBottleBooleans() {\r\n        ListTag result = new ListTag();\r\n        for (int i = 0; i < getMaxBottles(); i++) {\r\n            result.addObject(new ElementTag(getBrewingStand().hasBottle(i)));\r\n        }\r\n        return result;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getBottleBooleans().identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"bottles\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name bottles\r\n        // @input ListTag\r\n        // @description\r\n        // Sets the bottles in a brewing stand. Input is a list of booleans representing whether that slot has a bottle.\r\n        // @tags\r\n        // <MaterialTag.bottles>\r\n        // -->\r\n        if (mechanism.matches(\"bottles\")) {\r\n            ListTag bottles = mechanism.valueAsType(ListTag.class);\r\n            if (bottles.size() > getMaxBottles()) {\r\n                mechanism.echoError(\"Too many values specified! Brewing stand has a maximum of \" + getMaxBottles() + \" bottles.\");\r\n                return;\r\n            }\r\n            for (int i = 0; i < bottles.size(); i++) {\r\n                getBrewingStand().setBottle(i, new ElementTag(bottles.get(i)).asBoolean());\r\n            }\r\n        }\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialCampfire.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport org.bukkit.block.data.type.Campfire;\r\n\r\n@Deprecated\r\npublic class MaterialCampfire implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof Campfire;\r\n    }\r\n\r\n    public static MaterialCampfire getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialCampfire((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"signal_fire\"\r\n    };\r\n\r\n    public MaterialCampfire(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n        PropertyParser.registerTag(MaterialCampfire.class, ElementTag.class, \"signal_fire\", (attribute, material) -> {\r\n            BukkitImplDeprecations.materialCampfire.warn(attribute.context);\r\n            return new ElementTag(material.getCampfire().isSignalFire());\r\n        });\r\n    }\r\n\r\n    public Campfire getCampfire() {\r\n        return (Campfire) material.getModernData();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"signal_fire\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n        if (mechanism.matches(\"signal_fire\") && mechanism.requireBoolean()) {\r\n            BukkitImplDeprecations.materialCampfire.warn(mechanism.context);\r\n            getCampfire().setSignalFire(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialCount.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.type.*;\r\n\r\npublic class MaterialCount extends MaterialProperty<ElementTag> {\r\n    // TODO: once 1.21 is the minimum supported version, remove PinkPetals usage in favor of FlowerBed\r\n\r\n    // <--[property]\r\n    // @object MaterialTag\r\n    // @name count\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls the amount of pickles in a Sea Pickle material, eggs in a Turtle Egg material, charges in a Respawn Anchor material, candles in a Candle material, flowers in a flower bed, or leaves in a leaf litter.\r\n    // -->\r\n\r\n    public static boolean describes(MaterialTag material) {\r\n        BlockData data = material.getModernData();\r\n        return data instanceof SeaPickle\r\n                || data instanceof TurtleEgg\r\n                || data instanceof RespawnAnchor\r\n                || data instanceof Candle\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && data instanceof PinkPetals)\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && (data instanceof FlowerBed\r\n                                                                        || data instanceof LeafLitter));\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (getBlockData() instanceof SeaPickle seaPickle) {\r\n            return new ElementTag(seaPickle.getPickles());\r\n        }\r\n        else if (getBlockData() instanceof TurtleEgg turtleEgg) {\r\n            return new ElementTag(turtleEgg.getEggs());\r\n        }\r\n        else if (getBlockData() instanceof RespawnAnchor respawnAnchor) {\r\n            return new ElementTag(respawnAnchor.getCharges());\r\n        }\r\n        else if (getBlockData() instanceof Candle candle) {\r\n            return new ElementTag(candle.getCandles());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof PinkPetals pinkPetals) {\r\n            return new ElementTag(pinkPetals.getFlowerAmount());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof FlowerBed flowerBed) {\r\n            return new ElementTag(flowerBed.getFlowerAmount());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof LeafLitter leafLitter) {\r\n            return new ElementTag(leafLitter.getSegmentAmount());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (mechanism.requireInteger()) {\r\n            int count = value.asInt();\r\n            if (count < getMin() || count > getMax()) {\r\n                mechanism.echoError(\"Material count mechanism value '\" + count + \"' is not valid. Must be between \" + getMin() + \" and \" + getMax() + \".\");\r\n                return;\r\n            }\r\n            if (getBlockData() instanceof SeaPickle seaPickle) {\r\n                seaPickle.setPickles(count);\r\n            }\r\n            else if (getBlockData() instanceof TurtleEgg turtleEgg) {\r\n                turtleEgg.setEggs(count);\r\n            }\r\n            else if (getBlockData() instanceof RespawnAnchor respawnAnchor) {\r\n                respawnAnchor.setCharges(count);\r\n            }\r\n            else if (getBlockData() instanceof Candle candle) {\r\n                candle.setCandles(count);\r\n            }\r\n            else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof PinkPetals pinkPetals) {\r\n                pinkPetals.setFlowerAmount(count);\r\n            }\r\n            else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof FlowerBed flowerBed) {\r\n                flowerBed.setFlowerAmount(count);\r\n            }\r\n            else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof LeafLitter leafLitter) {\r\n                leafLitter.setSegmentAmount(count);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"count\";\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.count_max>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.count\r\n        // @group properties\r\n        // @description\r\n        // Returns the maximum amount of pickles allowed in a Sea Pickle material, eggs in a Turtle Egg material, charges in a Respawn Anchor material, candles in a Candle material, or petals in a Pink Petals material.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialCount.class, ElementTag.class, \"count_max\", (attribute, material) -> {\r\n            return new ElementTag(material.getMax());\r\n        }, \"pickle_max\");\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.count_min>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.count\r\n        // @group properties\r\n        // @description\r\n        // Returns the minimum amount of pickles allowed in a Sea Pickle material, eggs in a Turtle Egg material, charges in a Respawn Anchor material, candles in a Candle material, or petals in a Pink Petals material.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialCount.class, ElementTag.class, \"count_min\", (attribute, material) -> {\r\n            return new ElementTag(material.getMin());\r\n        }, \"pickle_min\");\r\n\r\n        autoRegister(\"count\", MaterialCount.class, ElementTag.class, false, \"pickle_count\");\r\n    }\r\n\r\n    public int getMax() {\r\n        if (getBlockData() instanceof SeaPickle seaPickle) {\r\n            return seaPickle.getMaximumPickles();\r\n        }\r\n        else if (getBlockData() instanceof TurtleEgg turtleEgg) {\r\n            return turtleEgg.getMaximumEggs();\r\n        }\r\n        else if (getBlockData() instanceof RespawnAnchor respawnAnchor) {\r\n            return respawnAnchor.getMaximumCharges();\r\n        }\r\n        else if (getBlockData() instanceof Candle candle) {\r\n            return candle.getMaximumCandles();\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof PinkPetals pinkPetals) {\r\n            return pinkPetals.getMaximumFlowerAmount();\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof FlowerBed flowerBed) {\r\n            return flowerBed.getMaximumFlowerAmount();\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof LeafLitter leafLitter) {\r\n            return leafLitter.getMaximumSegmentAmount();\r\n        }\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public int getMin() {\r\n        if (getBlockData() instanceof SeaPickle seaPickle) {\r\n            return seaPickle.getMinimumPickles();\r\n        }\r\n        else if (getBlockData() instanceof TurtleEgg turtleEgg) {\r\n            return turtleEgg.getMinimumEggs();\r\n        }\r\n        else if (getBlockData() instanceof RespawnAnchor) {\r\n            return 0;\r\n        }\r\n        else if (getBlockData() instanceof Candle) {\r\n            return 1;\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof PinkPetals) {\r\n            return 1;\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof FlowerBed) {\r\n            return 1;\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof LeafLitter) {\r\n            return 1;\r\n        }\r\n        throw new UnsupportedOperationException();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialDelay.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.type.Repeater;\r\n\r\npublic class MaterialDelay implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof Repeater;\r\n    }\r\n\r\n    public static MaterialDelay getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialDelay((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"delay\"\r\n    };\r\n\r\n    public MaterialDelay(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.delay>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.delay\r\n        // @group properties\r\n        // @description\r\n        // Returns the current delay of a redstone repeater material.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialDelay.class, ElementTag.class, \"delay\", (attribute, material) -> {\r\n            return new ElementTag(material.getCurrent());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.max_delay>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.delay\r\n        // @group properties\r\n        // @description\r\n        // Returns the maximum delay allowed for the redstone repeater material.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialDelay.class, ElementTag.class, \"max_delay\", (attribute, material) -> {\r\n            return new ElementTag(material.getMax());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.min_delay>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.delay\r\n        // @group properties\r\n        // @description\r\n        // Returns the minimum delay allowed for the redstone repeater material.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialDelay.class, ElementTag.class, \"min_delay\", (attribute, material) -> {\r\n            return new ElementTag(material.getMin());\r\n        });\r\n\r\n    }\r\n\r\n    public Repeater getRepeater() {\r\n        return (Repeater) material.getModernData();\r\n    }\r\n\r\n    public int getCurrent() {\r\n        return getRepeater().getDelay();\r\n    }\r\n\r\n    public int getMax() {\r\n        return getRepeater().getMaximumDelay();\r\n    }\r\n\r\n    public int getMin() {\r\n        return getRepeater().getMinimumDelay();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(getCurrent());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"delay\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name delay\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the delay of a redstone repeater material.\r\n        // @tags\r\n        // <MaterialTag.delay>\r\n        // <MaterialTag.max_delay>\r\n        // <MaterialTag.min_delay>\r\n        // -->\r\n        if (mechanism.matches(\"delay\") && mechanism.requireInteger()) {\r\n            int delay = mechanism.getValue().asInt();\r\n            if (delay < getMin() || delay > getMax()) {\r\n                mechanism.echoError(\"Delay value '\" + delay + \"' is not valid. Must be between \" + getMin() + \" and \" + getMax() + \".\");\r\n                return;\r\n            }\r\n            getRepeater().setDelay(delay);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialDirectional.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.Axis;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.block.data.*;\r\nimport org.bukkit.block.data.type.PointedDripstone;\r\nimport org.bukkit.block.data.type.Jigsaw;\r\nimport org.bukkit.util.Vector;\r\n\r\npublic class MaterialDirectional implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        if (!(material instanceof MaterialTag)) {\r\n            return false;\r\n        }\r\n        MaterialTag mat = (MaterialTag) material;\r\n        if (!mat.hasModernData()) {\r\n            return false;\r\n        }\r\n        BlockData data = mat.getModernData();\r\n        return data instanceof Directional\r\n                || data instanceof Orientable\r\n                || data instanceof Rotatable\r\n                || data instanceof Rail\r\n                || data instanceof Jigsaw\r\n                || data instanceof PointedDripstone;\r\n    }\r\n\r\n    public static MaterialDirectional getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialDirectional((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"direction\"\r\n    };\r\n\r\n    public MaterialDirectional(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    public MaterialTag material;\r\n\r\n    public static BlockFace[] rotatableValidFaces = new BlockFace[] {\r\n            BlockFace.SOUTH, BlockFace.SOUTH_SOUTH_WEST, BlockFace.SOUTH_WEST, BlockFace.WEST_SOUTH_WEST, BlockFace.WEST,\r\n            BlockFace.WEST_NORTH_WEST, BlockFace.NORTH_WEST, BlockFace.NORTH_NORTH_WEST, BlockFace.NORTH, BlockFace.NORTH_NORTH_EAST,\r\n            BlockFace.NORTH_EAST, BlockFace.EAST_NORTH_EAST, BlockFace.EAST, BlockFace.EAST_SOUTH_EAST, BlockFace.SOUTH_EAST, BlockFace.SOUTH_SOUTH_EAST\r\n    };\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.valid_directions>\r\n        // @returns ListTag\r\n        // @mechanism MaterialTag.direction\r\n        // @group properties\r\n        // @description\r\n        // Returns a list of directions that are valid for a directional material.\r\n        // See also <@link tag MaterialTag.direction>\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialDirectional.class, ListTag.class, \"valid_directions\", (attribute, material) -> {\r\n            ListTag toReturn = new ListTag();\r\n            if (material.isOrientable()) {\r\n                for (Axis axis : material.getOrientable().getAxes()) {\r\n                    toReturn.add(axis.name());\r\n                }\r\n            }\r\n            else if (material.isRail()) {\r\n                for (Rail.Shape shape : material.getRail().getShapes()) {\r\n                    toReturn.add(shape.name());\r\n                }\r\n            }\r\n            else if (material.isDirectional()) {\r\n                for (BlockFace face : material.getDirectional().getFaces()) {\r\n                    toReturn.add(face.name());\r\n                }\r\n            }\r\n            else if (material.isRotatable()) {\r\n                for (BlockFace face : rotatableValidFaces) {\r\n                    toReturn.add(face.name());\r\n                }\r\n            }\r\n            else if (material.isDripstone()) {\r\n                for (BlockFace face : material.getDripstone().getVerticalDirections()) {\r\n                    toReturn.add(face.name());\r\n                }\r\n            }\r\n            else if (material.isJigsaw()) {\r\n                for (Jigsaw.Orientation orientation : Jigsaw.Orientation.values()) {\r\n                    toReturn.add(orientation.name());\r\n                }\r\n            }\r\n            else { // Unreachable\r\n                return null;\r\n            }\r\n            return toReturn;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.direction>\r\n        // @returns ElementTag\r\n        // @mechanism MaterialTag.direction\r\n        // @group properties\r\n        // @description\r\n        // Returns the current facing direction for a directional material (like a door or a bed).\r\n        // This includes materials that Spigot classifies as \"directional\", \"orientable\", or \"rotatable\", as well as rails, dripstone, and jigsaw blocks.\r\n        // Output is a direction name like \"NORTH\", or an axis like \"X\", or a rail direction like \"ASCENDING_NORTH\".\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialDirectional.class, ElementTag.class, \"direction\", (attribute, material) -> {\r\n            return new ElementTag(material.getDirectionName());\r\n        });\r\n    }\r\n\r\n    public Vector getDirectionVector() {\r\n        if (isOrientable()) {\r\n            switch (getOrientable().getAxis()) {\r\n                case X:\r\n                    return new Vector(1, 0, 0);\r\n                case Y:\r\n                    return new Vector(0, 1, 0);\r\n                default:\r\n                    return new Vector(0, 0, 1);\r\n            }\r\n        }\r\n        else if (isRotatable()) {\r\n            return getRotatable().getRotation().getDirection();\r\n        }\r\n        else if (isRail()) {\r\n            switch (getRail().getShape()) {\r\n                case ASCENDING_EAST:\r\n                    return new Vector(1, 1, 0);\r\n                case ASCENDING_NORTH:\r\n                    return new Vector(0, 1, -1);\r\n                case ASCENDING_SOUTH:\r\n                    return new Vector(0, 1, 1);\r\n                case ASCENDING_WEST:\r\n                    return new Vector(-1, 1, 0);\r\n                case EAST_WEST:\r\n                    return new Vector(1, 0, 0);\r\n                case NORTH_EAST:\r\n                    return new Vector(1, 0, -1);\r\n                case NORTH_SOUTH:\r\n                    return new Vector(0, 0, 1);\r\n                case NORTH_WEST:\r\n                    return new Vector(-1, 0, -1);\r\n                case SOUTH_EAST:\r\n                    return new Vector(1, 0, 1);\r\n                case SOUTH_WEST:\r\n                    return new Vector(-1, 0, 1);\r\n            }\r\n            return null; // Unreachable.\r\n        }\r\n        else if (isDirectional()) {\r\n            return getDirectional().getFacing().getDirection();\r\n        }\r\n        else if (isDripstone()) {\r\n            return getDripstone().getVerticalDirection().getDirection();\r\n        }\r\n        else if (isJigsaw()) {\r\n            switch (getJigsaw().getOrientation()) {\r\n                case DOWN_EAST:\r\n                    return new Vector(1, -1, 0);\r\n                case DOWN_NORTH:\r\n                    return new Vector(0, -1, -1);\r\n                case DOWN_SOUTH:\r\n                    return new Vector(0, -1, 1);\r\n                case DOWN_WEST:\r\n                    return new Vector(-1, -1, 0);\r\n                case EAST_UP:\r\n                case UP_EAST:\r\n                    return new Vector(1, 1, 0);\r\n                case NORTH_UP:\r\n                case UP_NORTH:\r\n                    return new Vector(0, 1, -1);\r\n                case SOUTH_UP:\r\n                case UP_SOUTH:\r\n                    return new Vector(0, 1, 1);\r\n                case WEST_UP:\r\n                case UP_WEST:\r\n                    return new Vector(-1, 1, 0);\r\n            }\r\n        }\r\n        return null; // Unreachable.\r\n    }\r\n\r\n    public String getDirectionName() {\r\n        if (isOrientable()) {\r\n            return getOrientable().getAxis().name();\r\n        }\r\n        else if (isRotatable()) {\r\n            return getRotatable().getRotation().name();\r\n        }\r\n        else if (isRail()) {\r\n            return getRail().getShape().name();\r\n        }\r\n        else if (isDirectional()) {\r\n            return getDirectional().getFacing().name();\r\n        }\r\n        else if (isDripstone()) {\r\n            return getDripstone().getVerticalDirection().name();\r\n        }\r\n        else if (isJigsaw()) {\r\n            return getJigsaw().getOrientation().name();\r\n        }\r\n        return null; // Unreachable\r\n    }\r\n\r\n    public boolean isOrientable() {\r\n        return material.getModernData() instanceof Orientable;\r\n    }\r\n\r\n    public boolean isRotatable() {\r\n        return material.getModernData() instanceof Rotatable;\r\n    }\r\n\r\n    public boolean isDirectional() {\r\n        return material.getModernData() instanceof Directional;\r\n    }\r\n\r\n    public boolean isDripstone() {\r\n        return material.getModernData() instanceof PointedDripstone;\r\n    }\r\n\r\n    public boolean isRail() {\r\n        return material.getModernData() instanceof Rail;\r\n    }\r\n\r\n    public boolean isJigsaw() {\r\n        return material.getModernData() instanceof Jigsaw;\r\n    }\r\n\r\n    public Orientable getOrientable() {\r\n        return (Orientable) material.getModernData();\r\n    }\r\n\r\n    public Rotatable getRotatable() {\r\n        return (Rotatable) material.getModernData();\r\n    }\r\n\r\n    public Directional getDirectional() {\r\n        return (Directional) material.getModernData();\r\n    }\r\n\r\n    public Jigsaw getJigsaw() {\r\n        return (Jigsaw) material.getModernData();\r\n    }\r\n\r\n    public PointedDripstone getDripstone() {\r\n        return (PointedDripstone) material.getModernData();\r\n    }\r\n\r\n    public Rail getRail() {\r\n        return (Rail) material.getModernData();\r\n    }\r\n\r\n    public void setFacing(BlockFace face) {\r\n        if (isOrientable()) {\r\n            Axis axis;\r\n            Vector vec = face.getDirection();\r\n            if (Math.abs(vec.getX()) >= 0.5) {\r\n                axis = Axis.X;\r\n            }\r\n            else if (Math.abs(vec.getY()) >= 0.5) {\r\n                axis = Axis.Y;\r\n            }\r\n            else {\r\n                axis = Axis.Z;\r\n            }\r\n            getOrientable().setAxis(axis);\r\n        }\r\n        else if (isRotatable()) {\r\n            getRotatable().setRotation(face);\r\n        }\r\n        else if (isRail()) {\r\n            switch (face) {\r\n                case EAST:\r\n                case WEST:\r\n                    getRail().setShape(Rail.Shape.EAST_WEST);\r\n                case NORTH:\r\n                case SOUTH:\r\n                    getRail().setShape(Rail.Shape.NORTH_SOUTH);\r\n                case NORTH_EAST:\r\n                    getRail().setShape(Rail.Shape.NORTH_EAST);\r\n                case NORTH_WEST:\r\n                    getRail().setShape(Rail.Shape.NORTH_WEST);\r\n                case SOUTH_EAST:\r\n                    getRail().setShape(Rail.Shape.SOUTH_EAST);\r\n                case SOUTH_WEST:\r\n                    getRail().setShape(Rail.Shape.SOUTH_WEST);\r\n                default:\r\n                    Debug.echoError(\"Unsupported rail direction '\" + face + \"'.\");\r\n            }\r\n        }\r\n        else if (isDirectional()) {\r\n            getDirectional().setFacing(face);\r\n        }\r\n        else if (isDripstone()) {\r\n            getDripstone().setVerticalDirection(face);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getDirectionName();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"direction\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name direction\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the current facing direction for a directional material (like a door or a bed).\r\n        // @tags\r\n        // <MaterialTag.direction>\r\n        // <MaterialTag.valid_directions>\r\n        // -->\r\n        if (mechanism.matches(\"direction\")) {\r\n            if (isOrientable() && mechanism.requireEnum(Axis.class)) {\r\n                getOrientable().setAxis(Axis.valueOf(mechanism.getValue().asString().toUpperCase()));\r\n            }\r\n            else if (isRail() && mechanism.requireEnum(Rail.Shape.class)) {\r\n                getRail().setShape(Rail.Shape.valueOf(mechanism.getValue().asString().toUpperCase()));\r\n            }\r\n            else if (isJigsaw() && mechanism.requireEnum(Jigsaw.Orientation.class)) {\r\n                getJigsaw().setOrientation(Jigsaw.Orientation.valueOf(mechanism.getValue().asString().toUpperCase()));\r\n            }\r\n            else if (!isJigsaw() && mechanism.requireEnum(BlockFace.class)) {\r\n                setFacing(BlockFace.valueOf(mechanism.getValue().asString().toUpperCase()));\r\n            }\r\n            else {\r\n                mechanism.echoError(\"MaterialTag.Direction mechanism has bad input: directional value '\" + mechanism.getValue().asString() + \"' is invalid.\");\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialDistance.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.type.Leaves;\r\nimport org.bukkit.block.data.type.Scaffolding;\r\n\r\npublic class MaterialDistance implements Property {\r\n\r\n    public static boolean describes(Object material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && (((MaterialTag) material).getModernData() instanceof Scaffolding\r\n                || ((MaterialTag) material).getModernData() instanceof Leaves);\r\n    }\r\n\r\n    public static MaterialDistance getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialDistance((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"distance\"\r\n    };\r\n\r\n    public MaterialDistance(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.distance>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.distance\r\n        // @group properties\r\n        // @description\r\n        // Returns the horizontal distance between a scaffolding block and the nearest scaffolding block placed above a 'bottom' scaffold,\r\n        // or between a leaves block and the nearest log (a distance of 7 will cause a leaf to decay if 'persistent' is also false, less than 7 will prevent decay).\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialDistance.class, ElementTag.class, \"distance\", (attribute, material) -> {\r\n            return new ElementTag(material.getDistance());\r\n        });\r\n    }\r\n\r\n    public int getDistance() {\r\n        if (isScaffolding()) {\r\n            return getScaffolding().getDistance();\r\n        }\r\n        else if (isLeaves()) {\r\n            return getLeaves().getDistance();\r\n        }\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public Scaffolding getScaffolding() {\r\n        return (Scaffolding) material.getModernData();\r\n    }\r\n\r\n    public Leaves getLeaves() {\r\n        return (Leaves) material.getModernData();\r\n    }\r\n\r\n    public boolean isScaffolding() {\r\n        return material.getModernData() instanceof Scaffolding;\r\n    }\r\n\r\n    public boolean isLeaves() {\r\n        return material.getModernData() instanceof Leaves;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(getDistance());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"distance\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name distance\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the horizontal distance between a scaffolding block and the nearest scaffolding block placed above a 'bottom' scaffold, or between a leaves block and the nearest log.\r\n        // @tags\r\n        // <MaterialTag.distance>\r\n        // -->\r\n        if (mechanism.matches(\"distance\") && mechanism.requireInteger()) {\r\n            int distance = mechanism.getValue().asInt();\r\n            if (isScaffolding()) {\r\n                if (distance >= 0 && distance <= getScaffolding().getMaximumDistance()) {\r\n                    getScaffolding().setDistance(distance);\r\n                }\r\n                else {\r\n                    mechanism.echoError(\"Distance must be between 0 and \" + getScaffolding().getMaximumDistance());\r\n                }\r\n            }\r\n            else if (isLeaves()) {\r\n                getLeaves().setDistance(distance);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialDrags.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport org.bukkit.block.data.type.BubbleColumn;\r\n\r\n@Deprecated\r\npublic class MaterialDrags implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof BubbleColumn;\r\n    }\r\n\r\n    public static MaterialDrags getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialDrags((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"drags\"\r\n    };\r\n\r\n    public MaterialDrags(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n        PropertyParser.registerTag(MaterialDrags.class, ElementTag.class, \"drags\", (attribute, material) -> {\r\n            BukkitImplDeprecations.materialDrags.warn(attribute.context);\r\n            return new ElementTag(material.isDrag());\r\n        });\r\n    }\r\n\r\n    public BubbleColumn getBubbleColumn() {\r\n        return (BubbleColumn) material.getModernData();\r\n    }\r\n\r\n    public boolean isDrag() {\r\n        return getBubbleColumn().isDrag();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"drags\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n        if (mechanism.matches(\"drags\") && mechanism.requireBoolean()) {\r\n            BukkitImplDeprecations.materialDrags.warn(mechanism.context);\r\n            getBubbleColumn().setDrag(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialFaces.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.block.data.MultipleFacing;\r\n\r\npublic class MaterialFaces implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof MultipleFacing;\r\n    }\r\n\r\n    public static MaterialFaces getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialFaces((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"faces\"\r\n    };\r\n\r\n    public MaterialFaces(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.valid_faces>\r\n        // @returns ListTag\r\n        // @mechanism MaterialTag.faces\r\n        // @group properties\r\n        // @description\r\n        // Returns a list of faces that are valid for a material that has multiple faces.\r\n        // See also <@link tag MaterialTag.faces>\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialFaces.class, ListTag.class, \"valid_faces\", (attribute, material) -> {\r\n            ListTag toReturn = new ListTag();\r\n            for (BlockFace face : material.getFaces().getAllowedFaces()) {\r\n                toReturn.add(face.name());\r\n            }\r\n            return toReturn;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.faces>\r\n        // @returns ListTag\r\n        // @mechanism MaterialTag.faces\r\n        // @group properties\r\n        // @description\r\n        // Returns a list of the current faces for a material that has multiple faces (like a mushroom block).\r\n        // Output is a direction name like \"NORTH\".\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialFaces.class, ListTag.class, \"faces\", (attribute, material) -> {\r\n            return material.getFaceList();\r\n        });\r\n    }\r\n\r\n    public ListTag getFaceList() {\r\n        ListTag toReturn = new ListTag();\r\n        for (BlockFace face : getFaces().getFaces()) {\r\n            toReturn.add(face.name());\r\n        }\r\n        return toReturn;\r\n    }\r\n\r\n    public MultipleFacing getFaces() {\r\n        return (MultipleFacing) material.getModernData();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getFaceList().identify();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"faces\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name faces\r\n        // @input ListTag\r\n        // @description\r\n        // Sets the current faces for a material that has multiple faces (like a mushroom block).\r\n        // @tags\r\n        // <MaterialTag.faces>\r\n        // <MaterialTag.valid_faces>\r\n        // -->\r\n        if (mechanism.matches(\"faces\")) {\r\n            MultipleFacing facing = getFaces();\r\n            for (BlockFace face : facing.getAllowedFaces()) {\r\n                facing.setFace(face, false);\r\n            }\r\n            for (String faceName : mechanism.valueAsType(ListTag.class)) {\r\n                facing.setFace(BlockFace.valueOf(faceName.toUpperCase()), true);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialHalf.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.block.data.Bisected;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.Directional;\r\nimport org.bukkit.block.data.type.Bed;\r\nimport org.bukkit.block.data.type.Chest;\r\nimport org.bukkit.util.Vector;\r\n\r\npublic class MaterialHalf implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && isHalfData(((MaterialTag) material).getModernData());\r\n    }\r\n\r\n    public static boolean isHalfData(BlockData data) {\r\n        if (data instanceof Bisected || data instanceof Bed || data instanceof Chest) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static MaterialHalf getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialHalf((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"half\"\r\n    };\r\n\r\n    public MaterialHalf(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.half>\r\n        // @returns ElementTag\r\n        // @mechanism MaterialTag.half\r\n        // @group properties\r\n        // @description\r\n        // Returns the current half for a bisected material (like a door, double-plant, chest, or a bed).\r\n        // Output for \"Bisected\" blocks (doors/double plants/...) is \"BOTTOM\" or \"TOP\".\r\n        // Output for beds is \"HEAD\" or \"FOOT\".\r\n        // Output for chests is \"LEFT\" or \"RIGHT\".\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialHalf.class, ElementTag.class, \"half\", (attribute, material) -> {\r\n            String halfName = material.getHalfName();\r\n            if (halfName == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(halfName);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.relative_vector>\r\n        // @returns LocationTag\r\n        // @mechanism MaterialTag.half\r\n        // @group properties\r\n        // @description\r\n        // Returns a vector location of the other block for a bisected material.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialHalf.class, LocationTag.class, \"relative_vector\", (attribute, material) -> {\r\n            Vector vector = material.getRelativeBlockVector();\r\n            if (vector == null) {\r\n                return null;\r\n            }\r\n            return new LocationTag(vector);\r\n        });\r\n    }\r\n\r\n    public static String getHalfName(BlockData data) {\r\n        if (data instanceof Bisected) {\r\n            return ((Bisected) data).getHalf().name();\r\n        }\r\n        else if (data instanceof Bed) {\r\n            return ((Bed) data).getPart().name();\r\n        }\r\n        else if (data instanceof Chest) {\r\n            if (((Chest) data).getType() == Chest.Type.SINGLE) {\r\n                return null;\r\n            }\r\n            return ((Chest) data).getType().name();\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public String getHalfName() {\r\n        return getHalfName(material.getModernData());\r\n    }\r\n\r\n    public static void setHalfByName(BlockData data, String name) {\r\n        if (data instanceof Bisected) {\r\n            ((Bisected) data).setHalf(Bisected.Half.valueOf(name));\r\n        }\r\n        else if (data instanceof Bed) {\r\n            ((Bed) data).setPart(Bed.Part.valueOf(name));\r\n        }\r\n        else if (data instanceof Chest) {\r\n            ((Chest) data).setType(Chest.Type.valueOf(name));\r\n        }\r\n    }\r\n\r\n    public static Vector getRelativeBlockVector(BlockData data) {\r\n        if (data instanceof Bisected) {\r\n            if (((Bisected) data).getHalf() == Bisected.Half.TOP) {\r\n                return new Vector(0, -1, 0);\r\n            }\r\n            else {\r\n                return new Vector(0, 1, 0);\r\n            }\r\n        }\r\n        else if (data instanceof Bed) {\r\n            BlockFace face = ((Directional) data).getFacing();\r\n            if (((Bed) data).getPart() == Bed.Part.HEAD) {\r\n                face = face.getOppositeFace();\r\n            }\r\n            return face.getDirection();\r\n        }\r\n        else if (data instanceof Chest) {\r\n            if (((Chest) data).getType() == Chest.Type.SINGLE) {\r\n                return null;\r\n            }\r\n            Vector direction = ((Directional) data).getFacing().getDirection();\r\n            if (((Chest) data).getType() == Chest.Type.LEFT) {\r\n                return new Vector(-direction.getZ(), 0, direction.getX());\r\n            }\r\n            return new Vector(direction.getZ(), 0, -direction.getX());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public Vector getRelativeBlockVector() {\r\n        return getRelativeBlockVector(material.getModernData());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getHalfName();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"half\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name half\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the current half for a bisected material (like a door, double-plant, chest, or a bed).\r\n        // @tags\r\n        // <MaterialTag.half>\r\n        // -->\r\n        if (mechanism.matches(\"half\")) {\r\n            setHalfByName(material.getModernData(), mechanism.getValue().asString().toUpperCase());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialHinge.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.type.Door;\r\n\r\npublic class MaterialHinge implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof Door;\r\n    }\r\n\r\n    public static MaterialHinge getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialHinge((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"hinge\"\r\n    };\r\n\r\n    public MaterialHinge(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.hinge>\r\n        // @returns ElementTag\r\n        // @mechanism MaterialTag.hinge\r\n        // @group properties\r\n        // @description\r\n        // Returns a door's hinge side.\r\n        // Output is LEFT or RIGHT.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialHinge.class, ElementTag.class, \"hinge\", (attribute, material) -> {\r\n            return new ElementTag(material.getDoor().getHinge());\r\n        });\r\n    }\r\n\r\n    public Door getDoor() {\r\n        return (Door) material.getModernData();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getDoor().getHinge().name();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"hinge\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name hinge\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets a door's hinge side to LEFT or RIGHT.\r\n        // @tags\r\n        // <MaterialTag.hinge>\r\n        // -->\r\n        if (mechanism.matches(\"hinge\") && mechanism.requireEnum(Door.Hinge.class)) {\r\n            getDoor().setHinge(Door.Hinge.valueOf(mechanism.getValue().asString().toUpperCase()));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialInstrument.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.Instrument;\r\nimport org.bukkit.block.data.type.NoteBlock;\r\n\r\npublic class MaterialInstrument implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof NoteBlock;\r\n    }\r\n\r\n    public static MaterialInstrument getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialInstrument((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"instrument\"\r\n    };\r\n\r\n    public MaterialInstrument(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.instrument>\r\n        // @returns ElementTag\r\n        // @mechanism MaterialTag.instrument\r\n        // @group properties\r\n        // @description\r\n        // Returns the name of the instrument played from this note block,\r\n        // see list at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Instrument.html>.\r\n        // For the instrument that a material *would* produce if below a noteblock <@link tag MaterialTag.produced_instrument>.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialInstrument.class, ElementTag.class, \"instrument\", (attribute, material) -> {\r\n            return new ElementTag(material.getNoteBlock().getInstrument());\r\n        });\r\n    }\r\n\r\n    public NoteBlock getNoteBlock() {\r\n        return (NoteBlock) material.getModernData();\r\n    }\r\n\r\n    public void setInstrument(String instrument) {\r\n        getNoteBlock().setInstrument(Instrument.valueOf(instrument));\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getNoteBlock().getInstrument().name();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"instrument\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name instrument\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the instrument played from this note block,\r\n        // for valid instruments see list at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Instrument.html>.\r\n        // @tags\r\n        // <MaterialTag.instrument>\r\n        // -->\r\n        if (mechanism.matches(\"instrument\") && mechanism.requireEnum(Instrument.class)) {\r\n            setInstrument(mechanism.getValue().asString().toUpperCase());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialLeafSize.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.type.Bamboo;\r\n\r\npublic class MaterialLeafSize implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof Bamboo;\r\n    }\r\n\r\n    public static MaterialLeafSize getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialLeafSize((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"leaf_size\"\r\n    };\r\n\r\n    public MaterialLeafSize(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.leaf_size>\r\n        // @returns ElementTag\r\n        // @mechanism MaterialTag.leaf_size\r\n        // @group properties\r\n        // @description\r\n        // Returns the size of the leaves for this bamboo block.\r\n        // Output is SMALL, LARGE, or NONE.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialLeafSize.class, ElementTag.class, \"leaf_size\", (attribute, material) -> {\r\n            return new ElementTag(material.getBamboo().getLeaves());\r\n        });\r\n    }\r\n\r\n    public Bamboo getBamboo() {\r\n        return (Bamboo) material.getModernData();\r\n    }\r\n\r\n    public void setLeafSize(String size) {\r\n        getBamboo().setLeaves(Bamboo.Leaves.valueOf(size));\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getBamboo().getLeaves().name();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"leaf_size\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name leaf_size\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the size of the leaves for this bamboo block.\r\n        // Valid input is SMALL, LARGE, or NONE.\r\n        // @tags\r\n        // <MaterialTag.leaf_size>\r\n        // -->\r\n        if (mechanism.matches(\"leaf_size\") && mechanism.requireEnum(Bamboo.Leaves.class)) {\r\n            setLeafSize(mechanism.getValue().asString().toUpperCase());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialLevel.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.Brushable;\r\nimport org.bukkit.block.data.Levelled;\r\nimport org.bukkit.block.data.type.*;\r\n\r\npublic class MaterialLevel extends MaterialProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object MaterialTag\r\n    // @name level\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls the current level for a Levelled material, cake, beehives, snow, farmland, or brushable blocks.\r\n    // \"Levelled\" materials include: water, lava, cauldrons, composters, light blocks, brushable blocks, and any other future Levelled implementing types.\r\n    // For light blocks, this is the brightness of the light.\r\n    // For water/lava this is the height of the liquid block.\r\n    // For cauldrons, this is the amount of liquid contained.\r\n    // For cake, this is the number of bites left.\r\n    // For beehives/bee nests, this is the amount of honey contained.\r\n    // For snow, this is the number of partial layers, or the height, of a snow block.\r\n    // For farmland, this is the moisture level.\r\n    // For composters, this is the amount of compost.\r\n    // For brushable blocks (also referred to as \"suspicious blocks\"), this is the level of dusting. 1.20+ only.\r\n    // For dried ghasts, this is the level of hydration. 1.21+ only.\r\n    // See also <@link tag MaterialTag.maximum_level> and <@link tag MaterialTag.minimum_level>.\r\n    // -->\r\n\r\n    public static boolean describes(MaterialTag material) {\r\n        BlockData data = material.getModernData();\r\n        return data instanceof Levelled \r\n                || data instanceof Cake \r\n                || data instanceof Snow \r\n                || data instanceof Farmland \r\n                || data instanceof Beehive \r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && data instanceof Brushable)\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && data instanceof DriedGhast);\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (getBlockData() instanceof Cake cake) {\r\n            return new ElementTag(cake.getBites());\r\n        }\r\n        else if (getBlockData() instanceof Snow snow) {\r\n            return new ElementTag(snow.getLayers());\r\n        }\r\n        else if (getBlockData() instanceof Beehive beehive) {\r\n            return new ElementTag(beehive.getHoneyLevel());\r\n        }\r\n        else if (getBlockData() instanceof Farmland farmland) {\r\n            return new ElementTag(farmland.getMoisture());\r\n        }\r\n        else if (getBlockData() instanceof Levelled levelled) {\r\n            return new ElementTag(levelled.getLevel());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getBlockData() instanceof Brushable brushable) {\r\n            return new ElementTag(brushable.getDusted());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof DriedGhast driedGhast) {\r\n            return new ElementTag(driedGhast.getHydration());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"level\";\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (!mechanism.requireInteger()) {\r\n            return;\r\n        }\r\n        int level = value.asInt();\r\n        if (level < getMin() || level > getMax()) {\r\n            mechanism.echoError(\"Level value '\" + level + \"' is not valid. Must be between \" + getMin() + \" and \" + getMax() + \" for material '\" + getBlockData().getMaterial().name() + \"'.\");\r\n            return;\r\n        }\r\n        if (getBlockData() instanceof Cake cake) {\r\n            cake.setBites(level);\r\n        }\r\n        else if (getBlockData() instanceof Snow snow) {\r\n            snow.setLayers(level);\r\n        }\r\n        else if (getBlockData() instanceof Beehive beehive) {\r\n            beehive.setHoneyLevel(level);\r\n        }\r\n        else if (getBlockData() instanceof Farmland farmland) {\r\n            farmland.setMoisture(level);\r\n        }\r\n        else if (getBlockData() instanceof Levelled levelled) {\r\n            levelled.setLevel(level);\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getBlockData() instanceof Brushable brushable) {\r\n            brushable.setDusted(level);\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof DriedGhast driedGhast) {\r\n            driedGhast.setHydration(level);\r\n        }\r\n    }\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.maximum_level>\r\n        // @returns ElementTag(Number)\r\n        // @group properties\r\n        // @description\r\n        // Returns the maximum level for a Levelled material (like water, lava, and cauldrons), cake, beehives, snow, or farmland.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialLevel.class, ElementTag.class, \"maximum_level\", (attribute, material) -> {\r\n            return new ElementTag(material.getMax());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.minimum_level>\r\n        // @returns ElementTag(Number)\r\n        // @group properties\r\n        // @description\r\n        // Returns the minimum level for a Levelled material (like water, lava, and cauldrons), cake, beehives, snow, or farmland.\r\n        // This will return 0 for all valid materials aside from snow.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialLevel.class, ElementTag.class, \"minimum_level\", (attribute, material) -> {\r\n            return new ElementTag(material.getMin());\r\n        });\r\n\r\n        autoRegister(\"level\", MaterialLevel.class, ElementTag.class, false);\r\n    }\r\n\r\n    public int getMax() {\r\n        if (getBlockData() instanceof Cake cake) {\r\n            return cake.getMaximumBites();\r\n        }\r\n        else if (getBlockData() instanceof Snow snow) {\r\n            return snow.getMaximumLayers();\r\n        }\r\n        else if (getBlockData() instanceof Beehive beehive) {\r\n            return beehive.getMaximumHoneyLevel();\r\n        }\r\n        else if (getBlockData() instanceof Farmland farmland) {\r\n            return farmland.getMaximumMoisture();\r\n        }\r\n        else if (getBlockData() instanceof Levelled levelled) {\r\n            return levelled.getMaximumLevel();\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && getBlockData() instanceof Brushable brushable) {\r\n            return brushable.getMaximumDusted();\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof DriedGhast driedGhast) {\r\n            return driedGhast.getMaximumHydration();\r\n        }\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public int getMin() {\r\n        if (getBlockData() instanceof Snow snow) {\r\n            return snow.getMinimumLayers();\r\n        }\r\n        return 0;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialLightable.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport org.bukkit.block.data.Lightable;\r\n\r\n@Deprecated\r\npublic class MaterialLightable implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof Lightable;\r\n    }\r\n\r\n    public static MaterialLightable getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialLightable((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"lit\"\r\n    };\r\n\r\n    public MaterialLightable(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        PropertyParser.registerTag(MaterialLightable.class, ElementTag.class, \"lit\", (attribute, material) -> {\r\n            BukkitImplDeprecations.materialLit.warn(attribute.context);\r\n            return new ElementTag(material.getLightable().isLit());\r\n        });\r\n    }\r\n\r\n    public Lightable getLightable() {\r\n        return (Lightable) material.getModernData();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"lit\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        if (mechanism.matches(\"lit\") && mechanism.requireBoolean()) {\r\n            BukkitImplDeprecations.materialLit.warn(mechanism.context);\r\n            getLightable().setLit(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialLocked.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.type.Repeater;\r\n\r\npublic class MaterialLocked implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof Repeater;\r\n    }\r\n\r\n    public static MaterialLocked getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialLocked((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"is_locked\"\r\n    };\r\n\r\n    public MaterialLocked(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.is_locked>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism MaterialTag.is_locked\r\n        // @group properties\r\n        // @description\r\n        // Returns whether this redstone repeater material is locked.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialLocked.class, ElementTag.class, \"is_locked\", (attribute, material) -> {\r\n            return new ElementTag(material.isLocked());\r\n        });\r\n    }\r\n\r\n    public Repeater getRepeater() {\r\n        return (Repeater) material.getModernData();\r\n    }\r\n\r\n    public boolean isLocked() {\r\n        return getRepeater().isLocked();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(isLocked());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"is_locked\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name is_locked\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets this redstone repeater material to be locked.\r\n        // @tags\r\n        // <MaterialTag.is_locked>\r\n        // -->\r\n        if (mechanism.matches(\"is_locked\") && mechanism.requireBoolean()) {\r\n            getRepeater().setLocked(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialMode.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.type.*;\r\n\r\npublic class MaterialMode extends MaterialProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object MaterialTag\r\n    // @name mode\r\n    // @input ElementTag\r\n    // @description\r\n    // Controls a block's mode.\r\n    // For comparators, modes are COMPARE and SUBTRACT.\r\n    // For piston_heads, modes are NORMAL and SHORT.\r\n    // For bubble_columns, modes are NORMAL and DRAG.\r\n    // For structure_blocks, modes are CORNER, DATA, LOAD, and SAVE.\r\n    // For sculk_sensors, modes are ACTIVE, COOLDOWN, and INACTIVE.\r\n    // For daylight_detectors, modes are INVERTED and NORMAL.\r\n    // For command_blocks, modes are CONDITIONAL and NORMAL.\r\n    // For big_dripleafs, modes are FULL, NONE, PARTIAL, and UNSTABLE.\r\n    // For sculk_catalysts, modes are BLOOM and NORMAL.\r\n    // For sculk_shriekers, modes are SHRIEKING and NORMAL.\r\n    // For tripwires, modes are ARMED and DISARMED.\r\n    // For creaking_hearts, modes are AWAKE, DORMANT, and UPROOTED.\r\n    // For trial_spawners, modes are ACTIVE, COOLDOWN, EJECTING_REWARD, INACTIVE, WAITING_FOR_PLAYERS, and WAITING_FOR_REWARD_EJECTION.\r\n    // For vaults, modes are ACTIVE, EJECTING, INACTIVE, and UNLOCKING.\r\n    // -->\r\n\r\n    public static boolean describes(MaterialTag material) {\r\n        BlockData data = material.getModernData();\r\n        return data instanceof Comparator\r\n                || data instanceof PistonHead\r\n                || data instanceof BubbleColumn\r\n                || data instanceof StructureBlock\r\n                || data instanceof DaylightDetector\r\n                || data instanceof CommandBlock\r\n                || data instanceof SculkSensor\r\n                || data instanceof BigDripleaf\r\n                || data instanceof Tripwire\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && (data instanceof SculkCatalyst\r\n                                                                        || data instanceof SculkShrieker))\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && (data instanceof CreakingHeart\r\n                                                                        || data instanceof TrialSpawner\r\n                                                                        || data instanceof Vault));\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        if (getBlockData() instanceof Comparator comparator) {\r\n            return new ElementTag(comparator.getMode());\r\n        }\r\n        else if (getBlockData() instanceof BubbleColumn bubbleColumn) {\r\n            return new ElementTag(bubbleColumn.isDrag() ? \"DRAG\" : \"NORMAL\", true);\r\n        }\r\n        else if (getBlockData() instanceof PistonHead pistonHead) {\r\n            return new ElementTag(pistonHead.isShort() ? \"SHORT\" : \"NORMAL\", true);\r\n        }\r\n        else if (getBlockData() instanceof StructureBlock structureBlock) {\r\n            return new ElementTag(structureBlock.getMode());\r\n        }\r\n        else if (getBlockData() instanceof DaylightDetector daylightDetector) {\r\n            return new ElementTag(daylightDetector.isInverted() ? \"INVERTED\" : \"NORMAL\", true);\r\n        }\r\n        else if (getBlockData() instanceof CommandBlock cmdBlock) {\r\n            return new ElementTag(cmdBlock.isConditional() ? \"CONDITIONAL\" : \"NORMAL\", true);\r\n        }\r\n        else if (getBlockData() instanceof SculkSensor sculkSensor) {\r\n            return new ElementTag(sculkSensor.getPhase());\r\n        }\r\n        else if (getBlockData() instanceof BigDripleaf bigDripleaf) {\r\n            return new ElementTag(bigDripleaf.getTilt());\r\n        }\r\n        else if (getBlockData() instanceof Tripwire tripwire) {\r\n            return new ElementTag(tripwire.isDisarmed() ? \"DISARMED\" : \"ARMED\", true);\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof SculkCatalyst sculkCatalyst) {\r\n            return new ElementTag(sculkCatalyst.isBloom() ? \"BLOOM\" : \"NORMAL\", true);\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof SculkShrieker sculkShrieker) {\r\n            return new ElementTag(sculkShrieker.isShrieking() ? \"SHRIEKING\" : \"NORMAL\", true);\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof CreakingHeart creakingHeart) {\r\n            return new ElementTag(creakingHeart.getCreakingHeartState().name(), true); // TODO: once 1.21 is the minimum supported version, use the enum constructor\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof TrialSpawner trialSpawner) {\r\n            return new ElementTag(trialSpawner.getTrialSpawnerState().name(), true); // TODO: once 1.21 is the minimum supported version, use the enum constructor\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof Vault vault) {\r\n            return new ElementTag(vault.getVaultState().name(), true); // TODO: once 1.21 is the minimum supported version, use the enum constructor\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\r\n        if (getBlockData() instanceof Comparator comparator) {\r\n            if (mechanism.requireEnum(Comparator.Mode.class)) {\r\n                comparator.setMode(value.asEnum(Comparator.Mode.class));\r\n            }\r\n        }\r\n        else if (getBlockData() instanceof BubbleColumn bubbleColumn) {\r\n            bubbleColumn.setDrag(value.asLowerString().equals(\"drag\"));\r\n        }\r\n        else if (getBlockData() instanceof PistonHead pistonHead) {\r\n            pistonHead.setShort(value.asLowerString().equals(\"short\"));\r\n        }\r\n        else if (getBlockData() instanceof StructureBlock structureBlock) {\r\n            if (mechanism.requireEnum(StructureBlock.Mode.class)) {\r\n                structureBlock.setMode(value.asEnum(StructureBlock.Mode.class));\r\n            }\r\n        }\r\n        else if (getBlockData() instanceof DaylightDetector daylightDetector) {\r\n            daylightDetector.setInverted(value.asLowerString().equals(\"inverted\"));\r\n        }\r\n        else if (getBlockData() instanceof CommandBlock cmdBlock) {\r\n            cmdBlock.setConditional(value.asLowerString().equals(\"conditional\"));\r\n        }\r\n        else if (getBlockData() instanceof SculkSensor sculkSensor) {\r\n            if (mechanism.requireEnum(SculkSensor.Phase.class)) {\r\n                sculkSensor.setPhase(value.asEnum(SculkSensor.Phase.class));\r\n            }\r\n        }\r\n        else if (getBlockData() instanceof BigDripleaf bigDripleaf) {\r\n            if (mechanism.requireEnum(BigDripleaf.Tilt.class)) {\r\n                bigDripleaf.setTilt(value.asEnum(BigDripleaf.Tilt.class));\r\n            }\r\n        }\r\n        else if (getBlockData() instanceof Tripwire tripwire) {\r\n            tripwire.setDisarmed(value.asLowerString().equals(\"disarmed\"));\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof SculkCatalyst sculkCatalyst) {\r\n            sculkCatalyst.setBloom(value.asLowerString().equals(\"bloom\"));\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getBlockData() instanceof SculkShrieker sculkShrieker) {\r\n            sculkShrieker.setShrieking(value.asLowerString().equals(\"shrieking\"));\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof CreakingHeart creakingHeart) {\r\n            if (mechanism.requireEnum(CreakingHeart.State.class)) {\r\n                creakingHeart.setCreakingHeartState(value.asEnum(CreakingHeart.State.class));\r\n            }\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof TrialSpawner trialSpawner) {\r\n            if (mechanism.requireEnum(TrialSpawner.State.class)) {\r\n                trialSpawner.setTrialSpawnerState(value.asEnum(TrialSpawner.State.class));\r\n            }\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof Vault vault) {\r\n            if (mechanism.requireEnum(Vault.State.class)) {\r\n                vault.setVaultState(value.asEnum(Vault.State.class));\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"mode\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"mode\", MaterialMode.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialNote.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.Note;\r\nimport org.bukkit.block.data.type.NoteBlock;\r\n\r\npublic class MaterialNote implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof NoteBlock;\r\n    }\r\n\r\n    public static MaterialNote getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialNote((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"note\"\r\n    };\r\n\r\n    public MaterialNote(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.note_octave>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.note\r\n        // @group properties\r\n        // @description\r\n        // Returns the octave of note played from this note block, as 0, 1, or 2.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialNote.class, ElementTag.class, \"note_octave\", (attribute, material) -> {\r\n            return new ElementTag(material.getNoteBlock().getNote().getOctave());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.note_tone>\r\n        // @returns ElementTag\r\n        // @mechanism MaterialTag.note\r\n        // @group properties\r\n        // @description\r\n        // Returns the tone of note played from this note block, as a letter from A to F, sometimes with a # to indicate sharp.\r\n        // Like A or A#.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialNote.class, ElementTag.class, \"note_tone\", (attribute, material) -> {\r\n            Note note = material.getNoteBlock().getNote();\r\n            return new ElementTag(note.getTone().name() + (note.isSharped() ? \"#\" : \"\"));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.note>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.note\r\n        // @group properties\r\n        // @description\r\n        // Returns the note played from this note block, as an ID number from 0 to 24.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialNote.class, ElementTag.class, \"note\", (attribute, material) -> {\r\n            return new ElementTag(material.getNoteBlock().getNote().getId());\r\n        });\r\n    }\r\n\r\n    public NoteBlock getNoteBlock() {\r\n        return (NoteBlock) material.getModernData();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(getNoteBlock().getNote().getId());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"note\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name note\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the note played from this note block, as an ID number from 0 to 24.\r\n        // @tags\r\n        // <MaterialTag.note>\r\n        // <MaterialTag.note_tone>\r\n        // <MaterialTag.note_octave>\r\n        // -->\r\n        if (mechanism.matches(\"note\") && mechanism.requireInteger()) {\r\n            getNoteBlock().setNote(new Note(mechanism.getValue().asInt()));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialOminous.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\n\nimport com.denizenscript.denizen.objects.MaterialTag;\nimport com.denizenscript.denizencore.objects.Mechanism;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.block.data.type.TrialSpawner;\nimport org.bukkit.block.data.type.Vault;\n\npublic class MaterialOminous extends MaterialProperty<ElementTag> {\n\n    // <--[property]\n    // @object MaterialTag\n    // @name ominous\n    // @input ElementTag(Boolean)\n    // @description\n    // Controls whether a trial spawner or vault is in ominous mode.\n    // -->\n\n    public static boolean describes(MaterialTag material) {\n        return material.getModernData() instanceof TrialSpawner\n                || material.getModernData() instanceof Vault;\n    }\n\n    @Override\n    public ElementTag getPropertyValue() {\n        if (getBlockData() instanceof TrialSpawner trialSpawner) {\n            return new ElementTag(trialSpawner.isOminous());\n        }\n        return new ElementTag(as(Vault.class).isOminous());\n    }\n\n    @Override\n    public void setPropertyValue(ElementTag value, Mechanism mechanism) {\n        if (!mechanism.requireBoolean()) {\n            return;\n        }\n        if (getBlockData() instanceof TrialSpawner trialSpawner) {\n            trialSpawner.setOminous(value.asBoolean());\n        }\n        else {\n            as(Vault.class).setOminous(value.asBoolean());\n        }\n    }\n\n    @Override\n    public String getPropertyId() {\n        return \"ominous\";\n    }\n\n    public static void register() {\n        autoRegister(\"ominous\", MaterialOminous.class, ElementTag.class, false);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialPersistent.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.type.Leaves;\r\n\r\npublic class MaterialPersistent implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof Leaves;\r\n    }\r\n\r\n    public static MaterialPersistent getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialPersistent((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"persistent\"\r\n    };\r\n\r\n    public MaterialPersistent(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.persistent>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism MaterialTag.persistent\r\n        // @group properties\r\n        // @description\r\n        // Returns whether this block will decay from being too far away from a tree.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialPersistent.class, ElementTag.class, \"persistent\", (attribute, material) -> {\r\n            return new ElementTag(material.getLeaves().isPersistent());\r\n        });\r\n    }\r\n\r\n    public Leaves getLeaves() {\r\n        return (Leaves) material.getModernData();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(getLeaves().isPersistent());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"persistent\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name persistent\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets leaves blocks to ignore decay, or to obey it.\r\n        // @tags\r\n        // <MaterialTag.persistent>\r\n        // -->\r\n        if (mechanism.matches(\"persistent\") && mechanism.requireBoolean()) {\r\n            getLeaves().setPersistent(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialPower.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.AnaloguePowerable;\r\n\r\npublic class MaterialPower implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof AnaloguePowerable;\r\n    }\r\n\r\n    public static MaterialPower getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialPower((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"power\"\r\n    };\r\n\r\n    public MaterialPower(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.power>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.power\r\n        // @group properties\r\n        // @description\r\n        // Returns the redstone power level of an analogue-powerable block.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialPower.class, ElementTag.class, \"power\", (attribute, material) -> {\r\n            return new ElementTag(((AnaloguePowerable) material.material.getModernData()).getPower());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.max_power>\r\n        // @returns ElementTag(Number)\r\n        // @mechanism MaterialTag.power\r\n        // @group properties\r\n        // @description\r\n        // Returns the maximum redstone power an analogue-powerable block can have.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialPower.class, ElementTag.class, \"max_power\", (attribute, material) -> {\r\n            return new ElementTag(((AnaloguePowerable) material.material.getModernData()).getMaximumPower());\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(((AnaloguePowerable) material.getModernData()).getPower());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"power\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name power\r\n        // @input ElementTag(Number)\r\n        // @description\r\n        // Sets the redstone power level of an analogue-powerable block.\r\n        // @tags\r\n        // <MaterialTag.power>\r\n        // <MaterialTag.max_power>\r\n        // -->\r\n        if (mechanism.matches(\"power\") && mechanism.requireInteger()) {\r\n            int power = mechanism.getValue().asInt();\r\n            AnaloguePowerable powerable = (AnaloguePowerable) material.getModernData();\r\n            if (power < 0 || power > powerable.getMaximumPower()) {\r\n                mechanism.echoError(\"Material power mechanism value '\" + power + \"' is not valid. Must be between 0 and \" + powerable.getMaximumPower() + \".\");\r\n                return;\r\n            }\r\n            powerable.setPower(power);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialProperty.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.ObjectProperty;\r\nimport org.bukkit.block.data.BlockData;\r\n\r\npublic abstract class MaterialProperty<TData extends ObjectTag> extends ObjectProperty<MaterialTag, TData> {\r\n\r\n    public MaterialProperty() {\r\n    }\r\n\r\n    public MaterialProperty(MaterialTag material) {\r\n        object = material;\r\n    }\r\n\r\n    public BlockData getBlockData() {\r\n        return object.getModernData();\r\n    }\r\n\r\n    public <T extends BlockData> T as(Class<T> dataType) {\r\n        return (T) getBlockData();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialShape.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.type.Stairs;\r\n\r\npublic class MaterialShape implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof Stairs;\r\n    }\r\n\r\n    public static MaterialShape getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialShape((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"shape\"\r\n    };\r\n\r\n    public MaterialShape(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.shape>\r\n        // @returns ElementTag\r\n        // @mechanism MaterialTag.shape\r\n        // @group properties\r\n        // @description\r\n        // Returns the shape of a block.\r\n        // For stairs, output is the corner shape as INNER_LEFT, INNER_RIGHT, OUTER_LEFT, OUTER_RIGHT, or STRAIGHT.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialShape.class, ElementTag.class, \"shape\", (attribute, material) -> {\r\n            return new ElementTag(material.getStairs().getShape());\r\n        });\r\n    }\r\n\r\n    public Stairs getStairs() {\r\n        return (Stairs) material.getModernData();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return getStairs().getShape().name();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"shape\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name shape\r\n        // @input ElementTag\r\n        // @description\r\n        // Sets the shape of a block.\r\n        // For stairs, input is the corner shape as INNER_LEFT, INNER_RIGHT, OUTER_LEFT, OUTER_RIGHT, or STRAIGHT.\r\n        // @tags\r\n        // <MaterialTag.shape>\r\n        // -->\r\n        if (mechanism.matches(\"shape\") && mechanism.requireEnum(Stairs.Shape.class)) {\r\n            getStairs().setShape(Stairs.Shape.valueOf(mechanism.getValue().asString().toUpperCase()));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialSides.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.type.MossyCarpet;\r\nimport org.bukkit.block.data.type.RedstoneWire;\r\nimport org.bukkit.block.data.type.Wall;\r\n\r\nimport java.util.function.BiConsumer;\r\n\r\npublic class MaterialSides extends MaterialProperty<ListTag> {\r\n\r\n    // <--[property]\r\n    // @object MaterialTag\r\n    // @name sides\r\n    // @input ListTag\r\n    // @description\r\n    // Controls the heights for a wall block or mossy carpet, or connections for a redstone wire, in order North|East|South|West|Vertical.\r\n    // For wall blocks: For n/e/s/w, can be \"tall\", \"low\", or \"none\". For vertical, can be \"tall\" or \"none\".\r\n    // For redstone wires: For n/e/s/w, can be \"none\", \"side\", or \"up\". No vertical.\r\n    // For mossy carpets: For n/e/s/w, can be \"tall\", \"low\", or \"none\". Vertical controls the bottom, and can either be \"bottom\" or \"none\".\r\n    // -->\r\n\r\n    public static boolean describes(MaterialTag material) {\r\n        BlockData data = material.getModernData();\r\n        return data instanceof Wall\r\n                || data instanceof RedstoneWire\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && data instanceof MossyCarpet);\r\n    }\r\n\r\n    @Override\r\n    public ListTag getPropertyValue() {\r\n        ListTag list = new ListTag(5);\r\n        if (getBlockData() instanceof Wall wall) {\r\n            list.add(wall.getHeight(BlockFace.NORTH).name());\r\n            list.add(wall.getHeight(BlockFace.EAST).name());\r\n            list.add(wall.getHeight(BlockFace.SOUTH).name());\r\n            list.add(wall.getHeight(BlockFace.WEST).name());\r\n            list.add(wall.isUp() ? \"TALL\" : \"NONE\");\r\n        }\r\n        else if (getBlockData() instanceof RedstoneWire wire) {\r\n            list.add(wire.getFace(BlockFace.NORTH).name());\r\n            list.add(wire.getFace(BlockFace.EAST).name());\r\n            list.add(wire.getFace(BlockFace.SOUTH).name());\r\n            list.add(wire.getFace(BlockFace.WEST).name());\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof MossyCarpet carpet) {\r\n            list.add(carpet.getHeight(BlockFace.NORTH).name());\r\n            list.add(carpet.getHeight(BlockFace.EAST).name());\r\n            list.add(carpet.getHeight(BlockFace.SOUTH).name());\r\n            list.add(carpet.getHeight(BlockFace.WEST).name());\r\n            list.add(carpet.isBottom() ? \"BOTTOM\" : \"NONE\");\r\n        }\r\n        return list;\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ListTag list, Mechanism mechanism) {\r\n        if (getBlockData() instanceof Wall wall) {\r\n            if (list.size() != 5) {\r\n                mechanism.echoError(\"Invalid sides list, size must be 5.\");\r\n                return;\r\n            }\r\n            setSide(wall::setHeight, Wall.Height.class, BlockFace.NORTH, list, 0, mechanism);\r\n            setSide(wall::setHeight, Wall.Height.class, BlockFace.EAST, list, 1, mechanism);\r\n            setSide(wall::setHeight, Wall.Height.class, BlockFace.SOUTH, list, 2, mechanism);\r\n            setSide(wall::setHeight, Wall.Height.class, BlockFace.WEST, list, 3, mechanism);\r\n            wall.setUp(CoreUtilities.equalsIgnoreCase(list.get(4), \"tall\"));\r\n        }\r\n        else if (getBlockData() instanceof RedstoneWire wire) {\r\n            if (list.size() != 4) {\r\n                mechanism.echoError(\"Invalid sides list, size must be 4.\");\r\n                return;\r\n            }\r\n            setSide(wire::setFace, RedstoneWire.Connection.class, BlockFace.NORTH, list, 0, mechanism);\r\n            setSide(wire::setFace, RedstoneWire.Connection.class, BlockFace.EAST, list, 1, mechanism);\r\n            setSide(wire::setFace, RedstoneWire.Connection.class, BlockFace.SOUTH, list, 2, mechanism);\r\n            setSide(wire::setFace, RedstoneWire.Connection.class, BlockFace.WEST, list, 3, mechanism);\r\n        }\r\n        else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && getBlockData() instanceof MossyCarpet carpet) {\r\n            if (list.size() != 5) {\r\n                mechanism.echoError(\"Invalid sides list, size must be 5.\");\r\n                return;\r\n            }\r\n            setSide(carpet::setHeight, MossyCarpet.Height.class, BlockFace.NORTH, list, 0, mechanism);\r\n            setSide(carpet::setHeight, MossyCarpet.Height.class, BlockFace.EAST, list, 1, mechanism);\r\n            setSide(carpet::setHeight, MossyCarpet.Height.class, BlockFace.SOUTH, list, 2, mechanism);\r\n            setSide(carpet::setHeight, MossyCarpet.Height.class, BlockFace.WEST, list, 3, mechanism);\r\n            carpet.setBottom(CoreUtilities.equalsIgnoreCase(list.get(4), \"bottom\"));\r\n        }\r\n    }\r\n\r\n    public static <T extends Enum<T>> void setSide(BiConsumer<BlockFace, T> consumer, Class<T> type, BlockFace face, ListTag list, int index, Mechanism mechanism) {\r\n        T value = new ElementTag(list.get(index)).asEnum(type);\r\n        if (value == null) {\r\n            mechanism.echoError(\"'\" + list.get(index) + \"' is not a valid \" + DebugInternals.getClassNameOpti(type) + \".\");\r\n            return;\r\n        }\r\n        consumer.accept(face, value);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"sides\";\r\n    }\r\n\r\n    // <--[tag]\r\n    // @attribute <MaterialTag.heights>\r\n    // @returns ListTag\r\n    // @mechanism MaterialTag.heights\r\n    // @group properties\r\n    // @deprecated use 'sides'\r\n    // @description\r\n    // Deprecated in favor of <@link property MaterialTag.sides>\r\n    // -->\r\n\r\n    // <--[mechanism]\r\n    // @object MaterialTag\r\n    // @name heights\r\n    // @input ElementTag\r\n    // @deprecated use 'sides'\r\n    // @description\r\n    // Deprecated in favor of <@link property MaterialTag.sides>\r\n    // @tags\r\n    // <MaterialTag.heights>\r\n    // -->\r\n\r\n    public static void register() {\r\n        autoRegister(\"sides\", MaterialSides.class, ListTag.class, false, \"heights\");\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialSnowable.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.Snowable;\r\n\r\npublic class MaterialSnowable implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof Snowable;\r\n    }\r\n\r\n    public static MaterialSnowable getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialSnowable((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"snowy\"\r\n    };\r\n\r\n    public MaterialSnowable(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.snowy>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism MaterialTag.snowy\r\n        // @group properties\r\n        // @description\r\n        // Returns whether this material is covered in snow or not.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialSnowable.class, ElementTag.class, \"snowy\", (attribute, material) -> {\r\n            return new ElementTag(material.isSnowy());\r\n        });\r\n    }\r\n\r\n    public Snowable getSnowable() {\r\n        return (Snowable) material.getModernData();\r\n    }\r\n\r\n    public boolean isSnowy() {\r\n        return getSnowable().isSnowy();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(isSnowy());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"snowy\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name snowy\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets this material to be covered in snow, or not.\r\n        // @tags\r\n        // <MaterialTag.snowy>\r\n        // -->\r\n        if (mechanism.matches(\"snowy\") && mechanism.requireBoolean()) {\r\n            getSnowable().setSnowy(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialSwitchable.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.Lightable;\r\nimport org.bukkit.block.data.Powerable;\r\nimport org.bukkit.block.data.Openable;\r\nimport org.bukkit.block.data.type.*;\r\n\r\npublic class MaterialSwitchable implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        if (!(material instanceof MaterialTag)) {\r\n            return false;\r\n        }\r\n        MaterialTag mat = (MaterialTag) material;\r\n        if (!mat.hasModernData()) {\r\n            return false;\r\n        }\r\n        BlockData data = mat.getModernData();\r\n        return data instanceof Powerable\r\n                || data instanceof Openable\r\n                || data instanceof Dispenser\r\n                || data instanceof DaylightDetector\r\n                || data instanceof Piston\r\n                || data instanceof Lightable\r\n                || data instanceof EndPortalFrame\r\n                || data instanceof Hopper\r\n                || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && data instanceof SculkShrieker);\r\n    }\r\n\r\n    public static MaterialSwitchable getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialSwitchable((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"switched\"\r\n    };\r\n\r\n    public MaterialSwitchable(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    public MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.switched>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism MaterialTag.switched\r\n        // @synonyms MaterialTag.lit, MaterialTag.open, MaterialTag.active\r\n        // @group properties\r\n        // @description\r\n        // Returns whether a material is 'switched on', which has different semantic meaning depending on the material type.\r\n        // More specifically, this returns whether:\r\n        // - a Powerable material (like pressure plates) is activated\r\n        // - an Openable material (like doors) is open\r\n        // - a dispenser is powered and should dispense its contents\r\n        // - a daylight sensor is inverted (detects darkness instead of light)\r\n        // - a lightable block is lit\r\n        // - a piston block is extended\r\n        // - an end portal frame has an ender eye in it\r\n        // - a hopper is NOT being powered by redstone\r\n        // - a sculk_shrieker can summon a warden\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialSwitchable.class, ElementTag.class, \"switched\", (attribute, material) -> {\r\n            return new ElementTag(material.getState());\r\n        });\r\n    }\r\n\r\n    public boolean isPowerable() {\r\n        return material.getModernData() instanceof Powerable;\r\n    }\r\n\r\n    public boolean isOpenable() {\r\n        return material.getModernData() instanceof Openable;\r\n    }\r\n\r\n    public boolean isDisepnser() {\r\n        return material.getModernData() instanceof Dispenser;\r\n    }\r\n\r\n    public boolean isDaylightDetector() {\r\n        return material.getModernData() instanceof DaylightDetector;\r\n    }\r\n\r\n    public boolean isLightable() {\r\n        return material.getModernData() instanceof Lightable;\r\n    }\r\n\r\n    public boolean isPiston() {\r\n        return material.getModernData() instanceof Piston;\r\n    }\r\n\r\n    public boolean isEndFrame() {\r\n        return material.getModernData() instanceof EndPortalFrame;\r\n    }\r\n\r\n    public boolean isHopper() {\r\n        return material.getModernData() instanceof Hopper;\r\n    }\r\n\r\n    public boolean isSculkShrieker() {\r\n        return NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && material.getModernData() instanceof SculkShrieker;\r\n    }\r\n\r\n    public Powerable getPowerable() {\r\n        return (Powerable) material.getModernData();\r\n    }\r\n\r\n    public Openable getOpenable() {\r\n        return (Openable) material.getModernData();\r\n    }\r\n\r\n    public Dispenser getDispenser() {\r\n        return (Dispenser) material.getModernData();\r\n    }\r\n\r\n    public DaylightDetector getDaylightDetector() {\r\n        return (DaylightDetector) material.getModernData();\r\n    }\r\n\r\n    public Piston getPiston() {\r\n        return (Piston) material.getModernData();\r\n    }\r\n\r\n    public Lightable getLightable() {\r\n        return (Lightable) material.getModernData();\r\n    }\r\n\r\n    public EndPortalFrame getEndFrame() {\r\n        return (EndPortalFrame) material.getModernData();\r\n    }\r\n\r\n    public Hopper getHopper() {\r\n        return (Hopper) material.getModernData();\r\n    }\r\n\r\n    /*public SculkShrieker getSculkShrieker() { // TODO: 1.19\r\n        return (SculkShrieker) material.getModernData();\r\n    }*/\r\n\r\n    public boolean getState() {\r\n        if (isOpenable()) {\r\n            return getOpenable().isOpen();\r\n        }\r\n        else if (isLightable()) {\r\n            return getLightable().isLit();\r\n        }\r\n        else if (isPowerable()) {\r\n            return getPowerable().isPowered();\r\n        }\r\n        else if (isDisepnser()) {\r\n            return getDispenser().isTriggered();\r\n        }\r\n        else if (isDaylightDetector()) {\r\n            return getDaylightDetector().isInverted();\r\n        }\r\n        else if (isPiston()) {\r\n            return getPiston().isExtended();\r\n        }\r\n        else if (isEndFrame()) {\r\n            return getEndFrame().hasEye();\r\n        }\r\n        else if (isHopper()) {\r\n            return getHopper().isEnabled();\r\n        }\r\n        else if (isSculkShrieker()) {\r\n            return ((SculkShrieker) material.getModernData()).isCanSummon();\r\n        }\r\n        return false; // Unreachable\r\n    }\r\n\r\n    public void setState(boolean state) {\r\n        if (isOpenable()) {\r\n            getOpenable().setOpen(state);\r\n        }\r\n        else if (isLightable()) {\r\n            getLightable().setLit(state);\r\n        }\r\n        else if (isPowerable()) {\r\n            getPowerable().setPowered(state);\r\n        }\r\n        else if (isDisepnser()) {\r\n            getDispenser().setTriggered(state);\r\n        }\r\n        else if (isDaylightDetector()) {\r\n            getDaylightDetector().setInverted(state);\r\n        }\r\n        else if (isPiston()) {\r\n            getPiston().setExtended(state);\r\n        }\r\n        else if (isEndFrame()) {\r\n            getEndFrame().setEye(state);\r\n        }\r\n        else if (isHopper()) {\r\n            getHopper().setEnabled(state);\r\n        }\r\n        else if (isSculkShrieker()) {\r\n            ((SculkShrieker) material.getModernData()).setCanSummon(state);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(getState());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"switched\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name switched\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether a material is 'switched on', which has different semantic meaning depending on the material type.\r\n        // More specifically, this sets whether:\r\n        // - a Powerable material (like pressure plates) is activated\r\n        // - an Openable material (like doors) is open\r\n        // - a dispenser is powered and should dispense its contents\r\n        // - a daylight sensor can see the sun\r\n        // - a lightable block is lit\r\n        // - a piston block is extended\r\n        // - an end portal frame has an ender eye in it\r\n        // - a hopper is NOT being powered by redstone\r\n        // - a sculk_shrieker can summon a warden\r\n        // @tags\r\n        // <MaterialTag.switched>\r\n        // -->\r\n        if (mechanism.matches(\"switched\") && mechanism.requireBoolean()) {\r\n            setState(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialUnstable.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.type.TNT;\r\n\r\npublic class MaterialUnstable implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof TNT;\r\n    }\r\n\r\n    public static MaterialUnstable getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialUnstable((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"unstable\"\r\n    };\r\n\r\n    public MaterialUnstable(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.unstable>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism MaterialTag.unstable\r\n        // @group properties\r\n        // @description\r\n        // Returns whether this TNT block is unstable (explodes when punched).\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialUnstable.class, ElementTag.class, \"unstable\", (attribute, material) -> {\r\n            return new ElementTag(material.isUnstable());\r\n        });\r\n    }\r\n\r\n    public TNT getTNT() {\r\n        return (TNT) material.getModernData();\r\n    }\r\n\r\n    public boolean isUnstable() {\r\n        return getTNT().isUnstable();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(isUnstable());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"unstable\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name unstable\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets whether this TNT block is unstable (explodes when punched).\r\n        // @tags\r\n        // <MaterialTag.unstable>\r\n        // -->\r\n        if (mechanism.matches(\"unstable\") && mechanism.requireBoolean()) {\r\n            getTNT().setUnstable(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/material/MaterialWaterlogged.java",
    "content": "package com.denizenscript.denizen.objects.properties.material;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.properties.Property;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport org.bukkit.block.data.Waterlogged;\r\n\r\npublic class MaterialWaterlogged implements Property {\r\n\r\n    public static boolean describes(ObjectTag material) {\r\n        return material instanceof MaterialTag\r\n                && ((MaterialTag) material).hasModernData()\r\n                && ((MaterialTag) material).getModernData() instanceof Waterlogged;\r\n    }\r\n\r\n    public static MaterialWaterlogged getFrom(ObjectTag _material) {\r\n        if (!describes(_material)) {\r\n            return null;\r\n        }\r\n        else {\r\n            return new MaterialWaterlogged((MaterialTag) _material);\r\n        }\r\n    }\r\n\r\n    public static final String[] handledMechs = new String[] {\r\n            \"waterlogged\"\r\n    };\r\n\r\n    public MaterialWaterlogged(MaterialTag _material) {\r\n        material = _material;\r\n    }\r\n\r\n    MaterialTag material;\r\n\r\n    public static void register() {\r\n\r\n        // <--[tag]\r\n        // @attribute <MaterialTag.waterlogged>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism MaterialTag.waterlogged\r\n        // @group properties\r\n        // @description\r\n        // Returns whether this block is waterlogged or not.\r\n        // -->\r\n        PropertyParser.registerStaticTag(MaterialWaterlogged.class, ElementTag.class, \"waterlogged\", (attribute, material) -> {\r\n            return new ElementTag(material.isWaterlogged());\r\n        });\r\n    }\r\n\r\n    public Waterlogged getWaterlogged() {\r\n        return (Waterlogged) material.getModernData();\r\n    }\r\n\r\n    public boolean isWaterlogged() {\r\n        return getWaterlogged().isWaterlogged();\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyString() {\r\n        return String.valueOf(isWaterlogged());\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"waterlogged\";\r\n    }\r\n\r\n    @Override\r\n    public void adjust(Mechanism mechanism) {\r\n\r\n        // <--[mechanism]\r\n        // @object MaterialTag\r\n        // @name waterlogged\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Sets this block to be waterlogged, or not.\r\n        // @tags\r\n        // <MaterialTag.waterlogged>\r\n        // -->\r\n        if (mechanism.matches(\"waterlogged\") && mechanism.requireBoolean()) {\r\n            getWaterlogged().setWaterlogged(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/trade/TradeDemand.java",
    "content": "package com.denizenscript.denizen.objects.properties.trade;\r\n\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\n\r\npublic class TradeDemand extends TradeProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object TradeTag\r\n    // @name demand\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls the demand level of the trade.\r\n    // -->\r\n\r\n    public static boolean describes(TradeTag recipe) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getRecipe().getDemand());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        if (mechanism.requireInteger()) {\r\n            getRecipe().setDemand(mechanism.getValue().asInt());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"demand\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"demand\", TradeDemand.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/trade/TradeHasXp.java",
    "content": "package com.denizenscript.denizen.objects.properties.trade;\r\n\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\n\r\npublic class TradeHasXp extends TradeProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object TradeTag\r\n    // @name has_xp\r\n    // @input ElementTag(Boolean)\r\n    // @description\r\n    // Controls whether this trade will reward XP upon successful trading.\r\n    // -->\r\n\r\n    public static boolean describes(TradeTag recipe) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getRecipe().hasExperienceReward());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        if (mechanism.requireBoolean()) {\r\n            getRecipe().setExperienceReward(mechanism.getValue().asBoolean());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"has_xp\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"has_xp\", TradeHasXp.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/trade/TradeInputs.java",
    "content": "package com.denizenscript.denizen.objects.properties.trade;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class TradeInputs extends TradeProperty<ListTag> {\r\n\r\n    // <--[property]\r\n    // @object TradeTag\r\n    // @name inputs\r\n    // @input ListTag(ItemTag)\r\n    // @description\r\n    // Controls the items required to make a successful trade. Use an empty input to make the trade impossible.\r\n    // NOTE: If more than two items are specified, then only the first two items will be used.\r\n    // -->\r\n\r\n    public static boolean describes(TradeTag recipe) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ListTag getPropertyValue() {\r\n        return getIngredientsList();\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ListTag inList, Mechanism mechanism) {\r\n        List<ItemStack> ingredients = new ArrayList<>();\r\n        List<ItemTag> list = inList.filter(ItemTag.class, mechanism.context);\r\n        if (!mechanism.hasValue() || list.isEmpty()) {\r\n            getRecipe().setIngredients(ingredients);\r\n            return;\r\n        }\r\n        for (ItemTag item : list) {\r\n            ingredients.add(item.getItemStack());\r\n        }\r\n        if (ingredients.size() > 2) {\r\n            mechanism.echoError(\"Trade recipe input was given \" + list.size() + \" items. Only using the first two items!\");\r\n            ingredients = ingredients.subList(0, 2);\r\n        }\r\n        getRecipe().setIngredients(ingredients);\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"inputs\";\r\n    }\r\n\r\n    public ListTag getIngredientsList() {\r\n        ListTag result = new ListTag();\r\n        for (ItemStack item : getRecipe().getIngredients()) {\r\n            result.addObject(new ItemTag(item));\r\n        }\r\n        return result;\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"inputs\", TradeInputs.class, ListTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/trade/TradeMaxUses.java",
    "content": "package com.denizenscript.denizen.objects.properties.trade;\r\n\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\n\r\npublic class TradeMaxUses extends TradeProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object TradeTag\r\n    // @name max_uses\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls the maximum amount of times that the trade can be used.\r\n    // -->\r\n\r\n    public static boolean describes(TradeTag recipe) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getRecipe().getMaxUses());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        if (mechanism.requireInteger()) {\r\n            getRecipe().setMaxUses(mechanism.getValue().asInt());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"max_uses\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"max_uses\", TradeMaxUses.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/trade/TradePriceMultiplier.java",
    "content": "package com.denizenscript.denizen.objects.properties.trade;\r\n\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\n\r\npublic class TradePriceMultiplier extends TradeProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object TradeTag\r\n    // @name price_multiplier\r\n    // @input ElementTag(Decimal)\r\n    // @description\r\n    // Controls the price multiplier for this trade.\r\n    // -->\r\n\r\n    public static boolean describes(TradeTag recipe) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getRecipe().getPriceMultiplier());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        if (mechanism.requireFloat()) {\r\n            getRecipe().setPriceMultiplier(mechanism.getValue().asFloat());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"price_multiplier\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"price_multiplier\", TradePriceMultiplier.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/trade/TradeProperty.java",
    "content": "package com.denizenscript.denizen.objects.properties.trade;\r\n\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.ObjectProperty;\r\nimport org.bukkit.inventory.MerchantRecipe;\r\n\r\npublic abstract class TradeProperty<TData extends ObjectTag> extends ObjectProperty<TradeTag, TData> {\r\n\r\n    public MerchantRecipe getRecipe() {\r\n        return object.getRecipe();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/trade/TradeResult.java",
    "content": "package com.denizenscript.denizen.objects.properties.trade;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\n\r\npublic class TradeResult extends TradeProperty<ItemTag> {\r\n\r\n    // <--[property]\r\n    // @object TradeTag\r\n    // @name result\r\n    // @input ItemTag\r\n    // @description\r\n    // Controls what the trade will give the player.\r\n    // -->\r\n\r\n    public static boolean describes(TradeTag recipe) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ItemTag getPropertyValue() {\r\n        return new ItemTag(getRecipe().getResult());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ItemTag item, Mechanism mechanism) {\r\n        object.setRecipe(TradeTag.duplicateRecipe(item.getItemStack(), getRecipe()));\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"result\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"result\", TradeResult.class, ItemTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/trade/TradeSpecialPrice.java",
    "content": "package com.denizenscript.denizen.objects.properties.trade;\r\n\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\n\r\npublic class TradeSpecialPrice extends TradeProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object TradeTag\r\n    // @name special_price\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls the special price for this trade.\r\n    // -->\r\n\r\n    public static boolean describes(TradeTag recipe) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getRecipe().getSpecialPrice());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        if (mechanism.requireInteger()) {\r\n            getRecipe().setSpecialPrice(mechanism.getValue().asInt());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"special_price\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"special_price\", TradeSpecialPrice.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/trade/TradeUses.java",
    "content": "package com.denizenscript.denizen.objects.properties.trade;\r\n\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\n\r\npublic class TradeUses extends TradeProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object TradeTag\r\n    // @name uses\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls the amount of times the trade has been used.\r\n    // -->\r\n\r\n    public static boolean describes(TradeTag recipe) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getRecipe().getUses());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        if (mechanism.requireInteger()) {\r\n            getRecipe().setUses(mechanism.getValue().asInt());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"uses\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"uses\", TradeUses.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/objects/properties/trade/TradeVillagerXP.java",
    "content": "package com.denizenscript.denizen.objects.properties.trade;\r\n\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\n\r\npublic class TradeVillagerXP extends TradeProperty<ElementTag> {\r\n\r\n    // <--[property]\r\n    // @object TradeTag\r\n    // @name villager_xp\r\n    // @input ElementTag(Number)\r\n    // @description\r\n    // Controls the amount of experience a villager gains from this trade.\r\n    // -->\r\n\r\n    public static boolean describes(TradeTag recipe) {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public ElementTag getPropertyValue() {\r\n        return new ElementTag(getRecipe().getVillagerExperience());\r\n    }\r\n\r\n    @Override\r\n    public void setPropertyValue(ElementTag val, Mechanism mechanism) {\r\n        if (mechanism.requireInteger()) {\r\n            getRecipe().setVillagerExperience(mechanism.getValue().asInt());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getPropertyId() {\r\n        return \"villager_xp\";\r\n    }\r\n\r\n    public static void register() {\r\n        autoRegister(\"villager_xp\", TradeVillagerXP.class, ElementTag.class, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/BukkitCommandRegistry.java",
    "content": "package com.denizenscript.denizen.scripts.commands;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.scripts.commands.core.CooldownCommand;\r\nimport com.denizenscript.denizen.scripts.commands.core.ResetCommand;\r\nimport com.denizenscript.denizen.scripts.commands.core.ZapCommand;\r\nimport com.denizenscript.denizen.scripts.commands.entity.*;\r\nimport com.denizenscript.denizen.scripts.commands.item.*;\r\nimport com.denizenscript.denizen.scripts.commands.npc.*;\r\nimport com.denizenscript.denizen.scripts.commands.player.*;\r\nimport com.denizenscript.denizen.scripts.commands.server.*;\r\nimport com.denizenscript.denizen.scripts.commands.world.*;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\n\r\npublic class BukkitCommandRegistry {\r\n\r\n    public static class AutoNoCitizensCommand extends AbstractCommand {\r\n\r\n        public static void registerMany(String... names) {\r\n            for (String name : names) {\r\n                registerFor(name);\r\n            }\r\n        }\r\n\r\n        public static void registerFor(String name) {\r\n            AutoNoCitizensCommand cmd = new AutoNoCitizensCommand();\r\n            cmd.name = name;\r\n            cmd.syntax = \"(Citizens Required)\";\r\n            DenizenCore.commandRegistry.register(cmd.name, cmd);\r\n        }\r\n\r\n        public String name;\r\n\r\n        @Override\r\n        public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        }\r\n\r\n        @Override\r\n        public void execute(ScriptEntry scriptEntry) {\r\n            Debug.echoError(\"The command '\" + name + \"' is only available when Citizens is on the server.\");\r\n        }\r\n    }\r\n\r\n    public static void registerCommand(Class<? extends AbstractCommand> commandInstance) {\r\n        DenizenCore.commandRegistry.registerCommand(commandInstance);\r\n    }\r\n\r\n    public static void registerCitizensCommands() {\r\n        // entity\r\n        registerCommand(AnimateCommand.class);\r\n        // npc\r\n        registerCommand(ActionCommand.class);\r\n        registerCommand(AnchorCommand.class);\r\n        registerCommand(AssignmentCommand.class);\r\n        registerCommand(NPCBossBarCommand.class);\r\n        registerCommand(BreakCommand.class);\r\n        registerCommand(CreateCommand.class);\r\n        registerCommand(DespawnCommand.class);\r\n        registerCommand(DisengageCommand.class);\r\n        registerCommand(EngageCommand.class);\r\n        registerCommand(FishCommand.class);\r\n        registerCommand(LookcloseCommand.class);\r\n        registerCommand(PauseCommand.class);\r\n        registerCommand(PauseCommand.ResumeCommand.class);\r\n        registerCommand(PoseCommand.class);\r\n        registerCommand(PushableCommand.class);\r\n        registerCommand(SitCommand.class);\r\n        registerCommand(SleepCommand.class);\r\n        registerCommand(StandCommand.class);\r\n        registerCommand(TraitCommand.class);\r\n        registerCommand(TriggerCommand.class);\r\n        registerCommand(VulnerableCommand.class);\r\n        // player\r\n        registerCommand(ChatCommand.class);\r\n    }\r\n\r\n    public static void registerCommands() {\r\n        // core\r\n        registerCommand(CooldownCommand.class);\r\n        registerCommand(ResetCommand.class);\r\n        registerCommand(ZapCommand.class);\r\n        // entity\r\n        registerCommand(AgeCommand.class);\r\n        registerCommand(AttachCommand.class);\r\n        registerCommand(AttackCommand.class);\r\n        registerCommand(BurnCommand.class);\r\n        registerCommand(CastCommand.class);\r\n        registerCommand(EquipCommand.class);\r\n        registerCommand(FakeEquipCommand.class);\r\n        registerCommand(FakeInternalDataCommand.class);\r\n        registerCommand(FeedCommand.class);\r\n        registerCommand(FlyCommand.class);\r\n        registerCommand(FollowCommand.class);\r\n        registerCommand(HeadCommand.class);\r\n        registerCommand(HealCommand.class);\r\n        registerCommand(HealthCommand.class);\r\n        registerCommand(HurtCommand.class);\r\n        registerCommand(InvisibleCommand.class);\r\n        registerCommand(KillCommand.class);\r\n        registerCommand(LeashCommand.class);\r\n        registerCommand(LookCommand.class);\r\n        registerCommand(MountCommand.class);\r\n        registerCommand(PushCommand.class);\r\n        registerCommand(RemoveCommand.class);\r\n        registerCommand(RenameCommand.class);\r\n        registerCommand(RotateCommand.class);\r\n        registerCommand(ShootCommand.class);\r\n        registerCommand(SneakCommand.class);\r\n        registerCommand(SpawnCommand.class);\r\n        registerCommand(TeleportCommand.class);\r\n        registerCommand(WalkCommand.class);\r\n        // item\r\n        registerCommand(DisplayItemCommand.class);\r\n        registerCommand(FakeItemCommand.class);\r\n        registerCommand(GiveCommand.class);\r\n        registerCommand(InventoryCommand.class);\r\n        registerCommand(MapCommand.class);\r\n        registerCommand(TakeCommand.class);\r\n        // player\r\n        registerCommand(ActionBarCommand.class);\r\n        registerCommand(AdvancementCommand.class);\r\n        registerCommand(BlockCrackCommand.class);\r\n        registerCommand(ClickableCommand.class);\r\n        registerCommand(CompassCommand.class);\r\n        registerCommand(DebugBlockCommand.class);\r\n        registerCommand(DisguiseCommand.class);\r\n        registerCommand(ExperienceCommand.class);\r\n        registerCommand(FakeSpawnCommand.class);\r\n        registerCommand(GlowCommand.class);\r\n        registerCommand(GroupCommand.class);\r\n        registerCommand(ItemCooldownCommand.class);\r\n        registerCommand(KickCommand.class);\r\n        registerCommand(MoneyCommand.class);\r\n        registerCommand(NarrateCommand.class);\r\n        registerCommand(OpenTradesCommand.class);\r\n        registerCommand(OxygenCommand.class);\r\n        registerCommand(PermissionCommand.class);\r\n        registerCommand(ResourcePackCommand.class);\r\n        registerCommand(ShowFakeCommand.class);\r\n        registerCommand(SidebarCommand.class);\r\n        registerCommand(StatisticCommand.class);\r\n        registerCommand(TablistCommand.class);\r\n        registerCommand(TeamCommand.class);\r\n        registerCommand(TitleCommand.class);\r\n        registerCommand(ToastCommand.class);\r\n        // server\r\n        registerCommand(AnnounceCommand.class);\r\n        registerCommand(BanCommand.class);\r\n        registerCommand(BossBarCommand.class);\r\n        registerCommand(ExecuteCommand.class);\r\n        registerCommand(ScoreboardCommand.class);\r\n        // world\r\n        registerCommand(AdjustBlockCommand.class);\r\n        registerCommand(AnimateChestCommand.class);\r\n        registerCommand(ChunkLoadCommand.class);\r\n        registerCommand(CopyBlockCommand.class);\r\n        registerCommand(CreateWorldCommand.class);\r\n        registerCommand(DropCommand.class);\r\n        registerCommand(ExplodeCommand.class);\r\n        registerCommand(FireworkCommand.class);\r\n        registerCommand(GameRuleCommand.class);\r\n        registerCommand(LightCommand.class);\r\n        registerCommand(MidiCommand.class);\r\n        registerCommand(ModifyBlockCommand.class);\r\n        registerCommand(PlayEffectCommand.class);\r\n        registerCommand(PlaySoundCommand.class);\r\n        registerCommand(SchematicCommand.class);\r\n        registerCommand(SignCommand.class);\r\n        registerCommand(StrikeCommand.class);\r\n        registerCommand(SwitchCommand.class);\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            registerCommand(TickCommand.class);\r\n        }\r\n        registerCommand(TimeCommand.class);\r\n        registerCommand(WeatherCommand.class);\r\n        registerCommand(WorldBorderCommand.class);\r\n\r\n        if (Depends.citizens != null) {\r\n            registerCitizensCommands();\r\n        }\r\n        else {\r\n            AutoNoCitizensCommand.registerMany(\"ACTION\", \"ANCHOR\", \"ANIMATE\", \"ASSIGNMENT\", \"BREAK\", \"CHAT\", \"CREATE\", \"DESPAWN\",\r\n                    \"DISENGAGE\", \"ENGAGE\", \"FISH\", \"LOOKCLOSE\", \"PAUSE\", \"RESUME\", \"POSE\", \"PUSHABLE\", \"RENAME\", \"SIT\", \"STAND\", \"TRAIT\", \"TRIGGER\", \"VULNERABLE\");\r\n        }\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.echoApproval(\"Loaded core commands: \" + DenizenCore.commandRegistry.instances.keySet());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/core/CooldownCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.core;\r\n\r\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptContainer;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.objects.core.TimeTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\npublic class CooldownCommand extends AbstractCommand {\r\n\r\n    public CooldownCommand() {\r\n        setName(\"cooldown\");\r\n        setSyntax(\"cooldown [<duration>] (global) (script:<script>)\");\r\n        setRequiredArguments(1, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Cooldown\r\n    // @Syntax cooldown [<duration>] (global) (script:<script>)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Short Temporarily disables an interact script for the linked player.\r\n    // @Group core\r\n    //\r\n    // @Description\r\n    // Temporarily disables an interact script for the linked player.\r\n    //\r\n    // Cooldown requires a type (player or global), a script, and a duration.\r\n    // It also requires a valid link to a PlayerTag if using a non-global cooldown.\r\n    //\r\n    // To cooldown non-interact scripts automatically, consider <@link command ratelimit>.\r\n    //\r\n    // Cooldown periods are persistent through a server restart as they are saved in the 'saves.yml'.\r\n    //\r\n    // @Tags\r\n    // <ScriptTag.cooled_down[player]>\r\n    // <ScriptTag.cooldown>\r\n    //\r\n    // @Usage\r\n    // Use to keep the current interact script from meeting requirements.\r\n    // - cooldown 20m\r\n    //\r\n    // @Usage\r\n    // Use to keep a player from activating a script for a specified duration.\r\n    // - cooldown 11h script:bonus_script\r\n    // - cooldown 5s script:hit_indicator\r\n    //\r\n    // @Usage\r\n    // Use the 'global' argument to indicate the script to be on cooldown for all players.\r\n    // - cooldown global 24h script:daily_treasure_offering\r\n    // -->\r\n\r\n    private enum Type {GLOBAL, PLAYER}\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addScriptsOfType(InteractScriptContainer.class);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matchesPrefix(\"script\", \"s\")\r\n                    && arg.matchesArgumentType(ScriptTag.class)) {\r\n                scriptEntry.addObject(\"script\", arg.asType(ScriptTag.class));\r\n            }\r\n            else if (arg.matchesEnum(Type.class)) {\r\n                scriptEntry.addObject(\"type\", Type.valueOf(arg.getValue().toUpperCase()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"type\", Type.PLAYER);\r\n        scriptEntry.defaultObject(\"script\", scriptEntry.getScript());\r\n        if (!scriptEntry.hasObject(\"duration\")) {\r\n            throw new InvalidArgumentsException(\"Requires a valid duration!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ScriptTag script = scriptEntry.getObjectTag(\"script\");\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        Type type = (scriptEntry.hasObject(\"type\") ? (Type) scriptEntry.getObject(\"type\") : Type.PLAYER);\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"Type\", type.name()), script, (type.name().equalsIgnoreCase(\"player\") ? Utilities.getEntryPlayer(scriptEntry) : null), duration);\r\n        }\r\n        switch (type) {\r\n            case PLAYER:\r\n                setCooldown(Utilities.getEntryPlayer(scriptEntry), duration, script.getName(), false);\r\n                break;\r\n            case GLOBAL:\r\n                setCooldown(null, duration, script.getName(), true);\r\n                break;\r\n        }\r\n    }\r\n\r\n    public static DurationTag getCooldownDuration(PlayerTag player, String scriptName) {\r\n        TimeTag expires = DenizenCore.serverFlagMap.getFlagExpirationTime(\"__interact_cooldown.\" + scriptName);\r\n        if (expires != null) {\r\n            return new DurationTag((expires.millis() - TimeTag.now().millis()) / 1000.0);\r\n        }\r\n        if (player == null) {\r\n            return new DurationTag(0);\r\n        }\r\n        expires = player.getFlagTracker().getFlagExpirationTime(\"__interact_cooldown.\" + scriptName);\r\n        if (expires != null) {\r\n            return new DurationTag((expires.millis() - TimeTag.now().millis()) / 1000.0);\r\n        }\r\n        return new DurationTag(0);\r\n    }\r\n\r\n    public static boolean checkCooldown(PlayerTag player, String scriptName) {\r\n        DurationTag cooldown = getCooldownDuration(player, scriptName);\r\n        if (cooldown.getSeconds() > 0) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    public static void setCooldown(PlayerTag player, DurationTag duration, String scriptName, boolean global) {\r\n        TimeTag cooldownTime = new TimeTag(TimeTag.now().millis() + duration.getMillis());\r\n        if (global) {\r\n            DenizenCore.serverFlagMap.setFlag(\"__interact_cooldown.\" + scriptName, cooldownTime, cooldownTime);\r\n        }\r\n        else {\r\n            player.getFlagTracker().setFlag(\"__interact_cooldown.\" + scriptName, cooldownTime, cooldownTime);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/core/ResetCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.core;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\npublic class ResetCommand extends AbstractCommand {\r\n\r\n    public ResetCommand() {\r\n        setName(\"reset\");\r\n        setSyntax(\"reset (<player>|...) [cooldown/saves/global_cooldown] (<script>)\");\r\n        setRequiredArguments(1, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Reset\r\n    // @Syntax reset (<player>|...) [cooldown/global_cooldown] (<script>)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Short Resets various parts of Denizen's interact save data, including a script's cooldowns.\r\n    // @Group core\r\n    //\r\n    // @Description\r\n    // This command can reset save data for a player, or globally.\r\n    //\r\n    // The \"cooldown\" argument removes the player's cooldown for a specific script,\r\n    // as set by <@link command cooldown>.\r\n    //\r\n    // The \"global_cooldown\" argument removes all cooldowns for the specified script (not player-specific).\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to reset all cooldowns for a script when an event that limits usage completes.\r\n    // - reset global_cooldown MyScriptName\r\n    //\r\n    // -->\r\n\r\n    private enum Type {PLAYER_COOLDOWN, GLOBAL_COOLDOWN}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matches(\"cooldown\")\r\n                    && !scriptEntry.hasObject(\"type\")) {\r\n                scriptEntry.addObject(\"type\", Type.PLAYER_COOLDOWN);\r\n            }\r\n            else if (arg.matches(\"global_cooldown\")\r\n                    && !scriptEntry.hasObject(\"type\")) {\r\n                scriptEntry.addObject(\"type\", Type.GLOBAL_COOLDOWN);\r\n            }\r\n            else if (arg.matchesArgumentType(ScriptTag.class)) {\r\n                scriptEntry.addObject(\"script\", arg.asType(ScriptTag.class));\r\n            }\r\n            else if (arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"players\", arg.asType(ListTag.class));\r\n            }\r\n            // TODO: Reset NPCs option too!\r\n\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.getObject(\"type\").equals(Type.GLOBAL_COOLDOWN)) {\r\n            scriptEntry.defaultObject(\"players\", Utilities.getEntryPlayer(scriptEntry));\r\n        }\r\n        if (!scriptEntry.hasObject(\"script\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a script!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        // We allow players to be a single player or multiple players\r\n        ObjectTag player = scriptEntry.getObjectTag(\"players\");\r\n        ListTag players;\r\n        if (player instanceof PlayerTag) {\r\n            players = new ListTag(player);\r\n        }\r\n        else {\r\n            players = scriptEntry.getObjectTag(\"players\");\r\n        }\r\n        Type type = (Type) scriptEntry.getObject(\"type\");\r\n        ScriptTag script = scriptEntry.getObjectTag(\"script\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), players, db(\"type\", type), script);\r\n        }\r\n        // Deal with GLOBAL_COOLDOWN reset first, since there's no player/players involved\r\n        if (type == Type.GLOBAL_COOLDOWN) {\r\n            CooldownCommand.setCooldown(null, new DurationTag(0), script.getName(), true);\r\n            return;\r\n        }\r\n        // Now deal with the rest\r\n        for (String object : players) {\r\n            PlayerTag resettable = PlayerTag.valueOf(object, scriptEntry.context);\r\n            if (resettable.isValid()) {\r\n                switch (type) {\r\n                    case PLAYER_COOLDOWN:\r\n                        CooldownCommand.setCooldown(resettable, new DurationTag(0), script.getName(), false);\r\n                        return;\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/core/ZapCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.core;\r\n\r\nimport com.denizenscript.denizen.npc.traits.AssignmentTrait;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.AssignmentScriptContainer;\r\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptContainer;\r\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.objects.core.TimeTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport org.bukkit.event.Listener;\r\n\r\npublic class ZapCommand extends AbstractCommand implements Listener {\r\n\r\n    public ZapCommand() {\r\n        setName(\"zap\");\r\n        setSyntax(\"zap (<script>) [<step>] (<duration>)\");\r\n        setRequiredArguments(0, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Zap\r\n    // @Syntax zap (<script>) [<step>] (<duration>)\r\n    // @Required 0\r\n    // @Maximum 3\r\n    // @Short Changes the current interact script step.\r\n    // @Synonyms Step\r\n    // @Group core\r\n    // @Guide https://guide.denizenscript.com/guides/npcs/interact-scripts.html\r\n    //\r\n    // @Description\r\n    // Changes the current interact script step for the linked player.\r\n    //\r\n    // The step name input should match the name of a step in the interact script.\r\n    // The step name can be '*' to automatically zap to the default step.\r\n    //\r\n    // If used inside an interact script, will default to the current interact script.\r\n    // If used elsewhere, but there is a linked NPC with an assignment and interact, that NPC's interact script will be used.\r\n    // For anywhere else, you must specify the script by name.\r\n    //\r\n    // Optionally specify a duration. When the duration is up, the script will zap back to the step it was previously on.\r\n    // If any zap commands are used during the duration, that duration will be discarded.\r\n    //\r\n    // The command's name was inspired by a command in the language \"ZZT-OOP\", from a 1991 DOS game enjoyed by the original developer of Denizen.\r\n    //\r\n    // @Tags\r\n    // <ScriptTag.step[<player>]>\r\n    //\r\n    // @Usage\r\n    // Use to change the step to 2.\r\n    // - zap 2\r\n    //\r\n    // @Usage\r\n    // Use to return to the default step.\r\n    // - zap *\r\n    //\r\n    // @Usage\r\n    // Use to change the step to 3 in a script called Interact_Example.\r\n    // - zap 3 Interact_Example\r\n    //\r\n    // @Usage\r\n    // Use to change the step to 1 for the defined player in a script called InteractScript.\r\n    // - zap 1 InteractScript player:<[player]>\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addScriptsOfType(InteractScriptContainer.class);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"script\")\r\n                    && !scriptEntry.hasObject(\"step\")\r\n                    && arg.hasPrefix()\r\n                    && arg.getPrefix().matchesArgumentType(ScriptTag.class)) {\r\n                BukkitImplDeprecations.zapPrefix.warn(scriptEntry);\r\n                scriptEntry.addObject(\"script\", arg.getPrefix().asType(ScriptTag.class));\r\n                scriptEntry.addObject(\"step\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"script\")\r\n                    && arg.matchesArgumentType(ScriptTag.class)\r\n                    && arg.asType(ScriptTag.class).getContainer() instanceof InteractScriptContainer\r\n                    && arg.limitToOnlyPrefix(\"script\")) {\r\n                scriptEntry.addObject(\"script\", arg.asType(ScriptTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"step\")\r\n                    && arg.limitToOnlyPrefix(\"step\")) {\r\n                scriptEntry.addObject(\"step\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)\r\n                    && arg.limitToOnlyPrefix(\"duration\")) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        PlayerTag player = Utilities.getEntryPlayer(scriptEntry);\r\n        if (player == null || !player.isValid()) {\r\n            throw new InvalidArgumentsException(\"Must have player context!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"script\")) {\r\n            ScriptTag script = scriptEntry.getScript();\r\n            if (script != null) {\r\n                if (script.getContainer() instanceof InteractScriptContainer) {\r\n                    scriptEntry.addObject(\"script\", script);\r\n                }\r\n                else if (script.getContainer() instanceof AssignmentScriptContainer) {\r\n                    InteractScriptContainer interact = ((AssignmentScriptContainer) script.getContainer()).interact;\r\n                    if (interact != null) {\r\n                        scriptEntry.addObject(\"script\", new ScriptTag(interact));\r\n                    }\r\n                }\r\n            }\r\n            if (!scriptEntry.hasObject(\"script\")) {\r\n                NPCTag npc = Utilities.getEntryNPC(scriptEntry);\r\n                if (npc != null && npc.getCitizen().hasTrait(AssignmentTrait.class)) {\r\n                    AssignmentTrait trait = npc.getCitizen().getOrAddTrait(AssignmentTrait.class);\r\n                    for (AssignmentScriptContainer container : trait.containerCache) {\r\n                        if (container != null && container.getInteract() != null) {\r\n                            scriptEntry.addObject(\"script\", new ScriptTag(container.getInteract()));\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            if (!scriptEntry.hasObject(\"script\")) {\r\n                throw new InvalidArgumentsException(\"No script to zap! Must be in an interact script, or have a linked NPC with an associated interact script.\");\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        final ScriptTag script = scriptEntry.getObjectTag(\"script\");\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        ElementTag stepElement = scriptEntry.getElement(\"step\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), Utilities.getEntryPlayer(scriptEntry), script, stepElement != null ? stepElement : db(\"step\", \"++ (inc)\"), duration);\r\n        }\r\n        String step = stepElement == null ? null : stepElement.asString();\r\n        String currentStep = InteractScriptHelper.getCurrentStep(Utilities.getEntryPlayer(scriptEntry), script.getName());\r\n        // Special-case for backwards compatibility: ability to use ZAP to count up steps.\r\n        if (step == null) {\r\n            // Okay, no step was identified.. that means we should count up,\r\n            // ie. if currentStep = 1, new step should = 2\r\n            // If the currentStep is a number, increment it. If not, set it\r\n            // to '1' so it can be incremented next time.\r\n            if (ArgumentHelper.matchesInteger(currentStep)) {\r\n                step = String.valueOf(Integer.parseInt(currentStep) + 1);\r\n            }\r\n            else {\r\n                step = \"1\";\r\n            }\r\n        }\r\n        else if (step.equals(\"*\")) {\r\n            step = ((InteractScriptContainer) script.getContainer()).getDefaultStepName();\r\n        }\r\n        if (step.equalsIgnoreCase(currentStep)) {\r\n            Debug.echoError(scriptEntry, \"Zapping to own current step!\");\r\n            return;\r\n        }\r\n        TimeTag expiration = null;\r\n        if (duration != null && duration.getSeconds() > 0) {\r\n            expiration = new TimeTag(TimeTag.now().millis() + duration.getMillis());\r\n        }\r\n        Utilities.getEntryPlayer(scriptEntry).getFlagTracker().setFlag(\"__interact_step.\" + script.getName(), new ElementTag(step), expiration);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/AgeCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityAge;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.Deprecations;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.entity.Breedable;\r\n\r\nimport java.util.List;\r\n\r\npublic class AgeCommand extends AbstractCommand {\r\n\r\n    public AgeCommand() {\r\n        setName(\"age\");\r\n        setSyntax(\"age [<entity>|...] (adult/baby/<age>) (lock)\");\r\n        setRequiredArguments(1, 3);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Age\r\n    // @Syntax age [<entity>|...] (adult/baby/<age>) (lock)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Short Sets the ages of a list of entities, optionally locking them in those ages.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Some living entity types are 'ageable' which can affect an entities ability to breed, or whether they appear as a baby or an adult.\r\n    // Using the 'age' command allows modification of an entity's age.\r\n    // Specify an entity and either 'baby', 'adult', or an integer age to set the age of an entity.\r\n    // Using the 'lock' argument will keep the entity from increasing its age automatically.\r\n    // NPCs which use ageable entity types can also be specified.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.age>\r\n    //\r\n    // @Usage\r\n    // Use to make an ageable entity a permanant baby.\r\n    // - age <[some_entity]> baby lock\r\n    //\r\n    // @Usage\r\n    // ...or a mature adult.\r\n    // - age <[some_entity]> adult lock\r\n    //\r\n    // @Usage\r\n    // Use to make a baby entity an adult.\r\n    // - age <[some_npc]> adult\r\n    //\r\n    // @Usage\r\n    // Use to mature some animals so that they are old enough to breed.\r\n    // - age <player.location.find_entities.within[20]> adult\r\n    // -->\r\n\r\n    private enum AgeType { ADULT, BABY }\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"entities\") @ArgLinear ObjectTag entities,\r\n                                   @ArgName(\"age\") @ArgLinear @ArgDefaultText(\"1\") ObjectTag age,\r\n                                   @ArgName(\"lock\") boolean shouldLock) {\r\n        if (!age.asElement().isInt() && !age.asElement().matchesEnum(AgeType.class)) { // Compensate for legacy age/entity out-of-order support\r\n            Deprecations.outOfOrderArgs.warn(scriptEntry);\r\n            ObjectTag swap = entities;\r\n            entities = age;\r\n            age = swap;\r\n        }\r\n        int ageInt = 1;\r\n        ElementTag ageElement = age.asElement();\r\n        if (ageElement.matchesEnum(AgeType.class)) {\r\n            switch (ageElement.asEnum(AgeType.class)) {\r\n                case BABY -> ageInt = -24000;\r\n                case ADULT -> ageInt = 0;\r\n            }\r\n        }\r\n        else if (ageElement.isInt()) {\r\n            ageInt = ageElement.asInt();\r\n        }\r\n        List<EntityTag> entitiesList = entities.asType(ListTag.class, scriptEntry.context).filter(EntityTag.class, scriptEntry);\r\n        for (EntityTag entity : entitiesList) {\r\n            if (entity.isSpawned()) {\r\n                if (EntityAge.describes(entity)) {\r\n                    EntityAge property = new EntityAge(entity);\r\n                    property.setAge(ageInt);\r\n                    if (entity.getBukkitEntity() instanceof Breedable breedable) {\r\n                        breedable.setAgeLock(shouldLock);\r\n                    }\r\n                }\r\n                else {\r\n                    Debug.echoError(scriptEntry, entity.identify() + \" is not ageable!\");\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/AnimateCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.AnimationHelper;\r\nimport com.denizenscript.denizen.nms.interfaces.EntityAnimation;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.util.PlayerAnimation;\r\nimport org.bukkit.EntityEffect;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.List;\r\n\r\npublic class AnimateCommand extends AbstractCommand {\r\n\r\n    public AnimateCommand() {\r\n        setName(\"animate\");\r\n        setSyntax(\"animate [<entity>|...] [animation:<name>] (for:<player>|...)\");\r\n        setRequiredArguments(2, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Animate\r\n    // @Syntax animate [<entity>|...] [animation:<name>] (for:<player>|...)\r\n    // @Required 2\r\n    // @Maximum 3\r\n    // @Plugin Citizens\r\n    // @Short Makes a list of entities perform a certain animation.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Minecraft implements several player and entity animations which the animate command can use, just\r\n    // specify an entity and an animation.\r\n    //\r\n    // Player animations require a Player-type entity or NPC. Available player animations include:\r\n    // ARM_SWING, HURT, CRIT, MAGIC_CRIT, SIT, SLEEP, SNEAK, STOP_SITTING, STOP_SLEEPING, STOP_SNEAKING,\r\n    // START_USE_MAINHAND_ITEM, START_USE_OFFHAND_ITEM, STOP_USE_ITEM, EAT_FOOD, ARM_SWING_OFFHAND\r\n    //\r\n    // All entities also have available Bukkit's entity effect list:\r\n    // <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/EntityEffect.html>\r\n    // These EntityEffect options can optionally be played only for specific players with the \"for:\" argument input.\r\n    //\r\n    // In addition, Denizen adds a few new entity animations:\r\n    // SKELETON_START_SWING_ARM, SKELETON_STOP_SWING_ARM,\r\n    // POLAR_BEAR_START_STANDING, POLAR_BEAR_STOP_STANDING,\r\n    // HORSE_BUCK, HORSE_START_STANDING, HORSE_STOP_STANDING,\r\n    // IRON_GOLEM_ATTACK,\r\n    // VILLAGER_SHAKE_HEAD,\r\n    // SWING_MAIN_HAND, SWING_OFF_HAND\r\n    //\r\n    // Note that the above list only applies where logical, EG 'WOLF_' animations only apply to wolves.\r\n    //\r\n    // In versions 1.20+, to specify the direction of damage for the HURT animation, use <@link mechanism EntityTag.play_hurt_animation>\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to make a player appear to get hurt.\r\n    // - animate <player> animation:hurt\r\n    //\r\n    // @Usage\r\n    // Use to make a wolf NPC shake.\r\n    // - animate <npc> animation:wolf_shake\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addWithPrefix(\"animation:\", EntityEffect.values());\r\n        tab.addWithPrefix(\"animation:\", PlayerAnimation.values());\r\n        tab.addWithPrefix(\"animation:\", AnimationHelper.entityAnimations.keySet());\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"for\")\r\n                    && arg.matchesPrefix(\"for\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"for\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            if (!scriptEntry.hasObject(\"animation\") &&\r\n                    !scriptEntry.hasObject(\"effect\") &&\r\n                    !scriptEntry.hasObject(\"nms_animation\")) {\r\n                if (arg.matchesEnum(PlayerAnimation.class)) {\r\n                    scriptEntry.addObject(\"animation\", PlayerAnimation.valueOf(arg.getValue().toUpperCase()));\r\n                }\r\n                if (arg.matchesEnum(EntityEffect.class)) {\r\n                    scriptEntry.addObject(\"effect\", EntityEffect.valueOf(arg.getValue().toUpperCase()));\r\n                }\r\n                if (NMSHandler.animationHelper.hasEntityAnimation(arg.getValue())) {\r\n                    scriptEntry.addObject(\"nms_animation\", arg.getValue());\r\n                }\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            throw new InvalidArgumentsException(\"Must specify entity/entities!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"effect\") && !scriptEntry.hasObject(\"animation\") && !scriptEntry.hasObject(\"nms_animation\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a valid animation!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        List<PlayerTag> forPlayers = (List<PlayerTag>) scriptEntry.getObject(\"for\");\r\n        PlayerAnimation animation = scriptEntry.hasObject(\"animation\") ? (PlayerAnimation) scriptEntry.getObject(\"animation\") : null;\r\n        EntityEffect effect = scriptEntry.hasObject(\"effect\") ? (EntityEffect) scriptEntry.getObject(\"effect\") : null;\r\n        String nmsAnimation = scriptEntry.hasObject(\"nms_animation\") ? (String) scriptEntry.getObject(\"nms_animation\") : null;\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(),\r\n                    (animation != null ? db(\"animation\", animation.name()) : effect != null ? db(\"effect\", effect.name()) : db(\"animation\", nmsAnimation)),\r\n                    db(\"entities\", entities), db(\"for\", forPlayers));\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (entity.isSpawned()) {\r\n                try {\r\n                    if (animation != null && entity.getBukkitEntity() instanceof Player) {\r\n                        Player player = (Player) entity.getBukkitEntity();\r\n                        animation.play(player);\r\n                    }\r\n                    else if (effect != null) {\r\n                        if (forPlayers != null) {\r\n                            for (PlayerTag player : forPlayers) {\r\n                                NMSHandler.packetHelper.sendEntityEffect(player.getPlayerEntity(), entity.getBukkitEntity(), effect);\r\n                            }\r\n                        }\r\n                        else {\r\n                            entity.getBukkitEntity().playEffect(effect);\r\n                        }\r\n                    }\r\n                    else if (nmsAnimation != null) {\r\n                        EntityAnimation entityAnimation = NMSHandler.animationHelper.getEntityAnimation(nmsAnimation);\r\n                        entityAnimation.play(entity.getBukkitEntity());\r\n                    }\r\n                    else {\r\n                        Debug.echoError(\"No way to play the given animation on entity '\" + entity + \"'\");\r\n                    }\r\n                }\r\n                catch (Exception e) {\r\n                    Debug.echoError(scriptEntry, \"Error playing that animation!\");\r\n                    Debug.echoError(e);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/AttachCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\nimport java.util.List;\r\nimport java.util.UUID;\r\nimport java.util.function.BiConsumer;\r\n\r\npublic class AttachCommand extends AbstractCommand {\r\n\r\n    public AttachCommand() {\r\n        setName(\"attach\");\r\n        setSyntax(\"attach [<entity>|...] [to:<entity>/cancel] (offset:<offset>) (relative) (yaw_offset:<#.#>) (pitch_offset:<#.#>) (sync_server) (no_rotate/no_pitch) (for:<player>|...)\");\r\n        setRequiredArguments(2, 9);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name attach\r\n    // @Syntax attach [<entity>|...] [to:<entity>/cancel] (offset:<offset>) (relative) (yaw_offset:<#.#>) (pitch_offset:<#.#>) (sync_server) (no_rotate/no_pitch) (for:<player>|...)\r\n    // @Required 2\r\n    // @Maximum 9\r\n    // @Short Attaches a list of entities to another entity, for client-visible motion sync.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Attaches a list of entities to another entity, for client-visible motion sync.\r\n    //\r\n    // You must specify the entity or list of entities to be attached.\r\n    // You must specify the entity that they will be attached to, or 'cancel' to end attachment.\r\n    //\r\n    // Optionally, specify an offset location vector to be a positional offset. This can include a yaw/pitch to offset those as well.\r\n    // Note that setting an offset of 0,0,0 will produce slightly different visual results from not setting any offset.\r\n    //\r\n    // Optionally, specify 'relative' to indicate that the offset vector should rotate with the target entity.\r\n    // If relative is used, optionally specify yaw_offset and/or pitch_offset to add an offset to rotation of the target entity when calculating the attachment offset.\r\n    //\r\n    // Optionally, specify 'for' with a player or list of players to only sync motion for those players.\r\n    // If unspecified, will sync for everyone.\r\n    //\r\n    // Optionally, specify 'sync_server' to keep the serverside position of the attached entities near the target entity.\r\n    // This can reduce some visual artifacts (such as entity unloading at distance), but may produce unintended functional artifacts.\r\n    // Note that you should generally only use 'sync_server' when you exclude the 'for' argument.\r\n    //\r\n    // Optionally specify 'no_rotate' to retain the attached entity's own rotation and ignore the target rotation.\r\n    // Optionally instead specify 'no_pitch' to retain the attached entity's own pitch, but use the target yaw.\r\n    //\r\n    // Note that attaches involving a player will not be properly visible to that player, but will still be visible to *other* players.\r\n    //\r\n    // It may be ideal to change setting \"Packets.Auto init\" in the Denizen config to \"true\" to guarantee this command functions as expected.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.attached_entities[(<player>)]>\r\n    // <EntityTag.attached_to[(<player>)]>\r\n    // <EntityTag.attached_offset[(<player>)]>\r\n    //\r\n    // @Usage\r\n    // Use to attach random NPC to the air 3 blocks above a linked NPC.\r\n    // - attach <server.list_npcs.random> to:<npc> offset:0,3,0\r\n    // -->\r\n\r\n    public static void autoExecute(@ArgName(\"entities\") @ArgLinear @ArgSubType(EntityTag.class) List<EntityTag> entities,\r\n                                   @ArgName(\"to\") @ArgPrefixed @ArgDefaultNull EntityTag target,\r\n                                   @ArgName(\"cancel\") boolean cancel,\r\n                                   @ArgName(\"offset\") @ArgPrefixed @ArgDefaultNull LocationTag offset,\r\n                                   @ArgName(\"relative\") boolean relative,\r\n                                   @ArgName(\"yaw_offset\") @ArgPrefixed @ArgDefaultText(\"0\") float yawOffset,\r\n                                   @ArgName(\"pitch_offset\") @ArgPrefixed @ArgDefaultText(\"0\") float pitchOffset,\r\n                                   @ArgName(\"sync_server\") boolean syncServer,\r\n                                   @ArgName(\"no_rotate\") boolean noRotate,\r\n                                   @ArgName(\"no_pitch\") boolean noPitch,\r\n                                   @ArgName(\"for\") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> forPlayers) {\r\n        if (target == null && !cancel) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify a target entity, or 'cancel'!\");\r\n        }\r\n        BiConsumer<EntityTag, UUID> procPlayer = (entity, player) -> {\r\n            if (cancel) {\r\n                EntityAttachmentHelper.removeAttachment(entity.getUUID(), player);\r\n            }\r\n            else {\r\n                EntityAttachmentHelper.AttachmentData attachment = new EntityAttachmentHelper.AttachmentData();\r\n                attachment.attached = entity;\r\n                attachment.to = target;\r\n                attachment.positionalOffset = offset == null ? null : offset.clone();\r\n                attachment.offsetRelative = relative;\r\n                attachment.yawAngleOffset = yawOffset;\r\n                attachment.pitchAngleOffset = pitchOffset;\r\n                attachment.syncServer = syncServer;\r\n                attachment.forPlayer = player;\r\n                attachment.noRotate = noRotate;\r\n                attachment.noPitch = noPitch;\r\n                EntityAttachmentHelper.registerAttachment(attachment);\r\n            }\r\n        };\r\n        for (EntityTag entity : entities) {\r\n            if (!entity.isSpawned() && !entity.isFake && !cancel) {\r\n                Debug.echoError(\"Cannot attach entity '\" + entity + \"': entity is not spawned.\");\r\n                continue;\r\n            }\r\n            if (forPlayers == null || (forPlayers.isEmpty() && syncServer)) {\r\n                procPlayer.accept(entity, null);\r\n            }\r\n            else {\r\n                for (PlayerTag player : forPlayers) {\r\n                    procPlayer.accept(entity, player.getUUID());\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/AttackCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.api.ai.Navigator;\r\nimport net.citizensnpcs.api.ai.TargetType;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class AttackCommand extends AbstractCommand {\r\n\r\n    public AttackCommand() {\r\n        setName(\"attack\");\r\n        setSyntax(\"attack [<entity>|...] (target:<entity>/cancel)\");\r\n        setRequiredArguments(0, 2);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Attack\r\n    // @Syntax attack [<entity>|...] (target:<entity>/cancel)\r\n    // @Required 0\r\n    // @Maximum 2\r\n    // @Short Makes an entity, or list of entities, attack a target.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // The attack command causes a mob entity to attack a target mob entity or player.\r\n    //\r\n    // This technically can be used on an NPC, but it will trigger the Citizens internal punching-pathfinder.\r\n    // This attack mode doesn't work well. If you want NPC combat, consider using Sentinel instead: <@link url https://github.com/mcmonkeyprojects/Sentinel/blob/master/README.md>.\r\n    //\r\n    // To cancel an attack, use the 'cancel' argument instead of specifying a target.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.is_fighting>\r\n    // <NPCTag.attack_strategy>\r\n    // <NPCTag.target_entity>\r\n    //\r\n    // @Usage\r\n    // Use to make the player's target entity attack a nearby entity.\r\n    // - attack <player.target> target:<npc.location.find.living_entities.within[10].random>\r\n    //\r\n    // @Usage\r\n    // Use to make a random nearby entity attack a player.\r\n    // - attack <player.location.find.living_entities.within[10].random> target:<player>\r\n    //\r\n    // @Usage\r\n    // Use to stop an entity from attacking.\r\n    // - attack <[entity]> cancel\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"cancel\")\r\n                    && arg.matches(\"cancel\", \"stop\")) {\r\n                scriptEntry.addObject(\"cancel\", \"true\");\r\n            }\r\n            else if (!scriptEntry.hasObject(\"target\")\r\n                    && arg.matchesArgumentType(EntityTag.class)\r\n                    && arg.matchesPrefix(\"target\", \"t\")) {\r\n                scriptEntry.addObject(\"target\", arg.asType(EntityTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)\r\n                    && !arg.matchesPrefix(\"target\", \"t\")) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"target\")) {\r\n            scriptEntry.addObject(\"target\", Utilities.entryHasPlayer(scriptEntry) ? Utilities.getEntryPlayer(scriptEntry).getDenizenEntity() : null);\r\n        }\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            scriptEntry.defaultObject(\"entities\", Utilities.entryHasNPC(scriptEntry) ? Collections.singletonList(Utilities.getEntryNPC(scriptEntry).getDenizenEntity()) : null);\r\n            if (!scriptEntry.hasObject(\"entities\")) {\r\n                throw new InvalidArgumentsException(\"Must specify entity/entities!\");\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"target\") && !scriptEntry.hasObject(\"cancel\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a target!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        EntityTag target = scriptEntry.getObjectTag(\"target\");\r\n        boolean cancel = scriptEntry.hasObject(\"cancel\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), (cancel ? db(\"cancel\", \"true\") : \"\"), db(\"entities\", entities), db(\"target\", target));\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (entity.isCitizensNPC()) {\r\n                Navigator nav = entity.getDenizenNPC().getCitizen().getNavigator();\r\n                if (!cancel) {\r\n                    nav.setTarget(target.getBukkitEntity(), true);\r\n                }\r\n                else {\r\n                    // Only cancel navigation if the NPC is attacking something\r\n                    if (nav.isNavigating()\r\n                            && nav.getTargetType().equals(TargetType.ENTITY)\r\n                            && nav.getEntityTarget().isAggressive()) {\r\n                        nav.cancelNavigation();\r\n                    }\r\n                }\r\n            }\r\n            else {\r\n                if (!cancel) {\r\n                    entity.target(target.getLivingEntity());\r\n                }\r\n                else {\r\n                    entity.target(null);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/BurnCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\nimport java.util.List;\r\n\r\npublic class BurnCommand extends AbstractCommand {\r\n\r\n    public BurnCommand() {\r\n        setName(\"burn\");\r\n        setSyntax(\"burn [<entity>|...] (duration:<value>)\");\r\n        setRequiredArguments(1, 2);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Burn\r\n    // @Syntax burn [<entity>|...] (duration:<value>)\r\n    // @Required 1\r\n    // @Maximum 2\r\n    // @Short Sets a list of entities on fire.\r\n    // @Synonyms Ignite,Fire,Torch\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Burn will set a list of entities on fire.\r\n    // Just specify a list of entities (or a single entity) and optionally, a duration.\r\n    // Normal mobs and players will see damage afflicted, but NPCs will block damage from a burn unless 'vulnerable'.\r\n    // Since this command sets the total time of fire, it can also be used to cancel fire on a burning entity by specifying a duration of 0.\r\n    // Specifying no duration will result in a 5 second burn.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.fire_time>\r\n    // <EntityTag.on_fire>\r\n    //\r\n    // @Usage\r\n    // Use to set an entity on fire.\r\n    // - burn <player> duration:10s\r\n    //\r\n    // @Usage\r\n    // Use to cancel fire on entities.\r\n    // - burn <player.location.find.living_entities.within[10]> duration:0\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"entities\", Utilities.entryDefaultEntityList(scriptEntry, true));\r\n        scriptEntry.defaultObject(\"duration\", new DurationTag(5));\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        if (entities == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"Missing entity target input\");\r\n        }\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), duration, db(\"entities\", entities));\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (entity.isSpawned()) {\r\n                entity.getBukkitEntity().setFireTicks(duration.getTicksAsInt());\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/CastCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.Deprecations;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.potion.PotionEffect;\r\nimport org.bukkit.potion.PotionEffectType;\r\n\r\nimport java.util.List;\r\n\r\npublic class CastCommand extends AbstractCommand {\r\n\r\n    public CastCommand() {\r\n        setName(\"cast\");\r\n        setSyntax(\"cast [<effect>] (remove) (duration:<value>) (amplifier:<#>) (<entity>|...) (no_ambient) (hide_particles) (no_icon) (no_clear)\");\r\n        setRequiredArguments(1, 9);\r\n        addRemappedPrefixes(\"duration\", \"d\");\r\n        addRemappedPrefixes(\"amplifier\", \"power\", \"p\", \"a\");\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Cast\r\n    // @Syntax cast [<effect>] (remove) (duration:<value>) (amplifier:<#>) (<entity>|...) (no_ambient) (hide_particles) (no_icon) (no_clear)\r\n    // @Required 1\r\n    // @Maximum 9\r\n    // @Short Casts a potion effect to a list of entities.\r\n    // @Synonyms Potion,Magic\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Casts or removes a potion effect to or from a list of entities.\r\n    //\r\n    // The effect type must be from <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/potion/PotionEffectType.html>.\r\n    //\r\n    // If you don't specify a duration, it defaults to 60 seconds.\r\n    // An infinite duration will apply an infinite duration potion effect, refer to <@link objecttype DurationTag> for more details.\r\n    //\r\n    // The amplifier is how many levels to *add* over the normal level 1.\r\n    // If you don't specify an amplifier level, it defaults to 1, meaning an effect of level 2 (this is for historical compatibility reasons).\r\n    // Specify \"amplifier:0\" to have no amplifier applied (ie effect level 1).\r\n    //\r\n    // If no entity is specified, the command will target the linked player.\r\n    // If there isn't one, the command will target the linked NPC. If there isn't one either, the command will error.\r\n    //\r\n    // Optionally, specify \"no_ambient\" to hide some translucent additional particles, while still rendering the main particles.\r\n    // \"Ambient\" effects in vanilla come from a beacon, while non-ambient come from a potion.\r\n    //\r\n    // Optionally, specify \"hide_particles\" to remove the particle effects entirely.\r\n    //\r\n    // Optionally, specify \"no_icon\" to hide the effect icon in the corner of your screen.\r\n    //\r\n    // Optionally use \"no_clear\" to prevent clearing any previous effect instance before adding the new one.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.has_effect[<effect>]>\r\n    // <server.potion_effect_types>\r\n    // <EntityTag.effects_data>\r\n    //\r\n    // @Usage\r\n    // Use to cast a level 1 effect onto the linked player or NPC for 50 seconds.\r\n    // - cast speed duration:50s amplifier:0\r\n    //\r\n    // @Usage\r\n    // Use to cast an effect onto the linked player or NPC for an infinite duration with an amplifier of 3 (effect level 4).\r\n    // - cast jump duration:infinite amplifier:3\r\n    //\r\n    // @Usage\r\n    // Use to remove an effect from a specific entity.\r\n    // - cast jump remove <[entity]>\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        for (PotionEffectType effect : PotionEffectType.values()) { // Not an enum for some reason\r\n            tab.add(effect.getName());\r\n        }\r\n    }\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"effect\") @ArgLinear ObjectTag effectObject,\r\n                                   @ArgName(\"remove\") boolean remove,\r\n                                   @ArgName(\"cancel\") boolean cancel, // \"remove\" variant\r\n                                   @ArgName(\"duration\") @ArgPrefixed @ArgDefaultText(\"60s\") DurationTag duration,\r\n                                   @ArgName(\"amplifier\") @ArgPrefixed @ArgDefaultText(\"1\") ElementTag amplifier,\r\n                                   @ArgName(\"entities\") @ArgLinear @ArgDefaultNull ObjectTag entitiesObject,\r\n                                   @ArgName(\"no_ambient\") boolean noAmbient,\r\n                                   @ArgName(\"hide_particles\") boolean hideParticles,\r\n                                   @ArgName(\"no_icon\") boolean noIcon,\r\n                                   @ArgName(\"no_clear\") boolean noClear) {\r\n        PotionEffectType effectType = PotionEffectType.getByName(effectObject.toString());\r\n        if (effectType == null) {\r\n            if (entitiesObject != null && (effectType = PotionEffectType.getByName(entitiesObject.toString())) != null) {\r\n                Deprecations.outOfOrderArgs.warn(scriptEntry);\r\n                ObjectTag swapEntities = entitiesObject;\r\n                entitiesObject = effectObject;\r\n                effectObject = swapEntities;\r\n            }\r\n            if (effectType == null) {\r\n                throw new InvalidArgumentsRuntimeException(\"Invalid potion effect '\" + effectObject + \"' specified.\");\r\n            }\r\n        }\r\n        if (!amplifier.isInt()) {\r\n            throw new InvalidArgumentsRuntimeException(\"Invalid amplifier '\" + amplifier + \"' specified: must be a valid number.\");\r\n        }\r\n        List<EntityTag> entities = entitiesObject == null ? null : entitiesObject.asType(ListTag.class, scriptEntry.context).filter(EntityTag.class, scriptEntry.context);\r\n        if (entities == null) {\r\n            entities = Utilities.entryDefaultEntityList(scriptEntry, true);\r\n            if (entities == null) {\r\n                throw new InvalidArgumentsRuntimeException(\"Must specify entities to apply the effect to.\");\r\n            }\r\n        }\r\n        remove = remove || cancel;\r\n        PotionEffect potion = null;\r\n        if (!remove) {\r\n            // 32,780+ ticks shows up as infinite before 1.19\r\n            int ticks = duration.getSeconds() != 0d ? duration.getTicksAsInt() : NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) ? PotionEffect.INFINITE_DURATION : Integer.MAX_VALUE;\r\n            potion = new PotionEffect(effectType, ticks, amplifier.asInt(), !noAmbient, !hideParticles, !noIcon);\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if ((remove || !noClear) && entity.getLivingEntity().hasPotionEffect(effectType)) {\r\n                entity.getLivingEntity().removePotionEffect(effectType);\r\n            }\r\n            if (!remove) {\r\n                if (!entity.getLivingEntity().addPotionEffect(potion)) {\r\n                    Debug.echoError(\"Bukkit was unable to apply '\" + effectType.getName() + \"' to '\" + entity + \"'.\");\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/EquipCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.api.trait.trait.Equipment;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.inventory.HorseInventory;\r\n\r\nimport java.util.*;\r\n\r\npublic class EquipCommand extends AbstractCommand {\r\n\r\n    public EquipCommand() {\r\n        setName(\"equip\");\r\n        setSyntax(\"equip (<entity>|...) (hand:<item>) (offhand:<item>) (head:<item>) (chest:<item>) (legs:<item>) (boots:<item>) (saddle:<item>) (body:<item>)\");\r\n        setRequiredArguments(1, 9);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Equip\r\n    // @Syntax equip (<entity>|...) (hand:<item>) (offhand:<item>) (head:<item>) (chest:<item>) (legs:<item>) (boots:<item>) (saddle:<item>) (body:<item>)\r\n    // @Required 1\r\n    // @Maximum 9\r\n    // @Short Equips items and armor on a list of entities.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // This command equips an item or armor to an entity or list of entities to the specified slot(s).\r\n    // Set the item to 'air' to unequip any slot.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.equipment>\r\n    // <InventoryTag.equipment>\r\n    //\r\n    // @Usage\r\n    // Use to equip a stone block on the player's head.\r\n    // - equip <player> head:stone\r\n    //\r\n    // @Usage\r\n    // Use to equip an iron helmet on two defined players.\r\n    // - equip <[player]>|<[someplayer]> head:iron_helmet\r\n    //\r\n    // @Usage\r\n    // Use to unequip all armor off the player.\r\n    // - equip <player> head:air chest:air legs:air boots:air\r\n    //\r\n    // @Usage\r\n    // Use to equip a saddle on the horse the player is riding.\r\n    // - equip <player.vehicle> saddle:saddle\r\n    //\r\n    // @Usage\r\n    // Use to equip a saddle on all nearby pigs.\r\n    // - equip <player.location.find_entities[pig].within[10]> saddle:saddle\r\n    //\r\n    // @Usage\r\n    // Use to equip a horse with iron horse armor.\r\n    // - equip <[horse]> body:iron_horse_armor\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        Map<String, ItemTag> equipment = new HashMap<>();\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else if (arg.matchesArgumentType(ItemTag.class)\r\n                    && arg.matchesPrefix(\"head\", \"helmet\")) {\r\n                equipment.put(\"head\", ItemTag.valueOf(arg.getValue(), scriptEntry.getContext()));\r\n            }\r\n            else if (arg.matchesArgumentType(ItemTag.class)\r\n                    && arg.matchesPrefix(\"chest\", \"chestplate\")) {\r\n                equipment.put(\"chest\", ItemTag.valueOf(arg.getValue(), scriptEntry.getContext()));\r\n            }\r\n            else if (arg.matchesArgumentType(ItemTag.class)\r\n                    && arg.matchesPrefix(\"legs\", \"leggings\")) {\r\n                equipment.put(\"legs\", ItemTag.valueOf(arg.getValue(), scriptEntry.getContext()));\r\n            }\r\n            else if (arg.matchesArgumentType(ItemTag.class)\r\n                    && arg.matchesPrefix(\"boots\", \"feet\")) {\r\n                equipment.put(\"boots\", ItemTag.valueOf(arg.getValue(), scriptEntry.getContext()));\r\n            }\r\n            else if (arg.matchesArgumentType(ItemTag.class)\r\n                    && arg.matchesPrefix(\"saddle\")) {\r\n                equipment.put(\"saddle\", ItemTag.valueOf(arg.getValue(), scriptEntry.getContext()));\r\n            }\r\n            else if (arg.matchesArgumentType(ItemTag.class)\r\n                    && arg.matchesPrefix(\"horse_armor\", \"horse_armour\")) {\r\n                if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                    BukkitImplDeprecations.horseArmorEquipCommand.warn();\r\n                }\r\n                equipment.put(\"horse_armor\", ItemTag.valueOf(arg.getValue(), scriptEntry.getContext()));\r\n            }\r\n            else if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)\r\n                    && arg.matchesArgumentType(ItemTag.class)\r\n                    && arg.matchesPrefix(\"body\")) {\r\n                equipment.put(\"body\", ItemTag.valueOf(arg.getValue(), scriptEntry.getContext()));\r\n            }\r\n            else if (arg.matchesArgumentType(ItemTag.class)\r\n                    && arg.matchesPrefix(\"offhand\")) {\r\n                equipment.put(\"offhand\", ItemTag.valueOf(arg.getValue(), scriptEntry.getContext()));\r\n            }\r\n            // Default to item in hand if no prefix is used\r\n            else if (arg.matchesArgumentType(ItemTag.class)) {\r\n                equipment.put(\"hand\", ItemTag.valueOf(arg.getValue(), scriptEntry.getContext()));\r\n            }\r\n            else if (arg.matches(\"player\") && Utilities.entryHasPlayer(scriptEntry)) {\r\n                // Player arg for compatibility with old scripts\r\n                scriptEntry.addObject(\"entities\", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry).getDenizenEntity()));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (equipment.isEmpty()) {\r\n            throw new InvalidArgumentsException(\"Must specify equipment!\");\r\n        }\r\n        scriptEntry.addObject(\"equipment\", equipment);\r\n        scriptEntry.defaultObject(\"entities\", Utilities.entryDefaultEntityList(scriptEntry, false));\r\n\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        Map<String, ItemTag> equipment = (Map<String, ItemTag>) scriptEntry.getObject(\"equipment\");\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        if (entities == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"Missing entity target input\");\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"entities\", entities), db(\"equipment\", equipment));\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (entity.isGeneric()) {\r\n                Debug.echoError(scriptEntry, \"Cannot equip generic entity \" + entity.identify() + \"!\");\r\n            }\r\n            else if (entity.isCitizensNPC()) {\r\n                NPCTag npc = entity.getDenizenNPC();\r\n                if (npc != null) {\r\n                    Equipment trait = npc.getEquipmentTrait();\r\n                    if (equipment.get(\"hand\") != null) {\r\n                        trait.set(Equipment.EquipmentSlot.HAND, equipment.get(\"hand\").getItemStack());\r\n                    }\r\n                    if (equipment.get(\"head\") != null) {\r\n                        trait.set(Equipment.EquipmentSlot.HELMET, equipment.get(\"head\").getItemStack());\r\n                    }\r\n                    if (equipment.get(\"chest\") != null) {\r\n                        trait.set(Equipment.EquipmentSlot.CHESTPLATE, equipment.get(\"chest\").getItemStack());\r\n                    }\r\n                    if (equipment.get(\"legs\") != null) {\r\n                        trait.set(Equipment.EquipmentSlot.LEGGINGS, equipment.get(\"legs\").getItemStack());\r\n                    }\r\n                    if (equipment.get(\"boots\") != null) {\r\n                        trait.set(Equipment.EquipmentSlot.BOOTS, equipment.get(\"boots\").getItemStack());\r\n                    }\r\n                    if (equipment.get(\"offhand\") != null) {\r\n                        trait.set(Equipment.EquipmentSlot.OFF_HAND, equipment.get(\"offhand\").getItemStack());\r\n                    }\r\n                    if (equipment.get(\"body\") != null) {\r\n                        trait.set(Equipment.EquipmentSlot.BODY, equipment.get(\"body\").getItemStack());\r\n                    }\r\n                    if (npc.isSpawned()) {\r\n                        LivingEntity livingEntity = npc.getLivingEntity();\r\n                        // TODO: Citizens API for this blob?\r\n                        if (livingEntity instanceof AbstractHorse abstractHorse) {\r\n                            if (equipment.get(\"saddle\") != null) {\r\n                                abstractHorse.getInventory().setSaddle(equipment.get(\"saddle\").getItemStack());\r\n                            }\r\n                            if (equipment.get(\"horse_armor\") != null) {\r\n                                if (abstractHorse.getInventory() instanceof HorseInventory horseInventory) {\r\n                                    horseInventory.setArmor(equipment.get(\"horse_armor\").getItemStack());\r\n                                }\r\n                            }\r\n                        }\r\n                        else if (livingEntity instanceof Steerable steerable) {\r\n                            if (equipment.get(\"saddle\") != null) {\r\n                                steerable.setSaddle(equipment.get(\"saddle\").getBukkitMaterial() == Material.SADDLE);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            else {\r\n                LivingEntity livingEntity = entity.getLivingEntity();\r\n                if (livingEntity != null) {\r\n                    if (equipment.get(\"body\") != null) {\r\n                        livingEntity.getEquipment().setItem(EquipmentSlot.BODY, equipment.get(\"body\").getItemStack());\r\n                    }\r\n                    else if (livingEntity instanceof AbstractHorse abstractHorse) {\r\n                        if (equipment.get(\"saddle\") != null) {\r\n                            abstractHorse.getInventory().setSaddle(equipment.get(\"saddle\").getItemStack());\r\n                        }\r\n                        if (equipment.get(\"horse_armor\") != null) {\r\n                            if (abstractHorse.getInventory() instanceof HorseInventory horseInventory) {\r\n                                horseInventory.setArmor(equipment.get(\"horse_armor\").getItemStack());\r\n                            }\r\n                        }\r\n                    }\r\n                    else if (livingEntity instanceof Steerable steerable) {\r\n                        if (equipment.get(\"saddle\") != null) {\r\n                            steerable.setSaddle(equipment.get(\"saddle\").getBukkitMaterial() == Material.SADDLE);\r\n                        }\r\n                    }\r\n                    else {\r\n                        if (equipment.get(\"hand\") != null) {\r\n                            livingEntity.getEquipment().setItemInMainHand(equipment.get(\"hand\").getItemStack());\r\n                        }\r\n                        if (equipment.get(\"head\") != null) {\r\n                            livingEntity.getEquipment().setHelmet(equipment.get(\"head\").getItemStack());\r\n                        }\r\n                        if (equipment.get(\"chest\") != null) {\r\n                            livingEntity.getEquipment().setChestplate(equipment.get(\"chest\").getItemStack());\r\n                        }\r\n                        if (equipment.get(\"legs\") != null) {\r\n                            livingEntity.getEquipment().setLeggings(equipment.get(\"legs\").getItemStack());\r\n                        }\r\n                        if (equipment.get(\"boots\") != null) {\r\n                            livingEntity.getEquipment().setBoots(equipment.get(\"boots\").getItemStack());\r\n                        }\r\n                        if (equipment.get(\"offhand\") != null) {\r\n                            livingEntity.getEquipment().setItemInOffHand(equipment.get(\"offhand\").getItemStack());\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/FakeEquipCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\n\r\nimport java.util.*;\r\n\r\npublic class FakeEquipCommand extends AbstractCommand {\r\n\r\n    public FakeEquipCommand() {\r\n        setName(\"fakeequip\");\r\n        setSyntax(\"fakeequip [<entity>|...] (for:<player>|...) (duration:<duration>/reset) (hand:<item>) (offhand:<item>) (head:<item>) (chest:<item>) (legs:<item>) (boots:<item>)\");\r\n        setRequiredArguments(1, 9);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name FakeEquip\r\n    // @Syntax fakeequip [<entity>|...] (for:<player>|...) (duration:<duration>/reset) (hand:<item>) (offhand:<item>) (head:<item>) (chest:<item>) (legs:<item>) (boots:<item>)\r\n    // @Required 1\r\n    // @Maximum 9\r\n    // @Short Fake-equips items and armor on a list of entities for players to see without real change.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // This command fake-equips items and armor on a list of entities.\r\n    //\r\n    // The change doesn't happen on-server, and no armor effects will happen from it.\r\n    //\r\n    // The equipment can only be seen by certain players. By default, the linked player is used.\r\n    //\r\n    // The changes will remain in place for as long as the duration is specified (even if the real equipment is changed).\r\n    // The changes can be manually reset early by using the 'reset' argument.\r\n    // If you do not provide a duration, the fake equipment will last until manually reset.\r\n    // This does not persist across server restarts.\r\n    //\r\n    // Set the item to 'air' to unequip any slot.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.equipment>\r\n    // <InventoryTag.equipment>\r\n    //\r\n    // @Usage\r\n    // Use to fake-equip a stone block on the player's head.\r\n    // - fakeequip <player> head:stone duration:10s\r\n    //\r\n    // @Usage\r\n    // Use to fake-equip an iron helmet on two defined players.\r\n    // - fakeequip <[player]>|<[someplayer]> head:iron_helmet duration:1m\r\n    //\r\n    // @Usage\r\n    // Use to fake-unequip all armor off the player.\r\n    // - fakeequip <player> head:air chest:air legs:air boots:air duration:5s\r\n    //\r\n    // @Usage\r\n    // Use to make all players within 30 blocks of an entity see it permanently equip a shield.\r\n    // - fakeequip <[entity]> offhand:shield for:<[entity].find_players_within[30]> duration:0\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        EquipmentOverride equipment = new EquipmentOverride();\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else if (arg.matchesPrefix(\"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (arg.matches(\"reset\")) {\r\n                scriptEntry.addObject(\"reset\", new ElementTag(true));\r\n            }\r\n            else if (arg.matchesPrefix(\"for\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"for\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else if (arg.matchesPrefix(\"head\", \"helmet\")\r\n                    && arg.matchesArgumentType(ItemTag.class)) {\r\n                equipment.head = arg.asType(ItemTag.class);\r\n            }\r\n            else if (arg.matchesPrefix(\"chest\", \"chestplate\")\r\n                    && arg.matchesArgumentType(ItemTag.class)) {\r\n                equipment.chest = arg.asType(ItemTag.class);\r\n            }\r\n            else if (arg.matchesPrefix(\"legs\", \"leggings\")\r\n                    && arg.matchesArgumentType(ItemTag.class)) {\r\n                equipment.legs = arg.asType(ItemTag.class);\r\n            }\r\n            else if (arg.matchesPrefix(\"boots\", \"feet\")\r\n                    && arg.matchesArgumentType(ItemTag.class)) {\r\n                equipment.boots = arg.asType(ItemTag.class);\r\n            }\r\n            else if (arg.matchesPrefix(\"offhand\")\r\n                    && arg.matchesArgumentType(ItemTag.class)) {\r\n                equipment.offhand = arg.asType(ItemTag.class);\r\n            }\r\n            else if (arg.matchesPrefix(\"hand\")\r\n                    && arg.matchesArgumentType(ItemTag.class)) {\r\n                equipment.hand = arg.asType(ItemTag.class);\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (equipment.isEmpty() && !scriptEntry.hasObject(\"reset\")) {\r\n            throw new InvalidArgumentsException(\"Must specify equipment!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"for\")) {\r\n            PlayerTag player = Utilities.getEntryPlayer(scriptEntry);\r\n            if (player == null) {\r\n                throw new InvalidArgumentsException(\"Must specify a for player!\");\r\n            }\r\n            scriptEntry.addObject(\"for\", Collections.singletonList(player));\r\n        }\r\n        scriptEntry.addObject(\"equipment\", equipment);\r\n    }\r\n\r\n    public static class EquipmentOverride {\r\n\r\n        public ItemTag hand, offhand, head, chest, legs, boots;\r\n\r\n        public BukkitTask cancelTask;\r\n\r\n        public boolean isEmpty() {\r\n            return hand == null && offhand == null && head == null && chest == null && legs == null && boots == null;\r\n        }\r\n\r\n        @Override\r\n        public String toString() {\r\n            return \"Equipment{hand=\" + hand + \",offhand=\" + offhand + \",head=\" + head + \",chest=\" + chest + \",legs=\" + legs + \",boots=\" + boots + \"}\";\r\n        }\r\n\r\n        public void copyFrom(EquipmentOverride override) {\r\n            hand = override.hand == null ? hand : override.hand;\r\n            offhand = override.offhand == null ? offhand : override.offhand;\r\n            head = override.head == null ? head : override.head;\r\n            chest = override.chest == null ? chest : override.chest;\r\n            legs = override.legs == null ? legs : override.legs;\r\n            boots = override.boots == null ? boots : override.boots;\r\n        }\r\n\r\n        public EquipmentOverride getVariantFor(Player player) {\r\n            return this;\r\n        }\r\n    }\r\n\r\n    public static EquipmentOverride getOverrideFor(UUID entity, Player player) {\r\n        HashMap<UUID, EquipmentOverride> playerMap = overrides.get(player.getUniqueId());\r\n        if (playerMap == null) {\r\n            playerMap = overrides.get(null);\r\n            if (playerMap == null) {\r\n                return null;\r\n            }\r\n        }\r\n        EquipmentOverride override = playerMap.get(entity);\r\n        if (override == null) {\r\n            return null;\r\n        }\r\n        return override.getVariantFor(player);\r\n    }\r\n\r\n    public static HashMap<UUID, HashMap<UUID, EquipmentOverride>> overrides = new HashMap<>();\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        NetworkInterceptHelper.enable();\r\n        EquipmentOverride equipment = (EquipmentOverride) scriptEntry.getObject(\"equipment\");\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        List<PlayerTag> playersFor = (List<PlayerTag>) scriptEntry.getObject(\"for\");\r\n        ElementTag reset = scriptEntry.getElement(\"reset\");\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"entities\", entities), db(\"equipment\", equipment), reset, duration, db(\"for\", playersFor));\r\n        }\r\n        boolean isReset = reset != null && reset.asBoolean();\r\n        for (PlayerTag player : playersFor) {\r\n            HashMap<UUID, EquipmentOverride> playersMap = overrides.computeIfAbsent(player.getUUID(), (k) -> new HashMap<>());\r\n            for (EntityTag entity : entities) {\r\n                if (entity.isGeneric()) {\r\n                    Debug.echoError(scriptEntry, \"Cannot equip generic entity \" + entity.identify() + \"!\");\r\n                    continue;\r\n                }\r\n                LivingEntity livingEntity = entity.getLivingEntity();\r\n                if (livingEntity == null) {\r\n                    Debug.echoError(scriptEntry, \"Cannot equip invalid/non-living entity \" + entity.identify() + \"!\");\r\n                    continue;\r\n                }\r\n                EquipmentOverride entityData;\r\n                if (isReset) {\r\n                    entityData = playersMap.remove(entity.getUUID());\r\n                    if (playersMap.isEmpty()) {\r\n                        overrides.remove(player.getUUID());\r\n                    }\r\n                }\r\n                else {\r\n                    entityData = playersMap.computeIfAbsent(entity.getUUID(), (k) -> new EquipmentOverride());\r\n                    entityData.copyFrom(equipment);\r\n                }\r\n                if (entityData != null) {\r\n                    if (entityData.cancelTask != null && !entityData.cancelTask.isCancelled()) {\r\n                        entityData.cancelTask.cancel();\r\n                        entityData.cancelTask = null;\r\n                    }\r\n                    if (duration != null && duration.getTicks() > 0) {\r\n                        entityData.cancelTask = new BukkitRunnable() {\r\n                            @Override\r\n                            public void run() {\r\n                                entityData.cancelTask = null;\r\n                                HashMap<UUID, EquipmentOverride> playersMap = overrides.get(player.getUUID());\r\n                                if (playersMap != null) {\r\n                                    if (playersMap.remove(entity.getUUID()) != null) {\r\n                                        if (playersMap.isEmpty()) {\r\n                                            overrides.remove(player.getUUID());\r\n                                        }\r\n                                        NMSHandler.packetHelper.resetEquipment(player.getPlayerEntity(), livingEntity);\r\n                                    }\r\n                                }\r\n                            }\r\n                        }.runTaskLater(Denizen.getInstance(), duration.getTicks());\r\n                    }\r\n                }\r\n                NMSHandler.packetHelper.resetEquipment(player.getPlayerEntity(), livingEntity);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/FakeInternalDataCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.DenizenCore;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport com.denizenscript.denizencore.objects.core.MapTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.Player;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.LockSupport;\n\npublic class FakeInternalDataCommand extends AbstractCommand {\n\n    // <--[language]\n    // @name Internal Entity Data\n    // @group Minecraft Logic\n    // @description\n    // Each entity in Minecraft has a set of data values that get sent to the client, with each data value being a number id -> value pair.\n    // Denizen allows direct control over that data, as it can be useful for things like setting values that would usually be blocked.\n    // Because this is such a direct control that's meant to impose less restriction, there's no limitations/verification on the values being set other than basic type checking.\n    // Note as well that as these are raw internal values, they are liable to change between minecraft version updates, especially the numeric IDs.\n    // For all possible internal entity data values and their respective ids, see <@link url https://github.com/DenizenScript/Denizen/blob/dev/v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/EntityDataNameMapper.java#L50>.\n    // Alternatively, you can use the number id directly instead of the names listed there.\n    // For a list of all entity data ids and their values, see <@link url https://wiki.vg/Entity_metadata>\n    // (note that it documents the values that eventually get sent to the client, so the input this expects might be slightly different in some cases).\n    // You can input the equivalent denizen objects to have them be auto-converted to the internal types.\n    // -->\n\n    public FakeInternalDataCommand() {\n        setName(\"fakeinternaldata\");\n        setSyntax(\"fakeinternaldata [entity:<entity>] [data:<map>|...] (for:<player>|...) (speed:<duration>)\");\n        setRequiredArguments(2, 4);\n        autoCompile();\n    }\n\n    // <--[command]\n    // @Name FakeInternalData\n    // @Syntax fakeinternaldata [entity:<entity>] [data:<map>|...] (for:<player>|...) (speed:<duration>)\n    // @Required 2\n    // @Maximum 4\n    // @Short Sends fake entity data updates, optionally animating them with sub-tick precision.\n    // @Group entity\n    //\n    // @Description\n    // Sends fake internal entity data updates, optionally sending multiple over time.\n    // This supports sub-tick precision, allowing smooth/high FPS animations.\n    //\n    // The input to 'data:' is a list of <@link ObjectType MapTag>s, with each map being a frame to send; see <@link language Internal Entity Data> for more information.\n    //\n    // Optionally specify a list of players to fake the data for, defaults to the linked player.\n    //\n    // 'speed:' is the amount of time between each frame getting sent, supporting sub-tick delays.\n    // Note that this is the delay between each frame, regardless of their content (see examples).\n    //\n    // @Tags\n    // None\n    //\n    // @Usage\n    // Animates an item display entity's item for the linked player, and slowly scales it up.\n    // - fakeinternaldata entity:<[item_display]> data:[item=iron_ingot;scale=0.6,0.6,0.6]|[item=gold_ingot;scale=0.8,0.8,0.8]|[item=netherite_ingot;scale=1,1,1] speed:0.5s\n    //\n    // @Usage\n    // Changes an item display's item, then its scale a second later, then its item again another second later.\n    // - fakeinternaldata entity:<[item_display]> data:[item=stone]|[scale=2,2,2]|[item=waxed_weathered_cut_copper_slab] speed:1s\n    //\n    // @Usage\n    // Animates a rainbow glow on a display entity for all online players.\n    // - define color <color[red]>\n    // - repeat 256 from:0 as:hue:\n    //   - define frames:->:[glow_color=<[color].with_hue[<[hue]>].argb_integer>]\n    // - fakeinternaldata entity:<[display]> data:<[frames]> for:<server.online_players> speed:0.01s\n    // -->\n\n    public static void autoExecute(ScriptEntry scriptEntry,\n                                   @ArgName(\"entity\") @ArgPrefixed EntityTag inputEntity,\n                                   @ArgName(\"data\") @ArgPrefixed @ArgSubType(MapTag.class) List<MapTag> data,\n                                   @ArgName(\"for\") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> forPlayers,\n                                   @ArgName(\"speed\") @ArgPrefixed @ArgDefaultText(\"0s\") DurationTag speed) {\n        List<Player> sendTo;\n        if (forPlayers != null) {\n            sendTo = new ArrayList<>(forPlayers.size());\n            for (PlayerTag player : forPlayers) {\n                sendTo.add(player.getPlayerEntity());\n            }\n        }\n        else if (Utilities.entryHasPlayer(scriptEntry)) {\n            sendTo = List.of(Utilities.getEntryPlayer(scriptEntry).getPlayerEntity());\n        }\n        else {\n            throw new InvalidArgumentsRuntimeException(\"Must specify players to fake the internal data for.\");\n        }\n        Entity entity = inputEntity.getBukkitEntity();\n        if (data.size() == 1) {\n            NMSHandler.packetHelper.sendEntityDataPacket(sendTo, entity, NMSHandler.entityHelper.convertInternalEntityDataValues(entity, data.get(0)));\n            return;\n        }\n        List<List<Object>> frames = new ArrayList<>(data.size());\n        for (MapTag frame : data) {\n            frames.add(NMSHandler.entityHelper.convertInternalEntityDataValues(entity, frame));\n        }\n        long delayNanos = TimeUnit.MILLISECONDS.toNanos(speed.getMillis());\n        DenizenCore.runAsync(() -> {\n            long expectedTime = System.nanoTime();\n            for (List<Object> frame : frames) {\n                if (sendTo.isEmpty()) {\n                    break;\n                }\n                NMSHandler.packetHelper.sendEntityDataPacket(sendTo, entity, frame);\n                LockSupport.parkNanos(delayNanos + (expectedTime - System.nanoTime()));\n                expectedTime += delayNanos;\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/FeedCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.npc.traits.HungerTrait;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\npublic class FeedCommand extends AbstractCommand {\r\n\r\n    public FeedCommand() {\r\n        setName(\"feed\");\r\n        setSyntax(\"feed (<entity>) (amount:<#>) (saturation:<#.#>)\");\r\n        setRequiredArguments(0, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Feed\r\n    // @Syntax feed (<entity>) (amount:<#>) (saturation:<#.#>)\r\n    // @Required 0\r\n    // @Maximum 3\r\n    // @Short Feed the player or npc.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Feeds the player or npc specified.\r\n    //\r\n    // By default targets the player attached to the script queue and feeds a full amount.\r\n    //\r\n    // Accepts the 'amount:' argument, which is in half bar increments, up to a total of 20 food points.\r\n    // The amount may be negative, to cause hunger instead of satiating it.\r\n    //\r\n    // You can optionally also specify an amount to change the saturation by.\r\n    // By default, the saturation change will be the same as the food level change.\r\n    // This is also up to a total of 20 points. This value may also be negative.\r\n    //\r\n    // Also accepts the 'target:<entity>' argument to specify the entity which will be fed the amount.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.food_level>\r\n    // <PlayerTag.formatted_food_level>\r\n    // <PlayerTag.saturation>\r\n    //\r\n    // @Usage\r\n    // Use to feed the player for 5 foodpoints (or 2.5 bars).\r\n    // - feed amount:5\r\n    //\r\n    // @Usage\r\n    // Use to feed an NPC for 10 foodpoints (or 5 bars).\r\n    // - feed <npc> amount:10\r\n    //\r\n    // @Usage\r\n    // Use to feed a player from a definition fully without refilling saturation.\r\n    // - feed <[player]> saturation:0\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matchesInteger()\r\n                    && arg.matchesPrefix(\"amount\", \"amt\", \"quantity\", \"qty\", \"a\", \"q\")\r\n                    && !scriptEntry.hasObject(\"amount\")) {\r\n                scriptEntry.addObject(\"amount\", arg.asElement());\r\n            }\r\n            else if (arg.matchesInteger()\r\n                    && arg.matchesPrefix(\"saturation\", \"sat\", \"s\")\r\n                    && !scriptEntry.hasObject(\"saturation\")) {\r\n                scriptEntry.addObject(\"saturation\", arg.asElement());\r\n            }\r\n            else if (arg.matchesArgumentType(PlayerTag.class)\r\n                    && !scriptEntry.hasObject(\"targetplayer\")\r\n                    && !scriptEntry.hasObject(\"targetnpc\")) {\r\n                scriptEntry.addObject(\"targetplayer\", arg.asType(PlayerTag.class));\r\n            }\r\n            else if (Depends.citizens != null && arg.matchesArgumentType(NPCTag.class)\r\n                    && !scriptEntry.hasObject(\"targetplayer\")\r\n                    && !scriptEntry.hasObject(\"targetnpc\")) {\r\n                scriptEntry.addObject(\"targetnpc\", arg.asType(NPCTag.class));\r\n            }\r\n\r\n            // Backwards compatibility\r\n            else if (arg.matches(\"npc\")\r\n                    && !scriptEntry.hasObject(\"targetplayer\")\r\n                    && !scriptEntry.hasObject(\"targetnpc\")\r\n                    && Utilities.entryHasNPC(scriptEntry)) {\r\n                scriptEntry.addObject(\"targetnpc\", Utilities.getEntryNPC(scriptEntry));\r\n            }\r\n            else if (arg.matches(\"player\")\r\n                    && !scriptEntry.hasObject(\"targetplayer\")\r\n                    && !scriptEntry.hasObject(\"targetnpc\")\r\n                    && Utilities.entryHasPlayer(scriptEntry)) {\r\n                scriptEntry.addObject(\"targetplayer\", Utilities.getEntryPlayer(scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n\r\n        }\r\n        if (!scriptEntry.hasObject(\"targetplayer\") &&\r\n                !scriptEntry.hasObject(\"targetnpc\")) {\r\n            if (Utilities.entryHasPlayer(scriptEntry)) {\r\n                scriptEntry.addObject(\"targetplayer\", Utilities.getEntryPlayer(scriptEntry));\r\n            }\r\n            else if (Utilities.entryHasNPC(scriptEntry)) {\r\n                scriptEntry.addObject(\"targetnpc\", Utilities.getEntryNPC(scriptEntry));\r\n            }\r\n            else {\r\n                throw new InvalidArgumentsException(\"Must specify a player!\");\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"amount\", new ElementTag(20));\r\n        scriptEntry.defaultObject(\"saturation\", scriptEntry.getObject(\"amount\"));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        PlayerTag player = scriptEntry.getObjectTag(\"targetplayer\");\r\n        NPCTag npc = scriptEntry.getObjectTag(\"targetnpc\");\r\n        ElementTag amount = scriptEntry.getElement(\"amount\");\r\n        ElementTag saturation = scriptEntry.getElement(\"saturation\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), player, npc, amount, saturation);\r\n        }\r\n        if (npc != null) {\r\n            if (!npc.getCitizen().hasTrait(HungerTrait.class)) {\r\n                Debug.echoError(scriptEntry, \"This NPC does not have the HungerTrait enabled! Use /trait hunger\");\r\n                return;\r\n            }\r\n            npc.getCitizen().getOrAddTrait(HungerTrait.class).feed(amount.asInt());\r\n        }\r\n        else {\r\n            int result = Math.max(0, Math.min(20, player.getPlayerEntity().getFoodLevel() + amount.asInt()));\r\n            player.getPlayerEntity().setFoodLevel(result);\r\n            float satResult = Math.max(0, Math.min(20, player.getPlayerEntity().getSaturation() + saturation.asFloat()));\r\n            player.getPlayerEntity().setSaturation(satResult);\r\n            Debug.echoDebug(scriptEntry, \"Player food level updated to \" + result + \" food and \" +  satResult + \" saturation.\");\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/FlyCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.Conversion;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.entity.Position;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class FlyCommand extends AbstractCommand {\r\n\r\n    public FlyCommand() {\r\n        setName(\"fly\");\r\n        setSyntax(\"fly (cancel) [<entity>|...] (controller:<player>) (origin:<location>) (destinations:<location>|...) (speed:<#.#>) (rotationthreshold:<#.#>)\");\r\n        setRequiredArguments(1, 7);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Fly\r\n    // @Syntax fly (cancel) [<entity>|...] (controller:<player>) (origin:<location>) (destinations:<location>|...) (speed:<#.#>) (rotationthreshold:<#.#>)\r\n    // @Required 1\r\n    // @Maximum 7\r\n    // @Short Make an entity fly where its controller is looking or fly to waypoints.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Make an entity fly where its controller is looking or fly to waypoints.\r\n    // This is particularly useful as a way to make flying entities rideable (or make a non-flying entity start flying).\r\n    //\r\n    // Optionally specify a list of \"destinations\" to make it move through a series of checkpoints (disabling the \"controller\" functionality).\r\n    //\r\n    // Optionally specify an \"origin\" location where the entities should teleport to at the start.\r\n    //\r\n    // Optionally specify the \"speed\" argument to set how fast the entity should fly.\r\n    //\r\n    // Optionally specify the \"rotationthreshold\" to set the minimum threshold (in degrees) before the entity should forcibly rotate to the correct direction.\r\n    //\r\n    // Use the \"cancel\" argument to stop an existing flight.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.can_fly>\r\n    // <PlayerTag.fly_speed>\r\n    // <PlayerTag.is_flying>\r\n    //\r\n    // @Usage\r\n    // Use to make a player ride+fly a newly spawned dragon.\r\n    // - fly <player>|ender_dragon\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"cancel\")\r\n                    && arg.matches(\"cancel\")) {\r\n                scriptEntry.addObject(\"cancel\", \"\");\r\n            }\r\n            else if (!scriptEntry.hasObject(\"destinations\")\r\n                    && arg.matchesPrefix(\"destination\", \"destinations\", \"d\")) {\r\n                scriptEntry.addObject(\"destinations\", arg.asType(ListTag.class).filter(LocationTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"controller\")\r\n                    && arg.matchesArgumentType(PlayerTag.class)\r\n                    && arg.matchesPrefix(\"controller\", \"c\")) {\r\n                // Check if it matches a PlayerTag, but save it as a EntityTag\r\n                scriptEntry.addObject(\"controller\", arg.asType(EntityTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"origin\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"origin\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"rotation_threshold\")\r\n                    && arg.matchesPrefix(\"rotationthreshold\", \"rotation\", \"r\")\r\n                    && arg.matchesFloat()) {\r\n                scriptEntry.addObject(\"rotation_threshold\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"speed\")\r\n                    && arg.matchesFloat()) {\r\n                scriptEntry.addObject(\"speed\", arg.asElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"origin\", Utilities.entryDefaultLocation(scriptEntry, true));\r\n        scriptEntry.defaultObject(\"speed\", new ElementTag(1.2));\r\n        scriptEntry.defaultObject(\"rotation_threshold\", new ElementTag(15));\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            throw new InvalidArgumentsException(\"Must specify entity/entities!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"origin\")) {\r\n            throw new InvalidArgumentsException(\"Must specify an origin!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        LocationTag origin = scriptEntry.getObjectTag(\"origin\");\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        final List<LocationTag> destinations = scriptEntry.hasObject(\"destinations\") ? (List<LocationTag>) scriptEntry.getObject(\"destinations\") : new ArrayList<>();\r\n        // Set freeflight to true only if there are no destinations\r\n        final boolean freeflight = destinations.size() < 1;\r\n        EntityTag controller = scriptEntry.getObjectTag(\"controller\");\r\n        // If freeflight is on, we need to do some checks\r\n        if (freeflight) {\r\n            // If no controller was set, we need someone to control the\r\n            // flying entities, so try to find a player in the entity list\r\n            if (controller == null) {\r\n                for (EntityTag entity : entities) {\r\n                    if (entity.isPlayer()) {\r\n                        // If this player will be a rider on something, and will not\r\n                        // be at the bottom ridden by the other entities, set it as\r\n                        // the controller\r\n                        if (entities.get(entities.size() - 1) != entity) {\r\n                            controller = entity;\r\n                            if (scriptEntry.dbCallShouldDebug()) {\r\n                                Debug.echoDebug(scriptEntry, \"Flight control defaulting to \" + controller);\r\n                            }\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n                // If the controller is still null, we cannot continue\r\n                if (controller == null) {\r\n                    if (scriptEntry.dbCallShouldDebug()) {\r\n                        Debug.echoDebug(scriptEntry, \"There is no one to control the flight's path!\");\r\n                    }\r\n                    return;\r\n                }\r\n            }\r\n            // Else, if the controller was set, we need to make sure\r\n            // it is among the flying entities, and add it if it is not\r\n            else {\r\n                boolean found = false;\r\n                for (EntityTag entity : entities) {\r\n                    if (entity.identify().equals(controller.identify())) {\r\n                        found = true;\r\n                        break;\r\n                    }\r\n                }\r\n                // Add the controller to the entity list\r\n                if (!found) {\r\n                    if (scriptEntry.dbCallShouldDebug()) {\r\n                        Debug.echoDebug(scriptEntry, \"Adding controller \" + controller + \" to flying entities.\");\r\n                    }\r\n                    entities.add(0, controller);\r\n                }\r\n            }\r\n        }\r\n        final double speed = ((ElementTag) scriptEntry.getObject(\"speed\")).asDouble();\r\n        final float rotationThreshold = ((ElementTag) scriptEntry.getObject(\"rotation_threshold\")).asFloat();\r\n        boolean cancel = scriptEntry.hasObject(\"cancel\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), (cancel ? db(\"cancel\", true) : \"\"), origin, db(\"entities\", entities),\r\n                    db(\"speed\", speed), db(\"rotation threshold degrees\", rotationThreshold), (freeflight ? controller : db(\"destinations\", destinations)));\r\n        }\r\n        if (!cancel) {\r\n            for (EntityTag entity : entities) {\r\n                entity.spawnAt(origin);\r\n            }\r\n            Position.mount(Conversion.convertEntities(entities));\r\n        }\r\n        else {\r\n            Position.dismount(Conversion.convertEntities(entities));\r\n            return;\r\n        }\r\n        final Entity entity = entities.get(entities.size() - 1).getBukkitEntity();\r\n        final LivingEntity finalController = controller != null ? controller.getLivingEntity() : null;\r\n        BukkitRunnable task = new BukkitRunnable() {\r\n            Location location = null;\r\n            Boolean flying = true;\r\n            public void run() {\r\n                if (freeflight) {\r\n                    // If freeflight is on, and the flying entity\r\n                    // is ridden by another entity, let it keep\r\n                    // flying where the controller is looking\r\n                    if (!entity.isEmpty() && finalController.isInsideVehicle()) {\r\n                        location = finalController.getEyeLocation().add(finalController.getEyeLocation().getDirection().multiply(30));\r\n                    }\r\n                    else {\r\n                        flying = false;\r\n                    }\r\n                }\r\n                else {\r\n                    // If freelight is not on, keep flying only as long\r\n                    // as there are destinations left\r\n                    if (destinations.size() > 0) {\r\n                        location = destinations.get(0);\r\n                    }\r\n                    else {\r\n                        flying = false;\r\n                    }\r\n                }\r\n                if (flying && entity.isValid()) {\r\n                    // To avoid excessive turbulence, only have the entity rotate\r\n                    // when it really needs to\r\n                    if (!NMSHandler.entityHelper.isFacingLocation(entity, location, rotationThreshold)) {\r\n                        NMSHandler.entityHelper.faceLocation(entity, location);\r\n                    }\r\n                    Vector v1 = entity.getLocation().toVector();\r\n                    Vector v2 = location.toVector();\r\n                    Vector v3 = v2.clone().subtract(v1).normalize().multiply(speed);\r\n                    entity.setVelocity(v3);\r\n                    // If freeflight is off, check if the entity has reached its\r\n                    // destination, and remove the destination if that happens\r\n                    // to be the case\r\n                    if (!freeflight) {\r\n                        if (Math.abs(v2.getX() - v1.getX()) < 2 && Math.abs(v2.getY() - v1.getY()) < 2\r\n                                && Math.abs(v2.getZ() - v1.getZ()) < 2) {\r\n                            destinations.remove(0);\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    flying = false;\r\n                    this.cancel();\r\n                }\r\n            }\r\n        };\r\n        task.runTaskTimer(Denizen.getInstance(), 0, 3);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/FollowCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\npublic class FollowCommand extends AbstractCommand {\r\n\r\n    public FollowCommand() {\r\n        setName(\"follow\");\r\n        setSyntax(\"follow (followers:<entity>|...) (stop/target:<entity>) (lead:<#.#>) (max:<#.#>) (speed:<#.#>) (allow_wander) (no_teleport)\");\r\n        setRequiredArguments(0, 7);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Follow\r\n    // @Syntax follow (followers:<entity>|...) (stop/target:<entity>) (lead:<#.#>) (max:<#.#>) (speed:<#.#>) (allow_wander) (no_teleport)\r\n    // @Required 0\r\n    // @Maximum 7\r\n    // @Short Causes a list of entities to follow a target.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Causes a list of entities to follow a target.\r\n    //\r\n    // Specify the list of followers or just one. If no follower is specified, will use the linked NPC.\r\n    //\r\n    // Specify either the target to follow, or 'stop'. If no target is specified, will use the linked player.\r\n    //\r\n    // Use 'speed' to set the movement speed multiplier.\r\n    // Use 'lead' to set how far away the follower will remain from the target (ie, it won't try to get closer than the 'lead' distance).\r\n    // Use 'max' to set the maximum distance between the follower and the target before the follower will automatically start teleporting to keep up.\r\n    // Use 'no_teleport' to disable teleporting when the entity is out of range (instead, the entity will simply give up).\r\n    // Use 'allow_wander' to allow the entity to wander randomly.\r\n    //\r\n    // The 'max' and 'allow_wander' arguments can only be used on non-NPC entities.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.navigator.target_entity> returns the entity the npc is following.\r\n    //\r\n    // @Usage\r\n    // To make an NPC follow the player in an interact script.\r\n    // - follow\r\n    //\r\n    // @Usage\r\n    // To make an NPC stop following.\r\n    // - follow stop\r\n    //\r\n    // @Usage\r\n    // To explicitly make an NPC follow the player.\r\n    // - follow followers:<npc> target:<player>\r\n    //\r\n    // @Usage\r\n    // To make an NPC follow the player, slowly and at distance.\r\n    // - follow speed:0.7 lead:10\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"stop\") &&\r\n                    arg.matches(\"stop\")) {\r\n                scriptEntry.addObject(\"stop\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"lead\") &&\r\n                    arg.matchesFloat() &&\r\n                    arg.matchesPrefix(\"l\", \"lead\")) {\r\n                scriptEntry.addObject(\"lead\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"max\") &&\r\n                    arg.matchesFloat() &&\r\n                    arg.matchesPrefix(\"max\")) {\r\n                scriptEntry.addObject(\"max\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"allow_wander\") &&\r\n                    arg.matches(\"allow_wander\")) {\r\n                scriptEntry.addObject(\"allow_wander\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"no_teleport\") &&\r\n                    arg.matches(\"no_teleport\")) {\r\n                scriptEntry.addObject(\"no_teleport\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"speed\") &&\r\n                    arg.matchesFloat() &&\r\n                    arg.matchesPrefix(\"s\", \"speed\")) {\r\n                scriptEntry.addObject(\"speed\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\") &&\r\n                    arg.matchesPrefix(\"followers\", \"follower\") &&\r\n                    arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"target\") &&\r\n                    arg.matchesArgumentType(EntityTag.class)) {\r\n                scriptEntry.addObject(\"target\", arg.asType(EntityTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"target\")) {\r\n            if (Utilities.entryHasPlayer(scriptEntry)) {\r\n                scriptEntry.addObject(\"target\", Utilities.getEntryPlayer(scriptEntry).getDenizenEntity());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"stop\")) {\r\n                throw new InvalidArgumentsException(\"This command requires a linked player!\");\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            if (!Utilities.entryHasNPC(scriptEntry)) {\r\n                throw new InvalidArgumentsException(\"This command requires a linked NPC!\");\r\n            }\r\n            else {\r\n                scriptEntry.addObject(\"entities\",\r\n                        new ListTag(Utilities.getEntryNPC(scriptEntry)));\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"stop\", new ElementTag(false)).defaultObject(\"allow_wander\", new ElementTag(false));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag stop = scriptEntry.getElement(\"stop\");\r\n        ElementTag lead = scriptEntry.getElement(\"lead\");\r\n        ElementTag maxRange = scriptEntry.getElement(\"max\");\r\n        ElementTag allowWander = scriptEntry.getElement(\"allow_wander\");\r\n        ElementTag speed = scriptEntry.getElement(\"speed\");\r\n        ElementTag noTeleport = scriptEntry.getElement(\"no_teleport\");\r\n        ListTag entities = scriptEntry.getObjectTag(\"entities\");\r\n        EntityTag target = scriptEntry.getObjectTag(\"target\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), Utilities.getEntryPlayer(scriptEntry), (!stop.asBoolean() ? db(\"Action\", \"FOLLOW\") : db(\"Action\", \"STOP\")),\r\n                            lead, noTeleport, maxRange, allowWander, entities, target);\r\n        }\r\n        for (EntityTag entity : entities.filter(EntityTag.class, scriptEntry)) {\r\n            if (entity.isCitizensNPC()) {\r\n                NPCTag npc = entity.getDenizenNPC();\r\n                if (lead != null) {\r\n                    npc.getNavigator().getLocalParameters().distanceMargin(lead.asDouble());\r\n                }\r\n                if (speed != null) {\r\n                    npc.getNavigator().getLocalParameters().speedModifier(speed.asFloat());\r\n                }\r\n                if (noTeleport != null && noTeleport.asBoolean()) {\r\n                    npc.getNavigator().getLocalParameters().stuckAction(null);\r\n                }\r\n                if (stop.asBoolean()) {\r\n                    npc.getNavigator().cancelNavigation();\r\n                }\r\n                else {\r\n                    npc.getNavigator().setTarget(target.getBukkitEntity(), false);\r\n                }\r\n            }\r\n            else {\r\n                if (stop.asBoolean()) {\r\n                    NMSHandler.entityHelper.stopFollowing(entity.getBukkitEntity());\r\n                }\r\n                else {\r\n                    NMSHandler.entityHelper.follow(target.getBukkitEntity(), entity.getBukkitEntity(),\r\n                            speed != null ? speed.asDouble() : 0.3, lead != null ? lead.asDouble() : 5,\r\n                            maxRange != null ? maxRange.asDouble() : 8, allowWander.asBoolean(), noTeleport == null || !noTeleport.asBoolean());\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/GlowCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.entity.EntityMetadataCommandHelper;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport org.bukkit.entity.Entity;\r\n\r\nimport java.util.List;\r\n\r\npublic class GlowCommand extends AbstractCommand {\r\n\r\n    public static final EntityMetadataCommandHelper helper = new EntityMetadataCommandHelper(Entity::isGlowing, GlowCommand::setGlowing);\r\n\r\n    public GlowCommand() {\r\n        setName(\"glow\");\r\n        setSyntax(\"glow (<entity>|...) ({true}/false/toggle/reset) (for:<player>|...)\");\r\n        setRequiredArguments(0, 3);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Glow\r\n    // @Syntax glow (<entity>|...) ({true}/false/toggle/reset) (for:<player>|...)\r\n    // @Required 0\r\n    // @Maximum 3\r\n    // @Short Sets whether an NPC or entity is glowing.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Sets whether the specified entities glow, defaults to the linked player/NPC if none were specified.\r\n    //\r\n    // Optionally specify 'for:' with a list of players to fake the entities' glow state for these players.\r\n    // When using 'toggle' with the 'for:' argument, the glow state will be toggled for each player separately.\r\n    // If unspecified, will be set globally.\r\n    // 'for:' players remain tracked even when offline/reconnecting, but are forgotten after server restart.\r\n    //\r\n    // When not using 'for:', the glow is global / on the real entity, which will persist in that entity's data until changed.\r\n    //\r\n    // To reset an entity's fake glow use the 'reset' state.\r\n    // A reset is global by default, use the 'for:' argument to reset specific players.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.glowing>\r\n    //\r\n    // @Usage\r\n    // Use to make the linked player (or NPC, if there isn't one) glow.\r\n    // - glow\r\n    //\r\n    // @Usage\r\n    // Use to toggle whether the linked NPC is glowing.\r\n    // - glow <npc> toggle\r\n    //\r\n    // @Usage\r\n    // Use to make an entity glow for specific players, without changing the way other players see it.\r\n    // - glow <[entity]> for:<[player1]>|<[player2]>\r\n    //\r\n    // @Usage\r\n    // Use to reset an entity's fake glow state for the linked player.\r\n    // - glow <[entity]> reset for:<player>\r\n    // -->\r\n\r\n    public static void setGlowing(EntityTag entity, boolean glowing) {\r\n        if (entity.isCitizensNPC()) {\r\n            entity.getDenizenNPC().getCitizen().data().setPersistent(NPC.Metadata.GLOWING, glowing);\r\n        }\r\n        else {\r\n            entity.getBukkitEntity().setGlowing(glowing);\r\n        }\r\n    }\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"target\") @ArgLinear @ArgDefaultNull @ArgSubType(EntityTag.class) List<EntityTag> targets,\r\n                                   @ArgName(\"state\") @ArgDefaultText(\"true\") EntityMetadataCommandHelper.Action action,\r\n                                   @ArgName(\"for\") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> forPlayers) {\r\n        helper.execute(scriptEntry, targets, action, forPlayers);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/HeadCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport net.citizensnpcs.api.trait.trait.Equipment;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.meta.ItemMeta;\r\nimport org.bukkit.inventory.meta.SkullMeta;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class HeadCommand extends AbstractCommand {\r\n\r\n    public HeadCommand() {\r\n        setName(\"head\");\r\n        setSyntax(\"head (<entity>|...) [skin:<player_name>]\");\r\n        setRequiredArguments(1, 2);\r\n        isProcedural = false;\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"material\")\r\n                    && arg.matchesArgumentType(MaterialTag.class)\r\n                    && !arg.matchesPrefix(\"skin\", \"s\")) {\r\n                scriptEntry.addObject(\"material\", arg.asType(MaterialTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"skin\")\r\n                    && (arg.matchesPrefix(\"skin\", \"s\"))) {\r\n                scriptEntry.addObject(\"skin\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matches(\"player\")\r\n                    && Utilities.entryHasPlayer(scriptEntry)) {\r\n                scriptEntry.addObject(\"entities\", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry).getDenizenEntity()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"entities\", Utilities.entryDefaultEntityList(scriptEntry, false));\r\n        if (!scriptEntry.hasObject(\"skin\") && !scriptEntry.hasObject(\"material\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a skin or material!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        BukkitImplDeprecations.headCommand.warn(scriptEntry);\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        ElementTag skin = scriptEntry.getElement(\"skin\");\r\n        MaterialTag material = scriptEntry.getObjectTag(\"material\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"entities\", entities), skin, material);\r\n        }\r\n        ItemStack item = null;\r\n        if (skin != null) {\r\n            item = new ItemStack(Material.PLAYER_HEAD);\r\n            ItemMeta itemMeta = item.getItemMeta();\r\n            ((SkullMeta) itemMeta).setOwner(skin.asString());\r\n            item.setItemMeta(itemMeta);\r\n        }\r\n        else if (material != null) {\r\n            item = new ItemStack(material.getMaterial());\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (entity.isCitizensNPC()) {\r\n                if (!entity.getDenizenNPC().getCitizen().hasTrait(Equipment.class)) {\r\n                    entity.getDenizenNPC().getCitizen().addTrait(Equipment.class);\r\n                }\r\n                Equipment trait = entity.getDenizenNPC().getCitizen().getOrAddTrait(Equipment.class);\r\n                trait.set(Equipment.EquipmentSlot.HELMET, item);\r\n            }\r\n            else if (entity.isPlayer()) {\r\n                entity.getPlayer().getInventory().setHelmet(item);\r\n            }\r\n            else {\r\n                if (entity.isLivingEntity() && entity.getLivingEntity().getEquipment() != null) {\r\n                    entity.getLivingEntity().getEquipment().setHelmet(item);\r\n                }\r\n                else {\r\n                    Debug.echoError(scriptEntry, entity.identify() + \" is not a living entity!\");\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/HealCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class HealCommand extends AbstractCommand {\r\n\r\n    public HealCommand() {\r\n        setName(\"heal\");\r\n        setSyntax(\"heal (<#.#>) ({player}/<entity>|...)\");\r\n        setRequiredArguments(0, 2);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Heal\r\n    // @Syntax heal (<#.#>) ({player}/<entity>|...)\r\n    // @Required 0\r\n    // @Maximum 2\r\n    // @Short Heals the player or list of entities.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // This command heals a player, list of players, entity or list of entities.\r\n    //\r\n    // If no amount is specified it will heal the specified player(s)/entity(s) fully.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.health>\r\n    // <EntityTag.health_max>\r\n    //\r\n    // @Usage\r\n    // Use to fully heal a player.\r\n    // - heal\r\n    //\r\n    // @Usage\r\n    // Use to heal a player 5 hearts.\r\n    // - heal 10\r\n    //\r\n    // @Usage\r\n    // Use to heal a defined player fully.\r\n    // - heal <[someplayer]>\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n\r\n        boolean specified_targets = false;\r\n\r\n        for (Argument arg : scriptEntry) {\r\n\r\n            if (!scriptEntry.hasObject(\"amount\")\r\n                    && arg.matchesFloat()) {\r\n                scriptEntry.addObject(\"amount\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentType(ListTag.class)) {\r\n                // Entity arg\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n                specified_targets = true;\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentType(EntityTag.class)) {\r\n                // Entity arg\r\n                scriptEntry.addObject(\"entities\", Collections.singletonList(arg.asType(EntityTag.class)));\r\n                specified_targets = true;\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n\r\n        if (!scriptEntry.hasObject(\"amount\")) {\r\n            scriptEntry.addObject(\"amount\", new ElementTag(-1));\r\n        }\r\n\r\n        if (!specified_targets) {\r\n            List<EntityTag> entities = new ArrayList<>();\r\n            if (Utilities.getEntryPlayer(scriptEntry) != null) {\r\n                entities.add(Utilities.getEntryPlayer(scriptEntry).getDenizenEntity());\r\n            }\r\n            else if (Utilities.getEntryNPC(scriptEntry) != null) {\r\n                entities.add(Utilities.getEntryNPC(scriptEntry).getDenizenEntity());\r\n            }\r\n            else {\r\n                throw new InvalidArgumentsException(\"No valid target entities found.\");\r\n            }\r\n            scriptEntry.addObject(\"entities\", entities);\r\n        }\r\n\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        if (entities == null) {\r\n            return;\r\n        }\r\n        ElementTag amountelement = scriptEntry.getElement(\"amount\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), amountelement, db(\"entities\", entities));\r\n        }\r\n        if (amountelement.asDouble() == -1) {\r\n            for (EntityTag entity : entities) {\r\n                if (entity.isLivingEntity()) {\r\n                    entity.getLivingEntity().setHealth(entity.getLivingEntity().getMaxHealth());\r\n                }\r\n            }\r\n        }\r\n        else {\r\n            double amount = amountelement.asDouble();\r\n            for (EntityTag entity : entities) {\r\n                if (entity.getLivingEntity().getHealth() + amount < entity.getLivingEntity().getMaxHealth()) {\r\n                    entity.getLivingEntity().setHealth(entity.getLivingEntity().getHealth() + amount);\r\n                }\r\n                else {\r\n                    entity.getLivingEntity().setHealth(entity.getLivingEntity().getMaxHealth());\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/HealthCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\n\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.npc.traits.HealthTrait;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\nimport com.denizenscript.denizencore.objects.Argument;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\n\nimport java.util.Collections;\nimport java.util.List;\n\npublic class HealthCommand extends AbstractCommand {\n\n    public HealthCommand() {\n        setName(\"health\");\n        setSyntax(\"health ({npc}/<entity>|...) [<#>] (state:{true}/false/toggle) (heal)\");\n        setRequiredArguments(1, 4);\n        isProcedural = false;\n    }\n\n    // <--[command]\n    // @Name Health\n    // @Syntax health ({npc}/<entity>|...) [<#>] (state:{true}/false/toggle) (heal)\n    // @Required 1\n    // @Maximum 4\n    // @Short Changes the target's maximum health.\n    // @Group entity\n    //\n    // @Description\n    // Use this command to modify an entity's maximum health.\n    //\n    // If the target is an NPC, you can use the 'state' argument to enable, disable, or toggle the Health trait\n    // (which is used to track the NPC's health, and handle actions such as 'on death').\n    // The Health trait will be enabled by default.\n    //\n    // By default, this command will target the linked NPC but can be set to target any other living entity, such as a player or mob.\n    //\n    // Optionally specify the 'heal' argument to automatically heal the entity to the new health value.\n    // If not specified, the entity's health will remain wherever it was\n    // (so for example a change from 20 max to 50 max will leave an entity with 20 health out of 50 max).\n    //\n    // Additionally, you may input a list of entities, each one will calculate the effects explained above.\n    //\n    // @Tags\n    // <EntityTag.health>\n    // <EntityTag.health_max>\n    // <NPCTag.has_trait[health]>\n    //\n    // @Usage\n    // Use to set the NPC's maximum health to 50.\n    // - health 50\n    //\n    // @Usage\n    // Use to disable tracking of health value on the NPC.\n    // - health state:false\n    //\n    // @Usage\n    // Use to change a player's health limit and current health both to 50.\n    // - health <player> 50 heal\n    //\n    // @Usage\n    // Use to change a list of entities' health limits all to 50.\n    // - health <player.location.find.living_entities.within[10]> 50\n    // -->\n\n    @Override\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\n        for (Argument arg : scriptEntry) {\n            if (!scriptEntry.hasObject(\"target\")\n                    && arg.matches(\"player\")) {\n                if (!Utilities.entryHasPlayer(scriptEntry)) {\n                    throw new InvalidArgumentsException(\"No player attached!\");\n                }\n                scriptEntry.addObject(\"target\", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry).getDenizenEntity()));\n            }\n            else if (!scriptEntry.hasObject(\"quantity\")\n                    && arg.matchesFloat()) {\n                scriptEntry.addObject(\"quantity\", arg.asElement());\n            }\n            else if (!scriptEntry.hasObject(\"target\")\n                    && arg.matchesArgumentList(EntityTag.class)) {\n                scriptEntry.addObject(\"target\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\n            }\n            else if (!scriptEntry.hasObject(\"action\")\n                    && arg.matchesPrefix(\"state\")) {\n                scriptEntry.addObject(\"action\", arg.asElement());\n            }\n            else if (!scriptEntry.hasObject(\"heal\")\n                    && arg.matches(\"heal\")) {\n                scriptEntry.addObject(\"heal\", new ElementTag(true));\n            }\n            else {\n                arg.reportUnhandled();\n            }\n        }\n        if (!scriptEntry.hasObject(\"quantity\") && !scriptEntry.hasObject(\"action\")) {\n            throw new InvalidArgumentsException(\"Must specify a quantity!\");\n        }\n        if (!scriptEntry.hasObject(\"target\")) {\n            if (!Utilities.entryHasNPC(scriptEntry)) {\n                throw new InvalidArgumentsException(\"Missing NPC!\");\n            }\n            scriptEntry.addObject(\"target\", Collections.singletonList(Utilities.getEntryNPC(scriptEntry).getDenizenEntity()));\n        }\n        scriptEntry.defaultObject(\"heal\", new ElementTag(false));\n    }\n\n    @Override\n    public void execute(ScriptEntry scriptEntry) {\n        ElementTag quantity = scriptEntry.getElement(\"quantity\");\n        ElementTag action = scriptEntry.getElement(\"action\");\n        ElementTag heal = scriptEntry.getElement(\"heal\");\n        List<EntityTag> targets = (List<EntityTag>) scriptEntry.getObject(\"target\");\n        if (scriptEntry.dbCallShouldDebug()) {\n            Debug.report(scriptEntry, getName(), quantity, action, heal, db(\"target\", targets));\n        }\n        if (quantity == null && action == null) {\n            Debug.echoError(scriptEntry, \"Null quantity!\");\n        }\n        if (action == null) {\n            action = new ElementTag(true);\n        }\n        for (EntityTag target : targets) {\n            if (target.isCitizensNPC()) {\n                if (action.asString().equalsIgnoreCase(\"true\")) {\n                    target.getDenizenNPC().getCitizen().addTrait(HealthTrait.class);\n                }\n                else if (action.asString().equalsIgnoreCase(\"false\")) {\n                    target.getDenizenNPC().getCitizen().removeTrait(HealthTrait.class);\n                }\n                else if (target.getDenizenNPC().getCitizen().hasTrait(HealthTrait.class)) {\n                    target.getDenizenNPC().getCitizen().removeTrait(HealthTrait.class);\n                }\n                else {\n                    target.getDenizenNPC().getCitizen().addTrait(HealthTrait.class);\n                }\n            }\n            if (quantity != null) {\n                if (target.isCitizensNPC()) {\n                    if (target.getDenizenNPC().getCitizen().hasTrait(HealthTrait.class)) {\n                        HealthTrait trait = target.getDenizenNPC().getCitizen().getOrAddTrait(HealthTrait.class);\n                        trait.setMaxhealth(quantity.asInt());\n                        if (heal.asBoolean()) {\n                            trait.setHealth(quantity.asDouble());\n                        }\n                    }\n                    else {\n                        Debug.echoError(scriptEntry, \"NPC doesn't have health trait!\");\n                    }\n                }\n                else if (target.isLivingEntity()) {\n                    target.getLivingEntity().setMaxHealth(quantity.asDouble());\n                    if (heal.asBoolean()) {\n                        target.getLivingEntity().setHealth(quantity.asDouble());\n                    }\n                }\n                else {\n                    Debug.echoError(scriptEntry, \"Entity '\" + target.identify() + \"'is not alive!\");\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/HurtCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.Deprecations;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\n\r\nimport java.util.List;\r\n\r\npublic class HurtCommand extends AbstractCommand {\r\n\r\n    public HurtCommand() {\r\n        setName(\"hurt\");\r\n        setSyntax(\"hurt (<#.#>) ({player}/<entity>|...) (cause:<cause>) (source:<entity>/<location>)\");\r\n        setRequiredArguments(0, 4);\r\n        isProcedural = false;\r\n        addRemappedPrefixes(\"source\", \"s\");\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Hurt\r\n    // @Syntax hurt (<#.#>) ({player}/<entity>|...) (cause:<cause>) (source:<entity>/<location>)\r\n    // @Required 0\r\n    // @Maximum 4\r\n    // @Short Hurts the player or a list of entities.\r\n    // @Synonyms Damage,Injure\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Does damage to a list of entities, or to any single entity.\r\n    //\r\n    // If no entities are specified: if there is a linked player, the command targets that. If there is no linked\r\n    // player but there is a linked NPC, the command targets the NPC. If neither is available, the command will error.\r\n    //\r\n    // Does a specified amount of damage usually, but, if no damage is specified, does precisely 1HP worth of damage (half a heart).\r\n    //\r\n    // Optionally, specify (source:<entity>) to make the system treat that entity as the attacker.\r\n    // If using a block-type cause such as \"contact\", you *must* specify (source:<location>) to set that block location as the attacker. The block can be any block, even just \"<player.location>\" (as long as the player in inside a world).\r\n    //\r\n    // You may also specify a damage cause to fire a proper damage event with the given cause, only doing the damage if the event wasn't cancelled.\r\n    // Calculates the 'final damage' rather than using the raw damage input number. See <@link language damage cause> for damage causes.\r\n    //\r\n    // Using a valid 'cause' value is best when trying to replicate natural damage, excluding it is best when trying to force the raw damage through.\r\n    // Note that using invalid or impossible causes may lead to bugs\r\n    //\r\n    // @Tags\r\n    // <EntityTag.health>\r\n    // <EntityTag.last_damage.amount>\r\n    // <EntityTag.last_damage.cause>\r\n    // <EntityTag.last_damage.duration>\r\n    // <EntityTag.last_damage.max_duration>\r\n    //\r\n    // @Usage\r\n    // Use to hurt the player for 1 HP.\r\n    // - hurt\r\n    //\r\n    // @Usage\r\n    // Use to hurt the NPC for 5 HP.\r\n    // - hurt 5 <npc>\r\n    //\r\n    // @Usage\r\n    // Use to cause the player to hurt the NPC for all its health (if unarmored).\r\n    // - hurt <npc.health> <npc> cause:CUSTOM source:<player>\r\n    // -->\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"source\") @ArgPrefixed @ArgDefaultNull ObjectTag source,\r\n                                   @ArgName(\"cause\") @ArgPrefixed @ArgDefaultNull EntityDamageEvent.DamageCause cause,\r\n                                   @ArgName(\"amount\") @ArgLinear @ArgDefaultText(\"1\") ObjectTag amountObj,\r\n                                   @ArgName(\"entities\") @ArgLinear @ArgDefaultNull ObjectTag entitiesObj) {\r\n        if (!amountObj.asElement().isDouble()) { // Compensate for legacy out-of-order args\r\n            ObjectTag swapEntities = entitiesObj;\r\n            entitiesObj = amountObj;\r\n            if (swapEntities != null && swapEntities.asElement().isDouble()) {\r\n                Deprecations.outOfOrderArgs.warn(scriptEntry);\r\n                amountObj = swapEntities;\r\n            }\r\n            else {\r\n                amountObj = new ElementTag(1.0d);\r\n            }\r\n        }\r\n        List<EntityTag> entities = entitiesObj == null ? null : entitiesObj.asType(ListTag.class, scriptEntry.context).filter(EntityTag.class, scriptEntry.context);\r\n        if (entities == null) {\r\n            entities = Utilities.entryDefaultEntityList(scriptEntry, true);\r\n            if (entities == null) {\r\n                throw new InvalidArgumentsRuntimeException(\"No valid target entities found.\");\r\n            }\r\n        }\r\n        EntityTag sourceEntity = null;\r\n        LocationTag sourceLocation = null;\r\n        if (source != null) {\r\n            if (source.shouldBeType(LocationTag.class)) {\r\n                sourceLocation = source.asType(LocationTag.class, scriptEntry.context);\r\n            }\r\n            if (source.shouldBeType(EntityTag.class)) {\r\n                sourceEntity = source.asType(EntityTag.class, scriptEntry.context);\r\n            }\r\n        }\r\n        double amount = amountObj.asElement().asDouble();\r\n        for (EntityTag entity : entities) {\r\n            if (entity.getLivingEntity() == null) {\r\n                Debug.echoDebug(scriptEntry, entity + \" is not a living entity!\");\r\n                continue;\r\n            }\r\n            NMSHandler.entityHelper.damage(entity.getLivingEntity(), (float) amount, sourceEntity, sourceLocation, cause);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/InvisibleCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.npc.traits.InvisibleTrait;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.entity.EntityMetadataCommandHelper;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport org.bukkit.entity.ArmorStand;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.ItemFrame;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.potion.PotionEffectType;\r\n\r\nimport java.util.List;\r\n\r\npublic class InvisibleCommand extends AbstractCommand {\r\n\r\n    public static final EntityMetadataCommandHelper helper = new EntityMetadataCommandHelper(InvisibleCommand::isInvisible, InvisibleCommand::setInvisible);\r\n\r\n    public InvisibleCommand() {\r\n        setName(\"invisible\");\r\n        setSyntax(\"invisible (<entity>|...) ({true}/false/toggle/reset) (for:<player>|...)\");\r\n        setRequiredArguments(0, 3);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Invisible\r\n    // @Syntax invisible (<entity>|...) ({true}/false/toggle/reset) (for:<player>|...)\r\n    // @Required 0\r\n    // @Maximum 3\r\n    // @Short Sets whether an NPC or entity are invisible.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Sets whether the specified entities are invisible (equivalent to an invisibility potion), defaults to the linked player/NPC if none were specified.\r\n    // If an NPC was specified, the 'invisible' trait is applied.\r\n    //\r\n    // Optionally specify 'for:' with a list of players to fake the entities' visibility state for these players.\r\n    // When using 'toggle' with the 'for:' argument, the visibility state will be toggled for each player separately.\r\n    // If unspecified, will be set globally.\r\n    // 'for:' players remain tracked even when offline/reconnecting, but are forgotten after server restart.\r\n    // Note that using the 'for:' argument won't apply the 'invisible' trait to NPCs.\r\n    //\r\n    // When not using 'for:', the effect is global / on the real entity, which will persist in that entity's data until changed.\r\n    //\r\n    // To reset an entity's fake visibility use the 'reset' state.\r\n    // A reset is global by default, use the 'for:' argument to reset specific players.\r\n    //\r\n    // NPCs can't be made invisible if not added to the playerlist (the invisible trait adds the NPC to the playerlist when set).\r\n    // See <@link language invisible trait>\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to make the linked player (or NPC, if there isn't one) invisible.\r\n    // - invisible\r\n    //\r\n    // @Usage\r\n    // Use to make the linked NPC visible if previously invisible, and invisible if not.\r\n    // - invisible <npc> toggle\r\n    //\r\n    // @Usage\r\n    // Use to make an entity visible for specific players, without changing the way other players see it.\r\n    // - invisible <[entity]> false for:<[player1]>|<[player2]>\r\n    //\r\n    // @Usage\r\n    // Use to reset an entity's fake visibility state for the linked player.\r\n    // - invisible <[entity]> reset for:<player>\r\n    // -->\r\n\r\n    public static boolean isInvisible(Entity entity) {\r\n        if (EntityTag.isCitizensNPC(entity)) {\r\n            InvisibleTrait invisibleTrait = NPCTag.fromEntity(entity).getCitizen().getTraitNullable(InvisibleTrait.class);\r\n            return invisibleTrait != null && invisibleTrait.isInvisible();\r\n        }\r\n        else if (entity instanceof ArmorStand armorStand) {\r\n            return !armorStand.isVisible();\r\n        }\r\n        else if (entity instanceof ItemFrame itemFrame) {\r\n            return !itemFrame.isVisible();\r\n        }\r\n        else if (entity instanceof LivingEntity livingEntity) {\r\n            return livingEntity.isInvisible() || livingEntity.hasPotionEffect(PotionEffectType.INVISIBILITY);\r\n        }\r\n        else {\r\n            return NMSHandler.entityHelper.isInvisible(entity);\r\n        }\r\n    }\r\n\r\n    public static void setInvisible(EntityTag entity, boolean invisible) {\r\n        if (entity.isCitizensNPC()) {\r\n            entity.getDenizenNPC().getCitizen().getOrAddTrait(InvisibleTrait.class).setInvisible(invisible);\r\n        }\r\n        else if (entity.getBukkitEntity() instanceof ArmorStand armorStand) {\r\n            armorStand.setVisible(!invisible);\r\n        }\r\n        else if (entity.getBukkitEntity() instanceof ItemFrame itemFrame) {\r\n            itemFrame.setVisible(!invisible);\r\n        }\r\n        else if (!entity.isFake && entity.getBukkitEntity() instanceof LivingEntity livingEntity) {\r\n            livingEntity.setInvisible(invisible);\r\n            if (!invisible) {\r\n                // Remove the invisibility potion effect for compact with old uses (the command used to add it)\r\n                livingEntity.removePotionEffect(PotionEffectType.INVISIBILITY);\r\n            }\r\n        }\r\n        else {\r\n            NMSHandler.entityHelper.setInvisible(entity.getBukkitEntity(), invisible);\r\n        }\r\n    }\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"target\") @ArgLinear @ArgDefaultNull @ArgSubType(EntityTag.class) List<EntityTag> targets,\r\n                                   @ArgName(\"state\") @ArgDefaultText(\"true\") EntityMetadataCommandHelper.Action action,\r\n                                   @ArgName(\"for\") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> forPlayers) {\r\n        helper.execute(scriptEntry, targets, action, forPlayers);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/KillCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.entity.LivingEntity;\r\n\r\nimport java.util.List;\r\n\r\npublic class KillCommand extends AbstractCommand {\r\n\r\n    public KillCommand() {\r\n        setName(\"kill\");\r\n        setSyntax(\"kill ({player}/<entity>|...)\");\r\n        setRequiredArguments(0, 1);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Kill\r\n    // @Syntax kill ({player}/<entity>|...)\r\n    // @Required 0\r\n    // @Maximum 1\r\n    // @Short Kills the player or a list of entities.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Kills a list of entities, or a single entity.\r\n    //\r\n    // If no entities are specified, the command targets the linked player.\r\n    // If there is no linked player, the command targets the linked NPC.\r\n    // If neither is available, the command errors.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.is_spawned>\r\n    //\r\n    // @Usage\r\n    // Use to kill the linked player.\r\n    // - kill\r\n    //\r\n    // @Usage\r\n    // Use to kill the linked NPC.\r\n    // - kill <npc>\r\n    //\r\n    // @Usage\r\n    // Use to kill all monsters within 10 blocks of the linked player.\r\n    // - kill <player.location.find_entities[monster].within[10]>\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"entities\", Utilities.entryDefaultEntityList(scriptEntry, true));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        if (entities == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"Missing entity target input\");\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"entities\", entities));\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (!entity.isLivingEntity()) {\r\n                Debug.echoError(scriptEntry, entity + \" is not a living entity!\");\r\n                continue;\r\n            }\r\n            LivingEntity livingEntity = entity.getLivingEntity();\r\n            if (!livingEntity.isInvulnerable()) {\r\n                livingEntity.damage(Math.max(livingEntity.getHealth(), 9999));\r\n            }\r\n            if (livingEntity.getHealth() > 0) {\r\n                livingEntity.setHealth(0);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/LeashCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.LeashHitch;\r\n\r\nimport java.util.List;\r\n\r\npublic class LeashCommand extends AbstractCommand {\r\n\r\n    public LeashCommand() {\r\n        setName(\"leash\");\r\n        setSyntax(\"leash (cancel) [<entity>|...] (holder:<entity>/<location>)\");\r\n        setRequiredArguments(1, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Leash\r\n    // @Syntax leash (cancel) [<entity>|...] (holder:<entity>/<location>)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Short Sticks a leash on target entity, held by a fence or another entity.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Attaches a leash to the specified entity.\r\n    // The leash may be attached to a fence, or another entity.\r\n    // Players and Player NPCs may not be leashed.\r\n    // Note that releasing a mob from a fence post may leave the leash attached to that fence post.\r\n    //\r\n    // Non-player NPCs can be leashed if '/npc leashable' is enabled.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.is_leashed>\r\n    // <EntityTag.leash_holder>\r\n    //\r\n    // @Usage\r\n    // Use to attach a leash to the player's target.\r\n    // - leash <player.target> holder:<player>\r\n    //\r\n    // @Usage\r\n    // Use to attach the closest cow in 10 blocks to the fence the player is looking at.\r\n    // - leash <player.location.find_entities[cow].within[10].first> holder:<player.cursor_on>\r\n    //\r\n    // @Usage\r\n    // Use to release the target entity.\r\n    // - leash cancel <player.target>\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"cancel\")\r\n                    && arg.matches(\"cancel\", \"stop\")) {\r\n                scriptEntry.addObject(\"cancel\", \"\");\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"holder\")\r\n                    && arg.matchesPrefix(\"holder\", \"h\")) {\r\n\r\n                if (arg.matchesArgumentType(EntityTag.class)) {\r\n                    scriptEntry.addObject(\"holder\", arg.asType(EntityTag.class));\r\n                }\r\n                else if (arg.matchesArgumentType(LocationTag.class)) {\r\n                    scriptEntry.addObject(\"holder\", arg.asType(LocationTag.class));\r\n                }\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            throw new InvalidArgumentsException(\"Must specify entity/entities!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"cancel\")) {\r\n            scriptEntry.defaultObject(\"holder\", Utilities.entryDefaultEntity(scriptEntry, false));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        EntityTag holder = null;\r\n        LocationTag holderLoc = null;\r\n        Entity Holder = null;\r\n        Object holderObject = scriptEntry.getObject(\"holder\");\r\n        if (holderObject instanceof EntityTag) {\r\n            holder = scriptEntry.getObjectTag(\"holder\");\r\n            Holder = holder.getBukkitEntity();\r\n        }\r\n        else if (holderObject instanceof LocationTag) {\r\n            holderLoc = scriptEntry.getObjectTag(\"holder\");\r\n            Material material = holderLoc.getBlock().getType();\r\n            if (material.name().endsWith(\"_FENCE\")) {\r\n                Holder = holderLoc.getWorld().spawn(holderLoc, LeashHitch.class);\r\n            }\r\n            else {\r\n                Debug.echoError(scriptEntry, \"Bad holder location specified - only fences are permitted!\");\r\n                return;\r\n            }\r\n        }\r\n        boolean cancel = scriptEntry.hasObject(\"cancel\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), (cancel ? db(\"cancel\", \"true\") : \"\"), db(\"entities\", entities), db(\"holder\", holder), db(\"holder\", holderLoc));\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (entity.isSpawned() && entity.isLivingEntity()) {\r\n                if (cancel) {\r\n                    entity.getLivingEntity().setLeashHolder(null);\r\n                }\r\n                else {\r\n                    entity.getLivingEntity().setLeashHolder(Holder);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/LookCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgDefaultNull;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgLinear;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgName;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgPrefixed;\r\nimport com.denizenscript.denizencore.utilities.Deprecations;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic class LookCommand extends AbstractCommand {\r\n\r\n    public LookCommand() {\r\n        setName(\"look\");\r\n        setSyntax(\"look [<entity>|...] [<location>/cancel/yaw:<yaw> pitch:<pitch>] (duration:<duration>) (offthread_repeat:<#>)\");\r\n        setRequiredArguments(1, 5); // 1 instead of 2 for due to deprecated optional entity input\r\n        isProcedural = false;\r\n        addRemappedPrefixes(\"duration\", \"d\");\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Look\r\n    // @Syntax look [<entity>|...] [<location>/cancel/yaw:<yaw> pitch:<pitch>] (duration:<duration>) (offthread_repeat:<#>)\r\n    // @Required 2\r\n    // @Maximum 5\r\n    // @Short Causes the NPC or other entity to look at a target location.\r\n    // @Synonyms Turn,Face\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Makes the entity look towards the location.\r\n    //\r\n    // You can specify either a target location, or a yaw and pitch.\r\n    //\r\n    // Can be used on players.\r\n    //\r\n    // If a duration is set, the entity cannot look away from the location until the duration has expired.\r\n    // Use the cancel argument to end the duration earlier.\r\n    //\r\n    // Optionally, you can use the \"offthread_repeat:\" option alongside \"yaw:\" and \"pitch:\"\r\n    // to cause a player's rotation to be smoothed out with a specified number of extra async rotation packets within a single tick.\r\n    //\r\n    // @Tags\r\n    // <LocationTag.yaw>\r\n    // <LocationTag.pitch>\r\n    //\r\n    // @Usage\r\n    // Use to point an NPC towards a spot.\r\n    // - look <npc> <player.location>\r\n    //\r\n    // @Usage\r\n    // Use to force a player to stare at a spot for some time.\r\n    // - look <player> <npc.location> duration:10s\r\n    // -->\r\n\r\n    public static HashMap<UUID, BukkitTask> lookTasks = new HashMap<>();\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"entities\") @ArgDefaultNull @ArgLinear ObjectTag entitiesObj,\r\n                                   @ArgName(\"location\") @ArgDefaultNull @ArgLinear ObjectTag locationObj,\r\n                                   @ArgName(\"duration\") @ArgDefaultNull @ArgPrefixed DurationTag duration,\r\n                                   @ArgName(\"yaw\") @ArgDefaultNull @ArgPrefixed ElementTag yaw,\r\n                                   @ArgName(\"pitch\") @ArgDefaultNull @ArgPrefixed ElementTag pitch,\r\n                                   @ArgName(\"offthread_repeat\") @ArgDefaultNull @ArgPrefixed ElementTag offthreadRepeats) {\r\n        if (!(entitiesObj instanceof ListTag)) {\r\n            String entStr = entitiesObj.asElement().asLowerString();\r\n            if (entStr.equals(\"cancel\") || entStr.startsWith(\"l@\")) {\r\n                Deprecations.outOfOrderArgs.warn(scriptEntry);\r\n                ObjectTag swap = entitiesObj;\r\n                entitiesObj = locationObj;\r\n                locationObj = swap;\r\n            }\r\n        }\r\n        List<EntityTag> entities = entitiesObj == null ? null : entitiesObj.asType(ListTag.class, scriptEntry.context).filter(EntityTag.class, scriptEntry);\r\n        if (entities == null) {\r\n            BukkitImplDeprecations.lookCommandNoEntities.warn(scriptEntry);\r\n            entities = Utilities.entryDefaultEntityList(scriptEntry, false);\r\n            if (entities == null) {\r\n                return;\r\n            }\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (entity.isSpawned()) {\r\n                BukkitTask task = lookTasks.remove(entity.getUUID());\r\n                if (task != null) {\r\n                    task.cancel();\r\n                }\r\n            }\r\n        }\r\n        if (locationObj != null && !(locationObj instanceof LocationTag) && locationObj.asElement().asLowerString().equals(\"cancel\")) {\r\n            return;\r\n        }\r\n        LocationTag loc = locationObj == null ? null : locationObj.asType(LocationTag.class, scriptEntry.context);\r\n        if (loc == null && yaw == null && pitch == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"Missing or invalid Location input!\");\r\n        }\r\n        final float yawRaw = yaw == null ? 0 : yaw.asFloat();\r\n        final float pitchRaw = pitch == null ? 0 : pitch.asFloat();\r\n        for (EntityTag entity : entities) {\r\n            if (entity.isSpawned()) {\r\n                if (loc != null) {\r\n                    NMSHandler.entityHelper.faceLocation(entity.getBukkitEntity(), loc);\r\n                }\r\n                else {\r\n                    if (entity.isPlayer()) {\r\n                        Location playerTeleDest = entity.getLocation().clone();\r\n                        float relYaw = (yawRaw - playerTeleDest.getYaw()) % 360;\r\n                        if (relYaw > 180) {\r\n                            relYaw -= 360;\r\n                        }\r\n                        final float actualRelYaw = relYaw;\r\n                        float relPitch = pitchRaw - playerTeleDest.getPitch();\r\n                        playerTeleDest.setYaw(yawRaw);\r\n                        playerTeleDest.setPitch(pitchRaw);\r\n                        Player player = entity.getPlayer();\r\n                        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n                            NetworkInterceptHelper.enable();\r\n                            NMSHandler.packetHelper.sendRelativeLookPacket(player, actualRelYaw, relPitch);\r\n                        }\r\n                        else {\r\n                            PaperAPITools.instance.teleport(player, playerTeleDest, PlayerTeleportEvent.TeleportCause.PLUGIN, null, Arrays.asList(TeleportCommand.Relative.values()));\r\n                        }\r\n                        if (offthreadRepeats != null) {\r\n                            NetworkInterceptHelper.enable();\r\n                            int times = offthreadRepeats.asInt();\r\n                            int ms = 50 / (times + 1);\r\n                            DenizenCore.runAsync(() -> {\r\n                                try {\r\n                                    for (int i = 0; i < times; i++) {\r\n                                        Thread.sleep(ms);\r\n                                        NMSHandler.packetHelper.sendRelativeLookPacket(player, actualRelYaw, relPitch);\r\n                                    }\r\n                                }\r\n                                catch (Throwable ex) {\r\n                                    Debug.echoError(ex);\r\n                                }\r\n                            });\r\n                        }\r\n                    }\r\n                    else {\r\n                        NMSHandler.entityHelper.rotate(entity.getBukkitEntity(), yawRaw, pitchRaw);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (duration != null && duration.getTicks() > 1) {\r\n            for (EntityTag entity : entities) {\r\n                BukkitRunnable task = new BukkitRunnable() {\r\n                    long bounces = 0;\r\n                    public void run() {\r\n                        bounces++;\r\n                        if (bounces > duration.getTicks()) {\r\n                            this.cancel();\r\n                            lookTasks.remove(entity.getUUID());\r\n                            return;\r\n                        }\r\n                        if (entity.isSpawned()) {\r\n                            if (loc != null) {\r\n                                NMSHandler.entityHelper.faceLocation(entity.getBukkitEntity(), loc);\r\n                            }\r\n                            else {\r\n                                NMSHandler.entityHelper.rotate(entity.getBukkitEntity(), yawRaw, pitchRaw);\r\n                            }\r\n                        }\r\n                    }\r\n                };\r\n                BukkitTask newTask = task.runTaskTimer(Denizen.getInstance(), 0, 1);\r\n                lookTasks.put(entity.getUUID(), newTask);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/MountCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Conversion;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.entity.Position;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\nimport java.util.List;\r\n\r\npublic class MountCommand extends AbstractCommand {\r\n\r\n    public MountCommand() {\r\n        setName(\"mount\");\r\n        setSyntax(\"mount (cancel) [<entity>|...] (<location>)\");\r\n        setRequiredArguments(0, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Mount\r\n    // @Syntax mount (cancel) [<entity>|...] (<location>)\r\n    // @Required 0\r\n    // @Maximum 3\r\n    // @Short Mounts one entity onto another.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Mounts an entity onto another as though in a vehicle.\r\n    // Can be used to force a player into a vehicle or to mount an entity onto another entity, for example a player onto an NPC.\r\n    // If the entity(s) don't exist they will be spawned.\r\n    // Accepts a location, which the entities will be teleported to on mounting.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.vehicle>\r\n    // <EntityTag.is_inside_vehicle>\r\n    // <entry[saveName].mounted_entities> returns a list of entities that were mounted.\r\n    //\r\n    // @Usage\r\n    // Use to mount an NPC on top of a player.\r\n    // - mount <npc>|<player>\r\n    //\r\n    // @Usage\r\n    // Use to spawn a mutant pile of mobs.\r\n    // - mount cow|pig|sheep|chicken\r\n    //\r\n    // @Usage\r\n    // Use to place a diamond block above a player's head.\r\n    // - mount falling_block,diamond_block|<player>\r\n    //\r\n    // @Usage\r\n    // Use to force an entity in a vehicle.\r\n    // - mount <player>|boat\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        List<EntityTag> entities = null;\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"cancel\")\r\n                    && arg.matches(\"cancel\")) {\r\n                scriptEntry.addObject(\"cancel\", \"\");\r\n            }\r\n            else if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n                scriptEntry.addObject(\"custom_location\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                entities = arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry);\r\n                scriptEntry.addObject(\"entities\", entities);\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            throw new InvalidArgumentsException(\"Must specify entity/entities!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            if (entities != null) {\r\n                for (int i = entities.size() - 1; i >= 0; i--) {\r\n                    if (entities.get(i).isSpawned()) {\r\n                        scriptEntry.defaultObject(\"location\", entities.get(i).getLocation());\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            scriptEntry.defaultObject(\"location\", Utilities.entryDefaultLocation(scriptEntry, true));\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a location!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        boolean hasCustomLocation = scriptEntry.hasObject(\"custom_location\");\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        boolean cancel = scriptEntry.hasObject(\"cancel\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), (cancel ? db(\"cancel\", true) : \"\"), location, db(\"entities\", entities));\r\n        }\r\n        if (!cancel) {\r\n            for (EntityTag entity : entities) {\r\n                if (!entity.isSpawned() || hasCustomLocation) {\r\n                    entity.spawnAt(location);\r\n                }\r\n            }\r\n            Position.mount(Conversion.convertEntities(entities));\r\n        }\r\n        else {\r\n            Position.dismount(Conversion.convertEntities(entities));\r\n        }\r\n        ListTag entityList = new ListTag();\r\n        entityList.addObjects((List) entities);\r\n        scriptEntry.saveObject(\"mounted_entities\", entityList);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/PushCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.Conversion;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.entity.Position;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.Holdable;\r\nimport com.denizenscript.denizencore.scripts.containers.core.TaskScriptContainer;\r\nimport com.denizenscript.denizencore.scripts.queues.ScriptQueue;\r\nimport com.denizenscript.denizencore.utilities.ScriptUtilities;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.util.BoundingBox;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.List;\r\nimport java.util.function.Consumer;\r\n\r\npublic class PushCommand extends AbstractCommand implements Holdable {\r\n\r\n    public PushCommand() {\r\n        setName(\"push\");\r\n        setSyntax(\"push [<entity>|...] (origin:<entity>/<location>) (destination:<location>) (speed:<#.#>) (duration:<duration>) (script:<name>) (def:<element>|...) (force_along) (precision:<#>) (no_rotate) (no_damage) (ignore_collision)\");\r\n        setRequiredArguments(1, 12);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Push\r\n    // @Syntax push [<entity>|...] (origin:<entity>/<location>) (destination:<location>) (speed:<#.#>) (duration:<duration>) (script:<name>) (def:<element>|...) (force_along) (precision:<#>) (no_rotate) (no_damage) (ignore_collision)\r\n    // @Required 1\r\n    // @Maximum 12\r\n    // @Short Pushes entities through the air in a straight line.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Pushes entities through the air in a straight line at a certain speed and for a certain duration,\r\n    // triggering a script when they hit an obstacle or stop flying.\r\n    //\r\n    // You must specify an entity to be pushed.\r\n    //\r\n    // Usually, you should specify the origin and the destination. If unspecified, they will be assumed from contextual data.\r\n    //\r\n    // You can specify the script to be run with the (script:<name>) argument,\r\n    // and optionally specify definitions to be available in this script with the (def:<element>|...) argument.\r\n    //\r\n    // Using the 'no_damage' argument causes the entity to receive no damage when they stop moving.\r\n    //\r\n    // Optionally use the \"ignore_collision\" argument to ignore block collisions.\r\n    //\r\n    // Optionally use \"speed:#\" to set how fast it should be pushed.\r\n    //\r\n    // Optionally use \"force_along\" to cause the entity to teleport through any blockage.\r\n    //\r\n    // Optionally use \"no_rotate\" to prevent entities being rotated at the start of the push.\r\n    //\r\n    // Optionally use \"duration:#\" to set the max length of time to continue pushing.\r\n    //\r\n    // The push command is ~waitable. Refer to <@link language ~waitable>.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.velocity>\r\n    // <entry[saveName].pushed_entities> returns the list of pushed entities.\r\n    //\r\n    // @Usage\r\n    // Use to launch an arrow straight towards a target.\r\n    // - push arrow destination:<player.location>\r\n    //\r\n    // @Usage\r\n    // Use to launch an entity into the air.\r\n    // - push cow\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"origin\")\r\n                    && arg.matchesPrefix(\"origin\", \"o\", \"source\", \"shooter\", \"s\")) {\r\n                if (arg.matchesArgumentType(EntityTag.class)) {\r\n                    scriptEntry.addObject(\"origin_entity\", arg.asType(EntityTag.class));\r\n                }\r\n                else if (arg.matchesArgumentType(LocationTag.class)) {\r\n                    scriptEntry.addObject(\"origin_location\", arg.asType(LocationTag.class));\r\n                }\r\n                else {\r\n                    Debug.echoError(\"Ignoring unrecognized argument: \" + arg.getRawValue());\r\n                }\r\n            }\r\n            else if (!scriptEntry.hasObject(\"destination\")\r\n                    && arg.matchesArgumentType(LocationTag.class)\r\n                    && arg.matchesPrefix(\"destination\", \"d\")) {\r\n                scriptEntry.addObject(\"destination\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)\r\n                    && arg.matchesPrefix(\"duration\", \"d\")) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"speed\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"speed\", \"s\")) {\r\n                scriptEntry.addObject(\"speed\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"script\")\r\n                    && ((arg.matchesArgumentType(ScriptTag.class) && arg.asType(ScriptTag.class).getContainer() instanceof TaskScriptContainer)\r\n                    || arg.matchesPrefix(\"script\"))) {\r\n                scriptEntry.addObject(\"script\", arg.asType(ScriptTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"force_along\")\r\n                    && arg.matches(\"force_along\")) {\r\n                scriptEntry.addObject(\"force_along\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"no_rotate\")\r\n                    && arg.matches(\"no_rotate\")) {\r\n                scriptEntry.addObject(\"no_rotate\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"precision\")\r\n                    && arg.matchesPrefix(\"precision\")) {\r\n                scriptEntry.addObject(\"precision\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"no_damage\")\r\n                    && arg.matches(\"no_damage\")) {\r\n                scriptEntry.addObject(\"no_damage\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"ignore_collision\")\r\n                    && arg.matches(\"ignore_collision\")) {\r\n                scriptEntry.addObject(\"ignore_collision\", new ElementTag(true));\r\n            }\r\n            else if (arg.matchesPrefix(\"def\", \"define\", \"context\")) {\r\n                scriptEntry.addObject(\"definitions\", arg.asType(ListTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"origin_location\")) {\r\n            scriptEntry.defaultObject(\"origin_entity\", Utilities.entryDefaultEntity(scriptEntry, false));\r\n        }\r\n        scriptEntry.defaultObject(\"speed\", new ElementTag(1.5));\r\n        scriptEntry.defaultObject(\"duration\", new DurationTag(20));\r\n        scriptEntry.defaultObject(\"force_along\", new ElementTag(false));\r\n        scriptEntry.defaultObject(\"precision\", new ElementTag(2));\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            throw new InvalidArgumentsException(\"Must specify entity/entities!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"origin_entity\") && !scriptEntry.hasObject(\"origin_location\")) {\r\n            throw new InvalidArgumentsException(\"Must specify an origin location!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        EntityTag originEntity = scriptEntry.getObjectTag(\"origin_entity\");\r\n        LocationTag originLocation = scriptEntry.hasObject(\"origin_location\") ?\r\n                (LocationTag) scriptEntry.getObject(\"origin_location\") :\r\n                new LocationTag(originEntity.getEyeLocation()\r\n                        .add(originEntity.getEyeLocation().getDirection())\r\n                        .subtract(0, 0.4, 0));\r\n        boolean no_rotate = scriptEntry.hasObject(\"no_rotate\") && scriptEntry.getElement(\"no_rotate\").asBoolean();\r\n        final boolean no_damage = scriptEntry.hasObject(\"no_damage\") && scriptEntry.getElement(\"no_damage\").asBoolean();\r\n        // If there is no destination set, but there is a shooter, get a point in front of the shooter and set it as the destination\r\n        final LocationTag destination = scriptEntry.hasObject(\"destination\") ?\r\n                (LocationTag) scriptEntry.getObject(\"destination\") :\r\n                (originEntity != null ? new LocationTag(originEntity.getEyeLocation()\r\n                        .add(originEntity.getEyeLocation().getDirection()\r\n                                .multiply(30)))\r\n                        : null);\r\n        // TODO: Should this be checked in argument parsing?\r\n        if (destination == null) {\r\n            Debug.echoError(\"No destination specified!\");\r\n            scriptEntry.setFinished(true);\r\n            return;\r\n        }\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        final ScriptTag script = scriptEntry.getObjectTag(\"script\");\r\n        final ListTag definitions = scriptEntry.getObjectTag(\"definitions\");\r\n        ElementTag speedElement = scriptEntry.getElement(\"speed\");\r\n        DurationTag duration = (DurationTag) scriptEntry.getObject(\"duration\");\r\n        ElementTag force_along = scriptEntry.getElement(\"force_along\");\r\n        ElementTag precision = scriptEntry.getElement(\"precision\");\r\n        ElementTag ignore_collision = scriptEntry.getElement(\"ignore_collision\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"origin\", originEntity != null ? originEntity : originLocation),  db(\"entities\", entities),\r\n                    destination, speedElement, duration, script, force_along, precision, (no_rotate ? db(\"no_rotate\", \"true\") : \"\"), (no_damage ? db(\"no_damage\", \"true\") : \"\"),\r\n                    ignore_collision, definitions);\r\n        }\r\n        final boolean ignoreCollision = ignore_collision != null && ignore_collision.asBoolean();\r\n        final double speed = speedElement.asDouble();\r\n        final int maxTicks = duration.getTicksAsInt();\r\n        final boolean forceAlong = force_along.asBoolean();\r\n        // Keep a ListTag of entities that can be called using <entry[name].pushed_entities> later in the script queue\r\n        final ListTag entityList = new ListTag();\r\n        for (EntityTag entity : entities) {\r\n            entity.spawnAt(originLocation);\r\n            entityList.addObject(entity);\r\n            if (!no_rotate) {\r\n                NMSHandler.entityHelper.faceLocation(entity.getBukkitEntity(), destination);\r\n            }\r\n            if (entity.isProjectile() && originEntity != null) {\r\n                entity.setShooter(originEntity);\r\n            }\r\n        }\r\n        scriptEntry.saveObject(\"pushed_entities\", entityList);\r\n        Position.mount(Conversion.convertEntities(entities));\r\n        final EntityTag lastEntity = entities.get(entities.size() - 1);\r\n        final Vector v2 = destination.toVector();\r\n        final Vector Origin = originLocation.toVector();\r\n        final int prec = precision.asInt();\r\n        BukkitRunnable task = new BukkitRunnable() {\r\n            int runs = 0;\r\n            LocationTag lastLocation;\r\n            @Override\r\n            public void run() {\r\n                if (runs < maxTicks && lastEntity.isValid()) {\r\n                    Vector v1 = lastEntity.getLocation().toVector();\r\n                    Vector v3 = v2.clone().subtract(v1).normalize();\r\n                    if (forceAlong) {\r\n                        Vector newDest = v2.clone().subtract(Origin).normalize().multiply(runs * speed).add(Origin);\r\n                        lastEntity.teleport(new Location(lastEntity.getLocation().getWorld(),\r\n                                newDest.getX(), newDest.getY(), newDest.getZ(),\r\n                                lastEntity.getLocation().getYaw(), lastEntity.getLocation().getPitch()));\r\n                    }\r\n                    runs += prec;\r\n                    // Check if the entity is close to its destination\r\n                    if (Math.abs(v2.getX() - v1.getX()) < 1.5f && Math.abs(v2.getY() - v1.getY()) < 1.5f && Math.abs(v2.getZ() - v1.getZ()) < 1.5f) {\r\n                        runs = maxTicks;\r\n                        return;\r\n                    }\r\n                    Vector newVel = v3.multiply(speed);\r\n                    lastEntity.setVelocity(newVel);\r\n                    if (!ignoreCollision && lastEntity.isValid()) {\r\n                        BoundingBox box = lastEntity.getBukkitEntity().getBoundingBox().expand(newVel);\r\n                        Location ref = lastEntity.getLocation().clone();\r\n                        for (int x = (int) Math.floor(box.getMinX()); x < Math.ceil(box.getMaxX()); x++) {\r\n                            ref.setX(x);\r\n                            for (int y = (int) Math.floor(box.getMinY()); y < Math.ceil(box.getMaxY()); y++) {\r\n                                ref.setY(y);\r\n                                for (int z = (int) Math.floor(box.getMinZ()); z < Math.ceil(box.getMaxZ()); z++) {\r\n                                    ref.setZ(z);\r\n                                    if (!isSafeBlock(ref)) {\r\n                                        runs = maxTicks;\r\n                                    }\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                    if (no_damage && lastEntity.isLivingEntity()) {\r\n                        lastEntity.getLivingEntity().setFallDistance(0);\r\n                    }\r\n                    // Record the location in case the entity gets lost (EG, if a pushed arrow hits a mob)\r\n                    lastLocation = lastEntity.getLocation();\r\n                }\r\n                else {\r\n                    this.cancel();\r\n                    if (script != null) {\r\n                        Consumer<ScriptQueue> configure = (queue) -> {\r\n                            if (lastEntity.getLocation() != null) {\r\n                                queue.addDefinition(\"location\", lastEntity.getLocation());\r\n                            }\r\n                            else {\r\n                                queue.addDefinition(\"location\", lastLocation);\r\n                            }\r\n                            queue.addDefinition(\"pushed_entities\", entityList);\r\n                            queue.addDefinition(\"last_entity\", lastEntity);\r\n                        };\r\n                        ScriptUtilities.createAndStartQueue(script.getContainer(), null, scriptEntry.entryData, null, configure, null, null, definitions, scriptEntry);\r\n                    }\r\n                    scriptEntry.setFinished(true);\r\n                }\r\n            }\r\n        };\r\n        task.runTaskTimer(Denizen.getInstance(), 0, prec);\r\n    }\r\n\r\n    public static boolean isSafeBlock(Location loc) {\r\n        return !Utilities.isLocationYSafe(loc) || !loc.getBlock().getType().isSolid();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/RemoveCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.entity.DenizenEntityType;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.util.List;\r\n\r\npublic class RemoveCommand extends AbstractCommand {\r\n\r\n    public RemoveCommand() {\r\n        setName(\"remove\");\r\n        setSyntax(\"remove [<entity>|...] (world:<world>)\");\r\n        setRequiredArguments(1, 2);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Remove\r\n    // @Syntax remove [<entity>|...] (world:<world>)\r\n    // @Required 1\r\n    // @Maximum 2\r\n    // @Short Despawns an entity or list of entities, permanently removing any NPCs.\r\n    // @Group entity\r\n    // @Description\r\n    // Removes the selected entity. May also take a list of entities to remove.\r\n    //\r\n    // Any NPC removed this way is completely removed, as if by '/npc remove'.\r\n    // For temporary NPC removal, see <@link command despawn>.\r\n    //\r\n    // If a generic entity name is given (like 'zombie'), this will remove all entities of that type from the given world.\r\n    // Optionally, you may specify a world to target.\r\n    // (Defaults to the world of the player running the command)\r\n    //\r\n    // @Tags\r\n    // <EntityTag.is_spawned>\r\n    //\r\n    // @Usage\r\n    // Use to remove the entity the player is looking at.\r\n    // - remove <player.target>\r\n    //\r\n    // @Usage\r\n    // Use to remove all nearby entities around the player, excluding the player itself.\r\n    // - remove <player.location.find_entities.within[10].exclude[<player>]>\r\n    //\r\n    // @Usage\r\n    // Use to remove all dropped items in the world called cookies.\r\n    // - remove dropped_item world:cookies\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.add(EntityType.values());\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                EntityTag.allowDespawnedNpcs = true;\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n                EntityTag.allowDespawnedNpcs = false;\r\n            }\r\n            else if (!scriptEntry.hasObject(\"world\")\r\n                    && arg.matchesArgumentType(WorldTag.class)) {\r\n                scriptEntry.addObject(\"world\", arg.asType(WorldTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            throw new InvalidArgumentsException(\"Must specify entity/entities!\");\r\n        }\r\n        scriptEntry.defaultObject(\"world\", Utilities.entryDefaultWorld(scriptEntry, false));\r\n    }\r\n\r\n    public static boolean alwaysWarnOnMassRemove = false;\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        WorldTag world = scriptEntry.getObjectTag(\"world\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), world, db(\"entities\", entities));\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (entity.isUnique()) {\r\n                if (entity.isFake) {\r\n                    FakeEntity fakeEnt = FakeEntity.idsToEntities.get(entity.getUUID());\r\n                    if (fakeEnt != null) {\r\n                        fakeEnt.cancelEntity();\r\n                    }\r\n                }\r\n                else if (entity.isCitizensNPC()) {\r\n                    entity.getDenizenNPC().getCitizen().destroy();\r\n                }\r\n                else {\r\n                    if (!entity.isSpawned()) {\r\n                        Debug.echoError(\"Tried to remove already-removed entity.\");\r\n                        // Still remove() anyway to compensate for Spigot/NMS bugs\r\n                    }\r\n                    if (entity.entity != null) {\r\n                        entity.remove();\r\n                    }\r\n                }\r\n            }\r\n            else {\r\n                int removed = 0;\r\n                for (Entity worldEntity : world.getEntities()) {\r\n                    if (entity.getEntityType().equals(DenizenEntityType.getByEntity(worldEntity))) {\r\n                        worldEntity.remove();\r\n                        removed++;\r\n                    }\r\n                }\r\n                Debug.echoDebug(scriptEntry, \"Removed \" + removed + \" entities from the world.\");\r\n                if (alwaysWarnOnMassRemove) {\r\n                    Debug.echoError(\"Remove command 'Always warn on mass delete' in Denizen config is enabled - mass removal of '\" + entity.getEntityType() + \"' performed, removing \" + removed + \" entities.\");\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/RenameCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityFormObject;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\nimport java.util.function.Function;\r\n\r\npublic class RenameCommand extends AbstractCommand {\r\n\r\n    public RenameCommand() {\r\n        setName(\"rename\");\r\n        setSyntax(\"rename [<name>/cancel] (t:<entity>|...) (per_player) (for:<player>|...) (list_name_only)\");\r\n        setRequiredArguments(1, 5);\r\n        setParseArgs(false);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Rename\r\n    // @Syntax rename [<name>/cancel] (t:<entity>|...) (per_player) (for:<player>|...) (list_name_only)\r\n    // @Required 1\r\n    // @Maximum 5\r\n    // @Short Renames the linked NPC or list of entities.\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Renames the linked NPC or list of entities.\r\n    // Functions like the '/npc rename' command.\r\n    //\r\n    // Can rename a spawned or unspawned NPC to any name up to 256 characters.\r\n    //\r\n    // Can rename a vanilla entity to any name up to 256 characters, and will automatically make the nameplate visible.\r\n    //\r\n    // Can rename a player to any name up to 16 characters. This will affect only the player's nameplate.\r\n    //\r\n    // Optionally specify 'per_player' to reprocess the input tags for each player when renaming a vanilla entity\r\n    // (meaning, if you use \"- rename <player.name> t:<[someent]> per_player\", every player will see their own name on that entity).\r\n    // A per_player rename will remain active until the entity is renamed again or the server is restarted.\r\n    // Rename to \"cancel\" per_player to intentionally end a per_player rename.\r\n    // Optionally specify \"for:\" a list of players when using per_player.\r\n    //\r\n    // Optionally specify 'list_name_only' to only change the tab list name for a player. Works with 'per_player'.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.name>\r\n    // <NPCTag.nickname>\r\n    //\r\n    // @Usage\r\n    // Use to rename the linked NPC to 'Bob'.\r\n    // - rename Bob\r\n    //\r\n    // @Usage\r\n    // Use to rename a different NPC to 'Bob'.\r\n    // - rename Bob t:<[some_npc]>\r\n    //\r\n    // @Usage\r\n    // Use to make an entity show players their own name for 10 seconds.\r\n    // - rename <green><player.name> t:<[some_entity]> per_player\r\n    // - wait 10s\r\n    // - rename cancel t:<[some_entity]> per_player\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"targets\")\r\n                    && arg.matchesPrefix(\"t\", \"target\", \"targets\")) {\r\n                scriptEntry.addObject(\"targets\", ListTag.getListFor(TagManager.tagObject(arg.getValue(), scriptEntry.getContext()), scriptEntry.getContext()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"players\")\r\n                    && arg.matchesPrefix(\"for\")) {\r\n                scriptEntry.addObject(\"players\", ListTag.getListFor(TagManager.tagObject(arg.getValue(), scriptEntry.getContext()), scriptEntry.getContext()).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"per_player\")\r\n                    && arg.matches(\"per_player\")) {\r\n                scriptEntry.addObject(\"per_player\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"list_name_only\")\r\n                    && arg.matches(\"list_name_only\")) {\r\n                scriptEntry.addObject(\"list_name_only\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"name\")) {\r\n                scriptEntry.addObject(\"name\", arg.getRawElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"name\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a name!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"targets\")) {\r\n            if (Utilities.getEntryNPC(scriptEntry) == null || !Utilities.getEntryNPC(scriptEntry).isValid()) {\r\n                throw new InvalidArgumentsException(\"Must have an NPC attached, or specify a list of targets to rename!\");\r\n            }\r\n            scriptEntry.addObject(\"targets\", new ListTag(Utilities.getEntryNPC(scriptEntry)));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        final ElementTag name = scriptEntry.getElement(\"name\");\r\n        ElementTag perPlayer = scriptEntry.getElement(\"per_player\");\r\n        ElementTag listNameOnly = scriptEntry.getElement(\"list_name_only\");\r\n        ListTag targets = scriptEntry.getObjectTag(\"targets\");\r\n        List<PlayerTag> players = (List<PlayerTag>) scriptEntry.getObject(\"players\");\r\n        if (perPlayer != null && perPlayer.asBoolean()) {\r\n            NetworkInterceptHelper.enable();\r\n            if (scriptEntry.dbCallShouldDebug()) {\r\n                Debug.report(scriptEntry, getName(), name, targets, perPlayer, listNameOnly, db(\"for\", players));\r\n            }\r\n            for (ObjectTag target : targets.objectForms) {\r\n                EntityTag entity = target.asType(EntityTag.class, CoreUtilities.noDebugContext);\r\n                if (entity != null) {\r\n                    Entity bukkitEntity = entity.getBukkitEntity();\r\n                    if (bukkitEntity == null) {\r\n                        Debug.echoError(\"Invalid entity in rename command.\");\r\n                        continue;\r\n                    }\r\n                    if (name.asString().equals(\"cancel\")) {\r\n                        customNames.remove(bukkitEntity.getUniqueId());\r\n                        if (bukkitEntity.isCustomNameVisible()) {\r\n                            if (players == null) {\r\n                                for (Player player : NMSHandler.entityHelper.getPlayersThatSee(bukkitEntity)) {\r\n                                    NMSHandler.packetHelper.sendRename(player, bukkitEntity, bukkitEntity.getCustomName(), false);\r\n                                }\r\n                            }\r\n                            else {\r\n                                for (PlayerTag player : players) {\r\n                                    NMSHandler.packetHelper.sendRename(player.getPlayerEntity(), bukkitEntity, bukkitEntity.getCustomName(), false);\r\n                                }\r\n                            }\r\n                        }\r\n                        else {\r\n                            bukkitEntity.setCustomNameVisible(true);\r\n                            bukkitEntity.setCustomNameVisible(false); // Force a metadata update\r\n                        }\r\n                    }\r\n                    else {\r\n                        final BukkitTagContext originalContext = (BukkitTagContext) scriptEntry.context.clone();\r\n                        HashMap<UUID, RenameData> playerToFuncMap = customNames.computeIfAbsent(bukkitEntity.getUniqueId(), k -> new HashMap<>());\r\n                        Function<Player, String> nameGetter = p -> {\r\n                            originalContext.player = new PlayerTag(p);\r\n                            return TagManager.tag(name.asString(), originalContext);\r\n                        };\r\n                        RenameData renamer = new RenameData();\r\n                        renamer.nameFunction = nameGetter;\r\n                        renamer.listOnly = listNameOnly != null && listNameOnly.asBoolean();\r\n                        if (players == null) {\r\n                            playerToFuncMap.put(null, renamer);\r\n                        }\r\n                        else {\r\n                            for (PlayerTag player : players) {\r\n                                playerToFuncMap.put(player.getUUID(), renamer);\r\n                            }\r\n                        }\r\n                        if (players == null) {\r\n                            for (Player player : NMSHandler.entityHelper.getPlayersThatSee(bukkitEntity)) {\r\n                                NMSHandler.packetHelper.sendRename(player, bukkitEntity, \"\", renamer.listOnly);\r\n                            }\r\n                        }\r\n                        else {\r\n                            for (PlayerTag player : players) {\r\n                                NMSHandler.packetHelper.sendRename(player.getPlayerEntity(), bukkitEntity, \"\", renamer.listOnly);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            return;\r\n        }\r\n        String nameString = TagManager.tag(name.asString(), scriptEntry.context);\r\n        if (nameString.length() > 256) {\r\n            nameString = nameString.substring(0, 256);\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"name\", nameString), listNameOnly, targets);\r\n        }\r\n        for (ObjectTag target : targets.objectForms) {\r\n            EntityFormObject entity = target.asType(EntityTag.class, CoreUtilities.noDebugContext);\r\n            if (entity == null) {\r\n                entity = target.asType(NPCTag.class, scriptEntry.context);\r\n            }\r\n            else {\r\n                entity = ((EntityTag) entity).getDenizenObject();\r\n            }\r\n            if (entity == null) {\r\n                Debug.echoError(\"Invalid entity in rename command.\");\r\n                continue;\r\n            }\r\n            if (entity instanceof NPCTag) {\r\n                NPC npc = ((NPCTag) entity).getCitizen();\r\n                npc.setName(nameString);\r\n            }\r\n            else if (entity instanceof PlayerTag) {\r\n                if (listNameOnly != null && listNameOnly.asBoolean()) {\r\n                    PaperAPITools.instance.setPlayerListName(((PlayerTag) entity).getPlayerEntity(), nameString);\r\n                }\r\n                else {\r\n                    String limitedName = nameString.length() > 16 ? nameString.substring(0, 16) : nameString;\r\n                    NMSHandler.instance.getProfileEditor().setPlayerName(((PlayerTag) entity).getPlayerEntity(), limitedName);\r\n                }\r\n            }\r\n            else {\r\n                Entity bukkitEntity = entity.getDenizenEntity().getBukkitEntity();\r\n                customNames.remove(bukkitEntity.getUniqueId());\r\n                bukkitEntity.setCustomName(nameString);\r\n                bukkitEntity.setCustomNameVisible(true);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static HashMap<UUID, HashMap<UUID, RenameData>> customNames = new HashMap<>();\r\n\r\n    public static class RenameData {\r\n\r\n        public Function<Player, String> nameFunction;\r\n\r\n        public boolean listOnly;\r\n    }\r\n\r\n    public static boolean hasAnyDynamicRenames() {\r\n        return !customNames.isEmpty();\r\n    }\r\n\r\n    public static void addDynamicRename(Entity bukkitEntity, Player forPlayer, RenameData rename) {\r\n        NetworkInterceptHelper.enable();\r\n        HashMap<UUID, RenameData> playerToFuncMap = customNames.computeIfAbsent(bukkitEntity.getUniqueId(), k -> new HashMap<>());\r\n        playerToFuncMap.put(forPlayer == null ? null : forPlayer.getUniqueId(), rename);\r\n        if (forPlayer == null) {\r\n            for (Player player : NMSHandler.entityHelper.getPlayersThatSee(bukkitEntity)) {\r\n                NMSHandler.packetHelper.sendRename(player, bukkitEntity, \"\", rename.listOnly);\r\n            }\r\n        }\r\n        else {\r\n            NMSHandler.packetHelper.sendRename(forPlayer, bukkitEntity, \"\", rename.listOnly);\r\n        }\r\n    }\r\n\r\n    public static String getCustomNameFor(UUID entityId, Player player, boolean isForList) {\r\n        HashMap<UUID, RenameData> map = customNames.get(entityId);\r\n        if (map == null) {\r\n            return null;\r\n        }\r\n        RenameData rename = map.get(player.getUniqueId());\r\n        if (rename == null || (rename.listOnly && !isForList)) {\r\n            rename = map.get(null);\r\n            if (rename == null || (rename.listOnly && !isForList)) {\r\n                return null;\r\n            }\r\n        }\r\n        return rename.nameFunction.apply(player);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/RotateCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.Holdable;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.util.*;\r\n\r\npublic class RotateCommand extends AbstractCommand implements Holdable {\r\n\r\n    public RotateCommand() {\r\n        setName(\"rotate\");\r\n        setSyntax(\"rotate (cancel) (<entity>|...) (yaw:<#.#>) (pitch:<#.#>) (infinite/duration:<duration>) (frequency:<duration>)\");\r\n        setRequiredArguments(1, 6);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Rotate\r\n    // @Syntax rotate (cancel) (<entity>|...) (yaw:<#.#>) (pitch:<#.#>) (infinite/duration:<duration>) (frequency:<duration>)\r\n    // @Required 1\r\n    // @Maximum 6\r\n    // @Short Rotates a list of entities.\r\n    // @Synonyms Spin\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Induces incremental rotation on a list of entities over a period of time.\r\n    //\r\n    // The yaw and pitch arguments specify how much the entity will rotate each step. Default to 10 and 0 respectively.\r\n    //\r\n    // The frequency argument specifies how long it takes between each rotation step. Defaults to 1t.\r\n    //\r\n    // The duration argument specifies how long the whole rotation process will last. Defaults to 1s.\r\n    // Alternatively, use \"infinite\" if you want the entity to spin forever.\r\n    //\r\n    // You can use \"cancel\" to prematurely stop the ongoing rotation (useful when set to infinite)\r\n    //\r\n    // The rotate command is ~waitable. Refer to <@link language ~waitable>.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.location>\r\n    //\r\n    // @Usage\r\n    // Use to rotate the player's yaw by 10 every tick for 3 seconds total\r\n    // - rotate <player> duration:3s\r\n    //\r\n    // @Usage\r\n    // Use to rotate the player's pitch by 20 every 5 ticks for a second total\r\n    // - rotate <player> yaw:0.0 pitch:20.0 frequency:5t\r\n    //\r\n    // @Usage\r\n    // Use to prematurely stop the player's rotation\r\n    // - rotate cancel <player>\r\n    // -->\r\n\r\n    public static Set<UUID> rotatingEntities = new HashSet<>();\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"cancel\")\r\n                    && (arg.matches(\"cancel\") || arg.matches(\"stop\"))) {\r\n                scriptEntry.addObject(\"cancel\", new ElementTag(\"true\"));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"infinite\")\r\n                    && arg.matches(\"infinite\")) {\r\n                scriptEntry.addObject(\"infinite\", new ElementTag(\"true\"));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)\r\n                    && arg.matchesPrefix(\"duration\", \"d\")) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"frequency\")\r\n                    && arg.matchesArgumentType(DurationTag.class)\r\n                    && arg.matchesPrefix(\"frequency\", \"f\")) {\r\n                scriptEntry.addObject(\"frequency\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"yaw\")\r\n                    && arg.matchesPrefix(\"yaw\", \"y\", \"rotation\", \"r\")\r\n                    && arg.matchesFloat()) {\r\n                scriptEntry.addObject(\"yaw\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"pitch\")\r\n                    && arg.matchesPrefix(\"pitch\", \"p\", \"tilt\", \"t\")\r\n                    && arg.matchesFloat()) {\r\n                scriptEntry.addObject(\"pitch\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"entities\", Utilities.entryDefaultEntityList(scriptEntry, true));\r\n        scriptEntry.defaultObject(\"yaw\", new ElementTag(10));\r\n        scriptEntry.defaultObject(\"pitch\", new ElementTag(0));\r\n        scriptEntry.defaultObject(\"duration\", new DurationTag(20));\r\n        scriptEntry.defaultObject(\"frequency\", new DurationTag(1L));\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            throw new InvalidArgumentsException(\"Must specify entity/entities!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        final List<EntityTag> entities = new ArrayList<>((List<EntityTag>) scriptEntry.getObject(\"entities\"));\r\n        final DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        final DurationTag frequency = scriptEntry.getObjectTag(\"frequency\");\r\n        final ElementTag yaw = scriptEntry.getElement(\"yaw\");\r\n        final ElementTag pitch = scriptEntry.getElement(\"pitch\");\r\n        boolean cancel = scriptEntry.hasObject(\"cancel\");\r\n        final boolean infinite = scriptEntry.hasObject(\"infinite\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), (cancel ? db(\"cancel\", true) : \"\"), db(\"entities\", entities),\r\n                    (infinite ? db(\"duration\", \"infinite\") : duration), frequency, yaw, pitch);\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (cancel) {\r\n                rotatingEntities.remove(entity.getUUID());\r\n            }\r\n            else {\r\n                rotatingEntities.add(entity.getUUID());\r\n            }\r\n        }\r\n        if (cancel) {\r\n            return;\r\n        }\r\n        BukkitRunnable task = new BukkitRunnable() {\r\n            int ticks = 0;\r\n            int maxTicks = duration.getTicksAsInt();\r\n            ArrayList<EntityTag> unusedEntities = new ArrayList<>();\r\n            @Override\r\n            public void run() {\r\n                if (entities.isEmpty()) {\r\n                    scriptEntry.setFinished(true);\r\n                    this.cancel();\r\n                }\r\n                else if (infinite || ticks < maxTicks) {\r\n                    for (EntityTag entity : entities) {\r\n                        if (entity.isSpawned() && rotatingEntities.contains(entity.getUUID())) {\r\n                            NMSHandler.entityHelper.rotate(entity.getBukkitEntity(),\r\n                                    EntityHelper.normalizeYaw(entity.getLocation().getYaw() + yaw.asFloat()),\r\n                                    entity.getLocation().getPitch() + pitch.asFloat());\r\n                        }\r\n                        else {\r\n                            rotatingEntities.remove(entity.getUUID());\r\n                            unusedEntities.add(entity);\r\n                        }\r\n                    }\r\n                    if (!unusedEntities.isEmpty()) {\r\n                        for (EntityTag unusedEntity : unusedEntities) {\r\n                            entities.remove(unusedEntity);\r\n                        }\r\n                        unusedEntities.clear();\r\n                    }\r\n                    ticks = (int) (ticks + frequency.getTicks());\r\n                }\r\n                else {\r\n                    scriptEntry.setFinished(true);\r\n                    this.cancel();\r\n                }\r\n            }\r\n        };\r\n        task.runTaskTimer(Denizen.getInstance(), 0, frequency.getTicks());\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/ShootCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.Conversion;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.command.TabCompleteHelper;\r\nimport com.denizenscript.denizen.utilities.entity.Velocity;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.entity.Position;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.Holdable;\r\nimport com.denizenscript.denizencore.scripts.containers.core.TaskScriptContainer;\r\nimport com.denizenscript.denizencore.scripts.queues.ScriptQueue;\r\nimport com.denizenscript.denizencore.utilities.ScriptUtilities;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Projectile;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityDamageByEntityEvent;\r\nimport org.bukkit.event.entity.ProjectileHitEvent;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\nimport java.util.function.Consumer;\r\n\r\npublic class ShootCommand extends AbstractCommand implements Listener, Holdable {\r\n\r\n    public ShootCommand() {\r\n        setName(\"shoot\");\r\n        setSyntax(\"shoot [<entity>|...] (origin:<entity>/<location>) (destination:<location>) (height:<#.#>) (speed:<#.#>) (script:<name>) (def:<element>|...) (shooter:<entity>) (spread:<#.#>) (lead:<location>) (no_rotate)\");\r\n        setRequiredArguments(1, 11);\r\n        Bukkit.getServer().getPluginManager().registerEvents(this, Denizen.getInstance());\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Shoot\r\n    // @Syntax shoot [<entity>|...] (origin:<entity>/<location>) (destination:<location>) (height:<#.#>) (speed:<#.#>) (script:<name>) (def:<element>|...) (shooter:<entity>) (spread:<#.#>) (lead:<location>) (no_rotate)\r\n    // @Required 1\r\n    // @Maximum 11\r\n    // @Short Shoots an entity through the air, useful for things like firing arrows.\r\n    // @Synonyms Launch\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Shoots an entity through the air up to a certain height, optionally triggering a script on impact with a target.\r\n    //\r\n    // The launch has three modes: arc, lead, and direct.\r\n    //\r\n    // The \"arc\" mode calculates a launch arc to exactly hit the target location.\r\n    // If you want to use this, specify the \"height\" argument as how high the arc should go, in blocks.\r\n    // Do not specify \"speed\" or \"lead\".\r\n    // You can optionally specify a custom \"gravity\" (hidden from syntax line intentionally) if you know what you're doing and really need to.\r\n    //\r\n    // The \"lead\" mode calculates a modified arc intended to hit a target based on a lead factor (usually the entity's velocity).\r\n    // To use, specify the \"lead\" argument as a vector and \"speed\" as a launch speed.\r\n    // Do not specify \"height\".\r\n    //\r\n    // Generally, most users should prefer direct mode: it just launches straight in the direction of the destination, at the speed you specify.\r\n    // To use this, just input the \"speed\" argument, and don't specify \"height\" or \"lead\".\r\n    //\r\n    // If the origin is not an entity, you can specify a \"shooter\" so the damage handling code knows who to assume shot the projectile.\r\n    //\r\n    // Normally, a list of entities will spawn mounted on top of each other. To have them instead fire separately and spread out,\r\n    // specify the \"spread\" argument with a decimal number indicating how wide to spread the entities.\r\n    //\r\n    // Optionally, add \"no_rotate\" to prevent the shoot command from rotating launched entities.\r\n    //\r\n    // Use the \"script:<name>\" argument to run a task script when the projectiles land.\r\n    // When that script runs, the following definitions will be available:\r\n    // <[shot_entities]> for all shot entities (as in, the projectiles),\r\n    // <[last_entity]> for the last one (The controlling entity),\r\n    // <[location]> for the last known location of the last shot entity, and\r\n    // <[hit_entities]> for a list of any entities that were hit by fired projectiles.\r\n    //\r\n    // The shoot command is ~waitable. Refer to <@link language ~waitable>.\r\n    //\r\n    // Note that for ~waiting or the \"script\" arg, tracking is only accurate for projectile entities (such as arrows). This will be inaccurately estimated for other entity types.\r\n    //\r\n    // @Tags\r\n    // <entry[saveName].shot_entity> returns the single entity that was shot (as in, the projectile) (if you only shot one).\r\n    // <entry[saveName].shot_entities> returns a ListTag of entities that were shot (as in, the projectiles).\r\n    // <entry[saveName].hit_entities> returns a ListTag of entities that were hit (if any). (Only works when you ~wait for the command).\r\n    // <entry[saveName].location> returns the last known location of the last shot entity. (Only works when you ~wait for the command).\r\n    //\r\n    // @Usage\r\n    // Use to shoot an arrow from the NPC to perfectly hit the player.\r\n    // - shoot arrow origin:<npc> destination:<player.location>\r\n    //\r\n    // @Usage\r\n    // Use to shoot an arrow out of the player with a given speed.\r\n    // - shoot arrow origin:<player> speed:2\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        TabCompleteHelper.tabCompleteEntityTypes(tab);\r\n    }\r\n\r\n    Map<UUID, EntityTag> arrows = new HashMap<>();\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"origin\")\r\n                    && arg.matchesPrefix(\"origin\", \"o\", \"source\", \"s\")) {\r\n\r\n                if (arg.matchesArgumentType(EntityTag.class)) {\r\n                    scriptEntry.addObject(\"origin_entity\", arg.asType(EntityTag.class));\r\n                }\r\n                else if (arg.matchesArgumentType(LocationTag.class)) {\r\n                    scriptEntry.addObject(\"origin_location\", arg.asType(LocationTag.class));\r\n                }\r\n                else {\r\n                    Debug.echoError(\"Ignoring unrecognized argument: \" + arg.getRawValue());\r\n                }\r\n            }\r\n            else if (!scriptEntry.hasObject(\"destination\")\r\n                    && arg.matchesArgumentType(LocationTag.class)\r\n                    && arg.matchesPrefix(\"destination\", \"d\")) {\r\n                scriptEntry.addObject(\"destination\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"lead\")\r\n                    && arg.matchesArgumentType(LocationTag.class)\r\n                    && arg.matchesPrefix(\"lead\")) {\r\n                scriptEntry.addObject(\"lead\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"height\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"height\", \"h\")) {\r\n                scriptEntry.addObject(\"height\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"speed\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"speed\")) {\r\n                scriptEntry.addObject(\"speed\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"script\")\r\n                    && arg.matchesPrefix(\"script\")) {\r\n                String script = arg.asElement().asString();\r\n                int dot = script.indexOf('.');\r\n                String subPath = null;\r\n                if (dot != -1) {\r\n                    subPath = script.substring(dot + 1);\r\n                    script = script.substring(0, dot);\r\n                }\r\n                ScriptTag scriptTag = ScriptTag.valueOf(script, scriptEntry.getContext());\r\n                if (scriptTag == null || !(scriptTag.getContainer() instanceof TaskScriptContainer)) {\r\n                    throw new InvalidArgumentsException(\"Invalid script specified - must name a task script container.\");\r\n                }\r\n                scriptEntry.addObject(\"script\", scriptTag);\r\n                if (subPath != null) {\r\n                    scriptEntry.addObject(\"path\", new ElementTag(subPath));\r\n                }\r\n            }\r\n            else if (!scriptEntry.hasObject(\"shooter\")\r\n                    && arg.matchesArgumentType(EntityTag.class)\r\n                    && arg.matchesPrefix(\"shooter\")) {\r\n                scriptEntry.addObject(\"shooter\", arg.asType(EntityTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            // Don't document this argument; it is for debug purposes only\r\n            else if (!scriptEntry.hasObject(\"gravity\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"gravity\", \"g\")) {\r\n                scriptEntry.addObject(\"gravity\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"spread\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"spread\")) {\r\n                scriptEntry.addObject(\"spread\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"no_rotate\")\r\n                    && arg.matches(\"no_rotate\")) {\r\n                scriptEntry.addObject(\"no_rotate\", new ElementTag(true));\r\n            }\r\n            else if (arg.matchesPrefix(\"def\", \"define\", \"context\")) {\r\n                scriptEntry.addObject(\"definitions\", arg.asType(ListTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        // Use the NPC or player's locations as the origin if one is not specified\r\n        if (!scriptEntry.hasObject(\"origin_location\")) {\r\n            scriptEntry.defaultObject(\"origin_entity\", Utilities.entryDefaultEntity(scriptEntry, false));\r\n        }\r\n        scriptEntry.defaultObject(\"height\", new ElementTag(3));\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            throw new InvalidArgumentsException(\"Must specify entity/entities!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"origin_entity\") && !scriptEntry.hasObject(\"origin_location\")) {\r\n            throw new InvalidArgumentsException(\"Must specify an origin location!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        EntityTag originEntity = scriptEntry.getObjectTag(\"origin_entity\");\r\n        LocationTag originLocation = scriptEntry.hasObject(\"origin_location\") ?\r\n                (LocationTag) scriptEntry.getObject(\"origin_location\") :\r\n                new LocationTag(originEntity.getEyeLocation()\r\n                        .add(originEntity.getEyeLocation().getDirection()));\r\n        boolean no_rotate = scriptEntry.hasObject(\"no_rotate\") && scriptEntry.getElement(\"no_rotate\").asBoolean();\r\n        // If there is no destination set, but there is a shooter, get a point\r\n        // in front of the shooter and set it as the destination\r\n        final LocationTag destination = scriptEntry.hasObject(\"destination\") ?\r\n                (LocationTag) scriptEntry.getObject(\"destination\") :\r\n                (originEntity != null ? new LocationTag(originEntity.getEyeLocation().clone()\r\n                        .add(originEntity.getEyeLocation().clone().getDirection().multiply(30)))\r\n                        : (originLocation != null ? new LocationTag(originLocation.clone().add(\r\n                        originLocation.getDirection().multiply(30))) : null));\r\n        // TODO: Same as PUSH -- is this the place to do this?\r\n        if (destination == null) {\r\n            Debug.echoError(\"No destination specified!\");\r\n            return;\r\n        }\r\n        final List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        final ScriptTag script = scriptEntry.getObjectTag(\"script\");\r\n        final ElementTag subPath = scriptEntry.getElement(\"path\");\r\n        final ListTag definitions = scriptEntry.getObjectTag(\"definitions\");\r\n        EntityTag shooter = scriptEntry.getObjectTag(\"shooter\");\r\n        ElementTag height = scriptEntry.getElement(\"height\");\r\n        ElementTag gravity = scriptEntry.getElement(\"gravity\");\r\n        ElementTag speed = scriptEntry.getElement(\"speed\");\r\n        ElementTag spread = scriptEntry.getElement(\"spread\");\r\n        LocationTag lead = scriptEntry.getObjectTag(\"lead\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), originEntity, originLocation, db(\"entities\", entities),\r\n                    destination, height, gravity, speed, script, subPath, shooter, spread, lead, (no_rotate ? db(\"no_rotate\", \"true\") : \"\"), definitions);\r\n        }\r\n        final ListTag entityList = new ListTag();\r\n        if (!no_rotate) {\r\n            originLocation = new LocationTag(NMSHandler.entityHelper.faceLocation(originLocation, destination));\r\n        }\r\n        for (EntityTag entity : entities) {\r\n            if (!entity.isSpawned() || !no_rotate) {\r\n                entity.spawnAt(originLocation);\r\n            }\r\n            entityList.addObject(entity);\r\n            if (entity.isProjectile()) {\r\n                if (shooter != null || originEntity != null) {\r\n                    entity.setShooter(shooter != null ? shooter : originEntity);\r\n                }\r\n                if (script != null || scriptEntry.shouldWaitFor()) {\r\n                    arrows.put(entity.getUUID(), null);\r\n                }\r\n            }\r\n        }\r\n        scriptEntry.saveObject(\"shot_entities\", entityList);\r\n        if (entityList.size() == 1) {\r\n            scriptEntry.saveObject(\"shot_entity\", entityList.getObject(0));\r\n        }\r\n        if (spread == null) {\r\n            Position.mount(Conversion.convertEntities(entities));\r\n        }\r\n        final EntityTag lastEntity = entities.get(entities.size() - 1);\r\n        if (speed == null) {\r\n            if (gravity == null) {\r\n                gravity = new ElementTag(lastEntity.getEntityType().getGravity());\r\n            }\r\n            Vector v1 = lastEntity.getLocation().toVector();\r\n            Vector v2 = destination.toVector();\r\n            Vector v3 = Velocity.calculate(v1, v2, gravity.asDouble(), height.asDouble());\r\n            lastEntity.setVelocity(v3);\r\n        }\r\n        else if (lead == null) {\r\n            Vector relative = destination.clone().subtract(originLocation).toVector();\r\n            lastEntity.setVelocity(relative.normalize().multiply(speed.asDouble()));\r\n        }\r\n        else {\r\n            double g = 20;\r\n            double v = speed.asDouble();\r\n            Vector relative = destination.clone().subtract(originLocation).toVector();\r\n            double testAng = Velocity.launchAngle(originLocation, destination.toVector(), v, relative.getY(), g);\r\n            double hangTime = Velocity.hangtime(testAng, v, relative.getY(), g);\r\n            Vector to = destination.clone().add(lead.clone().multiply(hangTime)).toVector();\r\n            relative = to.clone().subtract(originLocation.toVector());\r\n            double dist = Math.sqrt(relative.getX() * relative.getX() + relative.getZ() * relative.getZ());\r\n            if (dist == 0) {\r\n                dist = 0.1d;\r\n            }\r\n            testAng = Velocity.launchAngle(originLocation, to, v, relative.getY(), g);\r\n            relative.setY(Math.tan(testAng) * dist);\r\n            relative = relative.normalize();\r\n            v = v + (1.188 * Math.pow(hangTime, 2));\r\n            relative = relative.multiply(v / 20.0d);\r\n            lastEntity.setVelocity(relative);\r\n        }\r\n        if (spread != null) {\r\n            Vector base = lastEntity.getVelocity().clone();\r\n            double spreadDouble = spread.asDouble();\r\n            for (EntityTag entity : entities) {\r\n                entity.setVelocity(Velocity.randomSpread(base, spreadDouble));\r\n            }\r\n        }\r\n        final LocationTag start = new LocationTag(lastEntity.getLocation());\r\n        // A task used to trigger a script if the entity is no longer\r\n        // being shot, when the script argument is used\r\n        BukkitRunnable task = new BukkitRunnable() {\r\n            boolean flying = true;\r\n            LocationTag lastLocation = null;\r\n            Vector lastVelocity = null;\r\n            public void run() {\r\n                // If the entity is no longer spawned, stop the task\r\n                if (!lastEntity.isSpawned()) {\r\n                    if (CoreConfiguration.debugVerbose) {\r\n                        Debug.log(\"Shoot ended because entity not spawned\");\r\n                    }\r\n                    flying = false;\r\n                }\r\n                // Otherwise, if the entity is no longer traveling through\r\n                // the air, stop the task\r\n                else if (lastLocation != null && lastVelocity != null && !(lastEntity.getBukkitEntity() instanceof Projectile)) {\r\n                    if (lastLocation.getWorld() != lastEntity.getBukkitEntity().getWorld()\r\n                            || (lastLocation.distanceSquared(lastEntity.getBukkitEntity().getLocation()) < 0.1\r\n                            && lastVelocity.distanceSquared(lastEntity.getBukkitEntity().getVelocity()) < 0.1)) {\r\n                        if (CoreConfiguration.debugVerbose) {\r\n                            Debug.log(\"Shoot ended because distances short - locations: \" + (lastLocation.distanceSquared(lastEntity.getBukkitEntity().getLocation()))\r\n                                    + \", velocity: \" + (lastVelocity.distanceSquared(lastEntity.getBukkitEntity().getVelocity()) < 0.1));\r\n                        }\r\n                        flying = false;\r\n                    }\r\n                }\r\n                if (!arrows.containsKey(lastEntity.getUUID()) || arrows.get(lastEntity.getUUID()) != null) {\r\n                    if (CoreConfiguration.debugVerbose) {\r\n                        Debug.log(\"Shoot ended because uuid was updated (hit entity?)\");\r\n                    }\r\n                    flying = false;\r\n                }\r\n                // Stop the task and run the script if conditions\r\n                // are met\r\n                if (!flying) {\r\n                    this.cancel();\r\n                    ListTag hitEntities = new ListTag();\r\n                    for (EntityTag entity : entities) {\r\n                        if (arrows.containsKey(entity.getUUID())) {\r\n                            EntityTag hit = arrows.get(entity.getUUID());\r\n                            arrows.remove(entity.getUUID());\r\n                            if (hit != null) {\r\n                                hitEntities.addObject(hit.getDenizenObject());\r\n                            }\r\n                        }\r\n                    }\r\n                    if (lastLocation == null) {\r\n                        lastLocation = start;\r\n                    }\r\n                    scriptEntry.saveObject(\"location\", new LocationTag(lastLocation));\r\n                    scriptEntry.saveObject(\"hit_entities\", hitEntities);\r\n                    if (script != null) {\r\n                        Consumer<ScriptQueue> configure = (queue) -> {\r\n                            queue.addDefinition(\"location\", new LocationTag(lastLocation));\r\n                            queue.addDefinition(\"shot_entities\", entityList);\r\n                            queue.addDefinition(\"last_entity\", lastEntity);\r\n                            queue.addDefinition(\"hit_entities\", hitEntities);\r\n                        };\r\n                        ScriptUtilities.createAndStartQueue(script.getContainer(), subPath == null ? null : subPath.asString(), scriptEntry.entryData, null, configure, null, null, definitions, scriptEntry);\r\n                    }\r\n                    scriptEntry.setFinished(true);\r\n                }\r\n                else {\r\n                    // Record its position in case the entity dies\r\n                    lastLocation = lastEntity.getLocation();\r\n                    lastVelocity = lastEntity.getVelocity();\r\n                }\r\n            }\r\n        };\r\n        if (script != null || scriptEntry.shouldWaitFor()) {\r\n            task.runTaskTimer(Denizen.getInstance(), 1, 2);\r\n        }\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\r\n    public void projectileHit(ProjectileHitEvent event) {\r\n        if (!arrows.containsKey(event.getEntity().getUniqueId())) {\r\n            return;\r\n        }\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.log(\"Shoot ending because hit\");\r\n        }\r\n        if (event.getHitEntity() != null) {\r\n            arrows.put(event.getEntity().getUniqueId(), new EntityTag(event.getHitEntity()));\r\n        }\r\n        else {\r\n            arrows.remove(event.getEntity().getUniqueId());\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void arrowDamage(EntityDamageByEntityEvent event) {\r\n        Entity arrow = event.getDamager();\r\n        if (!(arrow instanceof Projectile)) {\r\n            return;\r\n        }\r\n        if (!arrows.containsKey(arrow.getUniqueId())) {\r\n            return;\r\n        }\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.log(\"Shoot ending because damage\");\r\n        }\r\n        arrows.put(arrow.getUniqueId(), new EntityTag(event.getEntity()));\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/SneakCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.npc.traits.SneakingTrait;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic class SneakCommand extends AbstractCommand {\r\n\r\n    public SneakCommand() {\r\n        setName(\"sneak\");\r\n        setSyntax(\"sneak [<entity>|...] ({start}/stop) (fake/stopfake) (for:<player>|...)\");\r\n        setRequiredArguments(1, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Sneak\r\n    // @Syntax sneak [<entity>|...] ({start}/stop) (fake/stopfake) (for:<player>|...)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Short Causes the entity to start or stop sneaking.\r\n    // @Synonyms Crouch,Shift\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Causes an entity to start or stop sneaking.\r\n    // If the entity is NPC, adds the SneakingTrait to apply the sneak setting persistent.\r\n    //\r\n    // Can optionally use the 'fake' argument to apply a fake sneak using packets, either globally or for specific players.\r\n    // Use 'stopfake' to disable faking of sneak.\r\n    // A fake sneak only affects the name plate, not the entity's pose.\r\n    //\r\n    // Note: using this command on a player will only show to other players. You cannot alter a player in their own view.\r\n    // Note that <@link property EntityTag.is_sneaking> is also available.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.is_sneaking>\r\n    //\r\n    // @Usage\r\n    // Make the linked NPC start sneaking.\r\n    // - sneak <npc>\r\n    //\r\n    // @Usage\r\n    // Make the linked NPC stop sneaking.\r\n    // - sneak <npc> stop\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matches(\"fake\")\r\n                    && !scriptEntry.hasObject(\"fake\")\r\n                    && !scriptEntry.hasObject(\"stopfake\")) {\r\n                scriptEntry.addObject(\"fake\", new ElementTag(true));\r\n            }\r\n            else if (arg.matches(\"stopfake\")\r\n                    && !scriptEntry.hasObject(\"fake\")\r\n                    && !scriptEntry.hasObject(\"stopfake\")) {\r\n                scriptEntry.addObject(\"stopfake\", new ElementTag(true));\r\n            }\r\n            else if ((arg.matches(\"start\") || arg.matches(\"stop\"))\r\n                    && !scriptEntry.hasObject(\"mode\")) {\r\n                scriptEntry.addObject(\"mode\", arg.asElement());\r\n            }\r\n            else if (arg.matchesPrefix(\"for\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)\r\n                    && !scriptEntry.hasObject(\"for_players\")) {\r\n                scriptEntry.addObject(\"for_players\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else if (arg.matchesArgumentList(EntityTag.class)\r\n                    && !scriptEntry.hasObject(\"entities\")) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"entities\")) {\r\n            throw new InvalidArgumentsException(\"Missing entities argument.\");\r\n        }\r\n        scriptEntry.defaultObject(\"mode\", new ElementTag(\"start\"));\r\n    }\r\n\r\n    public static HashMap<UUID, HashMap<UUID, Boolean>> forceSetSneak = new HashMap<>();\r\n\r\n    public static void updateFakeSneak(UUID entity, UUID player, boolean shouldSneak, boolean start) {\r\n        NetworkInterceptHelper.enable();\r\n        HashMap<UUID, Boolean> subMap = forceSetSneak.get(entity);\r\n        if (subMap == null) {\r\n            if (!start) {\r\n                return;\r\n            }\r\n            subMap = new HashMap<>();\r\n            forceSetSneak.put(entity, subMap);\r\n        }\r\n        if (start) {\r\n            subMap.put(player, shouldSneak);\r\n        }\r\n        else {\r\n            subMap.remove(player);\r\n            if (subMap.isEmpty()) {\r\n                forceSetSneak.remove(entity);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static Boolean shouldSneak(UUID entity, UUID player) {\r\n        HashMap<UUID, Boolean> subMap = forceSetSneak.get(entity);\r\n        if (subMap == null) {\r\n            return null;\r\n        }\r\n        Boolean b = subMap.get(player);\r\n        if (b != null) {\r\n            return b;\r\n        }\r\n        return subMap.get(null);\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag fake = scriptEntry.getElement(\"fake\");\r\n        ElementTag stopfake = scriptEntry.getElement(\"stopfake\");\r\n        ElementTag mode = scriptEntry.getElement(\"mode\");\r\n        List<PlayerTag> forPlayers = (List<PlayerTag>) scriptEntry.getObject(\"for_players\");\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), mode, db(\"entities\", entities), db(\"for_players\", forPlayers), fake, stopfake);\r\n        }\r\n        boolean shouldSneak = mode.asString().equalsIgnoreCase(\"start\");\r\n        boolean shouldFake = fake != null && fake.asBoolean();\r\n        boolean shouldStopFake = stopfake != null && stopfake.asBoolean();\r\n        for (EntityTag entity : entities) {\r\n            if (shouldFake || shouldStopFake) {\r\n                if (forPlayers == null) {\r\n                    updateFakeSneak(entity.getUUID(), null, shouldSneak, shouldFake);\r\n                    for (Player player : NMSHandler.entityHelper.getPlayersThatSee(entity.getBukkitEntity())) {\r\n                        NMSHandler.packetHelper.sendEntityMetadataFlagsUpdate(player, entity.getBukkitEntity());\r\n                    }\r\n                }\r\n                else {\r\n                    for (PlayerTag player : forPlayers) {\r\n                        updateFakeSneak(entity.getUUID(), player.getUUID(), shouldSneak, shouldFake);\r\n                        NMSHandler.packetHelper.sendEntityMetadataFlagsUpdate(player.getPlayerEntity(), entity.getBukkitEntity());\r\n                    }\r\n                }\r\n            }\r\n            else if (entity.isCitizensNPC()) {\r\n                SneakingTrait trait = entity.getDenizenNPC().getCitizen().getOrAddTrait(SneakingTrait.class);\r\n                if (shouldSneak) {\r\n                    trait.sneak();\r\n                }\r\n                else {\r\n                    trait.stand();\r\n                }\r\n            }\r\n            else if (entity.isSpawned()) {\r\n                NMSHandler.entityHelper.setSneaking(entity.getBukkitEntity(), shouldSneak);\r\n            }\r\n            else {\r\n                Debug.echoError(\"Cannot make unspawned entity sneak.\");\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/SpawnCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.command.TabCompleteHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\n\r\nimport java.util.List;\r\n\r\npublic class SpawnCommand extends AbstractCommand {\r\n\r\n    public SpawnCommand() {\r\n        setName(\"spawn\");\r\n        setSyntax(\"spawn [<entity>|...] (<location>) (target:<entity>) (persistent) (reason:<reason>)\");\r\n        setRequiredArguments(1, 5);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Spawn\r\n    // @Syntax spawn [<entity>|...] (<location>) (target:<entity>) (persistent) (reason:<reason>)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Short Spawns a list of entities at a certain location.\r\n    // @Group entity\r\n    //\r\n    // @Synonyms summon\r\n    //\r\n    // @Description\r\n    // Spawn an entity or list of entities at the specified location.\r\n    //\r\n    // Accepts the 'target:<entity>' argument which will cause all spawned entities to follow and attack the targeted entity.\r\n    //\r\n    // If the persistent argument is present, the entity will not despawn when no players are within range, causing the entity to remain until killed.\r\n    //\r\n    // Optionally specify 'reason:<reason>' (Paper only) to specify the reason an entity is spawning for the 'entity spawns' event,\r\n    // using any reason from <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/CreatureSpawnEvent.SpawnReason.html>\r\n    //\r\n    // If the location isn't specified, will use either the linked NPC's location, or the linked player's location.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.is_spawned>\r\n    // <server.entity_types>\r\n    // <entry[saveName].spawned_entities> returns a list of entities that were spawned.\r\n    // <entry[saveName].spawned_entity> returns the entity that was spawned (if you only spawned one).\r\n    //\r\n    // @Usage\r\n    // Use to spawn a spider at the player's location.\r\n    // - spawn spider <player.location>\r\n    //\r\n    // @Usage\r\n    // Use to spawn a spider at the player's location which will automatically target the player.\r\n    // - spawn spider <player.location> target:<player>\r\n    //\r\n    // @Usage\r\n    // Use to spawn a swarm of creepers around the npc, which will not despawn until killed.\r\n    // - spawn creeper|creeper|creeper|creeper|creeper <npc.location> persistent\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        TabCompleteHelper.tabCompleteEntityTypes(tab);\r\n        tab.addWithPrefix(\"reason\", CreatureSpawnEvent.SpawnReason.values());\r\n    }\r\n\r\n    public static void autoExecute(final ScriptEntry scriptEntry,\r\n                                   @ArgLinear @ArgName(\"entities\") ObjectTag entityListInput,\r\n                                   @ArgDefaultNull @ArgLinear @ArgName(\"location\") ObjectTag locationInput,\r\n                                   @ArgDefaultNull @ArgPrefixed @ArgName(\"target\") EntityTag target,\r\n                                   @ArgDefaultNull @ArgPrefixed @ArgName(\"spread\") ElementTag spread, // TODO: proper native optional int support somehow?\r\n                                   @ArgName(\"persistent\") boolean persistent,\r\n                                   @ArgDefaultText(\"custom\") @ArgPrefixed @ArgName(\"reason\") CreatureSpawnEvent.SpawnReason reason) {\r\n        if (locationInput != null && entityListInput.shouldBeType(LocationTag.class)) {\r\n            ObjectTag swap = locationInput;\r\n            locationInput = entityListInput;\r\n            entityListInput = swap;\r\n        }\r\n        LocationTag location = locationInput == null ? Utilities.entryDefaultLocation(scriptEntry, false) : locationInput.asType(LocationTag.class, scriptEntry.context);\r\n        if (location == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify a location!\");\r\n        }\r\n        EntityTag.allowDespawnedNpcs = true;\r\n        List<EntityTag> entities = entityListInput.asType(ListTag.class, scriptEntry.context).filter(EntityTag.class, scriptEntry.context);\r\n        EntityTag.allowDespawnedNpcs = false;\r\n        // Keep a ListTag of entities that can be called using <entry[name].spawned_entities> later in the script queue\r\n        ListTag entityList = new ListTag();\r\n        // Go through all the entities and spawn them or teleport them, then set their targets if applicable\r\n        for (EntityTag entity : entities) {\r\n            Location loc = location.clone();\r\n            if (spread != null) {\r\n                loc.add(CoreUtilities.getRandom().nextInt(spread.asInt() * 2) - spread.asInt(),\r\n                        0,\r\n                        CoreUtilities.getRandom().nextInt(spread.asInt() * 2) - spread.asInt());\r\n            }\r\n            entity = entity.duplicate();\r\n            entity.spawnAt(loc, PlayerTeleportEvent.TeleportCause.PLUGIN, reason);\r\n            entityList.addObject(entity);\r\n            if (!entity.isSpawned()) {\r\n                Debug.echoDebug(scriptEntry, \"Failed to spawn \" + entity + \" (blocked by other plugin, script, or gamerule?).\");\r\n            }\r\n            else {\r\n                if (persistent && entity.isLivingEntity()) {\r\n                    entity.getLivingEntity().setRemoveWhenFarAway(false);\r\n                }\r\n                if (target != null) {\r\n                    entity.target(target.getLivingEntity());\r\n                }\r\n            }\r\n        }\r\n        // Add entities to context so that the specific entities created/spawned can be fetched.\r\n        scriptEntry.saveObject(\"spawned_entities\", entityList);\r\n        if (entities.size() != 0) {\r\n            scriptEntry.saveObject(\"spawned_entity\", entityList.getObject(0));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/TeleportCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.Deprecations;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.citizensnpcs.trait.CurrentLocation;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\n\r\npublic class TeleportCommand extends AbstractCommand {\r\n\r\n    public TeleportCommand() {\r\n        setName(\"teleport\");\r\n        setSyntax(\"teleport (<entity>|...) [<location>] (cause:<cause>) (entity_options:<option>|...) (relative) (relative_axes:<axis>|...) (offthread_repeat:<#>) (offthread_yaw) (offthread_pitch)\");\r\n        setRequiredArguments(1, 9);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Teleport\r\n    // @Syntax teleport (<entity>|...) [<location>] (cause:<cause>) (entity_options:<option>|...) (relative) (relative_axes:<axis>|...) (offthread_repeat:<#>) (offthread_yaw) (offthread_pitch)\r\n    // @Required 1\r\n    // @Maximum 9\r\n    // @Short Teleports the entity(s) to a new location.\r\n    // @Synonyms tp\r\n    // @Group entity\r\n    //\r\n    // @Description\r\n    // Teleports the entity or entities to the new location.\r\n    // Entities can be teleported between worlds using this command.\r\n    // You may optionally specify a teleport cause for player entities, allowing proper teleport event handling. When not specified, this is \"PLUGIN\". See <@link language teleport cause> for causes.\r\n    //\r\n    // Instead of a valid entity, an unspawned NPC or an offline player may also be used.\r\n    //\r\n    // Optionally specify \"relative\" to use relative teleportation (Paper only). This is primarily useful only for players, but available for all entities.\r\n    // Relative teleports are smoother for the client when teleporting over short distances.\r\n    // Optionally, you may use \"relative_axes:\" to specify a set of axes to move relative on (and other axes will be treated as absolute), as any of \"X\", \"Y\", \"Z\", \"YAW\", \"PITCH\".\r\n    // Optionally, you may use \"offthread_repeat:\" with the relative arg when teleporting a player to smooth out the teleport with a specified number of extra async packets sent within a single tick.\r\n    // Optionally, specify \"offthread_yaw\" or \"offthread_pitch\" while using offthread_repeat to smooth the player's yaw/pitch to the new location's yaw/pitch.\r\n    //\r\n    // Optionally, specify additional teleport options using the 'entity_options:' arguments (Paper only).\r\n    // This allows things like retaining an open inventory when teleporting - see the links below for more information.\r\n    // See <@link url https://jd.papermc.io/paper/1.19/io/papermc/paper/entity/TeleportFlag.EntityState.html> for all possible options.\r\n    // Note that the API this is based on is marked as experimental in Paper, and so may change in the future.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.location>\r\n    //\r\n    // @Usage\r\n    // Use to teleport a player to the location their cursor is pointing at.\r\n    // - teleport <player> <player.cursor_on>\r\n    //\r\n    // @Usage\r\n    // Use to teleport a player high above.\r\n    // - teleport <player> <player.location.above[200]>\r\n    //\r\n    // @Usage\r\n    // Use to teleport to a random online player.\r\n    // - teleport <player> <server.online_players.random.location>\r\n    //\r\n    // @Usage\r\n    // Use to teleport all players to your location.\r\n    // - teleport <server.online_players> <player.location>\r\n    //\r\n    // @Usage\r\n    // Use to teleport the NPC to a location that was noted with the <@link command note> command.\r\n    // - teleport <npc> my_prenoted_location\r\n    //\r\n    // @Usage\r\n    // Use to teleport a player to some location, and inform events that it was caused by a nether portal.\r\n    // - teleport <player> <server.flag[nether_hub_location]> cause:nether_portal\r\n    //\r\n    // @Usage\r\n    // Use to teleport the player without closing their currently open inventory.\r\n    // - teleport <player> <player.location.below[5]> entity_options:retain_open_inventory\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addNotesOfType(LocationTag.class);\r\n        tab.addWithPrefix(\"entity_options:\", EntityState.values());\r\n        tab.addWithPrefix(\"relative_axes:\", Relative.values());\r\n    }\r\n\r\n    public enum EntityState { RETAIN_PASSENGERS, RETAIN_VEHICLE, RETAIN_OPEN_INVENTORY }\r\n    public enum Relative { X, Y, Z, YAW, PITCH }\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgLinear @ArgName(\"entities\") ObjectTag entityList,\r\n                                   @ArgLinear @ArgName(\"location\") @ArgDefaultNull ObjectTag locationRaw,\r\n                                   @ArgPrefixed @ArgName(\"cause\") @ArgDefaultText(\"plugin\") PlayerTeleportEvent.TeleportCause cause,\r\n                                   @ArgName(\"entity_options\") @ArgPrefixed @ArgDefaultNull @ArgSubType(EntityState.class) List<EntityState> entityOptions,\r\n                                   @ArgName(\"relative_axes\") @ArgPrefixed @ArgDefaultNull @ArgSubType(Relative.class) List<Relative> relativeAxes,\r\n                                   @ArgName(\"relative\") boolean relative,\r\n                                   @ArgName(\"offthread_repeat\") @ArgDefaultNull @ArgPrefixed ElementTag offthreadRepeats,\r\n                                   @ArgName(\"offthread_yaw\") boolean offthreadYaw,\r\n                                   @ArgName(\"offthread_pitch\") boolean offthreadPitch) {\r\n        if (locationRaw == null) { // Compensate for legacy \"- teleport <loc>\" default fill\r\n            locationRaw = entityList;\r\n            entityList = Utilities.entryDefaultEntity(scriptEntry, true);\r\n        }\r\n        else if (entityList.identify().startsWith(\"l@\")) { // Compensate for legacy entity/location out-of-order support\r\n            ObjectTag swap = locationRaw;\r\n            locationRaw = entityList;\r\n            entityList = swap;\r\n            Deprecations.outOfOrderArgs.warn(scriptEntry);\r\n        }\r\n        if (relative && relativeAxes == null) {\r\n            relativeAxes = Arrays.asList(Relative.values());\r\n        }\r\n        LocationTag location = locationRaw.asType(LocationTag.class, scriptEntry.context);\r\n        ListTag entities = entityList.asType(ListTag.class, scriptEntry.context);\r\n        if (location == null || entities == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"Location or entity list missing or invalid for Teleport command\");\r\n        }\r\n        for (ObjectTag entityObj : entities.objectForms) {\r\n            if (entityObj.shouldBeType(PlayerTag.class)) {\r\n                PlayerTag player = entityObj.asType(PlayerTag.class, scriptEntry.context);\r\n                if (player != null && !player.isOnline()) {\r\n                    player.setLocation(location);\r\n                    continue;\r\n                }\r\n            }\r\n            if (entityObj.shouldBeType(NPCTag.class)) {\r\n                NPCTag npc = entityObj.asType(NPCTag.class, scriptEntry.context);\r\n                if (npc != null && !npc.isSpawned()) {\r\n                    npc.getCitizen().getOrAddTrait(CurrentLocation.class).setLocation(location.clone());\r\n                    continue;\r\n                }\r\n            }\r\n            EntityTag entity = entityObj.asType(EntityTag.class, scriptEntry.context);\r\n            if (entity == null) {\r\n                Debug.echoError(\"Cannot interpret object '\" + entityObj + \"' as an EntityTag.\");\r\n                continue;\r\n            }\r\n            if (entity.isFake && entity.getWorld().equals(location.getWorld())) {\r\n                NMSHandler.entityHelper.snapPositionTo(entity.getBukkitEntity(), location.toVector());\r\n                NMSHandler.entityHelper.look(entity.getBukkitEntity(), location.getYaw(), location.getPitch());\r\n                return;\r\n            }\r\n            if (offthreadRepeats != null && relativeAxes != null && entity.isPlayer()) {\r\n                NetworkInterceptHelper.enable();\r\n                int times = offthreadRepeats.asInt() + 1;\r\n                int ms = 50 / times;\r\n                Player player = entity.getPlayer();\r\n                Vector increment = location.clone().subtract(player.getLocation()).toVector().multiply(1.0 / times);\r\n                double x = relativeAxes.contains(Relative.X) ? increment.getX() : location.getX();\r\n                double y = relativeAxes.contains(Relative.Y) ? increment.getY() : location.getY();\r\n                double z = relativeAxes.contains(Relative.Z) ? increment.getZ() : location.getZ();\r\n                float yaw;\r\n                if (relativeAxes.contains(Relative.YAW)) {\r\n                    float relYaw = (location.getYaw() - player.getLocation().getYaw()) % 360;\r\n                    if (relYaw > 180) {\r\n                        relYaw -= 360;\r\n                    }\r\n                    yaw = offthreadYaw ? relYaw / times : 0;\r\n                }\r\n                else {\r\n                    yaw = location.getYaw();\r\n                }\r\n                float pitch;\r\n                if (relativeAxes.contains(Relative.PITCH)) {\r\n                    pitch = offthreadPitch ? (location.getPitch() - player.getLocation().getPitch()) / times : 0;\r\n                }\r\n                else {\r\n                    pitch = location.getPitch();\r\n                }\r\n                List<Relative> finalRelativeAxes = relativeAxes;\r\n                NMSHandler.packetHelper.sendRelativePositionPacket(player, x, y, z, yaw, pitch, finalRelativeAxes);\r\n                DenizenCore.runAsync(() -> {\r\n                    try {\r\n                        for (int i = 0; i < times - 1; i++) {\r\n                            Thread.sleep(ms);\r\n                            NMSHandler.packetHelper.sendRelativePositionPacket(player, x, y, z, yaw, pitch, finalRelativeAxes);\r\n                        }\r\n                    }\r\n                    catch (Throwable ex) {\r\n                        Debug.echoError(ex);\r\n                    }\r\n                });\r\n                continue;\r\n            }\r\n            if (entityOptions != null || relativeAxes != null) {\r\n                PaperAPITools.instance.teleport(entity.getBukkitEntity(), location, cause, entityOptions, relativeAxes);\r\n                continue;\r\n            }\r\n            entity.teleport(location, cause);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/WalkCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.utilities.depends.Depends;\nimport com.denizenscript.denizencore.objects.Argument;\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport com.denizenscript.denizencore.scripts.commands.Holdable;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class WalkCommand extends AbstractCommand implements Holdable {\n\n    public WalkCommand() {\n        setName(\"walk\");\n        setSyntax(\"walk (<entity>|...) [<location>/stop] (speed:<#.#>) (auto_range) (radius:<#.#>) (lookat:<location>)\");\n        setRequiredArguments(1, 6);\n        if (Depends.citizens != null) {\n            Denizen.getInstance().getServer().getPluginManager().registerEvents(new WalkCommandCitizensEvents(), Denizen.getInstance());\n        }\n        isProcedural = false;\n    }\n\n    // <--[command]\n    // @Name Walk\n    // @Syntax walk (<entity>|...) [<location>/stop] (speed:<#.#>) (auto_range) (radius:<#.#>) (lookat:<location>)\n    // @Required 1\n    // @Maximum 6\n    // @Short Causes an entity or list of entities to walk to another location.\n    // @Group entity\n    //\n    // @Description\n    // Causes an entity or list of entities to walk to another location.\n    //\n    // Specify a destination location to walk to, or 'stop' to stop all walking.\n    //\n    // Optionally, specify a \"speed:<#.#>\" argument to control the speed of the NPCs.\n    //\n    // Optionally, specify \"auto_range\" to automatically set the path range for the walk instruction\n    // (if not specified, an NPC will not be able to walk to a location outside of its existing path range, by default 25 blocks).\n    // (Does not apply to non-NPC entities).\n    //\n    // Note that in most cases, the walk command should not be used for paths longer than 100 blocks.\n    // For ideal performance, keep it below 25.\n    //\n    // Optionally, specify a list of entities to give them all the same walk instruction at the same time.\n    // If the list is of NPCs, optionally specify a \"radius:<#.#>\" argument to change the flocking radius.\n    // ('Radius' does not apply to non-NPC entities).\n    //\n    // Optionally, specify \"lookat:<location>\" to cause the NPCs to stare at a specific location while walking (as opposed to straight ahead).\n    // ('Radius' does not apply to non-NPC entities).\n    //\n    // The walk command is ~waitable. Refer to <@link language ~waitable>.\n    //\n    // @Tags\n    // <NPCTag.is_navigating>\n    // <NPCTag.speed>\n    // <NPCTag.range>\n    // <NPCTag.target_location>\n    //\n    // @Usage\n    // Use to make the NPC walk to an anchored position.\n    // - walk <npc> <npc.anchor[spot1]>\n    //\n    // @Usage\n    // Use to make the NPC walk to an anchored position that may be far away.\n    // - walk <npc> <npc.anchor[spot2]> auto_range\n    //\n    // @Usage\n    // Use to make the NPC walk to an anchored position while looking backwards.\n    // - walk <npc> <npc.anchor[spot3]> lookat:<npc.anchor[spot2]>\n    //\n    // @Usage\n    // Use to make the NPC walk to an anchored position, and then say something after arrival, using ~waitable syntax.\n    // - ~walk <npc> <npc.anchor[spot4]>\n    // - chat \"I'm here!\"\n    //\n    // @Usage\n    // Use to make a list of NPCs stored in a flag all move together, with a flocking radius based on the number of NPCs included.\n    // - walk <player.flag[squad]> radius:<player.flag[squad].size> <player.location>\n    // -->\n\n    @Override\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\n        tab.addNotesOfType(LocationTag.class);\n    }\n\n    @Override\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\n        for (Argument arg : scriptEntry) {\n            if (!scriptEntry.hasObject(\"lookat\")\n                    && arg.matchesPrefix(\"lookat\")\n                    && arg.matchesArgumentType(LocationTag.class)) {\n                scriptEntry.addObject(\"lookat\", arg.asType(LocationTag.class));\n            }\n            else if (!scriptEntry.hasObject(\"speed\")\n                    && arg.matchesFloat()\n                    && arg.matchesPrefix(\"s\", \"speed\")) {\n                scriptEntry.addObject(\"speed\", arg.asElement());\n            }\n            else if (!scriptEntry.hasObject(\"auto_range\")\n                    && arg.matches(\"auto_range\")) {\n                scriptEntry.addObject(\"auto_range\", new ElementTag(true));\n            }\n            else if (!scriptEntry.hasObject(\"radius\")\n                    && arg.matchesFloat()\n                    && arg.matchesPrefix(\"radius\")) {\n                scriptEntry.addObject(\"radius\", arg.asElement());\n            }\n            else if (!scriptEntry.hasObject(\"stop\")\n                    && arg.matches(\"stop\")) {\n                scriptEntry.addObject(\"stop\", new ElementTag(true));\n            }\n            else if (!scriptEntry.hasObject(\"location\")\n                    && arg.matchesArgumentType(LocationTag.class)) {\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\n            }\n            else if (!scriptEntry.hasObject(\"entities\")\n                    && arg.matchesArgumentList(EntityTag.class)) {\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\n            }\n            else {\n                arg.reportUnhandled();\n            }\n        }\n        if (!scriptEntry.hasObject(\"location\") && !scriptEntry.hasObject(\"stop\")) {\n            throw new InvalidArgumentsException(\"Must specify a location!\");\n        }\n        if (!scriptEntry.hasObject(\"entities\")) {\n            if (Utilities.getEntryNPC(scriptEntry) == null\n                    || !Utilities.getEntryNPC(scriptEntry).isValid()\n                    || !Utilities.getEntryNPC(scriptEntry).isSpawned()) {\n                throw new InvalidArgumentsException(\"Must have a valid spawned NPC attached, or an entity specified.\");\n            }\n            else {\n                scriptEntry.addObject(\"entities\", Collections.singletonList(Utilities.getEntryNPC(scriptEntry).getDenizenEntity()));\n            }\n        }\n        scriptEntry.defaultObject(\"stop\", new ElementTag(false));\n    }\n\n    @Override\n    public void execute(ScriptEntry scriptEntry) {\n        LocationTag loc = scriptEntry.getObjectTag(\"location\");\n        ElementTag speed = scriptEntry.getElement(\"speed\");\n        ElementTag auto_range = scriptEntry.getElement(\"auto_range\");\n        ElementTag radius = scriptEntry.getElement(\"radius\");\n        ElementTag stop = scriptEntry.getElement(\"stop\");\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\n        final LocationTag lookat = scriptEntry.getObjectTag(\"lookat\");\n        if (scriptEntry.dbCallShouldDebug()) {\n            Debug.report(scriptEntry, getName(), loc, speed, auto_range, radius, lookat, stop, (db(\"entities\", entities)));\n        }\n        boolean shouldStop = stop.asBoolean();\n        List<NPCTag> npcs = new ArrayList<>();\n        final List<EntityTag> waitForEntities = new ArrayList<>();\n        for (final EntityTag entity : entities) {\n            if (entity.isCitizensNPC()) {\n                NPCTag npc = entity.getDenizenNPC();\n                npcs.add(npc);\n                if (!npc.isSpawned()) {\n                    Debug.echoError(scriptEntry, \"NPC \" + npc.identify() + \" is not spawned!\");\n                    continue;\n                }\n                if (shouldStop) {\n                    npc.getNavigator().cancelNavigation();\n                    continue;\n                }\n                if (auto_range != null && auto_range.asBoolean()) {\n                    double distance = npc.getLocation().distance(loc);\n                    if (npc.getNavigator().getLocalParameters().range() < distance + 10) {\n                        npc.getNavigator().getLocalParameters().range((float) distance + 10);\n                    }\n                }\n                npc.getNavigator().setTarget(loc);\n                if (lookat != null) {\n                    npc.getNavigator().getLocalParameters().lookAtFunction(nav -> lookat);\n                }\n                if (speed != null) {\n                    npc.getNavigator().getLocalParameters().speedModifier(speed.asFloat());\n                }\n                if (radius != null) {\n                    npc.getNavigator().getLocalParameters().addRunCallback(WalkCommandCitizensEvents.generateNewFlocker(npc.getCitizen(), radius.asDouble()));\n                }\n            }\n            else if (shouldStop) {\n                NMSHandler.entityHelper.stopWalking(entity.getBukkitEntity());\n            }\n            else {\n                waitForEntities.add(entity);\n                NMSHandler.entityHelper.walkTo(entity.getLivingEntity(), loc, speed != null ? speed.asDouble() : null,\n                        () -> checkHeld(entity));\n            }\n        }\n        if (scriptEntry.shouldWaitFor()) {\n            held.add(scriptEntry);\n            if (!npcs.isEmpty()) { // TODO: de-jank this\n                scriptEntry.addObject(\"tally\", npcs);\n            }\n            if (!waitForEntities.isEmpty()) {\n                scriptEntry.addObject(\"entities\", waitForEntities);\n            }\n        }\n    }\n\n    // Held script entries\n    public static List<ScriptEntry> held = new ArrayList<>();\n\n    public void checkHeld(EntityTag entity) {\n        for (int i = 0; i < held.size(); i++) {\n            ScriptEntry entry = held.get(i);\n            List<EntityTag> waitForEntities = (List<EntityTag>) entry.getObject(\"entities\");\n            if (waitForEntities == null) {\n                continue;\n            }\n            waitForEntities.remove(entity);\n            if (waitForEntities.isEmpty()) {\n                if (!entry.hasObject(\"tally\") || ((List<NPCTag>) entry.getObject(\"tally\")).isEmpty()) {\n                    entry.setFinished(true);\n                    held.remove(i);\n                    i--;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/entity/WalkCommandCitizensEvents.java",
    "content": "package com.denizenscript.denizen.scripts.commands.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport net.citizensnpcs.api.ai.event.NavigationCancelEvent;\r\nimport net.citizensnpcs.api.ai.event.NavigationCompleteEvent;\r\nimport net.citizensnpcs.api.ai.event.NavigationEvent;\r\nimport net.citizensnpcs.api.ai.flocking.*;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\nimport java.util.List;\r\n\r\npublic class WalkCommandCitizensEvents implements Listener {\r\n\r\n    public static double HIGH_INFLUENCE = 1.0 / 20.0;\r\n    public static double LOW_INFLUENCE = 1.0 / 200.0;\r\n\r\n    public static Flocker generateNewFlocker(NPC npc, double radius) {\r\n        NPCFlock flock = new RadiusNPCFlock(radius);\r\n        return new Flocker(npc, flock, new SeparationBehavior(LOW_INFLUENCE),\r\n                new CohesionBehavior(LOW_INFLUENCE), new AlignmentBehavior(HIGH_INFLUENCE));\r\n    }\r\n\r\n    @EventHandler\r\n    public void finish(NavigationCompleteEvent e) {\r\n        if (WalkCommand.held.isEmpty()) {\r\n            return;\r\n        }\r\n        checkHeld(e);\r\n    }\r\n\r\n    @EventHandler\r\n    public void cancel(NavigationCancelEvent e) {\r\n        if (WalkCommand.held.isEmpty()) {\r\n            return;\r\n        }\r\n        checkHeld(e);\r\n    }\r\n\r\n    public void checkHeld(NavigationEvent e) {\r\n        if (e.getNPC() == null) {\r\n            return;\r\n        }\r\n        for (int i = 0; i < WalkCommand.held.size(); i++) {\r\n            ScriptEntry entry = WalkCommand.held.get(i);\r\n            List<NPCTag> tally = (List<NPCTag>) entry.getObject(\"tally\");\r\n            if (tally == null) {\r\n                WalkCommand.held.remove(i--);\r\n                continue;\r\n            }\r\n            for (int x = 0; x < tally.size(); x++) {\r\n                if (!tally.get(x).isSpawned()) {\r\n                    tally.remove(x--);\r\n                }\r\n            }\r\n            tally.remove(new NPCTag(e.getNPC()));\r\n            if (tally.isEmpty()) {\r\n                Bukkit.getScheduler().runTaskLater(Denizen.getInstance(), () -> entry.setFinished(true), 1);\r\n                WalkCommand.held.remove(i--);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/DisplayItemCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.item;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.utilities.command.TabCompleteHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Item;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityPickupItemEvent;\r\nimport org.bukkit.event.entity.ItemDespawnEvent;\r\nimport org.bukkit.event.entity.ItemMergeEvent;\r\nimport org.bukkit.event.inventory.InventoryPickupItemEvent;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.HashSet;\r\nimport java.util.UUID;\r\n\r\npublic class DisplayItemCommand extends AbstractCommand implements Listener {\r\n\r\n    public DisplayItemCommand() {\r\n        setName(\"displayitem\");\r\n        setSyntax(\"displayitem [<item>] [<location>] (duration:<value>)\");\r\n        setRequiredArguments(2, 3);\r\n        Bukkit.getPluginManager().registerEvents(this, Denizen.getInstance());\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name DisplayItem\r\n    // @Syntax displayitem [<item>] [<location>] (duration:<value>)\r\n    // @Required 2\r\n    // @Maximum 3\r\n    // @Short Makes a non-touchable item spawn for players to view.\r\n    // @Group item\r\n    //\r\n    // @Description\r\n    // This command drops an item at the specified location which cannot be picked up by players.\r\n    //\r\n    // It accepts a duration which determines how long the item will stay for until disappearing.\r\n    // If no duration is specified the item will stay for 1 minute, after which the item will disappear.\r\n    // Use \"duration:infinite\" to indicate that the item should never remove itself.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.item>\r\n    // <entry[saveName].dropped> returns a EntityTag of the spawned item.\r\n    //\r\n    // @Usage\r\n    // Use to display a stone block dropped at a players location.\r\n    // - displayitem stone <player.location>\r\n    //\r\n    // @Usage\r\n    // Use to display a diamond sword dropped at a relevant location.\r\n    // - displayitem diamond_sword <context.location>\r\n    //\r\n    // @Usage\r\n    // Use to display redstone dust dropped at a related location disappear after 10 seconds.\r\n    // - displayitem redstone <context.location> duration:10s\r\n    //\r\n    // @Usage\r\n    // Use to save the dropped item to save entry 'item_dropped'.\r\n    // - displayitem redstone <context.location> duration:10s save:item_dropped\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        TabCompleteHelper.tabCompleteItems(tab);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matchesArgumentType(DurationTag.class)\r\n                    && !scriptEntry.hasObject(\"duration\")) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (arg.matchesArgumentType(LocationTag.class)\r\n                    && !scriptEntry.hasObject(\"location\")) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (arg.matchesArgumentType(ItemTag.class)\r\n                    && !scriptEntry.hasObject(\"item\")) {\r\n                scriptEntry.addObject(\"item\", arg.asType(ItemTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"item\")) {\r\n            throw new InvalidArgumentsException(\"Must specify an item to display.\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a location!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"duration\")) {\r\n            scriptEntry.addObject(\"duration\", new DurationTag(60));\r\n        }\r\n    }\r\n\r\n    public final HashSet<UUID> protectedEntities = new HashSet<>();\r\n\r\n    @EventHandler\r\n    public void onItemMerge(ItemMergeEvent event) {\r\n        if (protectedEntities.contains(event.getEntity().getUniqueId())\r\n                || protectedEntities.contains(event.getTarget().getUniqueId())) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void onItemInventoryPickup(InventoryPickupItemEvent event) {\r\n        if (protectedEntities.contains(event.getItem().getUniqueId())) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void onItemEntityPickup(EntityPickupItemEvent event) {\r\n        if (protectedEntities.contains(event.getItem().getUniqueId())) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n\r\n    public void onItemDespawn(ItemDespawnEvent event) {\r\n        if (protectedEntities.contains(event.getEntity().getUniqueId())) {\r\n            event.setCancelled(true);\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> {\r\n                if (event.getEntity().isValid() && !event.getEntity().isDead()) {\r\n                    NMSHandler.entityHelper.setTicksLived(event.getEntity(), -1000);\r\n                }\r\n            }, 1);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ItemTag item = scriptEntry.getObjectTag(\"item\");\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), item, duration, location);\r\n        }\r\n        // Drop the item\r\n        final Item dropped = location.getWorld().dropItem(location.getBlockLocation().clone().add(0.5, 1.5, 0.5), item.getItemStack());\r\n        dropped.setVelocity(new Vector(0, 0, 0));\r\n        dropped.setGravity(false);\r\n        dropped.setPickupDelay(32767);\r\n        int ticks = duration.getTicksAsInt();\r\n        NMSHandler.entityHelper.setTicksLived(dropped, ticks <= 0 ? -32768 : -ticks);\r\n        if (!dropped.isValid()) {\r\n            Debug.echoDebug(scriptEntry, \"Item failed to spawned (likely blocked by some plugin).\");\r\n            return;\r\n        }\r\n        final UUID itemUUID = dropped.getUniqueId();\r\n        protectedEntities.add(itemUUID);\r\n        scriptEntry.saveObject(\"dropped\", new EntityTag(dropped));\r\n        if (ticks > 0) {\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(),\r\n                    () -> {\r\n                        protectedEntities.remove(itemUUID);\r\n                        if (dropped.isValid() && !dropped.isDead()) {\r\n                            dropped.remove();\r\n                        }\r\n                    }, ticks);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/FakeItemCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.item;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.command.TabCompleteHelper;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryViewUtil;\r\nimport com.denizenscript.denizen.utilities.inventory.SlotHelper;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.scheduling.OneTimeSchedulable;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.CraftingInventory;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryView;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class FakeItemCommand extends AbstractCommand {\r\n\r\n    public FakeItemCommand() {\r\n        setName(\"fakeitem\");\r\n        setSyntax(\"fakeitem [<item>|...] [slot:<slot>] (duration:<duration>) (players:<player>|...) (raw)\");\r\n        setRequiredArguments(2, 5);\r\n        isProcedural = false;\r\n        setBooleansHandled(\"raw\");\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name FakeItem\r\n    // @Syntax fakeitem [<item>|...] [slot:<slot>] (duration:<duration>) (players:<player>|...) (raw)\r\n    // @Required 2\r\n    // @Maximum 5\r\n    // @Short Show a fake item in a player's inventory.\r\n    // @Group item\r\n    //\r\n    // @Description\r\n    // This command allows you to display an item in an inventory that is not really there.\r\n    //\r\n    // To make it automatically disappear at a specific time, use the 'duration:' argument.\r\n    // Note that the reset can be unreliable, especially if the player changes their open inventory view. Consider using \"- inventory update\" after a delay instead.\r\n    //\r\n    // By default, it will use any inventory the player currently has open.\r\n    //\r\n    // Slots function as follows:\r\n    // Player inventory is slots 1-36, same as normal inventory slot indices.\r\n    // If the player has an open inventory, to apply the item to a slot in that inventory, add 36 to the slot index.\r\n    // If the player does not have an open inventory, slots 36-40 are equipment, 41 is offhand, 42 is recipe result, 43-46 are recipe.\r\n    //\r\n    // For modifying equipment, consider <@link mechanism PlayerTag.fake_equipment> instead.\r\n    //\r\n    // The slot argument can be any valid slot, see <@link language Slot Inputs>.\r\n    //\r\n    // Optionally specify 'raw' to indicate that the slow is a raw network slot ID.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to show a clientside-only pumpkin on the player's head.\r\n    // - fakeitem pumpkin slot:head\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        TabCompleteHelper.tabCompleteItems(tab);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"slot\")\r\n                    && arg.matchesPrefix(\"slot\")) {\r\n                scriptEntry.addObject(\"slot\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"duration\")\r\n                    && arg.matchesPrefix(\"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"item\")\r\n                    && arg.matchesArgumentList(ItemTag.class)) {\r\n                scriptEntry.addObject(\"item\", arg.asType(ListTag.class).filter(ItemTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"players\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)\r\n                    && arg.matchesPrefix(\"players\")) {\r\n                scriptEntry.addObject(\"players\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"item\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a valid item to fake!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"slot\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a valid slot!\");\r\n        }\r\n        scriptEntry.defaultObject(\"duration\", new DurationTag(0))\r\n                .defaultObject(\"players\", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry)));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        boolean raw = scriptEntry.argAsBoolean(\"raw\");\r\n        List<ItemTag> items = (List<ItemTag>) scriptEntry.getObject(\"item\");\r\n        final ElementTag elSlot = scriptEntry.getElement(\"slot\");\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        final List<PlayerTag> players = (List<PlayerTag>) scriptEntry.getObject(\"players\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"items\", items), elSlot, duration, db(\"players\", players), db(\"raw\", raw));\r\n        }\r\n        if (players.size() == 0) {\r\n            return;\r\n        }\r\n        int slot = SlotHelper.nameToIndex(elSlot.asString(), players.get(0).getPlayerEntity());\r\n        if (slot == -1) {\r\n            Debug.echoError(scriptEntry, \"The input '\" + elSlot.asString() + \"' is not a valid slot!\");\r\n            return;\r\n        }\r\n        for (ItemTag item : items) {\r\n            if (item == null) {\r\n                slot++;\r\n                continue;\r\n            }\r\n            int slotSnapshot = slot;\r\n            for (PlayerTag player : players) {\r\n                final Player ent = player.getPlayerEntity();\r\n                final int translated = raw ? slot : translateSlot(ent, slot);\r\n                final InventoryView view = ent.getOpenInventory();\r\n                final Inventory top = InventoryViewUtil.getTopInventory(view);\r\n                NMSHandler.packetHelper.setSlot(ent, translated, item.getItemStack(), false);\r\n                if (duration.getSeconds() > 0) {\r\n                    DenizenCore.schedule(new OneTimeSchedulable(() -> {\r\n                        if (!ent.isOnline()) {\r\n                            return;\r\n                        }\r\n                        if (top == InventoryViewUtil.getTopInventory(view)) {\r\n                            ItemStack original = InventoryViewUtil.getItem(view, translated);\r\n                            NMSHandler.packetHelper.setSlot(ent, translated, original, false);\r\n                        }\r\n                        else if (slotSnapshot < 36) {\r\n                            NMSHandler.packetHelper.setSlot(ent, translateSlot(ent, slotSnapshot), ent.getInventory().getItem(slotSnapshot), false);\r\n                        }\r\n                    }, (float) duration.getSeconds()));\r\n                }\r\n            }\r\n            slot++;\r\n        }\r\n    }\r\n\r\n    static int translateSlot(Player player, int slot) {\r\n        // This translates Spigot slot standards to vanilla slots.\r\n        // The slot order is different when a player is viewing an inventory vs not doing so, leading to this chaos.\r\n        int topSize = InventoryViewUtil.getTopInventory(player.getOpenInventory()).getSize();\r\n        if (InventoryViewUtil.getTopInventory(player.getOpenInventory()) instanceof CraftingInventory) {\r\n            topSize = 9;\r\n            if (slot > 35) {\r\n                if (slot < 40) { // Armor equipment\r\n                    return 8 - (slot - 36);\r\n                }\r\n                else if (slot == 40) { // Offhand\r\n                    return 45;\r\n                }\r\n                else if (slot < 46) { // Recipe (Server slot IDs for this are effectively made up just to be linearly on the end)\r\n                    return slot - 41;\r\n                }\r\n            }\r\n        }\r\n        int result;\r\n        int total = 36 + topSize;\r\n        int rowCount = (int) Math.ceil(total / 9.0);\r\n        if (slot < 9) { // First row on server is last row on client\r\n            int row = (int) Math.floor(slot / 9.0);\r\n            int flippedRow = rowCount - row - 1;\r\n            result = flippedRow * 9 + slot;\r\n        }\r\n        else if (slot < 36) { // player inv insides on client come after the top inv\r\n            result = slot + (rowCount - 5) * 9;\r\n        }\r\n        else { // Top-inv slots are same server/client, but offset by the size of player inv\r\n            result = slot - 36;\r\n        }\r\n        if (result < 0) {\r\n            return 0;\r\n        }\r\n        if (result > total) {\r\n            return total - 1;\r\n        }\r\n        return result;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/GiveCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.item;\n\nimport com.denizenscript.denizen.objects.InventoryTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizen.utilities.command.TabCompleteHelper;\nimport com.denizenscript.denizen.utilities.inventory.SlotHelper;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport org.bukkit.Location;\nimport org.bukkit.Material;\nimport org.bukkit.inventory.ItemStack;\n\nimport java.util.List;\n\npublic class GiveCommand extends AbstractCommand {\n\n    public GiveCommand() {\n        setName(\"give\");\n        setSyntax(\"give [<item>|...] (quantity:<#>) (unlimit_stack_size) (to:<inventory>) (slot:<slot>) (allowed_slots:<slot-matcher>) (ignore_leftovers)\");\n        setRequiredArguments(1, 7);\n        isProcedural = false;\n        addRemappedPrefixes(\"to\", \"t\");\n        autoCompile();\n    }\n\n    // <--[command]\n    // @Name Give\n    // @Syntax give [<item>|...] (quantity:<#>) (unlimit_stack_size) (to:<inventory>) (slot:<slot>) (allowed_slots:<slot-matcher>) (ignore_leftovers)\n    // @Required 1\n    // @Maximum 7\n    // @Short Gives the player an item or xp.\n    // @Group item\n    //\n    // @Description\n    // Gives the linked player items.\n    //\n    // Optionally specify a slot to put the items into. If the slot is already filled, the next available slot will be used.\n    // If the inventory is full, the items will be dropped on the ground at the inventory's location.\n    // For player inventories, only the storage contents are valid - to equip armor or an offhand item, use <@link command equip>.\n    //\n    // Specifying \"unlimit_stack_size\" will allow an item to stack up to 64. This is useful for stacking items\n    // with a max stack size that is less than 64 (for example, most weapon and armor items have a stack size of 1).\n    //\n    // When giving an item, you can specify any valid inventory as a target. If unspecified, the linked player's inventory will be used.\n    // You may optionally specify a \"slot\" as any valid slot input per <@link language Slot Inputs> to be the starting slot index.\n    // You may optionally specify \"allowed_slots\" to forcibly restrict the item to only be given to certain specific slots that match a slot-matcher.\n    // You may optionally specify \"ignore_leftovers\" to cause leftover items to be ignored. If not specified, leftover items will be dropped.\n    //\n    // To give xp to a player, use <@link command experience>.\n    // To give money to a player, use <@link command money>.\n    //\n    // @Tags\n    // <PlayerTag.inventory>\n    // <entry[saveName].leftover_items> returns a ListTag of any item(s) that didn't fit into the inventory.\n    //\n    // @Usage\n    // Use to give an item to the player.\n    // - give iron_sword\n    //\n    // @Usage\n    // Use to give an item and place it in a specific slot if possible.\n    // - give WATCH slot:5\n    //\n    // @Usage\n    // Use to give an item to some other defined player.\n    // - give diamond player:<[target]>\n    // -->\n\n    @Override\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\n        TabCompleteHelper.tabCompleteItems(tab);\n    }\n\n    public static void autoExecute(ScriptEntry scriptEntry,\n                                   @ArgName(\"quantity\") @ArgPrefixed @ArgDefaultText(\"-1\") double quantity,\n                                   @ArgName(\"unlimit_stack_size\") boolean unlimitStackSize,\n                                   @ArgName(\"ignore_leftovers\") boolean ignoreLeftovers,\n                                   @ArgName(\"allowed_slots\") @ArgPrefixed @ArgDefaultNull String allowedSlots,\n                                   @ArgName(\"to\") @ArgPrefixed @ArgDefaultNull InventoryTag inventory,\n                                   @ArgName(\"slot\") @ArgPrefixed @ArgDefaultText(\"1\") String slot,\n                                   @ArgName(\"items\") @ArgLinear @ArgSubType(ItemTag.class) List<ItemTag> items) {\n        ListTag leftoverSave = new ListTag();\n        if (inventory == null) {\n            if (!Utilities.entryHasPlayer(scriptEntry)) {\n                throw new InvalidArgumentsRuntimeException(\"Must specify an inventory to give to!\");\n            }\n            inventory = Utilities.getEntryPlayer(scriptEntry).getInventory();\n        }\n        boolean limited = !unlimitStackSize;\n        for (ItemTag item : items) {\n            ItemStack is = new ItemStack(item.getItemStack());\n            if (is.getType() == Material.AIR) {\n                Debug.echoError(\"Cannot give air!\");\n                continue;\n            }\n            if (quantity >= 0) {\n                is.setAmount((int) quantity);\n            }\n            int slotId = SlotHelper.nameToIndexFor(slot, inventory.getInventory().getHolder());\n            if (slotId == -1) {\n                Debug.echoError(\"The input '\" + slot + \"' is not a valid slot!\");\n                return;\n            }\n            List<ItemStack> leftovers = inventory.addWithLeftovers(slotId, allowedSlots, limited, is);\n            for (ItemStack extraItem : leftovers) {\n                leftoverSave.addObject(new ItemTag(extraItem));\n            }\n            if (!leftovers.isEmpty() && !ignoreLeftovers) {\n                Debug.echoDebug(scriptEntry, \"The inventory didn't have enough space, the rest of the items have been placed on the floor.\");\n                Location inventoryLocation = inventory.getLocation();\n                if (inventoryLocation == null) {\n                    Debug.echoError(\"Cannot drop extras from failed give command - no inventory location.\");\n                    return;\n                }\n                for (ItemStack leftoverItem : leftovers) {\n                    inventoryLocation.getWorld().dropItem(inventoryLocation, leftoverItem);\n                }\n            }\n        }\n        scriptEntry.saveObject(\"leftover_items\", leftoverSave);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/InventoryCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.item;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.InventoryScriptHelper;\r\nimport com.denizenscript.denizen.utilities.Conversion;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryTrackerSystem;\r\nimport com.denizenscript.denizen.utilities.inventory.InventoryViewUtil;\r\nimport com.denizenscript.denizen.utilities.inventory.SlotHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.TimeTag;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.core.FlagCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.data.DataAction;\r\nimport com.denizenscript.denizencore.utilities.data.DataActionHelper;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.AbstractHorse;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.InventoryOpenEvent;\r\nimport org.bukkit.event.inventory.InventoryType;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryView;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.AbstractMap;\r\nimport java.util.HashSet;\r\nimport java.util.List;\r\nimport java.util.function.Consumer;\r\n\r\npublic class InventoryCommand extends AbstractCommand implements Listener {\r\n\r\n    public InventoryCommand() {\r\n        setName(\"inventory\");\r\n        setSyntax(\"inventory [open/close/copy/move/swap/set/keep/exclude/fill/clear/update/adjust <mechanism>:<value>/flag <name>(:<action>)[:<value>] (expire:<time>)] (destination:<inventory>) (origin:<inventory>/<item>|...) (slot:<slot>)\");\r\n        setRequiredArguments(1, 6);\r\n        isProcedural = false;\r\n        allowedDynamicPrefixes = true;\r\n        Bukkit.getPluginManager().registerEvents(this, Denizen.instance);\r\n        autoCompile();\r\n        addRemappedPrefixes(\"origin\", \"o\", \"source\", \"items\", \"i\", \"from\", \"f\");\r\n        addRemappedPrefixes(\"destination\", \"dest\", \"d\", \"target\", \"to\", \"t\");\r\n        addRemappedPrefixes(\"slot\", \"s\");\r\n        addRemappedPrefixes(\"expiration\", \"expires\", \"expire\", \"duration\");\r\n    }\r\n\r\n    // <--[language]\r\n    // @name Virtual Inventories\r\n    // @group Inventory System\r\n    // @description\r\n    // Virtual inventories are inventories that have no attachment to anything within the world of Minecraft.\r\n    // They can be used for a wide range of purposes - from looting fallen enemies to serving as interactive menus with item 'buttons'.\r\n    //\r\n    // In Denizen, all noted inventories (saved by the Note command) are automatically converted into a virtual copy of the saved inventory.\r\n    // This enables you to open and edit the items inside freely, with automatic saving, as if it were a normal inventory.\r\n    //\r\n    // Noting is not the only way to create virtual inventories, however.\r\n    // Using 'generic' along with inventory properties will allow you to create temporary custom inventories to do with as you please.\r\n    // The properties that can be used like this are:\r\n    //\r\n    // size=<size>\r\n    // contents=<item>|...\r\n    // title=<title>\r\n    // holder=<inventory type>\r\n    //\r\n    // For example, the following task script opens a virtual inventory with 18 slots,\r\n    // where the second slot is a snowball, all the rest are empty, and the title is \"My Awesome Inventory\" with some colors in it.\r\n    // <code>\r\n    // open random inventory:\r\n    //   type: task\r\n    //   script:\r\n    //   - inventory open \"d:generic[size=18;title=<red>My <green>Awesome <blue>Inventory;contents=air|snowball]\"\r\n    // </code>\r\n    //\r\n    // -->\r\n\r\n    // <--[extension]\r\n    // @name Inventory Adjust Extension\r\n    // @target_type command\r\n    // @target_name Adjust\r\n    // @description\r\n    // To adjust an item in an inventory, use <@link command inventory>, as '- inventory adjust slot:<#> <mechanism>:<value>'.\r\n    // Note that that is only for items, not actual inventories.\r\n    // To adjust an actual InventoryTag mechanism, you should still use the normal 'adjust' command, not 'inventory adjust'.\r\n    // -->\r\n\r\n    // <--[command]\r\n    // @Name Inventory\r\n    // @Syntax inventory [open/close/copy/move/swap/set/keep/exclude/fill/clear/update/adjust <mechanism>:<value>/flag <name>(:<action>)[:<value>] (expire:<time>)] (destination:<inventory>) (origin:<inventory>/<item>|...) (slot:<slot>)\r\n    // @Required 1\r\n    // @Maximum 6\r\n    // @Short Edits the inventory of a player, NPC, or chest.\r\n    // @Group item\r\n    //\r\n    // @Description\r\n    // Use this command to edit the state of inventories.\r\n    // By default, the destination inventory is the current attached player's inventory.\r\n    //\r\n    // If you are copying, swapping, removing from (including via \"keep\" and \"exclude\"), adding to, moving, or filling inventories,\r\n    // you'll need both destination and origin inventories.\r\n    //\r\n    // Origin inventories may be specified as a list of ItemTags, but destinations must be actual InventoryTags.\r\n    //\r\n    // Using \"open\", \"clear\", or \"update\" only require a destination.\r\n    // \"Update\" also requires the destination to be a valid player inventory.\r\n    //\r\n    // Using \"close\" closes any inventory that the currently attached player has opened.\r\n    //\r\n    // The \"adjust\" option adjusts mechanisms on an item within a specific slot of an inventory (the \"slot\" parameter is required).\r\n    // Note that this is only for items, it does NOT adjust the inventory itself. Use <@link command adjust> to adjust an inventory mechanism.\r\n    //\r\n    // The \"flag\" option sets a flag on items, similar to <@link command flag>.\r\n    // See also <@link language flag system>.\r\n    //\r\n    // The \"update\" option will refresh the client's view of an inventory to match the server's view, which is useful to workaround some sync bugs.\r\n    //\r\n    // Note that to add items to an inventory, you should usually use <@link command give>,\r\n    // and to remove items from an inventory, you should usually use <@link command take>.\r\n    //\r\n    // The slot argument can be any valid slot, see <@link language Slot Inputs>.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.inventory>\r\n    // <PlayerTag.enderchest>\r\n    // <PlayerTag.open_inventory>\r\n    // <NPCTag.inventory>\r\n    // <LocationTag.inventory>\r\n    //\r\n    // @Usage\r\n    // Use to open a chest inventory, at a location.\r\n    // - inventory open d:<context.location>\r\n    //\r\n    // @Usage\r\n    // Use to open a virtual inventory with a title and some items.\r\n    // - inventory open d:generic[size=27;title=BestInventory;contents=snowball|stick]\r\n    //\r\n    // @Usage\r\n    // Use to open another player's inventory.\r\n    // - inventory open d:<[player].inventory>\r\n    //\r\n    // @Usage\r\n    // Use to remove all items from a chest, except any items in the specified list.\r\n    // - inventory keep d:<context.location.inventory> o:snowball|ItemScript\r\n    //\r\n    // @Usage\r\n    // Use to remove all sticks and stones from the player's inventory.\r\n    // - inventory exclude origin:stick|stone\r\n    //\r\n    // @Usage\r\n    // Use to clear the player's inventory entirely.\r\n    // - inventory clear\r\n    //\r\n    // @Usage\r\n    // Use to swap two players' inventories.\r\n    // - inventory swap d:<[playerOne].inventory> o:<[playerTwo].inventory>\r\n    //\r\n    // @Usage\r\n    // Use to adjust a specific item in the player's inventory.\r\n    // - inventory adjust slot:5 \"lore:Item modified!\"\r\n    //\r\n    // @Usage\r\n    // Use to set a single stick into slot 10 of the player's inventory.\r\n    // - inventory set o:stick slot:10\r\n    //\r\n    // @Usage\r\n    // Use to set a temporary flag on the player's held item.\r\n    // - inventory flag slot:hand my_target:<player.cursor_on> expire:1d\r\n    // -->\r\n\r\n    public enum Action {OPEN, CLOSE, COPY, MOVE, SWAP, SET, KEEP, EXCLUDE, FILL, CLEAR, UPDATE, ADJUST, FLAG}\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.add(PropertyParser.propertiesByClass.get(ItemTag.class).propertiesByMechanism.keySet());\r\n        if (tab.arg.contains(\":\")) {\r\n            Consumer<String> addAll = (s) -> {\r\n                tab.add(\"o:\" + s);\r\n                tab.add(\"origin:\" + s);\r\n                tab.add(\"d:\" + s);\r\n                tab.add(\"dest:\" + s);\r\n                tab.add(\"destination:\" + s);\r\n            };\r\n            for (InventoryTag inventory : (HashSet<InventoryTag>) ((HashSet) NoteManager.notesByType.get(InventoryTag.class))) {\r\n                addAll.accept(inventory.noteName);\r\n            }\r\n            for (String script : InventoryScriptHelper.inventoryScripts.keySet()) {\r\n                addAll.accept(script);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static Player currentAltPlayer;\r\n    public static Location currentAltLocation;\r\n    public static String currentAltTitle, currentAltType;\r\n    public static ObjectTag currentAltHolder;\r\n\r\n    @EventHandler(priority = EventPriority.LOWEST)\r\n    public void onOpen(InventoryOpenEvent event) {\r\n        if (currentAltHolder == null || currentAltPlayer == null) {\r\n            return;\r\n        }\r\n        if (event.getInventory().getLocation() == null || currentAltLocation.distanceSquared(event.getInventory().getLocation()) > 1) {\r\n            return;\r\n        }\r\n        if (!event.getPlayer().getUniqueId().equals(currentAltPlayer.getUniqueId())) {\r\n            return;\r\n        }\r\n        if (currentAltTitle != null && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n            NMSHandler.getInstance().setInventoryTitle(event.getView(), currentAltTitle);\r\n        }\r\n        InventoryTag newTag = new InventoryTag(event.getInventory(), currentAltType, currentAltHolder);\r\n        InventoryTrackerSystem.trackTemporaryInventory(event.getInventory(), newTag);\r\n    }\r\n\r\n    public static void doSpecialOpen(InventoryType type, Player player, InventoryTag destination) {\r\n        try {\r\n            if (destination.customTitle != null || destination.idType.equals(\"script\")) {\r\n                currentAltType = destination.getIdType();\r\n                currentAltTitle = destination.customTitle;\r\n                currentAltHolder = destination.getIdHolder();\r\n                currentAltPlayer = player;\r\n                currentAltLocation = player.getLocation();\r\n                currentAltLocation.setX(currentAltLocation.getBlockX());\r\n                currentAltLocation.setZ(currentAltLocation.getBlockZ());\r\n                currentAltLocation.setY(-1000);\r\n            }\r\n            InventoryView view;\r\n            if (type == InventoryType.ANVIL) {\r\n                view = PaperAPITools.instance.openAnvil(player, currentAltLocation);\r\n            }\r\n            else if (type == InventoryType.WORKBENCH) {\r\n                view = player.openWorkbench(currentAltLocation, true);\r\n            }\r\n            else {\r\n                return;\r\n            }\r\n            Inventory newInv = InventoryViewUtil.getTopInventory(view);\r\n            newInv.setContents(destination.getContents());\r\n        }\r\n        finally {\r\n            currentAltHolder = null;\r\n            currentAltType = null;\r\n            currentAltPlayer = null;\r\n            currentAltLocation = null;\r\n            currentAltTitle = null;\r\n        }\r\n    }\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"actions\") @ArgLinear @ArgSubType(Action.class) List<Action> actions,\r\n                                   @ArgName(\"origin\") @ArgRaw @ArgPrefixed @ArgDefaultNull String originString,\r\n                                   @ArgName(\"destination\") @ArgRaw @ArgPrefixed @ArgDefaultNull String destinationString,\r\n                                   @ArgName(\"slot\") @ArgPrefixed @ArgDefaultNull String slot,\r\n                                   @ArgName(\"expiration\") @ArgPrefixed @ArgDefaultNull ObjectTag expiration,\r\n                                   @ArgName(\"data_action\") @ArgRaw @ArgLinear @ArgDefaultNull String dataAction) {\r\n        if (slot == null) {\r\n            if (actions.contains(Action.ADJUST)) {\r\n                throw new InvalidArgumentsRuntimeException(\"Inventory adjust must have an explicit slot!\");\r\n            }\r\n            else {\r\n                slot = \"1\";\r\n            }\r\n        }\r\n        TimeTag expire = null;\r\n        if (actions.contains(Action.FLAG) && expiration != null) {\r\n            if (expiration.canBeType(DurationTag.class)) {\r\n                TimeTag now = TimeTag.now();\r\n                expire = new TimeTag(now.millis() + expiration.asType(DurationTag.class, scriptEntry.context).getMillis(), now.instant.getZone());\r\n            }\r\n            else if (expiration.canBeType(TimeTag.class)) {\r\n                expire = expiration.asType(TimeTag.class, scriptEntry.context);\r\n            }\r\n            else {\r\n                throw new InvalidArgumentsRuntimeException(\"Flag expiration is not a DurationTag or TimeTag!\");\r\n            }\r\n        }\r\n        AbstractMap.SimpleEntry<Integer, InventoryTag> originEntry = originString != null ? Conversion.getInventory(new Argument(originString), scriptEntry) : null;\r\n        AbstractMap.SimpleEntry<Integer, InventoryTag> destinationEntry = destinationString != null ? Conversion.getInventory(new Argument(destinationString), scriptEntry) : null;\r\n        if (destinationEntry == null) {\r\n            InventoryTag inv = Utilities.getEntryPlayer(scriptEntry).getInventory();\r\n            if (inv == null) {\r\n                throw new InvalidArgumentsRuntimeException(\"Must specify a Destination Inventory!\");\r\n            }\r\n            destinationEntry = new AbstractMap.SimpleEntry<>(0, inv);\r\n        }\r\n        InventoryTag origin = originEntry != null ? originEntry.getValue() : null;\r\n        InventoryTag destination = destinationEntry.getValue();\r\n        int slotId = SlotHelper.nameToIndexFor(slot, destination.getInventory().getHolder());\r\n        if (slotId < 0) {\r\n            if (slotId == -1) {\r\n                throw new InvalidArgumentsRuntimeException(\"The input '\" + slot + \"' is not a valid slot (unrecognized)!\");\r\n            }\r\n            else {\r\n                throw new InvalidArgumentsRuntimeException(\"The input '\" + slot + \"' is not a valid slot (negative values are invalid)!\");\r\n            }\r\n        }\r\n        InventoryTag.trackTemporaryInventory(destination);\r\n        if (origin != null) {\r\n            InventoryTag.trackTemporaryInventory(origin);\r\n        }\r\n        PlayerTag player = Utilities.getEntryPlayer(scriptEntry);\r\n        for (Action action : actions) {\r\n            switch (action) {\r\n                // Make the attached player open the destination inventory\r\n                case OPEN:\r\n                    // Use special method to make opening workbenches and anvils work properly\r\n                    if ((destination.getInventoryType() == InventoryType.WORKBENCH || (destination.getInventoryType() == InventoryType.ANVIL && Denizen.supportsPaper)) && destination.getInventory().getLocation() == null) {\r\n                        doSpecialOpen(destination.getInventoryType(), player.getPlayerEntity(), destination);\r\n                    }\r\n                    // Also check if the holder is a horse to do special NMS inventory open\r\n                    else if (destination.getIdHolder() instanceof EntityTag entity && entity.getLivingEntity() instanceof AbstractHorse horse) {\r\n                        NMSHandler.entityHelper.openHorseInventory(player.getPlayerEntity(), horse);\r\n                    }\r\n                    // Otherwise, open inventory as usual\r\n                    else {\r\n                        player.getPlayerEntity().openInventory(destination.getInventory());\r\n                    }\r\n                    break;\r\n                // Make the attached player close any open inventory\r\n                case CLOSE:\r\n                    player.getPlayerEntity().closeInventory();\r\n                    break;\r\n                // Turn destination's contents into a copy of origin's\r\n                case COPY:\r\n                    if (origin == null) {\r\n                        throw new InvalidArgumentsRuntimeException(\"Missing origin argument!\");\r\n                    }\r\n                    replace(origin, destination);\r\n                    break;\r\n                // Copy origin's contents to destination, then empty origin\r\n                case MOVE:\r\n                    if (origin == null) {\r\n                        throw new InvalidArgumentsRuntimeException(\"Missing origin argument!\");\r\n                    }\r\n                    replace(origin, destination);\r\n                    origin.clear();\r\n                    break;\r\n                // Swap the contents of the two inventories\r\n                case SWAP:\r\n                    if (origin == null) {\r\n                        throw new InvalidArgumentsRuntimeException(\"Missing origin argument!\");\r\n                    }\r\n                    InventoryTag temp = new InventoryTag(destination.getInventory().getContents());\r\n                    replace(origin, destination);\r\n                    replace(temp, origin);\r\n                    break;\r\n                // Set items by slot\r\n                case SET:\r\n                    if (origin == null) {\r\n                        throw new InvalidArgumentsRuntimeException(\"Missing origin argument!\");\r\n                    }\r\n                    destination.setSlots(slotId, origin.getContents(), originEntry.getKey());\r\n                    break;\r\n                // Keep only items from the origin's contents in the destination\r\n                case KEEP: {\r\n                    if (origin == null) {\r\n                        throw new InvalidArgumentsRuntimeException(\"Missing origin argument!\");\r\n                    }\r\n                    ItemStack[] items = origin.getContents();\r\n                    for (ItemStack invStack : destination.getInventory()) {\r\n                        if (invStack != null) {\r\n                            boolean keep = false;\r\n                            // See if the item array contains\r\n                            // this inventory item\r\n                            for (ItemStack item : items) {\r\n                                if (invStack.isSimilar(item)) {\r\n                                    keep = true;\r\n                                    break;\r\n                                }\r\n                            }\r\n                            // If the item array did not contain\r\n                            // this inventory item, remove it\r\n                            // from the inventory\r\n                            if (!keep) {\r\n                                destination.getInventory().remove(invStack);\r\n                            }\r\n                        }\r\n                    }\r\n                    break;\r\n                }\r\n                // Exclude all items from the origin's contents in the destination\r\n                case EXCLUDE: {\r\n                    if (origin == null) {\r\n                        throw new InvalidArgumentsRuntimeException(\"Missing origin argument!\");\r\n                    }\r\n                    int oldCount = destination.count(null, false);\r\n                    int newCount = -1;\r\n                    while (oldCount != newCount) {\r\n                        oldCount = newCount;\r\n                        remove(destination.getInventory(), origin.getContents());\r\n                        newCount = destination.count(null, false);\r\n                    }\r\n                    break;\r\n                }\r\n                // Add origin's contents over and over to destination until it is full\r\n                case FILL: {\r\n                    if (origin == null) {\r\n                        throw new InvalidArgumentsRuntimeException(\"Missing origin argument!\");\r\n                    }\r\n                    int oldCount = destination.count(null, false);\r\n                    int newCount = -1;\r\n                    while (oldCount != newCount) {\r\n                        oldCount = newCount;\r\n                        newCount = destination.add(0, origin.getContents()).count(null, false);\r\n                    }\r\n                    break;\r\n                }\r\n                // Clear the content of the destination inventory\r\n                case CLEAR:\r\n                    destination.clear();\r\n                    break;\r\n                // If this is a player inventory, update it\r\n                case UPDATE:\r\n                    if (destination.idHolder instanceof PlayerTag) {\r\n                        ((PlayerTag) destination.idHolder).getPlayerEntity().updateInventory();\r\n                    }\r\n                    else {\r\n                        throw new InvalidArgumentsRuntimeException(\"Only player inventories can be force-updated!\");\r\n                    }\r\n                    break;\r\n                case ADJUST:\r\n                    if (dataAction == null) {\r\n                        throw new InvalidArgumentsRuntimeException(\"Inventory adjust must have a mechanism!\");\r\n                    }\r\n                    ItemTag toAdjust = new ItemTag(destination.getInventory().getItem(slotId));\r\n                    Argument mechanismArgument = new Argument(dataAction);\r\n                    boolean hasValue = mechanismArgument.hasPrefix();\r\n                    toAdjust.safeAdjust(new Mechanism(hasValue ? mechanismArgument.getPrefix().getValue() : mechanismArgument.getValue(), hasValue ? mechanismArgument.object : null, scriptEntry.getContext()));\r\n                    NMSHandler.itemHelper.setInventoryItem(destination.getInventory(), toAdjust.getItemStack(), slotId);\r\n                    break;\r\n                case FLAG:\r\n                    if (dataAction == null) {\r\n                        throw new InvalidArgumentsRuntimeException(\"Inventory flag must have a flag action!\");\r\n                    }\r\n                    ItemTag toFlag = new ItemTag(destination.getInventory().getItem(slotId));\r\n                    Argument flagArgument = new Argument(dataAction);\r\n                    DataAction flagAction = DataActionHelper.parse(new FlagCommand.FlagActionProvider(), flagArgument, scriptEntry.context);\r\n                    FlagCommand.FlagActionProvider provider = (FlagCommand.FlagActionProvider) flagAction.provider;\r\n                    provider.expiration = expire;\r\n                    provider.tracker = toFlag.getFlagTracker();\r\n                    flagAction.execute(scriptEntry.context);\r\n                    toFlag.reapplyTracker(provider.tracker);\r\n                    NMSHandler.itemHelper.setInventoryItem(destination.getInventory(), toFlag.getItemStack(), slotId);\r\n                    break;\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void replace(InventoryTag origin, InventoryTag destination) {\r\n        // If the destination is smaller than our current inventory, add as many items as possible\r\n        if (destination.getSize() < origin.getSize()) {\r\n            destination.clear();\r\n            destination.add(0, origin.getContents());\r\n        }\r\n        else {\r\n            destination.setContents(origin.getContents());\r\n        }\r\n    }\r\n\r\n    public static void remove(Inventory inventory, ItemStack[] items) {\r\n        for (ItemStack item : items) {\r\n            if (item != null) {\r\n                inventory.removeItem(item.clone());\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/MapCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.item;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.MapScriptContainer;\r\nimport com.denizenscript.denizen.utilities.maps.*;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.map.MapRenderer;\r\nimport org.bukkit.map.MapView;\r\n\r\npublic class MapCommand extends AbstractCommand {\r\n\r\n    public MapCommand() {\r\n        setName(\"map\");\r\n        setSyntax(\"map [<#>/new:<world>] (reset:<location>/reset_to_blank) (scale:<value>) (tracking) (image:<file>) (resize) (script:<script>) (dot:<color>) (radius:<#>) (x:<#>) (y:<#>) (text:<text>) (width:<#>) (height:<#>)\");\r\n        setRequiredArguments(2, 14);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Map\r\n    // @Syntax map [<#>/new:<world>] (reset:<location>/reset_to_blank) (scale:<value>) (tracking) (image:<file>) (resize) (script:<script>) (dot:<color>) (radius:<#>) (x:<#>) (y:<#>) (text:<text>) (width:<#>) (height:<#>)\r\n    // @Required 2\r\n    // @Maximum 14\r\n    // @Short Modifies a new or existing map by adding images or text.\r\n    // @Group item\r\n    //\r\n    // @Description\r\n    // This command modifies an existing map, or creates a new one. Using this will override existing non-Denizen map renderers with Denizen's custom map renderer.\r\n    //\r\n    // You must specify at least one of 'reset', 'reset_to_blank', 'script', 'image', 'dot', 'text'. You can specify multiple at once if you prefer.\r\n    //\r\n    // When using 'reset' or 'reset_to_blank', you can specify optionally 'scale' and/or 'tracking'.\r\n    // When using 'image' you can optionally specify 'resize' or 'width:<#>' and 'height:<#>'.\r\n    // When using 'dot', you can specify any valid ColorTag (it will be compressed to map's color space), and you can optionally also specify 'radius' as a number.\r\n    //    Use \"radius:0\" with dot to set on a single pixel. 1 or higher will make a circle centered on the x/y given.\r\n    //\r\n    // You can reset this at any time by using the 'reset:<location>' argument, which will remove all\r\n    // images and texts on the map and show the default world map at the specified location.\r\n    // You can also specify 'reset_to_blank' to reset without specified location.\r\n    //\r\n    // The 'scale' argument takes input of one of the values listed here:\r\n    // <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/map/MapView.Scale.html>\r\n    //\r\n    // The 'tracking' argument determines if the map will track its location on the map it displays.\r\n    // This is often the player holding the map's location.\r\n    //\r\n    // Note that all maps have a size of 128x128.\r\n    //\r\n    // The file path is relative to the 'plugins/Denizen/images/' folder.\r\n    // Instead of a local file path, an http(s) URL can be used, which will automatically download the image from the URL given.\r\n    // If the file path points to a .gif, the map will automatically be animated.\r\n    //\r\n    // Use escaping to let the image and text arguments have tags based on the player viewing the map.\r\n    //\r\n    // Custom maps will persist over restarts using the 'maps.yml' save file in the Denizen plugins folder.\r\n    //\r\n    // @Tags\r\n    // <entry[saveName].created_map> returns the map created by the 'new:' argument if used.\r\n    //\r\n    // @Usage\r\n    // Use to add an auto-resized background image to map 3.\r\n    // - map 3 image:my_map_images/my_background.png resize\r\n    //\r\n    // @Usage\r\n    // Use to add an image with the top-left corner at the center of a new map.\r\n    // - map new:WorldTag image:my_map_images/my_center_image.png x:64 y:64 save:map\r\n    // - give filled_map[map=<entry[map].created_map>]\r\n    //\r\n    // @Usage\r\n    // Use to reset map 3 to be centered at the player's location.\r\n    // - map 3 reset:<player.location>\r\n    //\r\n    // @Usage\r\n    // Use to remove any custom renderers on map 3 and then apply the contents of the named <@link language Map Script Containers> to map 3.\r\n    // - map 3 script:Map_Script_Name\r\n    //\r\n    // -->\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"map-id\") @ArgLinear @ArgDefaultNull ElementTag id,\r\n                                   @ArgName(\"new\") @ArgPrefixed @ArgDefaultNull WorldTag create,\r\n                                   @ArgName(\"reset\") @ArgPrefixed @ArgDefaultNull LocationTag resetLoc,\r\n                                   @ArgName(\"reset_to_blank\") boolean resetEmpty,\r\n                                   @ArgName(\"scale\") @ArgPrefixed @ArgDefaultNull MapView.Scale scale,\r\n                                   @ArgName(\"tracking\") boolean tracking,\r\n                                   @ArgName(\"image\") @ArgPrefixed @ArgDefaultNull String image,\r\n                                   @ArgName(\"resize\") boolean resize,\r\n                                   @ArgName(\"script\") @ArgPrefixed @ArgDefaultNull ScriptTag script,\r\n                                   @ArgName(\"dot\") @ArgPrefixed @ArgDefaultNull ColorTag dot,\r\n                                   @ArgName(\"radius\") @ArgPrefixed @ArgDefaultText(\"-1\") int radius,\r\n                                   @ArgName(\"x\") @ArgPrefixed @ArgDefaultText(\"0\") double x,\r\n                                   @ArgName(\"y\") @ArgPrefixed @ArgDefaultText(\"0\") double y,\r\n                                   @ArgName(\"width\") @ArgPrefixed @ArgDefaultText(\"-1\") int width,\r\n                                   @ArgName(\"height\") @ArgPrefixed @ArgDefaultText(\"-1\") int height,\r\n                                   @ArgName(\"text\") @ArgPrefixed @ArgDefaultNull ElementTag text) {\r\n        if (create == null && id == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify a map ID or create a new map!\");\r\n        }\r\n        if (resetLoc == null && image == null && script == null && dot == null && text == null && !resetEmpty) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify a valid action to perform!\");\r\n        }\r\n        MapView map;\r\n        if (create != null) {\r\n            map = Bukkit.getServer().createMap(create.getWorld());\r\n            scriptEntry.saveObject(\"created_map\", new ElementTag(map.getId()));\r\n            Debug.echoDebug(scriptEntry, \"Created map with id \" + map.getId() + \".\");\r\n        }\r\n        else { // id != null\r\n            map = Bukkit.getServer().getMap(id.asInt());\r\n            if (map == null) {\r\n                Debug.echoError(\"No map found for ID '\" + id.asInt() + \"'!\");\r\n                return;\r\n            }\r\n        }\r\n        if (resetEmpty || resetLoc != null) {\r\n            map.setTrackingPosition(tracking);\r\n            if (scale != null) {\r\n                map.setScale(scale);\r\n            }\r\n            for (MapRenderer renderer : DenizenMapManager.removeDenizenRenderers(map)) {\r\n                map.addRenderer(renderer);\r\n            }\r\n            if (resetLoc != null) {\r\n                map.setCenterX(resetLoc.getBlockX());\r\n                map.setCenterZ(resetLoc.getBlockZ());\r\n                map.setWorld(resetLoc.getWorld());\r\n            }\r\n        }\r\n        if (script != null) {\r\n            DenizenMapManager.removeDenizenRenderers(map);\r\n            ((MapScriptContainer) script.getContainer()).applyTo(map);\r\n        }\r\n        DenizenMapRenderer dmr = DenizenMapManager.getDenizenRenderer(map);\r\n        if (image != null) {\r\n            width = formatSize(width, resize);\r\n            height = formatSize(height, resize);\r\n            if (CoreUtilities.toLowerCase(image).endsWith(\".gif\")) {\r\n                dmr.autoUpdate = true;\r\n            }\r\n            dmr.addObject(new MapImage(dmr, String.valueOf(x), String.valueOf(y), \"true\", false, image, width, height));\r\n            dmr.hasChanged = true;\r\n        }\r\n        if (dot != null) {\r\n            dmr.addObject(new MapDot(String.valueOf(x), String.valueOf(y), \"true\", false, String.valueOf(radius), dot.toString()));\r\n            dmr.hasChanged = true;\r\n        }\r\n        if (text != null) {\r\n            dmr.addObject(new MapText(String.valueOf(x), String.valueOf(y), \"true\", false, text.asString(), null, null, null, null));\r\n            dmr.hasChanged = true;\r\n        }\r\n    }\r\n\r\n    public static int formatSize(int size, boolean resize) {\r\n        if (size != -1) {\r\n            return size;\r\n        }\r\n        return resize ? 128 : 0;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/item/TakeCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.item;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.utilities.inventory.SlotHelper;\r\nimport com.denizenscript.denizen.utilities.nbt.CustomNBT;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.meta.BookMeta;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\nimport java.util.List;\r\nimport java.util.function.Function;\r\n\r\npublic class TakeCommand extends AbstractCommand {\r\n\r\n    public TakeCommand() {\r\n        setName(\"take\");\r\n        setSyntax(\"take [iteminhand/cursoritem/bydisplay:<name>/bycover:<title>|<author>/slot:<slot>/flagged:<flag>/item:<matcher>] (quantity:<#>) (from:<inventory>)\");\r\n        setRequiredArguments(1, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Take\r\n    // @Syntax take [iteminhand/cursoritem/bydisplay:<name>/bycover:<title>|<author>/slot:<slot>/flagged:<flag>/item:<matcher>] (quantity:<#>) (from:<inventory>)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Short Takes an item from the player.\r\n    // @Group item\r\n    //\r\n    // @Description\r\n    // Takes items from a player or inventory.\r\n    //\r\n    // If the player or inventory does not have the item being taken, nothing happens.\r\n    //\r\n    // Using 'slot:' will take the items from that specific slot.\r\n    //\r\n    // Using 'flagged:' with a flag name will take items with the specified flag name, see <@link language flag system>.\r\n    //\r\n    // Using 'iteminhand' will take from the player's held item slot.\r\n    //\r\n    // Using 'cursoritem' will take from the player's held cursor item (as in, one that's actively being picked up and moved in an inventory screen).\r\n    //\r\n    // Using 'bydisplay:' will take items with the specified display name.\r\n    //\r\n    // Using 'bycover:' will take a written book by the specified book title + author pair.\r\n    //\r\n    // Using 'raw_exact:' (Intentionally undocumented) will compare all raw details of an item exactly. This is almost always a bad idea to use. DO NOT USE.\r\n    //\r\n    // Using 'item:' will take items that match an advanced item matcher, using the system behind <@link language Advanced Object Matching>.\r\n    //\r\n    // Flagged, Slot, ByDisplay, and Raw_Exact, all take a list as input to take multiple different item types at once.\r\n    //\r\n    // If no quantity is specified, exactly 1 item will be taken.\r\n    //\r\n    // Specifying a raw item without any matching method is considered unreliable and should be avoided.\r\n    //\r\n    // Optionally using 'from:' to specify a specific inventory to take from. If not specified, the linked player's inventory will be used.\r\n    //\r\n    // The options 'iteminhand' and 'cursoritem' require a linked player and will ignore the 'from:' inventory.\r\n    //\r\n    // To take xp from a player, use <@link command experience>.\r\n    // To take money from a player, use <@link command money>.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.item_in_hand>\r\n    //\r\n    // @Usage\r\n    // Use to take an arrow from the player's enderchest\r\n    // - take item:arrow from:<player.enderchest>\r\n    //\r\n    // @Usage\r\n    // Use to take the current holding item from the player's hand\r\n    // - take iteminhand\r\n    //\r\n    // @Usage\r\n    // Use to take 5 emeralds from the player's inventory\r\n    // - take item:emerald quantity:5\r\n    // -->\r\n\r\n    private enum Type {MONEY, XP, ITEMINHAND, CURSORITEM, ITEM, BYDISPLAY, SLOT, BYCOVER, SCRIPTNAME, NBT, MATERIAL, FLAGGED, RAWEXACT, MATCHER}\r\n\r\n    public static HashSet<Type> requiresPlayerTypes = new HashSet<>(Arrays.asList(Type.XP, Type.MONEY, Type.ITEMINHAND, Type.CURSORITEM));\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addWithPrefix(\"scriptname:\", ItemScriptHelper.item_scripts.keySet());\r\n        if (tab.arg.startsWith(\"material:\")) {\r\n            for (Material material : Material.values()) {\r\n                if (material.isItem()) {\r\n                    tab.add(\"material:\" + material.name());\r\n                }\r\n            }\r\n        }\r\n        else if (tab.arg.startsWith(\"item:\")) {\r\n            for (Material material : Material.values()) {\r\n                if (material.isItem()) {\r\n                    tab.add(\"item:\" + material.name());\r\n                }\r\n            }\r\n            tab.addWithPrefix(\"item:\", ItemScriptHelper.item_scripts.keySet());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"type\")\r\n                    && arg.matches(\"money\", \"coins\")) {\r\n                BukkitImplDeprecations.takeMoney.warn(scriptEntry);\r\n                scriptEntry.addObject(\"type\", Type.MONEY);\r\n            }\r\n            else if (!scriptEntry.hasObject(\"type\")\r\n                    && arg.matches(\"xp\", \"exp\")) {\r\n                scriptEntry.addObject(\"type\", Type.XP);\r\n            }\r\n            else if (!scriptEntry.hasObject(\"type\")\r\n                    && arg.matches(\"item_in_hand\", \"iteminhand\")) {\r\n                scriptEntry.addObject(\"type\", Type.ITEMINHAND);\r\n            }\r\n            else if (!scriptEntry.hasObject(\"type\")\r\n                    && arg.matches(\"cursoritem\", \"cursor_item\")) {\r\n                scriptEntry.addObject(\"type\", Type.CURSORITEM);\r\n            }\r\n            else if (!scriptEntry.hasObject(\"quantity\")\r\n                    && arg.matchesPrefix(\"q\", \"qty\", \"quantity\")\r\n                    && arg.matchesFloat()) {\r\n                if (arg.matchesPrefix(\"q\", \"qty\")) {\r\n                    BukkitImplDeprecations.qtyTags.warn(scriptEntry);\r\n                }\r\n                scriptEntry.addObject(\"quantity\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"items\")\r\n                    && arg.matchesPrefix(\"bydisplay\")\r\n                    && !scriptEntry.hasObject(\"type\")) {\r\n                scriptEntry.addObject(\"type\", Type.BYDISPLAY);\r\n                scriptEntry.addObject(\"displayname\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"items\")\r\n                    && arg.matchesPrefix(\"nbt\")\r\n                    && !scriptEntry.hasObject(\"type\")) {\r\n                BukkitImplDeprecations.itemNbt.warn(scriptEntry);\r\n                scriptEntry.addObject(\"type\", Type.NBT);\r\n                scriptEntry.addObject(\"nbt_key\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"items\")\r\n                    && arg.matchesPrefix(\"flagged\")\r\n                    && !scriptEntry.hasObject(\"type\")) {\r\n                scriptEntry.addObject(\"type\", Type.FLAGGED);\r\n                scriptEntry.addObject(\"flag_name\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"type\")\r\n                    && !scriptEntry.hasObject(\"items\")\r\n                    && arg.matchesPrefix(\"bycover\")) {\r\n                scriptEntry.addObject(\"type\", Type.BYCOVER);\r\n                scriptEntry.addObject(\"cover\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"type\")\r\n                    && !scriptEntry.hasObject(\"items\")\r\n                    && arg.matchesPrefix(\"item\")) {\r\n                scriptEntry.addObject(\"type\", Type.MATCHER);\r\n                scriptEntry.addObject(\"matcher_text\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"type\")\r\n                    && !scriptEntry.hasObject(\"items\")\r\n                    && arg.matchesPrefix(\"material\")) {\r\n                BukkitImplDeprecations.takeRawItems.warn(scriptEntry);\r\n                scriptEntry.addObject(\"type\", Type.MATERIAL);\r\n                scriptEntry.addObject(\"material\", arg.asType(ListTag.class).filter(MaterialTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"type\")\r\n                    && !scriptEntry.hasObject(\"items\")\r\n                    && arg.matchesPrefix(\"script\", \"scriptname\")) {\r\n                BukkitImplDeprecations.takeRawItems.warn(scriptEntry);\r\n                scriptEntry.addObject(\"type\", Type.SCRIPTNAME);\r\n                scriptEntry.addObject(\"scriptitem\", arg.asType(ListTag.class).filter(ItemTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"type\")\r\n                    && !scriptEntry.hasObject(\"items\")\r\n                    && arg.matchesPrefix(\"raw_exact\")) {\r\n                scriptEntry.addObject(\"type\", Type.RAWEXACT);\r\n                scriptEntry.addObject(\"items\", arg.asType(ListTag.class).filter(ItemTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"slot\")\r\n                    && !scriptEntry.hasObject(\"type\")\r\n                    && arg.matchesPrefix(\"slot\")) {\r\n                scriptEntry.addObject(\"type\", Type.SLOT);\r\n                scriptEntry.addObject(\"slot\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"items\")\r\n                    && !scriptEntry.hasObject(\"type\")\r\n                    && arg.matchesArgumentList(ItemTag.class)) {\r\n                BukkitImplDeprecations.takeRawItems.warn(scriptEntry);\r\n                scriptEntry.addObject(\"items\", arg.asType(ListTag.class).filter(ItemTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"inventory\")\r\n                    && arg.matchesPrefix(\"f\", \"from\")\r\n                    && arg.matchesArgumentType(InventoryTag.class)) {\r\n                scriptEntry.addObject(\"inventory\", arg.asType(InventoryTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"type\", Type.ITEM)\r\n                .defaultObject(\"quantity\", new ElementTag(1));\r\n        Type type = (Type) scriptEntry.getObject(\"type\");\r\n        if (type != Type.MONEY && scriptEntry.getObject(\"inventory\") == null) {\r\n            scriptEntry.addObject(\"inventory\", Utilities.entryHasPlayer(scriptEntry) ? Utilities.getEntryPlayer(scriptEntry).getInventory() : null);\r\n        }\r\n        if (!scriptEntry.hasObject(\"inventory\") && type != Type.MONEY) {\r\n            throw new InvalidArgumentsException(\"Must specify an inventory to take from!\");\r\n        }\r\n        if (requiresPlayerTypes.contains(type) && !Utilities.entryHasPlayer(scriptEntry)) {\r\n            throw new InvalidArgumentsException(\"Cannot take '\" + type.name() + \"' without a linked player.\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        InventoryTag inventory = scriptEntry.getObjectTag(\"inventory\");\r\n        ElementTag quantity = scriptEntry.getElement(\"quantity\");\r\n        ListTag displayNameList = scriptEntry.getObjectTag(\"displayname\");\r\n        List<ItemTag> scriptItemList = scriptEntry.getObjectTag(\"scriptitem\");\r\n        ListTag slotList = scriptEntry.getObjectTag(\"slot\");\r\n        ListTag titleAuthor = scriptEntry.getObjectTag(\"cover\");\r\n        ElementTag nbtKey = scriptEntry.getElement(\"nbt_key\");\r\n        ElementTag matcherText = scriptEntry.getElement(\"matcher_text\");\r\n        ListTag flagList = scriptEntry.getObjectTag(\"flag_name\");\r\n        List<MaterialTag> materialList = scriptEntry.getObjectTag(\"material\");\r\n        Type type = (Type) scriptEntry.getObject(\"type\");\r\n        List<ItemTag> items = scriptEntry.getObjectTag(\"items\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"Type\", type.name()), quantity, inventory, displayNameList, db(\"scriptname\", scriptItemList),\r\n                    db(\"Items\", items), slotList, nbtKey, flagList, matcherText, db(\"material\",  materialList), titleAuthor);\r\n        }\r\n        switch (type) {\r\n            case ITEMINHAND: {\r\n                Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity();\r\n                int inHandAmt = player.getEquipment().getItemInMainHand().getAmount();\r\n                int theAmount = (int) quantity.asDouble();\r\n                ItemStack newHandItem = new ItemStack(Material.AIR);\r\n                if (theAmount > inHandAmt) {\r\n                    Debug.echoDebug(scriptEntry, \"...player did not have enough of the item in hand, taking all...\");\r\n                    player.getEquipment().setItemInMainHand(newHandItem);\r\n                }\r\n                else {\r\n                    // amount is just right!\r\n                    if (theAmount == inHandAmt) {\r\n                        player.getEquipment().setItemInMainHand(newHandItem);\r\n                    }\r\n                    else {\r\n                        // amount is less than what's in hand, need to make a new itemstack of what's left...\r\n                        newHandItem = player.getEquipment().getItemInMainHand().clone();\r\n                        newHandItem.setAmount(inHandAmt - theAmount);\r\n                        player.getEquipment().setItemInMainHand(newHandItem);\r\n                        player.updateInventory();\r\n                    }\r\n                }\r\n                break;\r\n            }\r\n            case CURSORITEM: {\r\n                Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity();\r\n                int currentAmount = player.getItemOnCursor().getAmount();\r\n                int takeAmount = (int) quantity.asDouble();\r\n                ItemStack newItem = new ItemStack(Material.AIR);\r\n                if (takeAmount > currentAmount) {\r\n                    Debug.echoDebug(scriptEntry, \"...player did not have enough of the item on cursor, taking all...\");\r\n                    player.setItemOnCursor(newItem);\r\n                }\r\n                else {\r\n                    if (takeAmount == currentAmount) {\r\n                        player.setItemOnCursor(newItem);\r\n                    }\r\n                    else {\r\n                        newItem = player.getItemOnCursor().clone();\r\n                        newItem.setAmount(currentAmount - takeAmount);\r\n                        player.setItemOnCursor(newItem);\r\n                        player.updateInventory();\r\n                    }\r\n                }\r\n                break;\r\n            }\r\n            case MONEY: {\r\n                if (Depends.economy == null) {\r\n                    Debug.echoError(scriptEntry, \"No economy loaded! Have you installed Vault and a compatible economy plugin?\");\r\n                    return;\r\n                }\r\n                Depends.economy.withdrawPlayer(Utilities.getEntryPlayer(scriptEntry).getOfflinePlayer(), quantity.asDouble());\r\n                break;\r\n            }\r\n            case XP: {\r\n                BukkitImplDeprecations.takeExperience.warn(scriptEntry);\r\n                Utilities.getEntryPlayer(scriptEntry).getPlayerEntity().giveExp(-quantity.asInt());\r\n                break;\r\n            }\r\n            case RAWEXACT: {\r\n                if (items == null) {\r\n                    Debug.echoError(scriptEntry, \"Must specify item/items!\");\r\n                    return;\r\n                }\r\n                for (ItemTag targetItem : items) {\r\n                    takeByMatcher(inventory, (item) -> targetItem.matchesRawExact(new ItemTag(item)), quantity.asInt());\r\n                }\r\n                break;\r\n            }\r\n            case ITEM: {\r\n                if (items == null) {\r\n                    Debug.echoError(scriptEntry, \"Must specify item/items!\");\r\n                    return;\r\n                }\r\n                for (ItemTag item : items) {\r\n                    ItemStack is = item.getItemStack().clone();\r\n                    is.setAmount(quantity.asInt());\r\n                    if (!removeItem(inventory.getInventory(), item, item.getAmount())) {\r\n                        Debug.echoDebug(scriptEntry, \"Inventory does not contain at least \" + quantity.asInt() + \" of \" + item.identify() + \"... Taking all...\");\r\n                    }\r\n                }\r\n                break;\r\n            }\r\n            case BYDISPLAY: {\r\n                if (displayNameList == null) {\r\n                    Debug.echoError(scriptEntry, \"Must specify a displayname!\");\r\n                    return;\r\n                }\r\n                for (String name : displayNameList) {\r\n                    takeByMatcher(inventory, (item) -> item.hasItemMeta() && item.getItemMeta().hasDisplayName() &&\r\n                            item.getItemMeta().getDisplayName().equalsIgnoreCase(name), quantity.asInt());\r\n                }\r\n                break;\r\n            }\r\n            case BYCOVER: {\r\n                if (titleAuthor == null) {\r\n                    Debug.echoError(scriptEntry, \"Must specify a cover!\");\r\n                    return;\r\n                }\r\n                takeByMatcher(inventory, (item) -> item.hasItemMeta() && item.getItemMeta() instanceof BookMeta\r\n                                && equalOrNull(titleAuthor.get(0), ((BookMeta) item.getItemMeta()).getTitle())\r\n                                && (titleAuthor.size() == 1 || equalOrNull(titleAuthor.get(1), ((BookMeta) item.getItemMeta()).getAuthor())), quantity.asInt());\r\n                break;\r\n            }\r\n            case FLAGGED: {\r\n                if (flagList == null) {\r\n                    Debug.echoError(scriptEntry, \"Must specify a flag name!\");\r\n                    return;\r\n                }\r\n                for (String flag : flagList) {\r\n                    takeByMatcher(inventory, (item) -> new ItemTag(item).getFlagTracker().hasFlag(flag), quantity.asInt());\r\n                }\r\n                break;\r\n            }\r\n            case NBT: {\r\n                if (nbtKey == null) {\r\n                    Debug.echoError(scriptEntry, \"Must specify an NBT key!\");\r\n                    return;\r\n                }\r\n                takeByMatcher(inventory, (item) -> CustomNBT.hasCustomNBT(item, nbtKey.asString(), CustomNBT.KEY_DENIZEN), quantity.asInt());\r\n                break;\r\n            }\r\n            case SCRIPTNAME: {\r\n                if (scriptItemList == null) {\r\n                    Debug.echoError(scriptEntry, \"Must specify a valid script name!\");\r\n                    return;\r\n                }\r\n                for (ItemTag scriptedItem : scriptItemList) {\r\n                    String script = scriptedItem.getScriptName();\r\n                    if (script == null) {\r\n                        Debug.echoError(scriptEntry, \"Item '\" + scriptedItem.debuggable() + \"' is not a scripted item, cannot take by scriptname.\");\r\n                        continue;\r\n                    }\r\n                    takeByMatcher(inventory, (item) -> script.equalsIgnoreCase(new ItemTag(item).getScriptName()), quantity.asInt());\r\n                }\r\n                break;\r\n            }\r\n            case MATERIAL: {\r\n                if (materialList == null) {\r\n                    Debug.echoError(scriptEntry, \"Must specify a valid material!\");\r\n                    return;\r\n                }\r\n                for (MaterialTag material : materialList) {\r\n                    takeByMatcher(inventory, (item) -> item.getType() == material.getMaterial() && !(new ItemTag(item).isItemscript()), quantity.asInt());\r\n                }\r\n                break;\r\n            }\r\n            case MATCHER: {\r\n                if (matcherText == null) {\r\n                    Debug.echoError(scriptEntry, \"Must specify an item matcher!\");\r\n                    return;\r\n                }\r\n                takeByMatcher(inventory, (item) -> new ItemTag(item).tryAdvancedMatcher(matcherText.asString(), scriptEntry.getContext()), quantity.asInt());\r\n                break;\r\n            }\r\n            case SLOT: {\r\n                for (String slot : slotList) {\r\n                    int slotId = SlotHelper.nameToIndexFor(slot, inventory.getInventory().getHolder());\r\n                    if (slotId == -1 || slotId >= inventory.getSize()) {\r\n                        Debug.echoError(scriptEntry, \"The input '\" + slot + \"' is not a valid slot!\");\r\n                        return;\r\n                    }\r\n                    ItemStack original = inventory.getInventory().getItem(slotId);\r\n                    if (original != null && original.getType() != Material.AIR) {\r\n                        if (original.getAmount() > quantity.asInt()) {\r\n                            original.setAmount(original.getAmount() - quantity.asInt());\r\n                            inventory.setSlots(slotId, original);\r\n                        }\r\n                        else {\r\n                            inventory.setSlots(slotId, new ItemStack(Material.AIR));\r\n                        }\r\n                    }\r\n                }\r\n                break;\r\n            }\r\n        }\r\n    }\r\n\r\n    private static boolean equalOrNull(String a, String b) {\r\n        return b == null || a == null || a.equalsIgnoreCase(b);\r\n    }\r\n\r\n    public void takeByMatcher(InventoryTag inventory, Function<ItemStack, Boolean> matcher, int quantity) {\r\n        int itemsTaken = 0;\r\n        ItemStack[] contents = inventory.getInventory().getContents();\r\n        for (int i = 0; i < contents.length; i++) {\r\n            ItemStack it = contents[i];\r\n            if (itemsTaken < quantity\r\n                    && it != null\r\n                    && matcher.apply(it)) {\r\n                int amt = it.getAmount();\r\n                if (itemsTaken + amt <= quantity) {\r\n                    inventory.getInventory().setItem(i, new ItemStack(Material.AIR));\r\n                    itemsTaken += amt;\r\n                }\r\n                else {\r\n                    it.setAmount(amt - (quantity - itemsTaken));\r\n                    inventory.getInventory().setItem(i, it);\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    public boolean removeItem(Inventory inventory, ItemTag item, int amount) {\r\n        if (item == null) {\r\n            return false;\r\n        }\r\n        item = new ItemTag(item.getItemStack().clone());\r\n        item.setAmount(1);\r\n        String myItem = CoreUtilities.toLowerCase(item.identify());\r\n        for (int i = 0; i < inventory.getSize(); i++) {\r\n            ItemStack is = inventory.getItem(i);\r\n            if (is == null) {\r\n                continue;\r\n            }\r\n            is = is.clone();\r\n            int count = is.getAmount();\r\n            is.setAmount(1);\r\n            // Note: this double-parsing is intentional, as part of a hotfix for a larger issue\r\n            String newItem = CoreUtilities.toLowerCase(ItemTag.valueOf(new ItemTag(is).identify(), false).identify());\r\n            if (myItem.equals(newItem)) {\r\n                if (count <= amount) {\r\n                    NMSHandler.itemHelper.setInventoryItem(inventory, null, i);\r\n                    amount -= count;\r\n                    if (amount == 0) {\r\n                        return true;\r\n                    }\r\n                }\r\n                else {\r\n                    is.setAmount(count - amount);\r\n                    NMSHandler.itemHelper.setInventoryItem(inventory, is, i);\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/ActionCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\n\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\nimport com.denizenscript.denizencore.objects.*;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\n\nimport java.util.*;\n\npublic class ActionCommand extends AbstractCommand {\n\n    public ActionCommand() {\n        setName(\"action\");\n        setSyntax(\"action [<action name>|...] (<npc>|...) (context:<name>|<object>|...)\");\n        setRequiredArguments(1, 3);\n        isProcedural = false;\n    }\n\n    // <--[command]\n    // @Name Action\n    // @Syntax action [<action name>|...] (<npc>|...) (context:<name>|<object>|...)\n    // @Required 1\n    // @Maximum 3\n    // @Plugin Citizens\n    // @Short Manually fires an NPC action.\n    // @Group npc\n    //\n    // @Description\n    // This command will trigger an NPC action (an action within an 'assignment' type script attached to the NPC) exactly the same\n    // as if an actual serverside event had caused it.\n    // You can specify as many action names as you want in the list, they will all be fired.\n    // You may also specify as many NPCs as you would like to run the action on, in a list.\n    // If no NPCs are specified, the NPC linked to the script will be assumed.\n    // The script's linked player and the specified NPC will automatically be sent through to the action.\n    // To add context information (tags like <context.location>) to the action, simply specify all context values in a list.\n    // Note that there are some inherent limitations... EG, you can't directly add a list to the context currently.\n    // To do this, the best way is to just escape the list value (see <@link language Escaping System>).\n    //\n    // @Tags\n    // None\n    //\n    // @Usage\n    // Use to trigger a custom action\n    // - action \"custom action\"\n    //\n    // @Usage\n    // Use to trigger multiple custom action with context on a different NPC\n    // - action \"player dances|target enemy\" <[some_npc]> context:action|custom|target|<player.selected_npc>\n    // -->\n\n    @Override\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\n        for (Argument arg : scriptEntry) {\n            if (!scriptEntry.hasObject(\"npcs\")\n                    && arg.matchesArgumentList(NPCTag.class)) {\n                scriptEntry.addObject(\"npcs\", arg.asType(ListTag.class).filter(NPCTag.class, scriptEntry));\n            }\n            else if (!scriptEntry.hasObject(\"context\")\n                    && arg.matchesPrefix(\"context\", \"c\")) {\n                scriptEntry.addObject(\"context\", arg.asType(ListTag.class)); // TODO: MapTag?\n            }\n            else if (!scriptEntry.hasObject(\"actions\")) {\n                scriptEntry.addObject(\"actions\", arg.asType(ListTag.class));\n            }\n            else {\n                arg.reportUnhandled();\n            }\n        }\n        if (!scriptEntry.hasObject(\"actions\")) {\n            throw new InvalidArgumentsException(\"Must specify a list of action names!\");\n        }\n        if (!scriptEntry.hasObject(\"npcs\")) {\n            if (Utilities.entryHasNPC(scriptEntry)) {\n                scriptEntry.addObject(\"npcs\", Collections.singletonList(Utilities.getEntryNPC(scriptEntry)));\n            }\n            else {\n                throw new InvalidArgumentsException(\"Must specify an NPC to use!\");\n            }\n        }\n        scriptEntry.defaultObject(\"context\", new ListTag());\n    }\n\n    @Override\n    public void execute(ScriptEntry scriptEntry) {\n        ListTag actions = scriptEntry.getObjectTag(\"actions\");\n        ListTag context = scriptEntry.getObjectTag(\"context\");\n        List<NPCTag> npcs = (List<NPCTag>) scriptEntry.getObject(\"npcs\");\n        if (scriptEntry.dbCallShouldDebug()) {\n            Debug.report(scriptEntry, getName(), actions, context, db(\"npcs\", npcs));\n        }\n        if (context.size() % 2 == 1) { // Size is uneven!\n            context.add(\"null\");\n        }\n        // Change the context input to a list of objects\n        Map<String, ObjectTag> context_map = new HashMap<>();\n        for (int i = 0; i < context.size(); i += 2) {\n            context_map.put(context.get(i), ObjectFetcher.pickObjectFor(context.get(i + 1), scriptEntry.getContext()));\n        }\n        for (NPCTag npc : npcs) {\n            for (String action : actions) {\n                npc.action(action, Utilities.getEntryPlayer(scriptEntry), context_map);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/AnchorCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.trait.Anchors;\r\nimport net.citizensnpcs.util.Anchor;\r\n\r\nimport java.util.Arrays;\r\n\r\npublic class AnchorCommand extends AbstractCommand {\r\n\r\n    public AnchorCommand() {\r\n        setName(\"anchor\");\r\n        setSyntax(\"anchor [id:<name>] [remove/add <location>]\");\r\n        setRequiredArguments(2, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Anchor\r\n    // @Syntax anchor [id:<name>] [remove/add <location>]\r\n    // @Required 2\r\n    // @Maximum 3\r\n    // @Plugin Citizens\r\n    // @Short Controls an NPC's Anchor Trait.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // The anchor system inside Citizens allows locations to be 'bound' to an NPC, saved by an 'id'.\r\n    // The anchor command can add and remove new anchors.\r\n    // The Anchors Trait can also be used as a sort of 'waypoints' system.\r\n    // As the Anchor command is an NPC specific command, a valid npc object must be referenced in the script entry.\r\n    // If none is provided by default, use the 'npc:<npc>' argument.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.anchor[anchor_name]>\r\n    // <NPCTag.list_anchors>\r\n    // <NPCTag.has_anchors>\r\n    //\r\n    // @Usage\r\n    // Use to add and remove anchors to an NPC.\r\n    // - define location_name <context.message>\r\n    // - chat \"I have saved this location as <[location_name]>.'\r\n    // - anchor add <npc.location> id:<[location_name]>\r\n    // -->\r\n\r\n    private enum Action { ADD, REMOVE }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matchesEnum(Action.class)) {\r\n                scriptEntry.addObject(\"action\", Action.valueOf(arg.getValue().toUpperCase()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"range\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"range\", \"r\")) {\r\n                scriptEntry.addObject(\"range\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"id\")\r\n                    && arg.matchesPrefix(\"id\", \"i\")) {\r\n                scriptEntry.addObject(\"id\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!Utilities.entryHasNPC(scriptEntry)) {\r\n            throw new InvalidArgumentsException(\"NPC linked was missing or invalid.\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"action\")) {\r\n            throw new InvalidArgumentsException(\"Must specify an 'Anchor Action'. Valid: \" + Arrays.asList(Action.values()));\r\n        }\r\n        if (!scriptEntry.hasObject(\"id\")) {\r\n            throw new InvalidArgumentsException(\"Must specify an ID.\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        Action action = (Action) scriptEntry.getObject(\"action\");\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        ElementTag range = scriptEntry.getElement(\"range\");\r\n        ElementTag id = scriptEntry.getElement(\"id\");\r\n        NPCTag npc = Utilities.getEntryNPC(scriptEntry);\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), npc, db(\"action\", action.name()), id, location, range);\r\n        }\r\n        Anchors anchors = npc.getCitizen().getOrAddTrait(Anchors.class);\r\n        switch (action) {\r\n            case ADD: {\r\n                if (location == null) {\r\n                    Debug.echoError(\"Must specify a location!\");\r\n                    return;\r\n                }\r\n                Anchor existing = anchors.getAnchor(id.asString());\r\n                if (existing != null) {\r\n                    anchors.removeAnchor(existing);\r\n                }\r\n                anchors.addAnchor(id.asString(), location);\r\n                break;\r\n            }\r\n            case REMOVE: {\r\n                Anchor n = anchors.getAnchor(id.asString());\r\n                if (n == null) {\r\n                    Debug.echoError(scriptEntry, \"Invalid anchor name '\" + id.asString() + \"'\");\r\n                }\r\n                else {\r\n                    anchors.removeAnchor(n);\r\n                }\r\n                break;\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/AssignmentCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.AssignmentScriptContainer;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizen.npc.traits.AssignmentTrait;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\n\r\nimport java.util.List;\r\n\r\npublic class AssignmentCommand extends AbstractCommand {\r\n\r\n    public AssignmentCommand() {\r\n        setName(\"assignment\");\r\n        setSyntax(\"assignment [set/add/remove/clear] (script:<name>) (to:<npc>|...)\");\r\n        setRequiredArguments(1, 3);\r\n        autoCompile();\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Assignment\r\n    // @Syntax assignment [set/add/remove/clear] (script:<name>) (to:<npc>|...)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Plugin Citizens\r\n    // @Short Changes an NPC's assignment.\r\n    // @Group npc\r\n    // @Guide https://guide.denizenscript.com/guides/npcs/assignment-scripts.html\r\n    //\r\n    // @Description\r\n    // Changes an NPC's assignment as though you used the '/npc assignment' command.\r\n    //\r\n    // Uses the script: argument, which accepts an assignment-type script.\r\n    //\r\n    // Optionally, specify a list of NPCs to apply the trait to. If unspecified, the linked NPC will be used.\r\n    //\r\n    // 'Set' is equivalent to 'clear' + 'add'.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.script>\r\n    // <server.npcs_assigned[<assignment_script>]>\r\n    //\r\n    // @Usage\r\n    // Use to assign an npc with exactly one assignment script named 'Bob_the_Builder'.\r\n    // - assignment set script:Bob_the_Builder\r\n    //\r\n    // @Usage\r\n    // Use to give a different NPC an assignment.\r\n    // - assignment set script:Bob_the_Builder npc:<[some_npc]>\r\n    //\r\n    // @Usage\r\n    // Use to clear an npc's assignments.\r\n    // - assignment clear\r\n    //\r\n    // @Usage\r\n    // Use to add an extra assignment to the NPC.\r\n    // - assignment add script:name_fix_assign\r\n    //\r\n    // @Usage\r\n    // Use to remove an extra assignment from the NPC.\r\n    // - assignment remove script:name_fix_assign\r\n    // -->\r\n\r\n    public enum Action { SET, ADD, REMOVE, CLEAR }\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.add(Action.values());\r\n        tab.addScriptsOfType(AssignmentScriptContainer.class);\r\n    }\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"action\") Action action,\r\n                                   @ArgName(\"linearScript\") @ArgLinear @ArgDefaultNull ScriptTag linearScript,\r\n                                   @ArgName(\"linearTo\") @ArgLinear @ArgDefaultNull @ArgSubType(NPCTag.class) List<NPCTag> linearTo,\r\n                                   @ArgName(\"script\") @ArgPrefixed @ArgDefaultNull ScriptTag script,\r\n                                   @ArgName(\"to\") @ArgPrefixed @ArgDefaultNull @ArgSubType(NPCTag.class) List<NPCTag> to) {\r\n        PlayerTag player = Utilities.getEntryPlayer(scriptEntry);\r\n        if (linearScript != null) {\r\n            BukkitImplDeprecations.assignmentOptionalPrefixArgs.warn(scriptEntry);\r\n            script = linearScript;\r\n        }\r\n        if (linearTo != null) {\r\n            BukkitImplDeprecations.assignmentOptionalPrefixArgs.warn(scriptEntry);\r\n            to = linearTo;\r\n        }\r\n        if (to == null) {\r\n            if (!Utilities.entryHasNPC(scriptEntry)) {\r\n                throw new InvalidArgumentsRuntimeException(\"This command requires a linked NPC!\");\r\n            }\r\n            to = List.of(Utilities.getEntryNPC(scriptEntry));\r\n        }\r\n        switch (action) {\r\n            case SET -> {\r\n                if (script == null) {\r\n                    throw new InvalidArgumentsRuntimeException(\"Missing script!\");\r\n                }\r\n                if (!(script.getContainer() instanceof AssignmentScriptContainer assignmentScriptContainer)) {\r\n                    throw new InvalidArgumentsRuntimeException(\"Script specified is not an 'assignment-type' container.\");\r\n                }\r\n                for (NPCTag npc : to) {\r\n                    AssignmentTrait assignment = npc.getCitizen().getOrAddTrait(AssignmentTrait.class);\r\n                    assignment.clearAssignments(player);\r\n                    assignment.addAssignmentScript(assignmentScriptContainer, player);\r\n                }\r\n            }\r\n            case ADD -> {\r\n                if (script == null) {\r\n                    throw new InvalidArgumentsRuntimeException(\"Missing script!\");\r\n                }\r\n                if (!(script.getContainer() instanceof AssignmentScriptContainer assignmentScriptContainer)) {\r\n                    throw new InvalidArgumentsRuntimeException(\"Script specified is not an 'assignment-type' container.\");\r\n                }\r\n                for (NPCTag npc : to) {\r\n                    npc.getCitizen().getOrAddTrait(AssignmentTrait.class).addAssignmentScript(assignmentScriptContainer, player);\r\n                }\r\n            }\r\n            case REMOVE -> {\r\n                for (NPCTag npc : to) {\r\n                    if (script == null) {\r\n                        BukkitImplDeprecations.assignmentRemove.warn(scriptEntry);\r\n                        if (npc.getCitizen().hasTrait(AssignmentTrait.class)) {\r\n                            npc.getCitizen().getOrAddTrait(AssignmentTrait.class).clearAssignments(player);\r\n                            npc.getCitizen().removeTrait(AssignmentTrait.class);\r\n                        }\r\n                    }\r\n                    else {\r\n                        if (npc.getCitizen().hasTrait(AssignmentTrait.class)) {\r\n                            AssignmentTrait trait = npc.getCitizen().getOrAddTrait(AssignmentTrait.class);\r\n                            trait.removeAssignmentScript(script.getName(), player);\r\n                            trait.checkAutoRemove();\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            case CLEAR -> {\r\n                for (NPCTag npc : to) {\r\n                    if (npc.getCitizen().hasTrait(AssignmentTrait.class)) {\r\n                        npc.getCitizen().getOrAddTrait(AssignmentTrait.class).clearAssignments(player);\r\n                        npc.getCitizen().removeTrait(AssignmentTrait.class);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/BreakCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.Holdable;\r\nimport net.citizensnpcs.api.ai.tree.BehaviorStatus;\r\nimport net.citizensnpcs.api.npc.BlockBreaker;\r\nimport org.bukkit.Bukkit;\r\n\r\nimport java.util.HashMap;\r\n\r\npublic class BreakCommand extends AbstractCommand implements Holdable {\r\n\r\n    public BreakCommand() {\r\n        setName(\"break\");\r\n        setSyntax(\"break [<location>] (<npc>) (radius:<#.#>)\");\r\n        setRequiredArguments(1, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Break\r\n    // @Syntax break [<location>] (<npc>) (radius:<#.#>)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Plugin Citizens\r\n    // @Short Makes an NPC walk over and break a block.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // By itself, the 'break' command will act as an NPC command in the sense that an attached\r\n    // NPC will navigate to and break the block at the attached location. It can also accept a specified npc,\r\n    // to fulfill the command, just specify a 'fetchable' npc object. It can also accept a radius to start\r\n    // breaking the block from within. To specify the radius, prefix the radius with 'radius:'.\r\n    //\r\n    // The break command is ~waitable. Refer to <@link language ~waitable>.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.is_navigating>\r\n    // <NPCTag.target_location>\r\n    //\r\n    // @Usage\r\n    // Use to make the npc break a related block.\r\n    // - ~break <context.location>\r\n    //\r\n    // @Usage\r\n    // Use to make a different NPC break a related block.\r\n    // - ~break <context.location> <[some_npc]>\r\n    //\r\n    // @Usage\r\n    // Use to make a different NPC break a related block and start digging from 5 blocks away.\r\n    // - ~break <context.location> <[some_npc]> radius:5\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n\r\n        for (Argument arg : scriptEntry) {\r\n\r\n            if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"npc\")\r\n                    && arg.matchesArgumentType(NPCTag.class)) {\r\n                scriptEntry.addObject(\"npc\", arg.asType(NPCTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"radius\")\r\n                    && arg.matchesFloat()) {\r\n                scriptEntry.addObject(\"radius\", arg.asElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n\r\n        // Make sure location and entity were fulfilled\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a location!\");\r\n        }\r\n\r\n        // Use the NPC or the Player as the default entity\r\n        if (!scriptEntry.hasObject(\"npc\")) {\r\n            if (Utilities.entryHasNPC(scriptEntry)) {\r\n                scriptEntry.addObject(\"npc\", Utilities.getEntryNPC(scriptEntry));\r\n            }\r\n            else {\r\n                throw new InvalidArgumentsException(\"Must specify a valid NPC!\");\r\n            }\r\n        }\r\n\r\n        scriptEntry.defaultObject(\"radius\", new ElementTag(2));\r\n\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // dig\r\n    //\r\n    // @Triggers when the NPC breaks a block with the Break Command\r\n    //\r\n    // @Context\r\n    // <context.location> returns the location the NPC Dug\r\n    // <context.material> Returns the Block dug\r\n    //\r\n    // -->\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n\r\n        final LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        final NPCTag npc = scriptEntry.getObjectTag(\"npc\");\r\n        ElementTag radius = scriptEntry.getElement(\"radius\");\r\n        final HashMap<String, ObjectTag> context = new HashMap<>();\r\n        MaterialTag material = new MaterialTag(location.getBlock());\r\n        context.put(\"location\", location);\r\n        context.put(\"material\", material);\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), location, npc, radius);\r\n        }\r\n        final ScriptEntry se = scriptEntry;\r\n        BlockBreaker.BlockBreakerConfiguration config = new BlockBreaker.BlockBreakerConfiguration();\r\n        config.item(npc.getLivingEntity().getEquipment().getItemInMainHand());\r\n        config.radius(radius.asDouble());\r\n        config.callback(() -> {\r\n            npc.action(\"dig\", null, context);\r\n            se.setFinished(true);\r\n        });\r\n\r\n        BlockBreaker breaker = npc.getCitizen().getBlockBreaker(location.getBlock(), config);\r\n        if (breaker.shouldExecute()) {\r\n            TaskRunnable run = new TaskRunnable(breaker);\r\n            run.taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Denizen.getInstance(), run, 0, 1);\r\n        }\r\n        else {\r\n            se.setFinished(true);\r\n        }\r\n    }\r\n\r\n    private static class TaskRunnable implements Runnable {\r\n        private int taskId;\r\n        private final BlockBreaker breaker;\r\n\r\n        public TaskRunnable(BlockBreaker breaker) {\r\n            this.breaker = breaker;\r\n        }\r\n\r\n        @Override\r\n        public void run() {\r\n            if (breaker.run() != BehaviorStatus.RUNNING) {\r\n                Bukkit.getScheduler().cancelTask(taskId);\r\n                breaker.reset();\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/CreateCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.npc.MemoryNPCDataStore;\r\nimport net.citizensnpcs.api.npc.NPCRegistry;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\n\r\npublic class CreateCommand extends AbstractCommand {\r\n\r\n    public CreateCommand() {\r\n        setName(\"create\");\r\n        setSyntax(\"create [<entity>] [<name>] (<location>) (traits:<trait>|...) (registry:<name>)\");\r\n        setRequiredArguments(1, 5);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Create\r\n    // @Syntax create [<entity>] [<name>] (<location>) (traits:<trait>|...) (registry:<name>)\r\n    // @Required 1\r\n    // @Maximum 5\r\n    // @Plugin Citizens\r\n    // @Short Creates a new NPC, and optionally spawns it at a location.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Creates an npc which the entity type specified, or specify an existing npc to create a copy.\r\n    // If no location is specified the npc is created despawned.\r\n    // Use the 'save:<savename>' argument to return the npc for later use in a script.\r\n    //\r\n    // Optionally specify a list of traits to immediately apply when creating the NPC.\r\n    //\r\n    // Optionally specify a custom registry to create the NPC into. (Most users, leave this option off).\r\n    // Will generate a new registry if needed.\r\n    //\r\n    // @Tags\r\n    // <server.npcs>\r\n    // <entry[saveName].created_npc> returns the NPC that was created.\r\n    //\r\n    // @Usage\r\n    // Use to create a despawned NPC for later usage.\r\n    // - create player Bob\r\n    //\r\n    // @Usage\r\n    // Use to create an NPC and spawn it immediately.\r\n    // - create spider Joe <player.location>\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.add(EntityType.values());\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"entity_type\")\r\n                    && arg.matchesArgumentType(EntityTag.class)) {\r\n                // Avoid duplication of objects\r\n                EntityTag ent = arg.asType(EntityTag.class);\r\n                if (!ent.isGeneric() && !ent.isCitizensNPC()) {\r\n                    throw new InvalidArgumentsException(\"Entity supplied must be generic or a Citizens NPC!\");\r\n                }\r\n                scriptEntry.addObject(\"entity_type\", ent);\r\n            }\r\n            else if (!scriptEntry.hasObject(\"spawn_location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"spawn_location\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"name\")) {\r\n                scriptEntry.addObject(\"name\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"traits\")\r\n                    && arg.matchesPrefix(\"t\", \"trait\", \"traits\")) {\r\n                scriptEntry.addObject(\"traits\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"registry\")\r\n                    && arg.matchesPrefix(\"registry\")) {\r\n                scriptEntry.addObject(\"registry\", arg.asElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"name\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a name!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"entity_type\")) {\r\n            throw new InvalidArgumentsException(\"Must specify an entity type!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        ElementTag name = scriptEntry.getElement(\"name\");\r\n        EntityTag type = scriptEntry.getObjectTag(\"entity_type\");\r\n        LocationTag loc = scriptEntry.getObjectTag(\"spawn_location\");\r\n        ListTag traits = scriptEntry.getObjectTag(\"traits\");\r\n        ElementTag registry = scriptEntry.getElement(\"registry\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), name, type, loc, traits, registry);\r\n        }\r\n        NPCTag created;\r\n        if (!type.isGeneric() && type.isCitizensNPC()) {\r\n            created = new NPCTag(type.getDenizenNPC().getCitizen().clone());\r\n            created.getCitizen().setName(name.asString());\r\n        }\r\n        else {\r\n            NPCRegistry actualRegistry = CitizensAPI.getNPCRegistry();\r\n            if (registry != null) {\r\n                actualRegistry = NPCTag.getRegistryByName(registry.asString());\r\n                if (actualRegistry == null) {\r\n                    actualRegistry = CitizensAPI.createNamedNPCRegistry(registry.asString(), new MemoryNPCDataStore());\r\n                }\r\n            }\r\n            created = new NPCTag(actualRegistry.createNPC(type.getBukkitEntityType(), name.asString()));\r\n        }\r\n        // Add the created NPC into the script entry so it can be utilized if need be.\r\n        scriptEntry.saveObject(\"created_npc\", created);\r\n        if (created.isSpawned()) {\r\n            if (loc != null) {\r\n                created.getCitizen().teleport(loc, PlayerTeleportEvent.TeleportCause.PLUGIN);\r\n            }\r\n            else {\r\n                created.getCitizen().despawn();\r\n            }\r\n        }\r\n        else {\r\n            if (loc != null) {\r\n                created.getCitizen().spawn(loc);\r\n            }\r\n        }\r\n        if (traits != null) {\r\n            for (String trait_name : traits) {\r\n                Trait trait = CitizensAPI.getTraitFactory().getTrait(trait_name);\r\n                if (trait != null) {\r\n                    created.getCitizen().addTrait(trait);\r\n                }\r\n                else {\r\n                    Debug.echoError(scriptEntry, \"Could not add trait to NPC: \" + trait_name);\r\n                }\r\n            }\r\n        }\r\n        for (Mechanism mechanism : type.getWaitingMechanisms()) {\r\n            created.safeAdjust(mechanism);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/DespawnCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.api.event.DespawnReason;\r\nimport net.citizensnpcs.api.trait.trait.Spawned;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class DespawnCommand extends AbstractCommand {\r\n\r\n    public DespawnCommand() {\r\n        setName(\"despawn\");\r\n        setSyntax(\"despawn (<npc>|...)\");\r\n        setRequiredArguments(0, 1);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Despawn\r\n    // @Syntax despawn (<npc>|...)\r\n    // @Plugin Citizens\r\n    // @Required 0\r\n    // @Maximum 1\r\n    // @Plugin Citizens\r\n    // @Short Temporarily despawns the linked NPC or a list of NPCs.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // This command will temporarily despawn either the linked NPC or a list of other NPCs.\r\n    // Despawning means they are no longer visible or interactable, but they still exist and can be respawned.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.is_spawned>\r\n    //\r\n    // @Usage\r\n    // Use to despawn the linked NPC.\r\n    // - despawn\r\n    //\r\n    // @Usage\r\n    // Use to despawn several NPCs.\r\n    // - despawn <npc>|<player.selected_npc>|<[some_npc]>\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"npcs\")\r\n                    && arg.matchesArgumentList(NPCTag.class)) {\r\n                scriptEntry.addObject(\"npcs\", arg.asType(ListTag.class).filter(NPCTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"npcs\")) {\r\n            if (Utilities.entryHasNPC(scriptEntry)) {\r\n                scriptEntry.addObject(\"npcs\", Collections.singletonList(Utilities.getEntryNPC(scriptEntry)));\r\n            }\r\n            else {\r\n                throw new InvalidArgumentsException(\"Must specify a valid list of NPCs!\");\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        List<NPCTag> npcs = (List<NPCTag>) scriptEntry.getObject(\"npcs\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"NPCs\", npcs));\r\n        }\r\n        for (NPCTag npc : npcs) {\r\n            if (npc.getCitizen().hasTrait(Spawned.class)) {\r\n                npc.getCitizen().getOrAddTrait(Spawned.class).setSpawned(false);\r\n            }\r\n            if (npc.isSpawned()) {\r\n                npc.getCitizen().despawn(DespawnReason.PLUGIN);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/DisengageCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\npublic class DisengageCommand extends AbstractCommand {\r\n\r\n    public DisengageCommand() {\r\n        setName(\"disengage\");\r\n        setSyntax(\"disengage (player)\");\r\n        setRequiredArguments(0, 1);\r\n        isProcedural = false;\r\n        setBooleansHandled(\"player\");\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Disengage\r\n    // @Syntax disengage (player)\r\n    // @Required 0\r\n    // @Maximum 1\r\n    // @Plugin Citizens\r\n    // @Short Enables an NPCs triggers that have been temporarily disabled by the engage command.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Re-enables any toggled triggers that have been disabled by disengage.\r\n    // Using disengage inside scripts must have an NPC to reference, or one may be specified by supplying a valid NPCTag object with the npc argument.\r\n    //\r\n    // Engaging an NPC by default affects all players attempting to interact with the NPC.\r\n    // You can optionally specify 'player' to only affect the linked player.\r\n    //\r\n    // This is mostly regarded as an 'interact script command', though it may be used inside other script types.\r\n    // This is because disengage works with the trigger system, which is an interact script-container feature.\r\n    //\r\n    // NPCs that are interacted with while engaged will fire an 'on unavailable' assignment script-container action.\r\n    //\r\n    // See <@link command Engage>\r\n    //\r\n    // @Tags\r\n    // <NPCTag.engaged>\r\n    //\r\n    // @Usage\r\n    // Use to reenable an NPC's triggers, disabled via 'engage'.\r\n    // - engage\r\n    // - chat 'Be right there!'\r\n    // - walk <player.location>\r\n    // - wait 5s\r\n    // - disengage\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        boolean linkedPlayer = scriptEntry.argAsBoolean(\"player\");\r\n        if (Utilities.getEntryNPC(scriptEntry) == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"This command requires a linked NPC!\");\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), Utilities.getEntryNPC(scriptEntry), db(\"player\", linkedPlayer));\r\n        }\r\n        EngageCommand.setEngaged(Utilities.getEntryNPC(scriptEntry).getCitizen(), linkedPlayer ? Utilities.getEntryPlayer(scriptEntry) : null, false);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/EngageCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.citizensnpcs.api.npc.NPC;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class EngageCommand extends AbstractCommand {\r\n\r\n    public EngageCommand() {\r\n        setName(\"engage\");\r\n        setSyntax(\"engage (<duration>) (player)\");\r\n        setRequiredArguments(0, 2);\r\n        isProcedural = false;\r\n        setBooleansHandled(\"player\");\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Engage\r\n    // @Syntax engage (<duration>) (player)\r\n    // @Required 0\r\n    // @Maximum 2\r\n    // @Plugin Citizens\r\n    // @Short Temporarily disables an NPCs toggled interact script-container triggers.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Engaging an NPC will temporarily disable any interact script-container triggers.\r\n    // To reverse this behavior, use either the disengage command, or specify a duration in which the engage should timeout.\r\n    // Specifying an engage without a duration will render the NPC engaged until a disengage is used on the NPC.\r\n    //\r\n    // Engaging an NPC by default affects all players attempting to interact with the NPC.\r\n    // You can optionally specify 'player' to only affect the linked player.\r\n    //\r\n    // While engaged, all triggers and actions associated with triggers will not 'fire',\r\n    // except the 'on unavailable' assignment script-container action, which will fire for triggers that were enabled previous to the engage command.\r\n    //\r\n    // Engage can be useful when NPCs are carrying out a task that shouldn't be interrupted, or to provide a good way to avoid accidental 'retrigger'.\r\n    //\r\n    // See <@link command Disengage>\r\n    //\r\n    // @Tags\r\n    // <NPCTag.engaged>\r\n    //\r\n    // @Usage\r\n    // Use to make an NPC appear 'busy'.\r\n    // - engage\r\n    // - chat 'Give me a few minutes while I mix you a potion!'\r\n    // - walk <npc.anchor[mixing_station]>\r\n    // - wait 10s\r\n    // - walk <npc.anchor[service_station]>\r\n    // - chat 'Here you go!'\r\n    // - give potion <player>\r\n    // - disengage\r\n    //\r\n    // @Usage\r\n    // Use to avoid 'retrigger'.\r\n    // - engage 5s\r\n    // - take quest_item\r\n    // - flag player finished_quests:->:super_quest\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"duration\", new DurationTag(0));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        if (!Utilities.entryHasNPC(scriptEntry)) {\r\n            throw new InvalidArgumentsRuntimeException(\"This command requires a linked NPC!\");\r\n        }\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        boolean linkedPlayer = scriptEntry.argAsBoolean(\"player\");\r\n        NPCTag npc = Utilities.getEntryNPC(scriptEntry);\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), npc, duration, db(\"player\", linkedPlayer));\r\n        }\r\n        if (duration.getSecondsAsInt() > 0) {\r\n            setEngaged(npc.getCitizen(), linkedPlayer ? Utilities.getEntryPlayer(scriptEntry) : null, duration.getSecondsAsInt());\r\n        }\r\n        else {\r\n            setEngaged(npc.getCitizen(), linkedPlayer ? Utilities.getEntryPlayer(scriptEntry) : null, true);\r\n        }\r\n    }\r\n\r\n    /*\r\n     * Engaged NPCs cannot interact with Players\r\n     */\r\n    private static Map<String, Long> currentlyEngaged = new HashMap<>();\r\n\r\n    public static String getID(NPC npc, PlayerTag player) {\r\n        if (player == null) {\r\n            return npc.getUniqueId().toString();\r\n        }\r\n        return npc.getUniqueId().toString() + \"_\" + player.getUUID().toString();\r\n    }\r\n\r\n    /**\r\n     * Checks if the NPCTag is ENGAGED. Engaged NPCs do not respond to\r\n     * Player interaction.\r\n     *\r\n     * @param npc the Denizen NPC being checked\r\n     * @return if the NPCTag is currently engaged\r\n     */\r\n    public static boolean getEngaged(NPC npc, PlayerTag player) {\r\n        String id = getID(npc, player);\r\n        if (currentlyEngaged.containsKey(id)) {\r\n            if (currentlyEngaged.get(id) > CoreUtilities.monotonicMillis()) {\r\n                return true;\r\n            }\r\n        }\r\n        if (player != null) {\r\n            return getEngaged(npc, null);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    /**\r\n     * Sets a NPCTag's ENGAGED status. Engaged NPCs do not respond to Player\r\n     * interaction. Note: Denizen NPC will automatically disengage after the\r\n     * engage_timeout_in_seconds which is set in the Denizen config.yml.\r\n     *\r\n     * @param npc     the NPCTag affected\r\n     * @param engaged true sets the NPCTag engaged, false sets the NPCTag as disengaged\r\n     */\r\n    public static void setEngaged(NPC npc, PlayerTag player, boolean engaged) {\r\n        if (engaged) {\r\n            setEngaged(npc, player, (int) DurationTag.valueOf(Settings.engageTimeoutInSeconds(), CoreUtilities.basicContext).getSeconds());\r\n        }\r\n        if (!engaged) {\r\n            currentlyEngaged.remove(getID(npc, player));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Sets a NPCTag as ENGAGED for a specific amount of seconds. Engaged NPCs do not\r\n     * respond to Player interaction. If the NPC is previously engaged, using this will\r\n     * over-ride the previously set duration.\r\n     *\r\n     * @param npc      the NPCTag to set as engaged\r\n     * @param duration the number of seconds to engage the NPCTag\r\n     */\r\n    public static void setEngaged(NPC npc, PlayerTag player, int duration) {\r\n        currentlyEngaged.put(getID(npc, player), CoreUtilities.monotonicMillis() + (long) duration * 1000L);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/FishCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.interfaces.FishingHelper;\r\nimport com.denizenscript.denizen.npc.traits.FishingTrait;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.api.trait.trait.Equipment;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\npublic class FishCommand extends AbstractCommand {\r\n\r\n    public FishCommand() {\r\n        setName(\"fish\");\r\n        setSyntax(\"fish [<location>] (catch:{none}/default/junk/treasure/fish) (stop) (chance:<#>)\");\r\n        setRequiredArguments(1, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Fish\r\n    // @Syntax fish [<location>/stop] (catch:{none}/default/junk/treasure/fish) (chance:<#>)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Plugin Citizens\r\n    // @Short Causes an NPC to begin fishing around a specified location.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Causes an NPC to begin fishing at the specified location.\r\n    // Setting catch determines what items the NPC may fish up, and the chance is the odds of the NPC fishing up an item.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Makes the NPC throw their fishing line out to where the player is looking, with a 50% chance of catching fish.\r\n    // - fish <player.cursor_on> catch:fish chance:50\r\n    //\r\n    // @Usage\r\n    // Makes the NPC stop fishing.\r\n    // - fish stop\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"catch\")\r\n                    && arg.matchesPrefix(\"catch\")\r\n                    && arg.matchesEnum(FishingHelper.CatchType.class)) {\r\n                scriptEntry.addObject(\"catch\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"stop\")\r\n                    && arg.matches(\"stop\")) {\r\n                scriptEntry.addObject(\"stop\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"percent\")\r\n                    && arg.matchesPrefix(\"catchpercent\", \"percent\", \"chance\", \"c\")\r\n                    && arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"percent\", arg.asElement());\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\") && !scriptEntry.hasObject(\"stop\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a valid location!\");\r\n        }\r\n        scriptEntry.defaultObject(\"catch\", new ElementTag(\"NONE\"))\r\n                .defaultObject(\"stop\", new ElementTag(false))\r\n                .defaultObject(\"percent\", new ElementTag(65));\r\n        if (!Utilities.entryHasNPC(scriptEntry) || !Utilities.getEntryNPC(scriptEntry).isSpawned()) {\r\n            throw new InvalidArgumentsException(\"This command requires a linked and spawned NPC!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        ElementTag catchtype = scriptEntry.getElement(\"catch\");\r\n        ElementTag stop = scriptEntry.getElement(\"stop\");\r\n        ElementTag percent = scriptEntry.getElement(\"percent\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), location, catchtype, percent, stop);\r\n        }\r\n        NPCTag npc = Utilities.getEntryNPC(scriptEntry);\r\n        FishingTrait trait = npc.getFishingTrait();\r\n        if (stop.asBoolean()) {\r\n            trait.stopFishing();\r\n            return;\r\n        }\r\n        npc.getEquipmentTrait().set(Equipment.EquipmentSlot.HAND, new ItemStack(Material.FISHING_ROD));\r\n        trait.setCatchPercent(percent.asInt());\r\n        trait.setCatchType(FishingHelper.CatchType.valueOf(catchtype.asString().toUpperCase()));\r\n        trait.startFishing(location);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/LookcloseCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.trait.LookClose;\r\n\r\npublic class LookcloseCommand extends AbstractCommand {\r\n\r\n    public LookcloseCommand() {\r\n        setName(\"lookclose\");\r\n        setSyntax(\"lookclose (<npc>) (state:<true/false>) (range:<#>) (realistic)\");\r\n        setRequiredArguments(0, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name LookClose\r\n    // @Syntax lookclose (<npc>) (state:<true/false>) (range:<#>) (realistic)\r\n    // @Required 0\r\n    // @Maximum 4\r\n    // @Plugin Citizens\r\n    // @Short Interacts with an NPCs 'lookclose' trait as provided by Citizens.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Use this command with any NPC to alter the state and options of its 'lookclose' trait.\r\n    // When an NPC's 'lookclose' trait is toggled to true, the NPC's head will follow nearby players.\r\n    // Specifying realistic will enable a higher precision and detection of players, while taking into account 'line-of-sight', however can use more CPU cycles.\r\n    // You may also specify a range integer to specify the number of blocks that will trigger the NPC's attention.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.lookclose>\r\n    //\r\n    // @Usage\r\n    // Use to cause the NPC to begin looking at nearby players.\r\n    // - lookclose true\r\n    //\r\n    // @Usage\r\n    // Use to cause the NPC to stop looking at nearby players.\r\n    // - lookclose false\r\n    //\r\n    // @Usage\r\n    // Use to change the range and make the NPC more realistic\r\n    // - lookclose true range:10 realistic\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matches(\"realistic\", \"realistically\")) {\r\n                scriptEntry.addObject(\"realistic\", new ElementTag(true));\r\n            }\r\n            else if (arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"range\", arg.asElement());\r\n            }\r\n            else if (arg.matchesBoolean()) {\r\n                scriptEntry.addObject(\"toggle\", arg.asElement());\r\n            }\r\n            else if (arg.matchesArgumentType(NPCTag.class)) {\r\n                scriptEntry.addObject(\"npc\", arg.asType(NPCTag.class));\r\n                ((BukkitScriptEntryData) scriptEntry.entryData).setNPC(arg.asType(NPCTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"npc\", Utilities.getEntryNPC(scriptEntry));\r\n        if (!scriptEntry.hasObject(\"npc\")) {\r\n            throw new InvalidArgumentsException(\"NPC linked was missing or invalid.\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag realistic = scriptEntry.getElement(\"realistic\");\r\n        ElementTag range = scriptEntry.getElement(\"range\");\r\n        ElementTag toggle = scriptEntry.getElement(\"toggle\");\r\n        NPCTag npc = scriptEntry.getObjectTag(\"npc\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), npc, realistic, range, toggle);\r\n        }\r\n        LookClose trait = npc.getCitizen().getOrAddTrait(LookClose.class);\r\n        if (toggle != null) {\r\n            trait.lookClose(toggle.asBoolean());\r\n        }\r\n        if (realistic != null && realistic.asBoolean()) {\r\n            trait.setRealisticLooking(true);\r\n        }\r\n        else {\r\n            trait.setRealisticLooking(false);\r\n        }\r\n        if (range != null) {\r\n            trait.setRange(range.asInt());\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/NPCBossBarCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\n\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgDefaultNull;\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgName;\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgPrefixed;\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgSubType;\nimport net.citizensnpcs.trait.versioned.BossBarTrait;\nimport org.bukkit.boss.BarColor;\nimport org.bukkit.boss.BarFlag;\nimport org.bukkit.boss.BarStyle;\n\nimport java.util.List;\n\npublic class NPCBossBarCommand extends AbstractCommand {\n\n    public NPCBossBarCommand() {\n        setName(\"npcbossbar\");\n        setSyntax(\"npcbossbar (remove) (color:<color>) (options:<option>|...) (range:<#>) (style:<style>) (title:<title>) (progress:<progress>) (view_permission:<permission>) (visible:<true/false>)\");\n        setRequiredArguments(1, 8);\n        autoCompile();\n    }\n\n    // <--[command]\n    // @Name npcbossbar\n    // @Syntax npcbossbar (remove) (color:<color>) (options:<option>|...) (range:<#>) (style:<style>) (title:<title>) (progress:<progress>) (view_permission:<permission>) (visible:<true/false>)\n    // @Required 1\n    // @Maximum 8\n    // @Short Controls or removes the linked NPC's bossbar.\n    // @Group npc\n    //\n    // @Description\n    // Controls or removes the linked NPC's bossbar.\n    //\n    // Progress can be a number between 1 and 100 or 'health' to make it track the NPC's health.\n    // Placeholder API/Citizens placeholders are supported.\n    //\n    // Optionally specify a range around the NPC where the bossbar is visible, and/or a permission required to view it.\n    // Input an empty view permission to remove it ('view_permission:').\n    //\n    // Valid colors: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/boss/BarColor.html>.\n    // Valid styles: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/boss/BarStyle.html>.\n    // Valid options: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/boss/BarFlag.html>.\n    //\n    // @Usage\n    // Makes the linked NPC's bossbar green, and changes its title.\n    // - npcbossbar color:green \"title:This bossbar is green!\"\n    //\n    // @Usage\n    // Makes it so the linked NPC's bossbar can only be visible 5 blocks away from it.\n    // - npcbossbar range:5\n    //\n    // @Usage\n    // Removes a specific NPC's bossbar.\n    // - npcbossbar remove npc:<[theNPC]>\n    // -->\n\n    @Override\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\n        tab.addWithPrefix(\"color:\", BarColor.values());\n        tab.addWithPrefix(\"options:\", BarFlag.values());\n        tab.addWithPrefix(\"style:\", BarStyle.values());\n        tab.add(\"progress:health\");\n    }\n\n    public static void autoExecute(ScriptEntry scriptEntry,\n                                   @ArgName(\"remove\") boolean remove,\n                                   @ArgName(\"color\") @ArgPrefixed @ArgDefaultNull BarColor color,\n                                   @ArgName(\"options\") @ArgPrefixed @ArgDefaultNull @ArgSubType(BarFlag.class) List<BarFlag> options,\n                                   @ArgName(\"range\") @ArgPrefixed @ArgDefaultNull ElementTag range,\n                                   @ArgName(\"style\") @ArgPrefixed @ArgDefaultNull BarStyle style,\n                                   @ArgName(\"title\") @ArgPrefixed @ArgDefaultNull String title,\n                                   @ArgName(\"progress\") @ArgPrefixed @ArgDefaultNull String progress,\n                                   @ArgName(\"view_permission\") @ArgPrefixed @ArgDefaultNull String viewPermission,\n                                   @ArgName(\"visible\") @ArgPrefixed @ArgDefaultNull ElementTag visible) {\n        NPCTag npc = Utilities.getEntryNPC(scriptEntry);\n        if (npc == null) {\n            throw new InvalidArgumentsRuntimeException(\"Must have a linked NPC.\");\n        }\n        if (remove) {\n            npc.getCitizen().removeTrait(BossBarTrait.class);\n            return;\n        }\n        BossBarTrait trait = npc.getCitizen().getOrAddTrait(BossBarTrait.class);\n        if (color != null) {\n            trait.setColor(color);\n        }\n        if (range != null) {\n            if (!range.isInt()) {\n                throw new InvalidArgumentsRuntimeException(\"Invalid number '\" + range + \"' specified for 'range'.\");\n            }\n            trait.setRange(range.asInt());\n        }\n        if (options != null) {\n            trait.setFlags(options);\n        }\n        if (style != null) {\n            trait.setStyle(style);\n        }\n        if (title != null) {\n            trait.setTitle(title);\n        }\n        if (progress != null) {\n            trait.setTrackVariable(progress);\n        }\n        if (viewPermission != null) {\n            trait.setViewPermission(viewPermission.isEmpty() ? null : viewPermission);\n        }\n        if (visible != null) {\n            if (!visible.isBoolean()) {\n                throw new InvalidArgumentsRuntimeException(\"Invalid boolean '\" + visible + \"' specified for 'visible'.\");\n            }\n            trait.setVisible(visible.asBoolean());\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/PauseCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.trait.waypoint.Waypoints;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class PauseCommand extends AbstractCommand {\r\n\r\n    public static class ResumeCommand extends PauseCommand {\r\n\r\n        public ResumeCommand() {\r\n            setName(\"resume\");\r\n            setSyntax(\"resume [waypoints/activity] (<duration>)\");\r\n        }\r\n    }\r\n\r\n    public PauseCommand() {\r\n        setName(\"pause\");\r\n        setSyntax(\"pause [waypoints/activity] (<duration>)\");\r\n        setRequiredArguments(1, 2);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Pause\r\n    // @Syntax pause [waypoints/activity] (<duration>)\r\n    // @Required 1\r\n    // @Maximum 2\r\n    // @Plugin Citizens\r\n    // @Short Pauses an NPC's waypoint navigation or goal activity temporarily or indefinitely.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // The pause command pauses an NPC's waypoint navigation or goal activity temporarily or indefinitely.\r\n    // This works along side <@link command resume>.\r\n    //\r\n    // \"Waypoints\" refers to the NPC's path navigation, usually set via \"/npc path\".\r\n    //\r\n    // \"Activity\" refers to the Citizens AI Goal system, which may be used by some plugins but usually is not.\r\n    //\r\n    // If no duration is specified, the resume command must be used to unpause it.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.is_navigating>\r\n    //\r\n    // @Usage\r\n    // Use to pause an NPC's waypoint navigation indefinitely.\r\n    // - pause waypoints\r\n    //\r\n    // @Usage\r\n    // Use to pause an NPC's goal activity temporarily.\r\n    // - pause activity 1m\r\n    //\r\n    // @Usage\r\n    // Use to pause an NPC's waypoint navigation and then resume it.\r\n    // - pause waypoints\r\n    // - resume waypoints\r\n    // -->\r\n\r\n    // <--[command]\r\n    // @Name Resume\r\n    // @Syntax resume [waypoints/activity] (<duration>)\r\n    // @Required 1\r\n    // @Plugin Citizens\r\n    // @Short Resumes an NPC's waypoint navigation or goal activity temporarily or indefinitely.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // The resume command resumes an NPC's waypoint navigation or goal activity temporarily or indefinitely.\r\n    // This works along side <@link command pause>.\r\n    // See the documentation of the pause command for more details.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.is_navigating>\r\n    //\r\n    // @Usage\r\n    // Use to pause an NPC's waypoint navigation and then resume it.\r\n    // - pause waypoints\r\n    // - resume waypoints\r\n    // -->\r\n\r\n    private Map<String, Integer> durations = new HashMap<>();\r\n\r\n    enum PauseType {ACTIVITY, WAYPOINTS, NAVIGATION}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matchesArgumentType(DurationTag.class)\r\n                    && !scriptEntry.hasObject(\"duration\")) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"pause_type\")\r\n                    && arg.matchesEnum(PauseType.class)) {\r\n                scriptEntry.addObject(\"pause_type\", arg.asElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"pause_type\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a pause type!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        ElementTag pauseTypeElement = scriptEntry.getElement(\"pause_type\");\r\n        PauseType pauseType = PauseType.valueOf(pauseTypeElement.asString().toUpperCase());\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), duration, pauseTypeElement);\r\n        }\r\n        NPCTag npc = null;\r\n        if (Utilities.getEntryNPC(scriptEntry) != null) {\r\n            npc = Utilities.getEntryNPC(scriptEntry);\r\n        }\r\n        pause(npc, pauseType, !scriptEntry.getCommandName().equalsIgnoreCase(\"RESUME\"));\r\n        if (duration != null) {\r\n            if (durations.containsKey(npc.getCitizen().getId() + pauseType.name())) {\r\n                try {\r\n                    Denizen.getInstance().getServer().getScheduler().cancelTask(durations.get(npc.getCitizen().getId() + pauseType.name()));\r\n                }\r\n                catch (Exception e) {\r\n                    Debug.echoError(scriptEntry, \"There was an error pausing that!\");\r\n                    Debug.echoError(scriptEntry, e);\r\n                }\r\n            }\r\n            Debug.echoDebug(scriptEntry, \"Running delayed task: Unpause \" + pauseType);\r\n            final NPCTag theNpc = npc;\r\n            final ScriptEntry se = scriptEntry;\r\n            durations.put(npc.getId() + pauseType.name(), Denizen.getInstance()\r\n                    .getServer().getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(),\r\n                            () -> {\r\n                                Debug.echoDebug(se, \"Running delayed task: Pausing \" + pauseType);\r\n                                pause(theNpc, pauseType, false);\r\n\r\n                            }, duration.getTicks()));\r\n        }\r\n    }\r\n\r\n    public void pause(NPCTag denizen, PauseType pauseType, boolean pause) {\r\n        switch (pauseType) {\r\n            case WAYPOINTS:\r\n                denizen.getCitizen().getOrAddTrait(Waypoints.class).getCurrentProvider().setPaused(pause);\r\n                if (pause) {\r\n                    denizen.getNavigator().cancelNavigation();\r\n                }\r\n                return;\r\n            case ACTIVITY:\r\n                denizen.getCitizen().getDefaultBehaviorController().setPaused(pause);\r\n                return;\r\n            case NAVIGATION:\r\n                // TODO: Finish this\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/PoseCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.trait.Poses;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.Player;\r\n\r\npublic class PoseCommand extends AbstractCommand {\r\n\r\n    public PoseCommand() {\r\n        setName(\"pose\");\r\n        setSyntax(\"pose (add/remove/{assume}) [id:<name>] (player/{npc}) (<location>)\");\r\n        setRequiredArguments(1, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Pose\r\n    // @Syntax pose (add/remove/{assume}) [id:<name>] (player/{npc}) (<location>)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Plugin Citizens\r\n    // @Short Rotates the player or NPC to match a pose, or adds/removes an NPC's poses.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Makes a player or NPC assume the position of a pose saved on an NPC, removes a\r\n    // pose with a specified ID from the current linked NPC, or adds a pose to the NPC\r\n    // with an ID and a location, although the only thing that matters in the location\r\n    // is the pitch and yaw.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.has_pose[<name>]>\r\n    // <NPCTag.pose[<name>]>\r\n    //\r\n    // @Usage\r\n    // Make an NPC assume a pose.\r\n    // - pose id:MyPose1\r\n    //\r\n    // @Usage\r\n    // Add a pose to an NPC. (Note that only the last 2 numbers matter)\r\n    // - pose add id:MyPose2 0,0,0,-2.3,5.4\r\n    //\r\n    // @Usage\r\n    // Remove a pose from an NPC.\r\n    // - pose remove id:MyPose1\r\n    // -->\r\n\r\n    private enum TargetType {NPC, PLAYER}\r\n\r\n    private enum Action {ADD, REMOVE, ASSUME}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matches(\"add\", \"assume\", \"remove\")) {\r\n                scriptEntry.addObject(\"action\", Action.valueOf(arg.getValue().toUpperCase()));\r\n            }\r\n            else if (arg.matchesPrefix(\"id\")) {\r\n                scriptEntry.addObject(\"pose_id\", arg.asElement());\r\n            }\r\n            else if (arg.matches(\"player\")) {\r\n                scriptEntry.addObject(\"target\", TargetType.PLAYER);\r\n            }\r\n            else if (arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"pose_loc\", arg.asType(LocationTag.class));\r\n            }\r\n        }\r\n        // Even if the target is a player, this command requires an NPC to get the pose from.\r\n        if (!Utilities.entryHasNPC(scriptEntry)) {\r\n            throw new InvalidArgumentsException(\"This command requires an NPC!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"pose_id\")) {\r\n            throw new InvalidArgumentsException(\"No ID specified!\");\r\n        }\r\n        scriptEntry.defaultObject(\"target\", TargetType.NPC);\r\n        scriptEntry.defaultObject(\"action\", Action.ASSUME);\r\n        // If the target is a player, it needs a player! However, you can't ADD/REMOVE poses from players, so only allow ASSUME.\r\n        if (scriptEntry.getObject(\"target\") == TargetType.PLAYER) {\r\n            if (scriptEntry.getObject(\"action\") != Action.ASSUME) {\r\n                throw new InvalidArgumentsException(\"You cannot add or remove poses from a player.\");\r\n            }\r\n            else if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                throw new InvalidArgumentsException(\"This command requires a linked player!\");\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        TargetType target = (TargetType) scriptEntry.getObject(\"target\");\r\n        NPCTag npc = Utilities.getEntryNPC(scriptEntry);\r\n        Action action = (Action) scriptEntry.getObject(\"action\");\r\n        ElementTag idElement = scriptEntry.getElement(\"pose_id\");\r\n        LocationTag pose_loc = scriptEntry.getObjectTag(\"pose_loc\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"Target\", target), (target == TargetType.PLAYER ? Utilities.getEntryPlayer(scriptEntry) : \"\"), npc, db(\"Action\", action), idElement, pose_loc);\r\n        }\r\n        if (!npc.getCitizen().hasTrait(Poses.class)) {\r\n            npc.getCitizen().addTrait(Poses.class);\r\n        }\r\n        Poses poses = npc.getCitizen().getOrAddTrait(Poses.class);\r\n        String id = idElement.asString();\r\n        switch (action) {\r\n            case ASSUME:\r\n                if (!poses.hasPose(id)) {\r\n                    Debug.echoError(\"Pose \\\"\" + id + \"\\\" doesn't exist for \" + npc);\r\n                }\r\n                if (target.name().equals(\"NPC\")) {\r\n                    poses.assumePose(id);\r\n                }\r\n                else {\r\n                    Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity();\r\n                    Location location = player.getLocation();\r\n                    location.setYaw(poses.getPose(id).getYaw());\r\n                    location.setPitch(poses.getPose(id).getPitch());\r\n                    // The only way to change a player's yaw and pitch in Bukkit is to use teleport on them\r\n                    player.teleport(location);\r\n                }\r\n                break;\r\n            case ADD:\r\n                if (!poses.addPose(id, pose_loc)) {\r\n                    Debug.echoError(npc + \" already has that pose!\");\r\n                }\r\n                break;\r\n            case REMOVE:\r\n                if (!poses.removePose(id)) {\r\n                    Debug.echoError(npc + \" does not have that pose!\");\r\n                }\r\n                break;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/PushableCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.npc.traits.PushableTrait;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\npublic class PushableCommand extends AbstractCommand {\r\n\r\n    public PushableCommand() {\r\n        setName(\"pushable\");\r\n        setSyntax(\"pushable (state:true/false/{toggle}) (delay:<duration>) (returnable:true/false)\");\r\n        setRequiredArguments(0, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Pushable\r\n    // @Syntax pushable (state:true/false/{toggle}) (delay:<duration>) (returnable:true/false)\r\n    // @Required 0\r\n    // @Maximum 3\r\n    // @Plugin Citizens\r\n    // @Short Edits the pushable trait for NPCs.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Enables, disables, toggles, or edits the Pushable trait on the attached NPC.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to toggle the Pushable trait for a specified NPC.\r\n    // - pushable npc:<[some_npc]>\r\n    //\r\n    // @Usage\r\n    // Use to enable the Pushable trait and return after 2 seconds.\r\n    // - pushable state:true delay:2s returnable:true\r\n    // -->\r\n\r\n    private enum Toggle {TOGGLE, TRUE, FALSE, ON, OFF}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"state\")\r\n                    && arg.matchesPrefix(\"state\", \"s\")\r\n                    && arg.matchesEnum(Toggle.class)) {\r\n                scriptEntry.addObject(\"state\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"delay\")\r\n                    && arg.matchesPrefix(\"delay\", \"d\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"delay\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"return\")\r\n                    && arg.matchesPrefix(\"return\", \"r\")\r\n                    && arg.matchesBoolean()) {\r\n                scriptEntry.addObject(\"return\", arg.asElement());\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        NPCTag denizenNPC = Utilities.getEntryNPC(scriptEntry);\r\n        if (denizenNPC == null) {\r\n            Debug.echoError(\"No valid NPC attached to this queue!\");\r\n            return;\r\n        }\r\n        PushableTrait trait = denizenNPC.getPushableTrait();\r\n        ElementTag state = scriptEntry.getElement(\"state\");\r\n        DurationTag delay = scriptEntry.getObjectTag(\"delay\");\r\n        ElementTag returnable = scriptEntry.getElement(\"return\");\r\n        if (state == null && delay == null && returnable == null) {\r\n            state = new ElementTag(\"TOGGLE\");\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), denizenNPC, state, delay, returnable);\r\n        }\r\n        if (delay != null) {\r\n            trait.setDelay(delay.getSecondsAsInt());\r\n        }\r\n        if (returnable != null) {\r\n            trait.setReturnable(returnable.asBoolean());\r\n        }\r\n        if (state != null) {\r\n            switch (Toggle.valueOf(state.asString().toUpperCase())) {\r\n\r\n                case TRUE:\r\n                case ON:\r\n                    trait.setPushable(true);\r\n                    break;\r\n\r\n                case FALSE:\r\n                case OFF:\r\n                    trait.setPushable(false);\r\n                    break;\r\n\r\n                case TOGGLE:\r\n                    trait.setPushable(!trait.isPushable());\r\n                    break;\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/SitCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.npc.traits.SittingTrait;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.entity.*;\r\n\r\npublic class SitCommand extends AbstractCommand {\r\n\r\n    public SitCommand() {\r\n        setName(\"sit\");\r\n        setSyntax(\"sit (<location>)\");\r\n        setRequiredArguments(0, 1);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Sit\r\n    // @Syntax sit (<location>)\r\n    // @Required 0\r\n    // @Maximum 1\r\n    // @Plugin Citizens\r\n    // @Short Causes the NPC to sit. To make them stand, see <@link command Stand>.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Makes the linked NPC sit at the specified location.\r\n    // Use <@link command Stand> to make the NPC stand up again.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.is_sitting>\r\n    //\r\n    // @Usage\r\n    // Make the linked NPC sit at the player's cursor location.\r\n    // - sit <player.cursor_on>\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matchesArgumentType(LocationTag.class)\r\n                    && !scriptEntry.hasObject(\"location\")) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!Utilities.entryHasNPC(scriptEntry)) {\r\n            throw new InvalidArgumentsException(\"This command requires a linked NPC!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        NPCTag npc = Utilities.getEntryNPC(scriptEntry);\r\n        if (!(npc.getEntity() instanceof Player || npc.getEntity() instanceof Sittable)) {\r\n            Debug.echoError(\"Entities of type \" + npc.getEntityType().getName() + \" cannot sit.\");\r\n            return;\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), npc, location);\r\n        }\r\n        Entity entity = npc.getEntity();\r\n        if (entity instanceof Sittable) {\r\n            ((Sittable) entity).setSitting(true);\r\n        }\r\n        else {\r\n            SittingTrait trait = npc.getCitizen().getOrAddTrait(SittingTrait.class);\r\n            if (location != null) {\r\n                trait.sit(location);\r\n            }\r\n            else {\r\n                trait.sit();\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/SleepCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.npc.traits.SleepingTrait;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Villager;\r\n\r\npublic class SleepCommand extends AbstractCommand {\r\n\r\n    public SleepCommand() {\r\n        setName(\"sleep\");\r\n        setSyntax(\"sleep (<location>)\");\r\n        setRequiredArguments(0, 1);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name sleep\r\n    // @Syntax sleep (<location>)\r\n    // @Required 0\r\n    // @Maximum 1\r\n    // @Plugin Citizens\r\n    // @Short Causes the NPC to sleep. To make them wake up, see <@link command Stand>.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Makes the linked NPC sleep at the specified location.\r\n    // Use <@link command Stand> to make the NPC wake back up.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.is_sleeping>\r\n    //\r\n    // @Usage\r\n    // Make the linked NPC sleep at the player's cursor location.\r\n    // - sleep <player.cursor_on>\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matchesArgumentType(LocationTag.class)\r\n                    && !scriptEntry.hasObject(\"location\")) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!Utilities.entryHasNPC(scriptEntry)) {\r\n            throw new InvalidArgumentsException(\"This command requires a linked NPC!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        NPCTag npc = Utilities.getEntryNPC(scriptEntry);\r\n        if (npc.getEntityType() != EntityType.PLAYER && !(npc.getEntity() instanceof Villager)) {\r\n            Debug.echoError(\"Only Player or villager type NPCs can sit!\");\r\n            return;\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), npc, location);\r\n        }\r\n        SleepingTrait trait = npc.getCitizen().getOrAddTrait(SleepingTrait.class);\r\n        if (location != null) {\r\n            trait.toSleep(location);\r\n        }\r\n        else {\r\n            trait.toSleep();\r\n        }\r\n        if (!trait.isSleeping()) {\r\n            npc.getCitizen().removeTrait(SleepingTrait.class);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/StandCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.npc.traits.SleepingTrait;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.npc.traits.SittingTrait;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.entity.*;\r\n\r\npublic class StandCommand extends AbstractCommand {\r\n\r\n    public StandCommand() {\r\n        setName(\"stand\");\r\n        setSyntax(\"stand\");\r\n        setRequiredArguments(0, 0);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Stand\r\n    // @Syntax stand\r\n    // @Required 0\r\n    // @Maximum 0\r\n    // @Plugin Citizens\r\n    // @Short Causes the NPC to stand up from sitting or sleeping.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Makes the linked NPC stop sitting or sleeping.\r\n    // To make them sit, see <@link command Sit>.\r\n    // To make them sleep, see <@link command Sleep>.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Make the linked NPC stand up.\r\n    // - stand\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        //stand should have no additional arguments\r\n        for (Argument arg : scriptEntry) {\r\n            arg.reportUnhandled();\r\n        }\r\n        if (!Utilities.entryHasNPC(scriptEntry)) {\r\n            throw new InvalidArgumentsException(\"This command requires a linked NPC!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        NPCTag npc = Utilities.getEntryNPC(scriptEntry);\r\n        if (!(npc.getEntity() instanceof Player || npc.getEntity() instanceof Sittable || npc.getEntity() instanceof Villager)) {\r\n            Debug.echoError(\"Entities of type \" + npc.getEntityType().name() + \" cannot sit or sleep.\");\r\n            return;\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"npc\", Utilities.getEntryNPC(scriptEntry)));\r\n        }\r\n        Entity entity = npc.getEntity();\r\n        if (entity instanceof Sittable) {\r\n            ((Sittable) entity).setSitting(false);\r\n        }\r\n        else {\r\n            if (npc.getCitizen().hasTrait(SittingTrait.class)) {\r\n                SittingTrait trait = npc.getCitizen().getOrAddTrait(SittingTrait.class);\r\n                trait.stand();\r\n                npc.getCitizen().removeTrait(SittingTrait.class);\r\n            }\r\n            if (npc.getCitizen().hasTrait(SleepingTrait.class)) {\r\n                SleepingTrait trait = npc.getCitizen().getOrAddTrait(SleepingTrait.class);\r\n                trait.wakeUp();\r\n                npc.getCitizen().removeTrait(SleepingTrait.class);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/TraitCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport net.citizensnpcs.api.trait.Trait;\r\nimport net.citizensnpcs.api.trait.TraitInfo;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class TraitCommand extends AbstractCommand {\r\n\r\n    public TraitCommand() {\r\n        setName(\"trait\");\r\n        setSyntax(\"trait (state:true/false/{toggle}) [<trait>] (to:<npc>|...)\");\r\n        setRequiredArguments(1, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Trait\r\n    // @Syntax trait (state:true/false/{toggle}) [<trait>] (to:<npc>|...)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Plugin Citizens\r\n    // @Short Adds or removes a trait from an NPC.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // This command adds or removes a trait from an NPC.\r\n    //\r\n    // Use \"state:true\" to add or \"state:false\" to remove.\r\n    // If neither is specified, the default is \"toggle\", which means remove if already present or add if not.\r\n    //\r\n    // Note that a redundant instruction, like adding a trait that the NPC already has, will give an error message.\r\n    //\r\n    // The trait input is simply the name of the trait, like \"sentinel\".\r\n    //\r\n    // Optionally, specify a list of NPCs to apply the trait to. If unspecified, the linked NPC will be used.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.has_trait[<trait>]>\r\n    // <NPCTag.traits>\r\n    // <server.traits>\r\n    //\r\n    // @Usage\r\n    // Use to add the Sentinel trait to the linked NPC.\r\n    // - trait state:true sentinel\r\n    //\r\n    // @Usage\r\n    // Use to toggle the MobProx trait on the linked NPC.\r\n    // - trait mobprox\r\n    //\r\n    // -->\r\n\r\n    private enum Toggle {TOGGLE, TRUE, FALSE, ON, OFF}\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        for (TraitInfo trait : CitizensAPI.getTraitFactory().getRegisteredTraits()) {\r\n            tab.add(trait.getTraitName());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"state\")\r\n                    && arg.matchesPrefix(\"state\", \"s\")\r\n                    && arg.matchesEnum(Toggle.class)) {\r\n                scriptEntry.addObject(\"state\", new ElementTag(arg.getValue().toUpperCase()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"trait\")) {\r\n                scriptEntry.addObject(\"trait\", new ElementTag(arg.getValue()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"npcs\")\r\n                    && arg.matchesArgumentList(NPCTag.class)) {\r\n                scriptEntry.addObject(\"npcs\", arg.asType(ListTag.class).filter(NPCTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"trait\")) {\r\n            throw new InvalidArgumentsException(\"Missing trait argument!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"npcs\")) {\r\n            if (!Utilities.entryHasNPC(scriptEntry)) {\r\n                throw new InvalidArgumentsException(\"This command requires a linked NPC!\");\r\n            }\r\n            scriptEntry.addObject(\"npcs\", Collections.singletonList(Utilities.getEntryNPC(scriptEntry)));\r\n        }\r\n        scriptEntry.defaultObject(\"state\", new ElementTag(\"TOGGLE\"));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag toggle = scriptEntry.getElement(\"state\");\r\n        ElementTag traitName = scriptEntry.getElement(\"trait\");\r\n        List<NPCTag> npcs = (List<NPCTag>) scriptEntry.getObject(\"npcs\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), traitName, toggle, db(\"npc\", npcs));\r\n        }\r\n        Class<? extends Trait> trait = CitizensAPI.getTraitFactory().getTraitClass(traitName.asString());\r\n        if (trait == null) {\r\n            Debug.echoError(scriptEntry, \"Trait not found: \" + traitName.asString());\r\n            return;\r\n        }\r\n        for (NPCTag npcTag : npcs) {\r\n            NPC npc = npcTag.getCitizen();\r\n            switch (Toggle.valueOf(toggle.asString())) {\r\n                case TRUE:\r\n                case ON:\r\n                    if (npc.hasTrait(trait)) {\r\n                        Debug.echoError(scriptEntry, \"NPC already has trait '\" + traitName.asString() + \"'\");\r\n                    }\r\n                    else {\r\n                        npc.addTrait(trait);\r\n                    }\r\n                    break;\r\n                case FALSE:\r\n                case OFF:\r\n                    if (!npc.hasTrait(trait)) {\r\n                        Debug.echoError(scriptEntry, \"NPC does not have trait '\" + traitName.asString() + \"'\");\r\n                    }\r\n                    else {\r\n                        npc.removeTrait(trait);\r\n                    }\r\n                    break;\r\n                case TOGGLE:\r\n                    if (npc.hasTrait(trait)) {\r\n                        npc.removeTrait(trait);\r\n                    }\r\n                    else {\r\n                        npc.addTrait(trait);\r\n                    }\r\n                    break;\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/TriggerCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.npc.traits.TriggerTrait;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\npublic class TriggerCommand extends AbstractCommand {\r\n\r\n    public TriggerCommand() {\r\n        setName(\"trigger\");\r\n        setSyntax(\"trigger [name:<trigger>] (state:{toggle}/true/false) (cooldown:<duration>) (radius:<#>)\");\r\n        setRequiredArguments(1, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Trigger\r\n    // @Syntax trigger [name:<trigger>] (state:{toggle}/true/false) (cooldown:<duration>) (radius:<#>)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Plugin Citizens\r\n    // @Short Enables or disables a trigger.\r\n    // @Group npc\r\n    // @Guide https://guide.denizenscript.com/guides/npcs/interact-scripts.html\r\n    //\r\n    // @Description\r\n    // This command enables or disables an interact script trigger for the linked NPC.\r\n    // This is generally meant to be used within the 'on assignment' action in an assignment script.\r\n    // This might also be useful on timed activations or other special events (such as an NPC that \"goes to bed\" at the end of the day,\r\n    // you might disable the proximity trigger that would otherwise normally show a greeting message).\r\n    //\r\n    // The \"name\" argument is required, and can have any supported trigger name.\r\n    // The 4 triggers available by default are chat, click, damage, and proximity.\r\n    // For more details of the available trigger types, refer to <@link language Interact Script Triggers>.\r\n    //\r\n    // The \"state\" argument can be 'true' (to enable it), 'false' (to disable it),\r\n    // or unspecified to toggle it (that is, enable if it's currently off, or disable if it's currently on).\r\n    //\r\n    // You can specify the \"cooldown\" argument to set how long the trigger must wait\r\n    // after any firing before it can be fired again.\r\n    //\r\n    // You can specify the \"radius\" argument to set how far away a player can be when activating it.\r\n    // Note that the way this applies varies from trigger to trigger.\r\n    // For the \"chat\" trigger, a large radius can be easily accidentally triggered by unrelated chatter.\r\n    // For the \"proximity\" trigger, the radius argument should almost always be specified, as you generally want to control this with care.\r\n    // For the \"click\" and \"damage\" trigger, the radius argument will be ignored.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.has_trigger[<trigger>]>\r\n    //\r\n    // @Usage\r\n    // Use to enable the click trigger.\r\n    // - trigger name:click state:true\r\n    //\r\n    // @Usage\r\n    // Use to enable the chat trigger with a 10-second cooldown and a radius of 5 blocks.\r\n    // - trigger name:chat state:true cooldown:10s radius:5\r\n    //\r\n    // @Usage\r\n    // Use to disable the proximity trigger.\r\n    // - trigger name:proximity state:false\r\n    // -->\r\n\r\n    private enum Toggle {TOGGLE, TRUE, FALSE}\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.add(\"name:click\", \"name:chat\", \"name:damage\", \"name:proximity\");\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"cooldown\")\r\n                    && arg.matchesPrefix(\"cooldown\", \"c\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"cooldown\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"radius\")\r\n                    && arg.matchesPrefix(\"radius\", \"r\")\r\n                    && arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"radius\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"toggle\")\r\n                    && arg.matchesEnum(Toggle.class)) {\r\n                scriptEntry.addObject(\"toggle\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"npc\")\r\n                    && arg.matchesArgumentType(NPCTag.class)) {\r\n                scriptEntry.addObject(\"npc\", arg.asType(NPCTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"trigger\")) {\r\n                scriptEntry.addObject(\"trigger\", arg.asElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"trigger\")) {\r\n            throw new InvalidArgumentsException(\"Missing name argument!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"toggle\")) {\r\n            scriptEntry.addObject(\"toggle\", new ElementTag(\"TOGGLE\"));\r\n        }\r\n        if (!Utilities.entryHasNPC(scriptEntry) && !scriptEntry.hasObject(\"npc\")) {\r\n            throw new InvalidArgumentsException(\"This command requires a linked NPC!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag toggle = scriptEntry.getElement(\"toggle\");\r\n        ElementTag trigger = scriptEntry.getElement(\"trigger\");\r\n        ElementTag radius = scriptEntry.getElement(\"radius\");\r\n        DurationTag cooldown = scriptEntry.getObjectTag(\"cooldown\");\r\n        NPCTag npc = scriptEntry.hasObject(\"npc\") ? (NPCTag) scriptEntry.getObject(\"npc\") : Utilities.getEntryNPC(scriptEntry);\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), trigger, toggle, radius, cooldown, npc);\r\n        }\r\n        // Add trigger trait\r\n        if (!npc.getCitizen().hasTrait(TriggerTrait.class)) {\r\n            npc.getCitizen().addTrait(TriggerTrait.class);\r\n        }\r\n        TriggerTrait trait = npc.getCitizen().getOrAddTrait(TriggerTrait.class);\r\n        if (!trait.triggerNameIsValid(trigger.asString())) {\r\n            throw new InvalidArgumentsRuntimeException(\"Invalid trigger name '\" + trigger + \"' - are you sure you spelled it right?\");\r\n        }\r\n        switch (Toggle.valueOf(toggle.asString().toUpperCase())) {\r\n            case TOGGLE:\r\n                trait.toggleTrigger(trigger.asString());\r\n                break;\r\n            case TRUE:\r\n                trait.toggleTrigger(trigger.asString(), true);\r\n                break;\r\n            case FALSE:\r\n                trait.toggleTrigger(trigger.asString(), false);\r\n                break;\r\n        }\r\n        if (radius != null) {\r\n            trait.setLocalRadius(trigger.asString(), radius.asInt());\r\n        }\r\n        if (cooldown != null) {\r\n            trait.setLocalCooldown(trigger.asString(), cooldown.getSeconds());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/npc/VulnerableCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.npc;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport net.citizensnpcs.api.npc.NPC;\r\n\r\npublic class VulnerableCommand extends AbstractCommand {\r\n\r\n    public VulnerableCommand() {\r\n        setName(\"vulnerable\");\r\n        setSyntax(\"vulnerable (state:{true}/false/toggle)\");\r\n        setRequiredArguments(0, 1);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Vulnerable\r\n    // @Syntax vulnerable (state:{true}/false/toggle)\r\n    // @Required 0\r\n    // @Maximum 1\r\n    // @Plugin Citizens\r\n    // @Short Sets whether an NPC is vulnerable.\r\n    // @Group npc\r\n    //\r\n    // @Description\r\n    // Toggles whether an NPC can be hurt or not.\r\n    //\r\n    // @Tags\r\n    // <NPCTag.invulnerable>\r\n    //\r\n    // @Usage\r\n    // Makes an NPC vulnerable.\r\n    // - vulnerable state:true\r\n    //\r\n    // @Usage\r\n    // Makes an NPC vulnerable if it is not, and invulnerable if it is.\r\n    // - vulnerable\r\n    //\r\n    // -->\r\n\r\n    enum Toggle {TRUE, FALSE, TOGGLE}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"action\") && arg.matchesEnum(Toggle.class)) {\r\n                scriptEntry.addObject(\"action\", arg.asElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"action\", new ElementTag(\"toggle\"));\r\n        if (!Utilities.entryHasNPC(scriptEntry)) {\r\n            throw new InvalidArgumentsException(\"This command requires a linked NPC!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag action = scriptEntry.getElement(\"action\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), Utilities.getEntryNPC(scriptEntry), action);\r\n        }\r\n        NPC npc = Utilities.getEntryNPC(scriptEntry).getCitizen();\r\n        Toggle toggle = Toggle.valueOf(action.asString().toUpperCase());\r\n        npc.setProtected(!(toggle == Toggle.TOGGLE ? npc.isProtected() : action.asBoolean()));\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ActionBarCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.containers.core.FormatScriptContainer;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.ChatMessageType;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class ActionBarCommand extends AbstractCommand {\r\n\r\n    public ActionBarCommand() {\r\n        setName(\"actionbar\");\r\n        setSyntax(\"actionbar [<text>] (targets:<player>|...) (format:<script>) (per_player)\");\r\n        setRequiredArguments(1, 4);\r\n        setParseArgs(false);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name ActionBar\r\n    // @Syntax actionbar [<text>] (targets:<player>|...) (format:<script>) (per_player)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Short Sends a message to a player's action bar.\r\n    // @group player\r\n    //\r\n    // @Description\r\n    // Sends a message to the target's action bar area.\r\n    // If no target is specified it will default to the attached player.\r\n    // Accepts the 'format:<name>' argument, which will reformat the text according to the specified format script. See <@link language Format Script Containers>.\r\n    //\r\n    // Optionally use 'per_player' with a list of player targets, to have the tags in the text input be reparsed for each and every player.\r\n    // So, for example, \"- actionbar 'hello <player.name>' targets:<server.online_players>\"\r\n    // would normally show \"hello bob\" to every player (every player sees the exact same name in the text, ie bob sees \"hello bob\", steve also sees \"hello bob\", etc)\r\n    // but if you use \"per_player\", each player online would see their own name (so bob sees \"hello bob\", steve sees \"hello steve\", etc).\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to send a message to the player's action bar.\r\n    // - actionbar \"Hey there <player.name>!\"\r\n    //\r\n    // @Usage\r\n    // Use to send a message to a list of players.\r\n    // - actionbar \"Hey, welcome to the server!\" targets:<[thatplayer]>|<[player]>|<[someplayer]>\r\n    //\r\n    // @Usage\r\n    // Use to send a message to a list of players, with a formatted message.\r\n    // - actionbar \"Hey there!\" targets:<[thatplayer]>|<[player]> format:ServerChat\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : ArgumentHelper.interpret(scriptEntry, scriptEntry.getOriginalArguments())) {\r\n            if (!scriptEntry.hasObject(\"format\")\r\n                    && arg.matchesPrefix(\"format\", \"f\")) {\r\n                String formatStr = TagManager.tag(arg.getValue(), scriptEntry.getContext());\r\n                FormatScriptContainer format = ScriptRegistry.getScriptContainer(formatStr);\r\n                if (format == null) {\r\n                    Debug.echoError(\"Could not find format script matching '\" + formatStr + \"'\");\r\n                }\r\n                scriptEntry.addObject(\"format\", new ScriptTag(format));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"targets\")\r\n                    && arg.matchesPrefix(\"targets\", \"target\")) {\r\n                scriptEntry.addObject(\"targets\", ListTag.getListFor(TagManager.tagObject(arg.getValue(), scriptEntry.getContext()), scriptEntry.getContext()).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"per_player\")\r\n                    && arg.matches(\"per_player\")) {\r\n                scriptEntry.addObject(\"per_player\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"text\")) {\r\n                scriptEntry.addObject(\"text\", arg.getRawElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"text\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a message!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"targets\") && !Utilities.entryHasPlayer(scriptEntry)) {\r\n            throw new InvalidArgumentsException(\"Must specify target(s).\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"targets\")) {\r\n            if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                throw new InvalidArgumentsException(\"Must specify valid player Targets!\");\r\n            }\r\n            else {\r\n                scriptEntry.addObject(\"targets\", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry)));\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        List<PlayerTag> targets = (List<PlayerTag>) scriptEntry.getObject(\"targets\");\r\n        String text = scriptEntry.getElement(\"text\").asString();\r\n        ScriptTag formatObj = scriptEntry.getObjectTag(\"format\");\r\n        ElementTag perPlayerObj = scriptEntry.getElement(\"per_player\");\r\n        boolean perPlayer = perPlayerObj != null && perPlayerObj.asBoolean();\r\n        BukkitTagContext context = (BukkitTagContext) scriptEntry.getContext();\r\n        if (!perPlayer) {\r\n            text = TagManager.tag(text, context);\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"message\", text), db(\"targets\", targets), formatObj, perPlayerObj);\r\n        }\r\n        FormatScriptContainer format = formatObj == null ? null : (FormatScriptContainer) formatObj.getContainer();\r\n        for (PlayerTag player : targets) {\r\n            if (player != null) {\r\n                if (!player.isOnline()) {\r\n                    Debug.echoDebug(scriptEntry, \"Player is offline, can't send actionbar to them. Skipping.\");\r\n                    continue;\r\n                }\r\n                String personalText = text;\r\n                if (perPlayer) {\r\n                    context.player = player;\r\n                    personalText = TagManager.tag(personalText, context);\r\n                }\r\n                player.getPlayerEntity().spigot().sendMessage(ChatMessageType.ACTION_BAR, FormattedTextHelper.parse(format != null ? format.getFormattedText(personalText, scriptEntry) : personalText, ChatColor.WHITE));\r\n            }\r\n            else {\r\n                Debug.echoError(\"Sent actionbar to non-existent player!?\");\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/AdvancementCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.util.Advancement;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class AdvancementCommand extends AbstractCommand {\r\n\r\n    public AdvancementCommand() {\r\n        setName(\"advancement\");\r\n        setSyntax(\"advancement [id:<name>] (delete/grant:<players>/revoke:<players>/{create}) (parent:<name>) (icon:<item>) (title:<text>) (description:<text>) (background:<key>) (frame:<type>) (toast:<boolean>) (announce:<boolean>) (hidden:<boolean>) (x:<offset>) (y:<offset>) (progress_length:<#>)\");\r\n        setRequiredArguments(1, 14);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Advancement\r\n    // @Syntax advancement [id:<name>] (delete/grant:<players>/revoke:<players>/{create}) (parent:<name>) (icon:<item>) (title:<text>) (description:<text>) (background:<key>) (frame:<type>) (toast:<boolean>) (announce:<boolean>) (hidden:<boolean>) (x:<offset>) (y:<offset>) (progress_length:<#>)\r\n    // @Required 1\r\n    // @Maximum 14\r\n    // @Short Controls a custom advancement.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Controls custom Minecraft player advancements. You should generally create advancements manually on server start.\r\n    // Currently, the ID argument may only refer to advancements added through this command.\r\n    // The default action is to create and register a new advancement.\r\n    // You may also delete an existing advancement, in which case do not provide any further arguments.\r\n    // You may grant or revoke an advancement for a list of players, in which case do not provide any further arguments.\r\n    // The parent argument sets the root advancement in the advancements menu, in the format \"namespace:key\".\r\n    // If no namespace is specified, the parent is assumed to have been created through this command.\r\n    // The icon argument sets the icon displayed in toasts and the advancements menu.\r\n    // As of MC 1.20, an icon is required. Dirt will be used if it is missing.\r\n    // The title argument sets the title that will show on toasts and in the advancements menu.\r\n    // The description argument sets the information that will show when scrolling over a chat announcement or in the advancements menu.\r\n    // The background argument sets the image to use if the advancement goes to a new tab.\r\n    // If the background is unspecified, defaults to \"minecraft:gui/advancements/backgrounds/stone\".\r\n    // The frame argument sets the type of advancement - valid arguments are CHALLENGE, GOAL, and TASK.\r\n    // The toast argument sets whether the advancement should display a toast message when a player completes it. Default is true.\r\n    // The announce argument sets whether the advancement should display a chat message to the server when a player completes it. Default is true.\r\n    // The hidden argument sets whether the advancement should be hidden until it is completed.\r\n    // The x and y arguments are offsets based on the size of an advancement icon in the menu. They are required for custom tabs to look reasonable.\r\n    //\r\n    // When creating an advancement, optionally specify 'progress_length' to make it require multiple parts.\r\n    // When granting an advancement, optionally specify 'progress_length' to only grant partial progress.\r\n    //\r\n    // To award a pre-existing vanilla advancement, instead use <@link mechanism PlayerTag.award_advancement>\r\n    //\r\n    // WARNING: Failure to re-create advancements on every server start may result in loss of data - use <@link event server prestart>.\r\n    //\r\n    // If you mess with datapacks, you will also need to re-create advancements during <@link event server resources reloaded>\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.has_advancement[<advancement>]>\r\n    // <PlayerTag.advancements>\r\n    // <server.advancement_types>\r\n    //\r\n    // @Usage\r\n    // Creates a new advancement that has a potato icon.\r\n    // - advancement id:hello_world icon:baked_potato \"title:Hello World\" \"description:You said hello to the world.\"\r\n    //\r\n    // @Usage\r\n    // Creates a new advancement with the parent \"hello_world\" and a CHALLENGE frame. Hidden until it is completed.\r\n    // - advancement id:hello_universe parent:hello_world icon:ender_pearl \"title:Hello Universe\" \"description:You said hello to the UNIVERSE.\" frame:challenge hidden:true x:1\r\n    //\r\n    // @Usage\r\n    // Grants the \"hello_world\" advancement to the current player.\r\n    // - advancement id:hello_world grant:<player>\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"id\")\r\n                    && arg.matchesPrefix(\"id\")) {\r\n                scriptEntry.addObject(\"id\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"parent\")\r\n                    && arg.matchesPrefix(\"parent\")) {\r\n                scriptEntry.addObject(\"parent\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"create\")\r\n                    && arg.matches(\"create\")) {\r\n                scriptEntry.addObject(\"create\", new ElementTag(true)); // unused, just to be explicit\r\n            }\r\n            else if (!scriptEntry.hasObject(\"delete\")\r\n                    && arg.matches(\"delete\", \"remove\")) {\r\n                scriptEntry.addObject(\"delete\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"grant\")\r\n                    && arg.matchesPrefix(\"grant\", \"give\", \"g\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"grant\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"revoke\")\r\n                    && arg.matchesPrefix(\"revoke\", \"take\", \"r\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"revoke\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"icon\")\r\n                    && arg.matchesPrefix(\"icon\", \"i\")\r\n                    && arg.matchesArgumentType(ItemTag.class)) {\r\n                scriptEntry.addObject(\"icon\", arg.asType(ItemTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"title\")\r\n                    && arg.matchesPrefix(\"title\", \"text\", \"t\")) {\r\n                scriptEntry.addObject(\"title\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"description\")\r\n                    && arg.matchesPrefix(\"description\", \"desc\", \"d\")) {\r\n                scriptEntry.addObject(\"description\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"background\")\r\n                    && arg.matchesPrefix(\"background\", \"bg\")) {\r\n                scriptEntry.addObject(\"background\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"frame\")\r\n                    && arg.matchesPrefix(\"frame\", \"f\")\r\n                    && arg.matchesEnum(Advancement.Frame.class)) {\r\n                scriptEntry.addObject(\"frame\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"toast\")\r\n                    && arg.matchesPrefix(\"toast\", \"show\")\r\n                    && arg.matchesBoolean()) {\r\n                scriptEntry.addObject(\"toast\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"announce\")\r\n                    && arg.matchesPrefix(\"announce\", \"chat\")\r\n                    && arg.matchesBoolean()) {\r\n                scriptEntry.addObject(\"announce\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"hidden\")\r\n                    && arg.matchesPrefix(\"hidden\", \"hide\", \"h\")\r\n                    && arg.matchesBoolean()) {\r\n                scriptEntry.addObject(\"hidden\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"x\")\r\n                    && arg.matchesPrefix(\"x\")\r\n                    && arg.matchesFloat()) {\r\n                scriptEntry.addObject(\"x\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"y\")\r\n                    && arg.matchesPrefix(\"y\")\r\n                    && arg.matchesFloat()) {\r\n                scriptEntry.addObject(\"y\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"progress_length\")\r\n                    && arg.matchesPrefix(\"progress_length\")\r\n                    && arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"progress_length\", arg.asElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"id\")) {\r\n            throw new InvalidArgumentsException(\"Must specify an ID!\");\r\n        }\r\n        scriptEntry.defaultObject(\"icon\", new ItemTag(Material.AIR));\r\n        scriptEntry.defaultObject(\"title\", new ElementTag(\"\"));\r\n        scriptEntry.defaultObject(\"description\", new ElementTag(\"\"));\r\n        scriptEntry.defaultObject(\"background\", new ElementTag(NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) ? \"minecraft:gui/advancements/backgrounds/stone\" : \"minecraft:textures/gui/advancements/backgrounds/stone.png\", true));\r\n        scriptEntry.defaultObject(\"frame\", new ElementTag(\"TASK\"));\r\n        scriptEntry.defaultObject(\"toast\", new ElementTag(true));\r\n        scriptEntry.defaultObject(\"announce\", new ElementTag(true));\r\n        scriptEntry.defaultObject(\"hidden\", new ElementTag(false));\r\n        scriptEntry.defaultObject(\"x\", new ElementTag(0f));\r\n        scriptEntry.defaultObject(\"y\", new ElementTag(0f));\r\n    }\r\n\r\n    public static final Map<NamespacedKey, Advancement> customRegistered = new HashMap<>();\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag id = scriptEntry.getElement(\"id\");\r\n        ElementTag parent = scriptEntry.getElement(\"parent\");\r\n        ElementTag delete = scriptEntry.getElement(\"delete\");\r\n        ListTag grant = scriptEntry.getObjectTag(\"grant\");\r\n        ListTag revoke = scriptEntry.getObjectTag(\"revoke\");\r\n        ItemTag icon = scriptEntry.getObjectTag(\"icon\");\r\n        ElementTag title = scriptEntry.getElement(\"title\");\r\n        ElementTag description = scriptEntry.getElement(\"description\");\r\n        ElementTag background = scriptEntry.getElement(\"background\");\r\n        ElementTag frame = scriptEntry.getElement(\"frame\");\r\n        ElementTag toast = scriptEntry.getElement(\"toast\");\r\n        ElementTag announce = scriptEntry.getElement(\"announce\");\r\n        ElementTag hidden = scriptEntry.getElement(\"hidden\");\r\n        ElementTag x = scriptEntry.getElement(\"x\");\r\n        ElementTag y = scriptEntry.getElement(\"y\");\r\n        ElementTag progressLength = scriptEntry.getElement(\"progress_length\");\r\n        if (icon.getBukkitMaterial().isAir()) {\r\n            icon = new ItemTag(Material.DIRT);\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, name, id, parent, delete, grant, revoke, icon, title, description, background, progressLength, frame, toast, announce, hidden, x, y);\r\n        }\r\n        NamespacedKey key = new NamespacedKey(Denizen.getInstance(), id.asString());\r\n        if (delete == null && grant == null && revoke == null) {\r\n            NamespacedKey parentKey = null;\r\n            NamespacedKey backgroundKey = null;\r\n            if (parent != null) {\r\n                List<String> split = CoreUtilities.split(parent.asString(), ':', 2);\r\n                if (split.size() == 1) {\r\n                    parentKey = new NamespacedKey(Denizen.getInstance(), split.get(0));\r\n                }\r\n                else {\r\n                    parentKey = new NamespacedKey(CoreUtilities.toLowerCase(split.get(0)), CoreUtilities.toLowerCase(split.get(1)));\r\n                }\r\n            }\r\n            else if (background != null) {\r\n                backgroundKey = Utilities.parseNamespacedKey(background.asString());\r\n                String path = backgroundKey.getKey();\r\n                if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && path.startsWith(\"textures/\") && path.endsWith(\".png\")) {\r\n                    BukkitImplDeprecations.advancementBackgroundFormat.warn(scriptEntry);\r\n                    backgroundKey = new NamespacedKey(backgroundKey.getNamespace(), path.substring(\"textures/\".length(), path.length() - \".png\".length()));\r\n                }\r\n            }\r\n            final Advancement advancement = new Advancement(false, key, parentKey,\r\n                    icon.getItemStack(), title.asString(), description.asString(),\r\n                    backgroundKey, Advancement.Frame.valueOf(frame.asString().toUpperCase()),\r\n                    toast.asBoolean(), announce.asBoolean(), hidden.asBoolean(), x.asFloat(), y.asFloat(), progressLength == null ? 1 : progressLength.asInt());\r\n            NMSHandler.advancementHelper.register(advancement);\r\n            customRegistered.put(key, advancement);\r\n            return;\r\n        }\r\n        Advancement advancement = customRegistered.get(key);\r\n        if (advancement == null) {\r\n            Debug.echoError(\"Invalid advancement key '\" + key + \"': not registered. Are you sure you registered it using the 'advancement' command previously?\"\r\n                     + \" Note that the 'advancement' command is not for vanilla advancements.\");\r\n            return;\r\n        }\r\n        if (delete != null) {\r\n            NMSHandler.advancementHelper.unregister(advancement);\r\n            customRegistered.remove(key);\r\n        }\r\n        else if (grant != null) {\r\n            for (PlayerTag target : grant.filter(PlayerTag.class, scriptEntry)) {\r\n                Player player = target.getPlayerEntity();\r\n                if (player != null) {\r\n                    if (progressLength == null) {\r\n                        NMSHandler.advancementHelper.grant(advancement, player);\r\n                    }\r\n                    else {\r\n                        NMSHandler.advancementHelper.grantPartial(advancement, player, progressLength.asInt());\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        else /*if (revoke != null)*/ {\r\n            for (PlayerTag target : revoke.filter(PlayerTag.class, scriptEntry)) {\r\n                Player player = target.getPlayerEntity();\r\n                if (player != null) {\r\n                    if (progressLength == null) {\r\n                        NMSHandler.advancementHelper.revoke(advancement, player);\r\n                    }\r\n                    else {\r\n                        NMSHandler.advancementHelper.revokePartial(advancement, player, progressLength.asInt());\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/BlockCrackCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.scheduling.RepeatingSchedulable;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic class BlockCrackCommand extends AbstractCommand {\r\n\r\n    public BlockCrackCommand() {\r\n        setName(\"blockcrack\");\r\n        setSyntax(\"blockcrack [<location>] [progress:<#>] (stack) (players:<player>|...) (duration:<duration>)\");\r\n        setRequiredArguments(2, 5);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name BlockCrack\r\n    // @Syntax blockcrack [<location>] [progress:<#>] (stack) (players:<player>|...) (duration:<duration>)\r\n    // @Required 2\r\n    // @Maximum 5\r\n    // @Short Shows the player(s) a block cracking animation.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // You must specify a progress number between 1 and 10, where 1 is the first stage and 10 is the last.\r\n    // To remove the animation, you must specify any number outside of that range. For example, 0.\r\n    // Optionally, you can stack multiple effects or set a duration for how long the effect should be shown.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to show a crack in a block to the currently attached player.\r\n    // - blockcrack <context.location> progress:4\r\n    //\r\n    // @Usage\r\n    // Use to stop showing a crack in a block to all online players.\r\n    // - blockcrack <context.location> progress:0 players:<server.online_players>\r\n    //\r\n    // @Usage\r\n    // Use to show all 10 layers of block cracking at the same time.\r\n    // - repeat 10:\r\n    //   - blockcrack <context.location> progress:<[value]> stack\r\n    //\r\n    // @Usage\r\n    // Use to show a crack in a block to the attached player for 5 seconds.\r\n    // - blockcrack <context.location> progress:4 duration:5s\r\n    // -->\r\n\r\n    private static class IntHolder {\r\n        public int theInt;\r\n        public int base;\r\n    }\r\n\r\n    private static final Map<Location, Map<UUID, IntHolder>> progressTracker = new HashMap<>();\r\n    private static int lastBase;\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                            @ArgName(\"location\") @ArgLinear LocationTag location,\r\n                            @ArgName(\"progress\") @ArgPrefixed int progress,\r\n                            @ArgName(\"stack\") boolean stack,\r\n                            @ArgName(\"players\") @ArgDefaultNull @ArgPrefixed @ArgSubType(PlayerTag.class) List<PlayerTag> players,\r\n                            @ArgName(\"duration\") @ArgPrefixed @ArgDefaultNull DurationTag duration) {\r\n        if (players == null) {\r\n            players = List.of(Utilities.getEntryPlayer(scriptEntry));\r\n        }\r\n        Location loc = location.getBlock().getLocation();\r\n        if (!progressTracker.containsKey(loc)) {\r\n            progressTracker.put(loc, new HashMap<>());\r\n        }\r\n        Map<UUID, IntHolder> uuidInt = progressTracker.get(loc);\r\n        for (PlayerTag player : players) {\r\n            if (!player.isOnline()) {\r\n                Debug.echoError(\"Players must be online!\");\r\n                continue;\r\n            }\r\n            Player playerEnt = player.getPlayerEntity();\r\n            UUID uuid = playerEnt.getUniqueId();\r\n            if (!uuidInt.containsKey(uuid)) {\r\n                lastBase += 10;\r\n                IntHolder newIntHolder = new IntHolder();\r\n                newIntHolder.theInt = lastBase;\r\n                newIntHolder.base = lastBase;\r\n                uuidInt.put(uuid, newIntHolder);\r\n            }\r\n            IntHolder intHolder = uuidInt.get(uuid);\r\n            if (!stack && intHolder.theInt > intHolder.base) {\r\n                for (int i = intHolder.base; i <= intHolder.theInt; i++) {\r\n                    showBlockCrack(playerEnt, i, loc, -1, duration);\r\n                }\r\n                intHolder.theInt = intHolder.base;\r\n            }\r\n            else if (stack && intHolder.theInt - intHolder.base > 10) {\r\n                continue;\r\n            }\r\n            int id = stack ? intHolder.theInt++ : intHolder.theInt;\r\n            showBlockCrack(playerEnt, id, loc, progress - 1, duration);\r\n        }\r\n    }\r\n\r\n    private static void showBlockCrack(Player player, int id, Location location, int progress, DurationTag duration) {\r\n        if (duration == null || progress == -1) {\r\n            NMSHandler.packetHelper.showBlockCrack(player, id, location, progress);\r\n            return;\r\n        }\r\n        final RepeatingSchedulable schedulable = new RepeatingSchedulable(null, 1);\r\n        long endTime = DenizenCore.serverTimeMillis + duration.getMillis();\r\n        // Showing it before the schedulable will allow the block crack to appear as soon as the command is run.\r\n        NMSHandler.packetHelper.showBlockCrack(player, id, location, progress);\r\n        schedulable.run = () -> {\r\n            if (!player.isOnline()) {\r\n                schedulable.cancel();\r\n                return;\r\n            }\r\n            if (endTime <= DenizenCore.serverTimeMillis) {\r\n                NMSHandler.packetHelper.showBlockCrack(player, id, location, -1);\r\n                schedulable.cancel();\r\n                return;\r\n            }\r\n            NMSHandler.packetHelper.showBlockCrack(player, id, location, progress);\r\n        };\r\n        DenizenCore.schedule(schedulable);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ChatCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\n\nimport com.denizenscript.denizen.npc.speech.DenizenSpeechContext;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.tags.BukkitTagContext;\nimport com.denizenscript.denizen.utilities.PaperAPITools;\nimport com.denizenscript.denizen.utilities.Settings;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\nimport com.denizenscript.denizencore.scripts.queues.ScriptQueue;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.tags.TagManager;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.citizensnpcs.api.CitizensAPI;\nimport net.citizensnpcs.api.ai.speech.Talkable;\nimport net.citizensnpcs.api.ai.speech.TalkableEntity;\nimport net.citizensnpcs.api.ai.speech.event.NPCSpeechEvent;\nimport org.bukkit.Bukkit;\nimport org.bukkit.entity.Entity;\n\nimport java.util.*;\n\npublic class ChatCommand extends AbstractCommand {\n\n    public ChatCommand() {\n        setName(\"chat\");\n        setSyntax(\"chat [<text>] (no_target/targets:<entity>|...) (talkers:<entity>|...) (range:<#.#>)\");\n        setRequiredArguments(1, 4);\n        isProcedural = false;\n        addRemappedPrefixes(\"targets\", \"target\", \"t\");\n        addRemappedPrefixes(\"talkers\", \"talker\");\n        addRemappedPrefixes(\"range\", \"r\");\n        autoCompile();\n    }\n\n    // TODO: Should the chat command be in the NPC group instead?\n    // <--[command]\n    // @Name Chat\n    // @Syntax chat [<text>] (no_target/targets:<entity>|...) (talkers:<entity>|...) (range:<#.#>)\n    // @Required 1\n    // @Maximum 4\n    // @Plugin Citizens\n    // @Short Causes an NPC/NPCs to send a chat message to nearby players.\n    // @Synonyms Say,Speak\n    // @Group player\n    //\n    // @Description\n    // Chat uses an NPC's speech controller provided by Denizen, typically inside 'interact' or 'task' script-containers.\n    // Typically there is already player and NPC context inside a queue that is using the 'chat' command.\n    // In this case, only a text input is required.\n    // Alternatively, target entities can be specified to have any Entity chat to a different target/targets,\n    // or specify 'no_target' to not send the message to any specific target.\n    //\n    // Chat from an NPC is formatted by the settings present in Denizen's config.yml.\n    // Players being chatted to see a slightly different message than surrounding players.\n    // By default, a 'chat' will allow other players nearby to also see the conversation. For example:\n    // <code>\n    // - chat 'Hello!'\n    // </code>\n    // The player being chatted to, by default the attached Player to the script queue, will see a message 'Jack says to you, Hello!',\n    // however surrounding entities will see something along the lines of 'Jack says to Bob, Hello!'.\n    // The format for this is configurable via the \"Denizen/config.yml\" file.\n    //\n    // If sending messages to the Player without any surrounding entities hearing the message is desirable,\n    // it is often times recommended to instead use the 'narrate' command.\n    // Alternatively, on a server-wide scale, the configuration node for the 'range' can be set to 0, however this is discouraged.\n    //\n    // @Tags\n    // None\n    //\n    // @Usage\n    // Use to emulate an NPC talking out loud to a Player within an interact script-container.\n    // - chat \"Hello, <player.name>! Nice day, eh?\"\n    //\n    // @Usage\n    // Use to have an NPC talk to a group of individuals.\n    // - chat targets:<npc.location.find_players_within[6].filter[has_flag[clan_initiate]]> \"Welcome, initiate!\"\n    // -->\n\n    public static void autoExecute(ScriptEntry scriptEntry,\n                                   @ArgName(\"message\") @ArgLinear String message,\n                                   @ArgName(\"talkers\") @ArgPrefixed @ArgDefaultNull @ArgSubType(EntityTag.class) List<EntityTag> talkers,\n                                   @ArgName(\"targets\") @ArgPrefixed @ArgDefaultNull @ArgSubType(EntityTag.class) List<EntityTag> targets,\n                                   @ArgName(\"no_target\") boolean noTarget,\n                                   @ArgName(\"range\") @ArgPrefixed @ArgDefaultText(\"-1\") double chatRange) {\n        if (targets == null) {\n            if (!noTarget) {\n                PlayerTag player = Utilities.getEntryPlayer(scriptEntry);\n                if (player == null) {\n                    throw new InvalidArgumentsRuntimeException(\"Missing targets!\");\n                }\n                if (!player.isOnline()) {\n                    Debug.echoDebug(scriptEntry, \"Player is not online, skipping.\");\n                    return;\n                }\n                targets = Collections.singletonList(player.getDenizenEntity());\n            }\n        }\n        if (talkers == null) {\n            NPCTag talker = Utilities.getEntryNPC(scriptEntry);\n            if (talker == null) {\n                throw new InvalidArgumentsRuntimeException(\"Missing talker!\");\n            }\n            if (!talker.isSpawned()) {\n                Debug.echoDebug(scriptEntry, \"Chat Talker is not spawned! Cannot talk.\");\n                return;\n            }\n            talkers = Collections.singletonList(talker.getDenizenEntity());\n        }\n        if (chatRange == -1) {\n            chatRange = Settings.chatBystandersRange();\n        }\n        DenizenSpeechContext context = new DenizenSpeechContext(message, scriptEntry, chatRange);\n        if (targets != null && !targets.isEmpty()) {\n            for (EntityTag ent : targets) {\n                context.addRecipient(ent.getBukkitEntity());\n            }\n        }\n        for (EntityTag talker : talkers) {\n            Entity entity = talker.getBukkitEntity();\n            if (entity != null) {\n                context.setTalker(entity);\n                speak(context);\n            }\n            else {\n                Debug.echoDebug(scriptEntry, \"Chat Talker is not spawned! Cannot talk.\");\n            }\n        }\n    }\n\n    public static void speak(DenizenSpeechContext speechContext) {\n        Entity talker = speechContext.getTalker().getEntity();\n        if (EntityTag.isCitizensNPC(talker)) {\n            NPCSpeechEvent event = new NPCSpeechEvent(CitizensAPI.getNPCRegistry().getNPC(talker), speechContext);\n            Bukkit.getPluginManager().callEvent(event);\n            if (event.isCancelled()) {\n                return;\n            }\n        }\n        ScriptQueue queue = speechContext.getScriptEntry().getResidingQueue();\n        ObjectTag defTalker = queue.getDefinitionObject(\"talker\");\n        ObjectTag defMessage = queue.getDefinitionObject(\"message\");\n        queue.addDefinition(\"talker\", new EntityTag(talker));\n        queue.addDefinition(\"message\", new ElementTag(speechContext.getMessage(), true));\n        talk(speechContext, queue);\n        if (defMessage != null) {\n            queue.addDefinition(\"message\", defMessage);\n        }\n        if (defTalker != null) {\n            queue.addDefinition(\"talker\", defTalker);\n        }\n    }\n\n    private static void talk(DenizenSpeechContext speechContext, ScriptQueue queue) {\n        TagContext tagContext = new BukkitTagContext(speechContext.getScriptEntry());\n        if (!speechContext.hasRecipients()) {\n            String text = TagManager.tag(Settings.chatNoTargetFormat(), tagContext);\n            talkToBystanders(text, speechContext);\n            return;\n        }\n        String text = PaperAPITools.instance.convertTextToMiniMessage(TagManager.tag(Settings.chatToTargetFormat(), tagContext), true);\n        for (Talkable entity : speechContext) {\n            entity.talkTo(speechContext, text);\n        }\n        if (!speechContext.isBystandersEnabled()) {\n            return;\n        }\n        if (speechContext.size() == 1) {\n            ObjectTag defTarget = queue.getDefinitionObject(\"target\");\n            queue.addDefinition(\"target\", new EntityTag(speechContext.iterator().next().getEntity()));\n            talkToBystanders(TagManager.tag(Settings.chatWithTargetToBystandersFormat(), tagContext), speechContext);\n            if (defTarget != null) {\n                queue.addDefinition(\"target\", defTarget);\n            }\n            return;\n        }\n        String[] format = Settings.chatMultipleTargetsFormat().split(\"%target%\");\n        if (format.length <= 1) {\n            Debug.echoError(\"Invalid 'Commands.Chat.Options.Multiple targets format' in config.yml! Must have at least 1 %target%\");\n        }\n        StringBuilder parsed = new StringBuilder();\n        int i = 0;\n        for (Talkable target : speechContext) {\n            parsed.append(format[i]);\n            if (i == format.length - 1) {\n                break;\n            }\n            parsed.append(new EntityTag(target.getEntity()).getName());\n            i++;\n        }\n        String targets = TagManager.tag(parsed.toString(), tagContext);\n        ObjectTag defTargets = queue.getDefinitionObject(\"targets\");\n        queue.addDefinition(\"targets\", new ElementTag(targets, true));\n        String bystanderText = TagManager.tag(Settings.chatWithTargetsToBystandersFormat(), tagContext);\n        talkToBystanders(bystanderText, speechContext);\n        if (defTargets != null) {\n            queue.addDefinition(\"targets\", defTargets);\n        }\n    }\n\n    private static void talkToBystanders(String text, DenizenSpeechContext speechContext) {\n        Set<UUID> recipients = new HashSet<>(speechContext.size());\n        for (Talkable recipient : speechContext) {\n            recipients.add(recipient.getEntity().getUniqueId());\n        }\n        String parsedText = PaperAPITools.instance.convertTextToMiniMessage(text, true);\n        double range = speechContext.getChatRange();\n        for (Entity bystander : range == 0d ? Bukkit.getOnlinePlayers() : speechContext.getTalker().getEntity().getNearbyEntities(range, range, range)) {\n            if (!recipients.contains(bystander.getUniqueId())) {\n                new TalkableEntity(bystander).talkNear(speechContext, parsedText);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ClickableCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\n\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\nimport com.denizenscript.denizencore.objects.Argument;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.*;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.BracedCommand;\nimport com.denizenscript.denizencore.scripts.containers.core.TaskScriptContainer;\nimport com.denizenscript.denizencore.scripts.queues.ContextSource;\nimport com.denizenscript.denizencore.scripts.queues.ScriptQueue;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.ScriptUtilities;\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\nimport org.bukkit.entity.Player;\n\nimport java.util.*;\nimport java.util.function.Consumer;\n\npublic class ClickableCommand extends BracedCommand {\n\n    public ClickableCommand() {\n        setName(\"clickable\");\n        setSyntax(\"clickable (<script>/cancel:<id>) (def:<element>|.../defmap:<map>/def.<name>:<value>) (usages:<#>) (for:<player>|...) (until:<duration>)\");\n        setRequiredArguments(0, -1);\n        isProcedural = false;\n        allowedDynamicPrefixes = true;\n        setPrefixesHandled(\"usages\", \"until\", \"for\", \"cancel\", \"def\");\n    }\n\n    // <--[command]\n    // @Name Clickable\n    // @Syntax clickable (<script>/cancel:<id>) (def:<element>|.../defmap:<map>/def.<name>:<value>) (usages:<#>) (for:<player>|...) (until:<duration>)\n    // @Required 0\n    // @Maximum -1\n    // @Short Generates a clickable command for players.\n    // @Group player\n    //\n    // @Description\n    // Generates a clickable command for players.\n    //\n    // Generally, prefer to write a command script and simply \"on_click[/yourcommandhere]\" rather than using generated clickables.\n    // Generated clickables are a utility intended to enable clickables that are restricted from being normally accessed without receiving a clickable message.\n    //\n    // Specify a task script to run, or put an executable script section as sub-commands.\n    //\n    // When running a task, optionally any definitions to pass.\n    //\n    // When using a sub-section, the running commands will be in their own queue, but copy out the original queue's definitions and context source.\n    //\n    // Optionally specify a maximum number of usages (defaults to unlimited).\n    //\n    // Optionally specify a maximum duration it can be used for with 'until'.\n    //\n    // If no duration is specified, the clickable will remain valid until the server stops or restarts.\n    // WARNING: if you use clickables very often without a duration limit, this can lead to a memory leak.\n    // Clickables that have a specified max duration will occasionally be cleaned from memory.\n    //\n    // Optionally specify what players are allowed to use it. Defaults to unrestricted (any player that sees the click message may use it).\n    // Note that it is possible for a player to find the generated command ID in their logs and send it to another player to \"/\" execute, so if you don't restrict player access it may be abused in that way.\n    //\n    // This internally generates a command of the form \"/denizenclickable <generated_id>\".\n    //\n    // Players will need the permission \"denizen.clickable\" to be able to use this.\n    //\n    // You can cancel a clickable at any time via \"cancel:<id>\", where ID is the generated ID from saving the initial generated command.\n    //\n    // @Tags\n    // <entry[saveName].command> returns the command to use in \"on_click\".\n    // <entry[saveName].id> returns the generate command's ID.\n    // <ElementTag.on_click[<command>]>\n    //\n    // @Usage\n    // Use to generate a clickable that just narrates \"hello there!\" when clicked.\n    // - clickable save:my_clickable:\n    //     - narrate \"Hello there!\"\n    // - narrate \"Click <blue><element[here].on_click[<entry[my_clickable].command>]><reset>!\"\n    //\n    // @Usage\n    // Use to generate a clickable message that will run a task script named 'test_script'.\n    // - clickable test_script save:my_clickable\n    // - narrate \"Click <blue><element[here].on_click[<entry[my_clickable].command>]><reset>!\"\n    //\n    // @Usage\n    // Use to generate a clickable message that will run a task script named 'reward_drop', that can be used by only the first person to click it.\n    // - clickable reward_drop usages:1 save:reward\n    // - announce \"<blue><bold><element[Reward Here].on_click[<entry[reward].command>]><reset>!\"\n    //\n    // @Usage\n    // Use to generate a clickable message exclusively for the linked player, that must be used within a minute.\n    // - clickable your_secret def:quest3 for:<player> until:1m save:secretmessage\n    // - narrate \"Do you want to know the secret? <blue><element[Yes].on_click[<entry[secretmessage].command>]><reset> / No.\"\n    // @Usage\n    // Use to generate a clickable message and cancel it manually later.\n    // - clickable test_script save:my_clickable save:myclickable\n    // - narrate \"Click <blue><element[here].on_click[<entry[my_clickable].command>]><reset> before you land!\"\n    // - waituntil rate:1s max:30s <player.is_on_ground>\n    // - clickable cancel:<entry[myclickable].id>\n    //\n    // -->\n\n    @Override\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\n        tab.addScriptsOfType(TaskScriptContainer.class);\n    }\n\n    public static class Clickable {\n        public HashSet<UUID> forPlayers;\n        public ListTag definitions;\n        public MapTag defMap;\n        public ScriptTag script;\n        public String path;\n        public NPCTag npc;\n        public int remainingUsages;\n        public TagContext context;\n        public long until;\n        public List<ScriptEntry> directEntries;\n        public ContextSource contextSource;\n        public String queueId;\n    }\n\n    public static HashMap<UUID, Clickable> clickables = new HashMap<>();\n\n    public static void runClickable(UUID id, Player player) {\n        Clickable clickable = clickables.get(id);\n        if (clickable == null) {\n            return;\n        }\n        if (clickable.until != 0 && CoreUtilities.monotonicMillis() > clickable.until) {\n            clickables.remove(id);\n            return;\n        }\n        if (clickable.forPlayers != null && !clickable.forPlayers.contains(player.getUniqueId())) {\n            return;\n        }\n        if (clickable.remainingUsages > 0) {\n            clickable.remainingUsages--;\n            if (clickable.remainingUsages <= 0) {\n                clickables.remove(id);\n            }\n        }\n        Consumer<ScriptQueue> configure = (queue) -> {\n            if (clickable.defMap != null) {\n                for (Map.Entry<StringHolder, ObjectTag> val : clickable.defMap.entrySet()) {\n                    queue.addDefinition(val.getKey().str, val.getValue());\n                }\n            }\n        };\n        BukkitScriptEntryData data = new BukkitScriptEntryData(new PlayerTag(player), clickable.npc);\n        if (clickable.directEntries != null) {\n            ScriptUtilities.createAndStartQueueArbitrary(clickable.queueId, clickable.directEntries, data, clickable.contextSource, configure);\n        }\n        else if (clickable.script.validate() != null) {\n            ScriptUtilities.createAndStartQueue(clickable.script.getContainer(), clickable.path, data, null, configure, null, clickable.queueId, clickable.definitions, clickable.context);\n        }\n    }\n\n    @Override\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\n        MapTag defMap = new MapTag();\n        for (Argument arg : scriptEntry) {\n            if (arg.matchesPrefix(\"defmap\")\n                    && arg.matchesArgumentType(MapTag.class)) {\n                defMap.putAll(arg.asType(MapTag.class));\n            }\n            else if (arg.hasPrefix()\n                    && arg.getPrefix().getRawValue().startsWith(\"def.\")) {\n                defMap.putObject(arg.getPrefix().getRawValue().substring(\"def.\".length()), arg.object);\n            }\n            else if (!scriptEntry.hasObject(\"script\")) {\n                String scriptName = arg.getRawValue();\n                int dotIndex = scriptName.indexOf('.');\n                if (dotIndex > 0) {\n                    scriptEntry.addObject(\"path\", new ElementTag(scriptName.substring(dotIndex + 1)));\n                    scriptName = scriptName.substring(0, dotIndex);\n                }\n                ScriptTag script = ScriptTag.valueOf(scriptName, CoreUtilities.noDebugContext);\n                if (script == null) {\n                    arg.reportUnhandled();\n                }\n                else {\n                    scriptEntry.addObject(\"script\", script);\n                }\n            }\n            else {\n                arg.reportUnhandled();\n            }\n        }\n        if (!defMap.isEmpty()) {\n            scriptEntry.addObject(\"def_map\", defMap);\n        }\n    }\n\n    public static long lastTimeCleaned = CoreUtilities.monotonicMillis();\n\n    private final static List<UUID> toCleanNow = new ArrayList<>();\n\n    public static void cleanClickables() {\n        long curTime = CoreUtilities.monotonicMillis();\n        if (curTime < lastTimeCleaned + (30 * 60 * 1000)) {\n            return;\n        }\n        lastTimeCleaned = curTime;\n        toCleanNow.clear();\n        for (Map.Entry<UUID, Clickable> clickable : clickables.entrySet()) {\n            if (clickable.getValue().until != 0 && curTime > clickable.getValue().until) {\n                toCleanNow.add(clickable.getKey());\n            }\n        }\n        for (UUID id : toCleanNow) {\n            clickables.remove(id);\n        }\n    }\n\n    @Override\n    public void execute(ScriptEntry scriptEntry) {\n        ScriptTag script = scriptEntry.getObjectTag(\"script\");\n        ElementTag path = scriptEntry.getElement(\"path\");\n        ElementTag cancel = scriptEntry.argForPrefixAsElement(\"cancel\", null);\n        List<PlayerTag> forPlayers = scriptEntry.argForPrefixList(\"for\", PlayerTag.class, true);\n        ElementTag usages = scriptEntry.argForPrefixAsElement(\"usages\", null);\n        ListTag definitions = scriptEntry.argForPrefix(\"def\", ListTag.class, true);\n        DurationTag until = scriptEntry.argForPrefix(\"until\", DurationTag.class, true);\n        MapTag defMap = scriptEntry.getObjectTag(\"def_map\");\n        if (script == null && cancel == null && scriptEntry.getInsideList() == null) {\n            throw new InvalidArgumentsRuntimeException(\"Missing script argument!\");\n        }\n        if (scriptEntry.dbCallShouldDebug()) {\n            Debug.report(scriptEntry, getName(), script, cancel, path, usages, definitions, defMap, until, db(\"for\", forPlayers));\n        }\n        if (cancel != null) {\n            UUID id;\n            try {\n                id = UUID.fromString(cancel.asString());\n            }\n            catch (IllegalArgumentException ex) {\n                Debug.echoError(\"Invalid cancel ID: \" + ex.getMessage());\n                return;\n            }\n            Clickable clicky = clickables.remove(id);\n            if (clicky == null) {\n                Debug.echoDebug(scriptEntry, \"Cancelled ID didn't exist, nothing to cancel.\");\n            }\n            else {\n                Debug.echoDebug(scriptEntry, \"Cancelled.\");\n            }\n            return;\n        }\n        cleanClickables();\n        UUID id = UUID.randomUUID();\n        Clickable newClickable = new Clickable();\n        if (script == null) {\n            newClickable.contextSource = scriptEntry.queue.contextSource;\n            newClickable.directEntries = getBracedCommandsDirect(scriptEntry, scriptEntry);\n            newClickable.defMap = scriptEntry.queue.definitions.duplicate();\n            if (defMap != null) {\n                newClickable.defMap.putAll(defMap);\n            }\n            newClickable.queueId = \"CLICKABLE_\" + (scriptEntry.getScript() == null ? \"UNKNOWN\" : scriptEntry.getScript().getName());\n        }\n        else {\n            newClickable.script = script;\n            newClickable.defMap = defMap;\n            newClickable.queueId = \"CLICKABLE_\" + script.getName();\n        }\n        newClickable.path = path == null ? null : path.asString();\n        newClickable.definitions = definitions;\n        newClickable.remainingUsages = usages == null ? -1 : usages.asInt();\n        newClickable.until = until == null ? 0 : (CoreUtilities.monotonicMillis() + until.getMillis());\n        newClickable.context = scriptEntry.context;\n        newClickable.npc = Utilities.getEntryNPC(scriptEntry);\n        if (forPlayers != null) {\n            newClickable.forPlayers = new HashSet<>(forPlayers.size());\n            for (PlayerTag player : forPlayers) {\n                newClickable.forPlayers.add(player.getUUID());\n            }\n        }\n        clickables.put(id, newClickable);\n        scriptEntry.saveObject(\"command\", new ElementTag(\"/denizenclickable \" + id));\n        scriptEntry.saveObject(\"id\", new ElementTag(id.toString()));\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/CompassCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\n\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\nimport com.denizenscript.denizencore.objects.Argument;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport org.bukkit.Location;\nimport org.bukkit.entity.Player;\n\npublic class CompassCommand extends AbstractCommand {\n\n    public CompassCommand() {\n        setName(\"compass\");\n        setSyntax(\"compass [<location>/reset]\");\n        setRequiredArguments(1, 1);\n        isProcedural = false;\n    }\n\n    // <--[command]\n    // @Name Compass\n    // @Syntax compass [<location>/reset]\n    // @Required 1\n    // @Maximum 1\n    // @Short Redirects the player's compass to target the given location.\n    // @Group player\n    //\n    // @Description\n    // Redirects the compass of the player, who is attached to the script queue.\n    //\n    // This is not the compass item, but the command is controlling the pointer the item should direct at.\n    // This means that all item compasses will point the same direction but differently for each player.\n    //\n    // To affect an individual compass item, use <@link mechanism ItemTag.lodestone_location>\n    //\n    // The y-axis is not used but its fine to be included in the location argument.\n    //\n    // Reset argument will turn the direction to default (spawn or bed)\n    //\n    // @Tags\n    // <PlayerTag.compass_target>\n    //\n    // @Usage\n    // Use to reset the compass direction to its default.\n    // - compass reset\n    //\n    // @Usage\n    // Use to point with a compass to the player's current location.\n    // - compass <player.location>\n    //\n    // @Usage\n    // Use to point with a compass to the world's spawn location.\n    // - compass <player.world.spawn_location>\n    // -->\n\n    @Override\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\n        tab.addNotesOfType(LocationTag.class);\n    }\n\n    @Override\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\n        for (Argument arg : scriptEntry) {\n            if (!scriptEntry.hasObject(\"location\")\n                    && arg.matchesArgumentType(LocationTag.class)) {\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\n            }\n            else if (!scriptEntry.hasObject(\"reset\")\n                    && arg.matches(\"reset\")) {\n                scriptEntry.addObject(\"reset\", new ElementTag(true));\n            }\n            else {\n                arg.reportUnhandled();\n            }\n        }\n        if (!scriptEntry.hasObject(\"location\") && !scriptEntry.hasObject(\"reset\")) {\n            throw new InvalidArgumentsException(\"Missing location argument!\");\n        }\n\n        scriptEntry.defaultObject(\"reset\", new ElementTag(false));\n    }\n\n    @Override\n    public void execute(ScriptEntry scriptEntry) {\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\n        ElementTag reset = scriptEntry.getElement(\"reset\");\n        Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity();\n        if (scriptEntry.dbCallShouldDebug()) {\n            Debug.report(scriptEntry, getName(), location, reset);\n        }\n        if (reset.asBoolean()) {\n            Location bed = player.getBedSpawnLocation();\n            player.setCompassTarget(bed != null ? bed : player.getWorld().getSpawnLocation());\n        }\n        else {\n            player.setCompassTarget(location);\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/DebugBlockCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class DebugBlockCommand extends AbstractCommand {\r\n\r\n    public DebugBlockCommand() {\r\n        setName(\"debugblock\");\r\n        setSyntax(\"debugblock [<location>|.../clear] (color:<color>) (name:<name>) (players:<player>|...) (d:<duration>{10s})\");\r\n        setRequiredArguments(1, 6);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name DebugBlock\r\n    // @Syntax debugblock [<location>|.../clear] (color:<color>) (name:<name>) (players:<player>|...) (d:<duration>{10s})\r\n    // @Required 1\r\n    // @Maximum 6\r\n    // @Short Shows or clears minecraft debug blocks.\r\n    // @Synonyms GameTestMarker\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Shows or clears minecraft debug blocks, AKA \"Game Test Markers\".\r\n    // These are block-grid-aligned markers that are a perfect cube of a single (specifiable) transparent color, and stay for a specified duration of time or until cleared.\r\n    // Markers can optionally also have simple text names.\r\n    //\r\n    // If arguments are unspecified, the default color is white, the default player is the linked player, the default name is none, and the default duration is 10 seconds.\r\n    //\r\n    // Note that on MC 1.21+ this has limitations (within Minecraft itself), namely:\r\n    // - the color is always green.\r\n    // - the duration is always 10 seconds.\r\n    // - the name can only be a LocationTag and defaults to the debug block's location if unspecified.\r\n    // - debug blocks can't be cleared.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to show a debug block where the player is looking.\r\n    // - debugblock <player.cursor_on>\r\n    //\r\n    // @Usage\r\n    // Use to show a transparent green debug block in front of the player for five seconds.\r\n    // - debugblock <player.eye_location.forward[2]> color:0,255,0,128 d:5s\r\n    //\r\n    // @Usage\r\n    // Use to remove all debug blocks,\r\n    // - debugblock clear\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"players\")\r\n                    && arg.matchesPrefix(\"to\", \"players\")) {\r\n                scriptEntry.addObject(\"players\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else if (arg.matchesPrefix(\"d\", \"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (arg.matchesPrefix(\"color\")\r\n                    && arg.matchesArgumentType(ColorTag.class)) {\r\n                scriptEntry.addObject(\"color\", arg.asType(ColorTag.class));\r\n            }\r\n            else if (arg.matchesPrefix(\"alpha\")\r\n                    && arg.matchesFloat()) {\r\n                BukkitImplDeprecations.debugBlockAlpha.warn(scriptEntry);\r\n                scriptEntry.addObject(\"alpha\", arg.asElement());\r\n            }\r\n            else if (arg.matchesPrefix(\"name\")) {\r\n                scriptEntry.addObject(\"name\", arg.asElement());\r\n            }\r\n            else if (arg.matches(\"clear\")) {\r\n                scriptEntry.addObject(\"clear\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"locations\")\r\n                    && arg.matchesArgumentList(LocationTag.class)) {\r\n                scriptEntry.addObject(\"locations\", arg.asType(ListTag.class).filter(LocationTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"players\") && Utilities.entryHasPlayer(scriptEntry)) {\r\n            scriptEntry.defaultObject(\"players\", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry)));\r\n        }\r\n        if (!scriptEntry.hasObject(\"locations\") && !scriptEntry.hasObject(\"clear\")) {\r\n            throw new InvalidArgumentsException(\"Must specify at least one valid location!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"players\")) {\r\n            throw new InvalidArgumentsException(\"Must have a valid, online player attached!\");\r\n        }\r\n        scriptEntry.defaultObject(\"duration\", new DurationTag(10));\r\n        scriptEntry.defaultObject(\"color\", new ColorTag(255, 255, 255));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        ElementTag clear = scriptEntry.getElement(\"clear\");\r\n        List<LocationTag> locations = (List<LocationTag>) scriptEntry.getObject(\"locations\");\r\n        List<PlayerTag> players = (List<PlayerTag>) scriptEntry.getObject(\"players\");\r\n        ColorTag color = scriptEntry.getObjectTag(\"color\");\r\n        ElementTag alpha = scriptEntry.getElement(\"alpha\");\r\n        ElementTag name = scriptEntry.getElement(\"name\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), clear == null ? db(\"locations\", locations) : clear, duration, db(\"players\", players), color, alpha, name);\r\n        }\r\n        if (clear != null && clear.asBoolean()) {\r\n            for (PlayerTag player : players) {\r\n                NMSHandler.packetHelper.clearDebugTestMarker(player.getPlayerEntity());\r\n            }\r\n        }\r\n        else {\r\n            if (alpha != null) {\r\n                color = new ColorTag(color);\r\n                color.alpha = (int)(alpha.asFloat() * 255);\r\n            }\r\n            for (LocationTag location : locations) {\r\n                for (PlayerTag player : players) {\r\n                    NMSHandler.packetHelper.showDebugTestMarker(player.getPlayerEntity(), location, color, name == null ? \"\" : name.asString(), (int) duration.getMillis());\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/DisguiseCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.HandlerList;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerJoinEvent;\r\nimport org.bukkit.event.player.PlayerMoveEvent;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.util.*;\r\n\r\npublic class DisguiseCommand extends AbstractCommand {\r\n\r\n    public DisguiseCommand() {\r\n        setName(\"disguise\");\r\n        setSyntax(\"disguise [<entity>] [cancel/as:<type>] (global/players:<player>|...) (self)\");\r\n        setRequiredArguments(2, 4);\r\n        setBooleansHandled(\"cancel\", \"global\", \"self\");\r\n        setPrefixesHandled(\"players\", \"as\");\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name disguise\r\n    // @Syntax disguise [<entity>] [cancel/as:<type>] (global/players:<player>|...) (self)\r\n    // @Required 2\r\n    // @Maximum 4\r\n    // @Short Makes the player see an entity as though it were a different type of entity.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Makes the player see an entity as though it were a different type of entity.\r\n    //\r\n    // The entity won't actually change on the server.\r\n    // The entity will still visibly behave the same as the real entity type does.\r\n    //\r\n    // Be warned that the replacement is imperfect, and visual or internal-client errors may arise from using this command.\r\n    //\r\n    // If you disguise a player to themself, they will see a slightly-lagging-behind copy of the disguise entity.\r\n    //\r\n    // The disguise will last until a server restart, or the cancel option is used.\r\n    //\r\n    // Optionally, specify a list of players to show or cancel the entity to.\r\n    // If unspecified, will default to the linked player.\r\n    // Or, specify 'global' to make the disguise or cancel apply for all players. If using global, use \"self\" to show to the self-player.\r\n    //\r\n    // To remove a disguise, use the 'cancel' argument.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.disguise_to_self[(<player>)]>\r\n    // <EntityTag.is_disguised[(<player>)]>\r\n    // <EntityTag.disguised_type[(<player>)]>\r\n    // <EntityTag.disguise_to_others[(<player>)]>\r\n    //\r\n    // @Usage\r\n    // Use to show a turn the NPC into a creeper for the linked player.\r\n    // - disguise <npc> as:creeper\r\n    //\r\n    // @Usage\r\n    // Use to show a turn the NPC into a red sheep for the linked player.\r\n    // - disguise <npc> as:sheep[color=red]\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addWithPrefix(\"as:\", EntityType.values());\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"entity\")\r\n                    && arg.matchesArgumentType(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entity\", arg.asType(EntityTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"entity\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a valid entity!\");\r\n        }\r\n    }\r\n\r\n    public static class TrackedDisguise implements Listener {\r\n\r\n        public EntityTag entity;\r\n\r\n        public EntityTag as;\r\n\r\n        public FakeEntity fakeToSelf;\r\n\r\n        public FakeEntity toOthers;\r\n\r\n        public boolean shouldFake;\r\n\r\n        public boolean isActive;\r\n\r\n        public TrackedDisguise(EntityTag entity, EntityTag as) {\r\n            this.entity = entity;\r\n            this.as = as;\r\n        }\r\n\r\n        public void removeFor(PlayerTag player) {\r\n            if (player.getUUID().equals(entity.getUUID())) {\r\n                if (fakeToSelf != null) {\r\n                    stopFake(player);\r\n                }\r\n                if (shouldFake) {\r\n                    HandlerList.unregisterAll(this);\r\n                    shouldFake = false;\r\n                }\r\n            }\r\n            else if (player.isOnline()) {\r\n                NMSHandler.playerHelper.deTrackEntity(player.getPlayerEntity(), entity.getBukkitEntity());\r\n            }\r\n        }\r\n\r\n        public void moveFakeNow(Location position) {\r\n            float yawOff = 0;\r\n            if (as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\r\n                yawOff = 180;\r\n            }\r\n            NMSHandler.entityHelper.snapPositionTo(fakeToSelf.entity.getBukkitEntity(), position.toVector());\r\n            NMSHandler.entityHelper.look(fakeToSelf.entity.getBukkitEntity(), position.getYaw() + yawOff, position.getPitch());\r\n        }\r\n\r\n        public void startFake(PlayerTag player) {\r\n            if (fakeToSelf != null) {\r\n                stopFake(player);\r\n            }\r\n            if (!shouldFake) {\r\n                shouldFake = true;\r\n                Bukkit.getPluginManager().registerEvents(this, Denizen.getInstance());\r\n            }\r\n            if (!player.isOnline()) {\r\n                return;\r\n            }\r\n            fakeToSelf = FakeEntity.showFakeEntityTo(Collections.singletonList(player), as, player.getLocation(), null, null);\r\n            NMSHandler.packetHelper.generateNoCollideTeam(player.getPlayerEntity(), fakeToSelf.entity.getUUID());\r\n            NMSHandler.packetHelper.sendEntityMetadataFlagsUpdate(player.getPlayerEntity(), player.getPlayerEntity());\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    if (fakeToSelf == null || !fakeToSelf.entity.isFakeValid || !player.isOnline()) {\r\n                        stopFake(player);\r\n                        cancel();\r\n                        return;\r\n                    }\r\n                    moveFakeNow(player.getLocation());\r\n                }\r\n            }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n        }\r\n\r\n        public void stopFake(PlayerTag player) {\r\n            if (fakeToSelf == null) {\r\n                return;\r\n            }\r\n            if (player.isOnline()) {\r\n                NMSHandler.packetHelper.removeNoCollideTeam(player.getPlayerEntity(), fakeToSelf.entity.getUUID());\r\n                new BukkitRunnable() {\r\n                    @Override\r\n                    public void run() {\r\n                        if (player.isOnline()) {\r\n                            NMSHandler.packetHelper.sendEntityMetadataFlagsUpdate(player.getPlayerEntity(), player.getPlayerEntity());\r\n                        }\r\n                    }\r\n                }.runTaskLater(Denizen.getInstance(), 2);\r\n            }\r\n            fakeToSelf.cancelEntity();\r\n            fakeToSelf = null;\r\n        }\r\n\r\n        public void sendTo(List<PlayerTag> players) {\r\n            PlayerTag remove = null;\r\n            for (PlayerTag player : players) {\r\n                if (player.getUUID().equals(entity.getUUID())) {\r\n                    remove = player;\r\n                    startFake(player);\r\n                }\r\n                else {\r\n                    NMSHandler.playerHelper.sendEntityDestroy(player.getPlayerEntity(), entity.getBukkitEntity());\r\n                }\r\n            }\r\n            if (remove != null) {\r\n                if (players.size() == 1) {\r\n                    return;\r\n                }\r\n                players.remove(remove);\r\n            }\r\n            if (players.isEmpty()) {\r\n                return;\r\n            }\r\n            if (toOthers == null) {\r\n                toOthers = NMSHandler.playerHelper.sendEntitySpawn(players, as.getEntityType(), entity.getLocation(), as.mechanisms == null ? null : new ArrayList<>(as.mechanisms), entity.getBukkitEntity().getEntityId(), entity.getUUID(), false);\r\n                toOthers.overrideUUID = UUID.randomUUID();\r\n                toOthers.entity.uuid = toOthers.overrideUUID;\r\n                FakeEntity.idsToEntities.put(toOthers.overrideUUID, toOthers);\r\n            }\r\n            else {\r\n                for (PlayerTag player : players) {\r\n                    toOthers.triggerSpawnPacket.accept(player);\r\n                }\r\n            }\r\n        }\r\n\r\n        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\r\n        public void onJoin(PlayerJoinEvent event) {\r\n            if (!shouldFake || !event.getPlayer().getUniqueId().equals(entity.getUUID())) {\r\n                return;\r\n            }\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    if (!event.getPlayer().isOnline() || !isActive) {\r\n                        return;\r\n                    }\r\n                    startFake(new PlayerTag(event.getPlayer()));\r\n                }\r\n            }.runTaskLater(Denizen.getInstance(), 2);\r\n        }\r\n\r\n        @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\r\n        public void onTeleport(PlayerTeleportEvent event) {\r\n            if (fakeToSelf == null || !event.getPlayer().getUniqueId().equals(entity.getUUID())) {\r\n                return;\r\n            }\r\n            stopFake(new PlayerTag(event.getPlayer()));\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    if (!event.getPlayer().isOnline() || !isActive) {\r\n                        return;\r\n                    }\r\n                    startFake(new PlayerTag(event.getPlayer()));\r\n                }\r\n            }.runTaskLater(Denizen.getInstance(), 2);\r\n        }\r\n\r\n\r\n        @EventHandler(priority = EventPriority.MONITOR)\r\n        public void onMove(PlayerMoveEvent event) {\r\n            if (fakeToSelf == null || !event.getPlayer().getUniqueId().equals(entity.getUUID())) {\r\n                return;\r\n            }\r\n            if (event.getTo() == null) {\r\n                return;\r\n            }\r\n            moveFakeNow(event.getTo());\r\n            if (fakeToSelf.triggerUpdatePacket != null) {\r\n                fakeToSelf.triggerUpdatePacket.run();\r\n            }\r\n        }\r\n    }\r\n\r\n    public static HashMap<UUID, HashMap<UUID, TrackedDisguise>> disguises = new HashMap<>();\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        NetworkInterceptHelper.enable();\r\n        EntityTag entity = scriptEntry.getObjectTag(\"entity\");\r\n        EntityTag as = scriptEntry.argForPrefix(\"as\", EntityTag.class, true);\r\n        boolean cancel = scriptEntry.argAsBoolean(\"cancel\");\r\n        boolean global = scriptEntry.argAsBoolean(\"global\");\r\n        boolean self = scriptEntry.argAsBoolean(\"self\");\r\n        List<PlayerTag> players = scriptEntry.argForPrefixList(\"players\", PlayerTag.class, true);\r\n        if (as == null && !cancel) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify a valid type to disguise as!\");\r\n        }\r\n        if (players == null && !global) {\r\n            PlayerTag player = Utilities.getEntryPlayer(scriptEntry);\r\n            if (player != null && player.isOnline()) {\r\n                players = Collections.singletonList(player);\r\n            }\r\n            else {\r\n                throw new InvalidArgumentsRuntimeException(\"Must have a valid player attached, or 'global' set!\");\r\n            }\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), entity, db(\"cancel\", cancel), as, db(\"global\", global), db(\"self\", self), db(\"players\", players));\r\n        }\r\n        HashMap<UUID, TrackedDisguise> playerMap = disguises.get(entity.getUUID());\r\n        if (playerMap != null) {\r\n            if (global) {\r\n                for (Map.Entry<UUID, TrackedDisguise> entry : playerMap.entrySet()) {\r\n                    entry.getValue().isActive = false;\r\n                    if (entry.getKey() == null) {\r\n                        if (entry.getValue().toOthers != null) {\r\n                            FakeEntity.idsToEntities.remove(entry.getValue().toOthers.overrideUUID);\r\n                        }\r\n                        for (Player player : entity.getWorld().getPlayers()) {\r\n                            if (!EntityTag.isNPC(player)) {\r\n                                entry.getValue().removeFor(new PlayerTag(player));\r\n                            }\r\n                        }\r\n                    }\r\n                    else {\r\n                        PlayerTag player = new PlayerTag(entry.getKey());\r\n                        entry.getValue().removeFor(player);\r\n                    }\r\n                }\r\n                disguises.remove(entity.getUUID());\r\n            }\r\n            else {\r\n                for (PlayerTag player : players) {\r\n                    TrackedDisguise disguise = playerMap.remove(player.getUUID());\r\n                    if (disguise != null) {\r\n                        disguise.isActive = false;\r\n                        disguise.removeFor(player);\r\n                        if (disguise.toOthers != null) {\r\n                            FakeEntity.idsToEntities.remove(disguise.toOthers.overrideUUID);\r\n                        }\r\n                        if (playerMap.isEmpty()) {\r\n                            disguises.remove(entity.getUUID());\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (!cancel) {\r\n            TrackedDisguise disguise = new TrackedDisguise(entity, as);\r\n            disguise.as.entity = NMSHandler.playerHelper.sendEntitySpawn(new ArrayList<>(), as.getEntityType(), entity.getLocation(), as.mechanisms == null ? null : new ArrayList<>(as.mechanisms), -1, null, false).entity.getBukkitEntity();\r\n            if (global) {\r\n                playerMap = disguises.computeIfAbsent(entity.getUUID(), k -> new HashMap<>());\r\n                playerMap.put(null, disguise);\r\n                disguise.isActive = true;\r\n                ArrayList<PlayerTag> playerSet = players == null ? new ArrayList<>() : new ArrayList<>(players);\r\n                for (Player player : entity.getWorld().getPlayers()) {\r\n                    if (!EntityTag.isNPC(player) && !playerSet.contains(new PlayerTag(player)) && (self || !player.getUniqueId().equals(entity.getUUID()))) {\r\n                        playerSet.add(new PlayerTag(player));\r\n                    }\r\n                }\r\n                disguise.sendTo(playerSet);\r\n            }\r\n            else {\r\n                for (PlayerTag player : players) {\r\n                    playerMap = disguises.computeIfAbsent(entity.getUUID(), k -> new HashMap<>());\r\n                    playerMap.put(player.getUUID(), disguise);\r\n                    disguise.isActive = true;\r\n                }\r\n                disguise.sendTo(players);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ExperienceCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgLinear;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgName;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\n\r\npublic class ExperienceCommand extends AbstractCommand {\r\n\r\n    public ExperienceCommand() {\r\n        setName(\"experience\");\r\n        setSyntax(\"experience [set/give/take] (level) [<#>]\");\r\n        setRequiredArguments(2, 3);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Experience\r\n    // @Syntax experience [set/give/take] (level) [<#>]\r\n    // @Required 2\r\n    // @Maximum 3\r\n    // @Short Gives or takes experience points to the player.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // This command allows modification of a players experience points.\r\n    //\r\n    // Experience can be modified in terms of XP points, or by levels.\r\n    //\r\n    // This command works with offline players, but using it on online players is safer.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.xp>\r\n    // <PlayerTag.xp_to_next_level>\r\n    // <PlayerTag.xp_total>\r\n    // <PlayerTag.xp_level>\r\n    //\r\n    // @Usage\r\n    // Use to set a player's total experience to 0.\r\n    // - experience set 0\r\n    //\r\n    // @Usage\r\n    // Use to give a player 1 level.\r\n    // - experience give level 1\r\n    //\r\n    // @Usage\r\n    // Use to take 1 level from a player.\r\n    // - experience take level 1\r\n    //\r\n    // @Usage\r\n    // Use to give a player with the name steve 10 experience points.\r\n    // - experience give 10 player:<[someplayer]>\r\n    // -->\r\n\r\n    public enum Type {SET, GIVE, TAKE}\r\n\r\n    public static int XP_FOR_NEXT_LEVEL(int level) {\r\n        return level >= 30 ? 112 + (level - 30) * 9 : (level >= 15 ? 37 + (level - 15) * 5 : 7 + level * 2);\r\n    }\r\n\r\n    public static long TOTAL_XP_FOR_LEVEL(int level) {\r\n        long count = 0;\r\n        for (int i = 0; i < level; i++) {\r\n            count += XP_FOR_NEXT_LEVEL(i);\r\n        }\r\n        return count;\r\n    }\r\n\r\n    public static void setTotalExperience(PlayerTag player, int exp) {\r\n        player.setTotalExperience(0);\r\n        player.setLevel(0);\r\n        player.setExp(0);\r\n        giveExperiencePoints(player, exp);\r\n    }\r\n\r\n    public static void giveExperiencePoints(PlayerTag player, int amount) {\r\n        if (player.isOnline()) {\r\n            player.getPlayerEntity().giveExp(amount);\r\n            return;\r\n        }\r\n        int level = player.getLevel();\r\n        float xp = player.getExp() + (float)amount / (float)XP_FOR_NEXT_LEVEL(level);\r\n        while(xp >= 1.0F) {\r\n            xp = (xp - 1.0F) * (float)XP_FOR_NEXT_LEVEL(level);\r\n            level++;\r\n            xp /= (float)XP_FOR_NEXT_LEVEL(level);\r\n        }\r\n        player.setTotalExperience(Math.min(player.getTotalExperience() + amount, Integer.MAX_VALUE));\r\n        player.setExp(xp);\r\n        player.setLevel(level);\r\n    }\r\n\r\n    public static void takeExperience(PlayerTag player, int toTake) {\r\n        int pastLevelStart = (int) (player.getExp() * XP_FOR_NEXT_LEVEL(player.getLevel()));\r\n        while (toTake >= pastLevelStart) {\r\n            toTake -= pastLevelStart;\r\n            player.setExp(0);\r\n            if (player.getLevel() == 0) {\r\n                return;\r\n            }\r\n            player.setLevel(player.getLevel() - 1);\r\n            pastLevelStart = XP_FOR_NEXT_LEVEL(player.getLevel());\r\n        }\r\n        int newAmount = pastLevelStart - toTake;\r\n        player.setExp(newAmount / (float) XP_FOR_NEXT_LEVEL(player.getLevel()));\r\n    }\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"type\") Type type,\r\n                                   @ArgName(\"level\") boolean level,\r\n                                   @ArgLinear @ArgName(\"quantity\") int quantity) {\r\n        PlayerTag player = Utilities.getEntryPlayer(scriptEntry);\r\n        if (player == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"The Experience command requires a linked player.\");\r\n        }\r\n        if (quantity < 0) {\r\n            switch (type) {\r\n                case SET:\r\n                    Debug.echoError(\"Cannot set negative experience.\");\r\n                    return;\r\n                case GIVE:\r\n                    quantity = -quantity;\r\n                    type = Type.TAKE;\r\n                    break;\r\n                case TAKE:\r\n                    quantity = -quantity;\r\n                    type = Type.GIVE;\r\n                    break;\r\n            }\r\n        }\r\n        switch (type) {\r\n            case SET:\r\n                if (level) {\r\n                    player.setLevel(quantity);\r\n                }\r\n                else {\r\n                    setTotalExperience(player, quantity);\r\n                }\r\n                break;\r\n            case GIVE:\r\n                if (level) {\r\n                    player.setLevel(player.getLevel() + quantity);\r\n                }\r\n                else {\r\n                    giveExperiencePoints(player, quantity);\r\n                }\r\n                break;\r\n            case TAKE:\r\n                if (level) {\r\n                    int value = player.getLevel() - quantity;\r\n                    player.setLevel(value <= 0 ? 0 : value);\r\n                }\r\n                else {\r\n                    takeExperience(player, quantity);\r\n                }\r\n                break;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/FakeSpawnCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.command.TabCompleteHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.Deprecations;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\nimport java.util.List;\r\n\r\npublic class FakeSpawnCommand extends AbstractCommand {\r\n\r\n    public FakeSpawnCommand() {\r\n        setName(\"fakespawn\");\r\n        setSyntax(\"fakespawn [<entity>] [<location>/cancel] (players:<player>|...) (duration:<duration>{10s}) (mount_to:<entity>)\");\r\n        setRequiredArguments(2, 5);\r\n        isProcedural = false;\r\n        addRemappedPrefixes(\"duration\", \"d\");\r\n        addRemappedPrefixes(\"players\", \"to\");\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name FakeSpawn\r\n    // @Syntax fakespawn [<entity>] [<location>/cancel] (players:<player>|...) (duration:<duration>{10s}) (mount_to:<entity>)\r\n    // @Required 2\r\n    // @Maximum 5\r\n    // @Short Makes the player see a fake entity spawn that didn't actually happen.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Makes the player see a fake entity spawn that didn't actually happen.\r\n    // This means that the server will not track the entity, and players not included in the command will not see the entity.\r\n    //\r\n    // You must specify an entity to spawn and a location to spawn it at, or to remove a fake entity, specify the fake entity object and 'cancel' instead of a location.\r\n    //\r\n    // Optionally, specify a list of players to show the entity to. If unspecified, will default to the linked player.\r\n    //\r\n    // Optionally, specify how long the fake entity should remain for. If unspecified, will default to 10 seconds.\r\n    // After the duration is up, the entity will be removed from the player(s).\r\n    //\r\n    // Optionally, specify an entity to mount the fake entity to via mount_to:<entity>.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.fake_entities>\r\n    // <entry[saveName].faked_entity> returns the spawned faked entity.\r\n    //\r\n    // @Usage\r\n    // Use to show a fake creeper in front of the attached player.\r\n    // - fakespawn creeper <player.location.forward[5]>\r\n    //\r\n    // @Usage\r\n    // Use to spawn and mount a fake armor stand to the player.\r\n    // - fakespawn armor_stand <player.location> mount_to:<player>\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        TabCompleteHelper.tabCompleteEntityTypes(tab);\r\n    }\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"entity\") @ArgLinear ObjectTag entityObj,\r\n                                   @ArgName(\"location\") @ArgLinear @ArgDefaultNull ObjectTag locationObj,\r\n                                   @ArgName(\"cancel\") boolean cancel,\r\n                                   @ArgName(\"players\") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> players,\r\n                                   @ArgName(\"duration\") @ArgPrefixed @ArgDefaultText(\"10s\") DurationTag duration,\r\n                                   @ArgName(\"mount_to\") @ArgPrefixed @ArgDefaultNull EntityTag vehicle) {\r\n        if (locationObj != null && entityObj.identify().startsWith(\"l@\")) { // Compensate for legacy entity/location out-of-order support\r\n            ObjectTag swap = locationObj;\r\n            locationObj = entityObj;\r\n            entityObj = swap;\r\n            Deprecations.outOfOrderArgs.warn(scriptEntry);\r\n        }\r\n        if (players == null) {\r\n            if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                throw new InvalidArgumentsRuntimeException(\"Must specify an online player!\");\r\n            }\r\n            players = List.of(Utilities.getEntryPlayer(scriptEntry));\r\n        }\r\n        LocationTag location = locationObj == null ? null : locationObj.asType(LocationTag.class, scriptEntry.context);\r\n        EntityTag entity = entityObj.asType(EntityTag.class, scriptEntry.context);\r\n        if (entity == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify a valid entity!\");\r\n        }\r\n        if (location == null && !cancel) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify a valid location!\");\r\n        }\r\n        if (vehicle != null && !vehicle.isValid()) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify a valid entity to mount to!\");\r\n        }\r\n        if (!cancel) {\r\n            FakeEntity created = FakeEntity.showFakeEntityTo(players, entity, location, duration, vehicle);\r\n            scriptEntry.saveObject(\"faked_entity\", created.entity);\r\n            return;\r\n        }\r\n        if (entity.isFake) {\r\n            FakeEntity fakeEnt = FakeEntity.idsToEntities.get(entity.getUUID());\r\n            if (fakeEnt != null) {\r\n                fakeEnt.cancelEntity();\r\n            }\r\n            else {\r\n                Debug.echoDebug(scriptEntry, \"Entity '\" + entity + \"' cannot be cancelled: not listed in fake-entity map.\");\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/GroupCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.OfflinePlayer;\r\nimport org.bukkit.World;\r\n\r\npublic class GroupCommand extends AbstractCommand {\r\n\r\n    public GroupCommand() {\r\n        setName(\"group\");\r\n        setSyntax(\"group [add/remove/set] [<group>] (<world>)\");\r\n        setRequiredArguments(2, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Group\r\n    // @Syntax group [add/remove/set] [<group>] (<world>)\r\n    // @Required 2\r\n    // @Maximum 3\r\n    // @Short Adds a player to, removes a player from, or sets a player's permissions group.\r\n    // @Group player\r\n    // @Plugin Vault\r\n    //\r\n    // @Description\r\n    // Controls a player's permission groups, which the ability to add, remove or set a player's groups.\r\n    // The 'add' argument adds the player to the group and any parent groups,\r\n    // and the 'remove' command does the opposite, removing the player from the group and any inheriting groups.\r\n    // The set command removes all existing groups and sets the player's group.\r\n    // Note: This requires a permissions plugin and Vault.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.in_group[<group>]>\r\n    // <PlayerTag.in_group[<group>].global>\r\n    // <PlayerTag.in_group[<group>].world>\r\n    // <PlayerTag.groups[(<world>)]>\r\n    // <server.permission_groups>\r\n    //\r\n    // @Usage\r\n    // Use to add a player to the Admin group.\r\n    // - group add Admin\r\n    //\r\n    // @Usage\r\n    // Use to remove a player from the Moderator group.\r\n    // - group remove Moderator\r\n    //\r\n    // @Usage\r\n    // Use to set a player to the Member group in the Creative world.\r\n    // - group set Member Creative\r\n    // -->\r\n\r\n    private enum Action {ADD, REMOVE, SET}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        if (Depends.permissions == null) {\r\n            throw new InvalidArgumentsException(\"Permissions not linked - is Vault improperly installed, or is there no permissions plugin?\");\r\n        }\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matchesEnum(Action.class)) {\r\n                scriptEntry.addObject(\"action\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"world\")\r\n                    && arg.matchesArgumentType(WorldTag.class)) {\r\n                scriptEntry.addObject(\"world\", arg.asType(WorldTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"group\")) {\r\n                scriptEntry.addObject(\"group\", arg.asElement());\r\n            }\r\n        }\r\n        if (!Utilities.entryHasPlayer(scriptEntry) || !Utilities.getEntryPlayer(scriptEntry).isValid()) {\r\n            throw new InvalidArgumentsException(\"Must have player context!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"action\")) {\r\n            throw new InvalidArgumentsException(\"Must specify valid action!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"group\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a group name!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag action = scriptEntry.getElement(\"action\");\r\n        WorldTag world = scriptEntry.getObjectTag(\"world\");\r\n        ElementTag group = scriptEntry.getElement(\"group\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), action, world, group);\r\n        }\r\n        World bukkitWorld = null;\r\n        if (world != null) {\r\n            bukkitWorld = world.getWorld();\r\n        }\r\n        OfflinePlayer player = Utilities.getEntryPlayer(scriptEntry).getOfflinePlayer();\r\n        boolean inGroup = Depends.permissions.playerInGroup((bukkitWorld == null ? null : bukkitWorld.getName()), player, group.asString());\r\n        switch (Action.valueOf(action.asString().toUpperCase())) {\r\n            case ADD:\r\n                if (inGroup) {\r\n                    Debug.echoDebug(scriptEntry, \"Player \" + player.getName() + \" is already in group \" + group);\r\n                }\r\n                else {\r\n                    Depends.permissions.playerAddGroup((bukkitWorld == null ? null : bukkitWorld.getName()), player, group.asString());\r\n                }\r\n                break;\r\n            case REMOVE:\r\n                if (!inGroup) {\r\n                    Debug.echoDebug(scriptEntry, \"Player \" + player.getName() + \" is not in group \" + group);\r\n                }\r\n                else {\r\n                    Depends.permissions.playerRemoveGroup((bukkitWorld == null ? null : bukkitWorld.getName()), player, group.asString());\r\n                }\r\n                break;\r\n            case SET:\r\n                for (String grp : Depends.permissions.getPlayerGroups((bukkitWorld == null ? null : bukkitWorld.getName()), player)) {\r\n                    Depends.permissions.playerRemoveGroup((bukkitWorld == null ? null : bukkitWorld.getName()), player, grp);\r\n                }\r\n                Depends.permissions.playerAddGroup((bukkitWorld == null ? null : bukkitWorld.getName()), player, group.asString());\r\n                break;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ItemCooldownCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\n\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.objects.MaterialTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\nimport com.denizenscript.denizencore.objects.Argument;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport org.bukkit.Material;\n\nimport java.util.ArrayList;\n\npublic class ItemCooldownCommand extends AbstractCommand {\n\n    public ItemCooldownCommand() {\n        setName(\"itemcooldown\");\n        setSyntax(\"itemcooldown [<material>|...] (duration:<duration>)\");\n        setRequiredArguments(1, 2);\n        isProcedural = false;\n    }\n\n    // <--[command]\n    // @Name ItemCooldown\n    // @Syntax itemcooldown [<material>|...] (duration:<duration>)\n    // @Required 1\n    // @Maximum 2\n    // @Short Places a cooldown on a material in a player's inventory.\n    // @Group player\n    //\n    // @Description\n    // Places a cooldown on a material in a player's inventory.\n    //\n    // @Tags\n    // <PlayerTag.item_cooldown[<material>]>\n    //\n    // @Usage\n    // Places a 1 second cooldown on using an ender pearl.\n    // - itemcooldown ender_pearl\n    //\n    // @Usage\n    // Places a 10 minute cooldown on using golden apples.\n    // - itemcooldown golden_apple d:10m\n    //\n    // -->\n\n    @Override\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\n        for (Material material : Material.values()) {\n            if (material.isItem()) {\n                tab.add(material.name());\n            }\n        }\n    }\n\n    @Override\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\n        for (Argument arg : scriptEntry) {\n            if (!scriptEntry.hasObject(\"materials\")\n                    && (arg.matchesArgumentType(MaterialTag.class)\n                    || arg.matchesArgumentType(ListTag.class))) {\n                scriptEntry.addObject(\"materials\", arg.asType(ListTag.class).filter(MaterialTag.class, scriptEntry));\n            }\n            else if (!scriptEntry.hasObject(\"duration\")\n                    && arg.matchesPrefix(\"d\", \"duration\")\n                    && arg.matchesArgumentType(DurationTag.class)) {\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\n            }\n            else {\n                arg.reportUnhandled();\n            }\n        }\n        if (!scriptEntry.hasObject(\"materials\")) {\n            throw new InvalidArgumentsException(\"Missing materials argument!\");\n        }\n        scriptEntry.defaultObject(\"duration\", new DurationTag(1));\n    }\n\n    @Override\n    public void execute(ScriptEntry scriptEntry) {\n        ArrayList<MaterialTag> materials = (ArrayList<MaterialTag>) scriptEntry.getObject(\"materials\");\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\n        PlayerTag player = Utilities.getEntryPlayer(scriptEntry);\n        if (player == null) {\n            Debug.echoError(\"Invalid linked player.\");\n            return;\n        }\n        if (scriptEntry.dbCallShouldDebug()) {\n            Debug.report(scriptEntry, getName(), db(\"materials\", materials), duration);\n        }\n        for (MaterialTag mat : materials) {\n            player.getPlayerEntity().setCooldown(mat.getMaterial(), duration.getTicksAsInt());\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/KickCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\nimport java.util.List;\r\n\r\npublic class KickCommand extends AbstractCommand {\r\n\r\n    public KickCommand() {\r\n        setName(\"kick\");\r\n        setSyntax(\"kick [<player>|...] (reason:<text>)\");\r\n        setRequiredArguments(1, 2);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name kick\r\n    // @Syntax kick [<player>|...] (reason:<text>)\r\n    // @Required 1\r\n    // @Maximum 2\r\n    // @Short Kicks a player from the server.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Kick a player or a list of players from the server and optionally specify a reason.\r\n    // If no reason is specified it defaults to \"Kicked.\"\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to kick the player with the default reason.\r\n    // - kick <player>\r\n    //\r\n    // @Usage\r\n    // Use to kick the player with a reason.\r\n    // - kick <player> \"reason:Because I can.\"\r\n    //\r\n    // @Usage\r\n    // Use to kick another player with a reason.\r\n    // - kick <[player]> \"reason:Because I can.\"\r\n    // -->\r\n\r\n    public static void autoExecute(@ArgName(\"targets\") @ArgLinear @ArgSubType(PlayerTag.class) List<PlayerTag> targets,\r\n                                   @ArgName(\"reason\") @ArgPrefixed @ArgDefaultText(\"Kicked.\") String reason) {\r\n        for (PlayerTag player : targets) {\r\n            if (player.isValid() && player.isOnline()) {\r\n                PaperAPITools.instance.kickPlayer(player.getPlayerEntity(), reason);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/MoneyCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.milkbowl.vault.economy.Economy;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class MoneyCommand extends AbstractCommand {\r\n\r\n    public MoneyCommand() {\r\n        setName(\"money\");\r\n        setSyntax(\"money [give/take/set] (quantity:<#.#>) (players:<player>|...)\");\r\n        setRequiredArguments(1, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Money\r\n    // @Syntax money [give/take/set] (quantity:<#.#>) (players:<player>|...)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Short Manage a player's money.\r\n    // @Group player\r\n    // @Plugin Vault\r\n    //\r\n    // @Description\r\n    // Give money to, take money from, and set the balance of a player.\r\n    // If no quantity is specified it defaults to '1'.\r\n    // You can specify a list of players to give to or take from. If no player(s) are specified, defaults to the attached player.\r\n    // NOTE: This requires an economy plugin or script, and Vault. May work for offline players depending on economy plugin.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.money>\r\n    //\r\n    // @Usage\r\n    // Use to give 1 money to the player.\r\n    // - money give\r\n    //\r\n    // @Usage\r\n    // Use to take 10 money from a player.\r\n    // - money take quantity:10 players:<[player]>\r\n    //\r\n    // @Usage\r\n    // Use to give all players on the server 100 money.\r\n    // - money give quantity:100 players:<server.players>\r\n    //\r\n    // @Usage\r\n    // Use to set the money of all online players to 250.\r\n    // - money set quantity:250 players:<server.online_players>\r\n    // -->\r\n\r\n    enum Action {\r\n        GIVE,\r\n        TAKE,\r\n        SET\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        if (Depends.economy == null) {\r\n            Debug.echoError(\"No economy loaded! Have you installed Vault and a compatible economy plugin?\");\r\n            return;\r\n        }\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matchesEnum(Action.class)) {\r\n                scriptEntry.addObject(\"action\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"quantity\")\r\n                    && arg.matchesPrefix(\"quantity\", \"qty\", \"q\")\r\n                    && arg.matchesFloat()) {\r\n                if (arg.matchesPrefix(\"q\", \"qty\")) {\r\n                    BukkitImplDeprecations.qtyTags.warn(scriptEntry);\r\n                }\r\n                scriptEntry.addObject(\"quantity\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"players\")\r\n                    && arg.matchesPrefix(\"to\", \"from\", \"players\", \"player\") &&\r\n                    arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"players\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"quantity\", new ElementTag(1));\r\n        if (!scriptEntry.hasObject(\"players\")) {\r\n            if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                throw new InvalidArgumentsException(\"This command must have a player attached!\");\r\n            }\r\n            else {\r\n                scriptEntry.addObject(\"players\",\r\n                        Collections.singletonList(Utilities.getEntryPlayer(scriptEntry)));\r\n            }\r\n        }\r\n        else if (!scriptEntry.hasObject(\"action\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a valid action!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag action = scriptEntry.getElement(\"action\");\r\n        ElementTag quantity = scriptEntry.getElement(\"quantity\");\r\n        List<PlayerTag> players = (List<PlayerTag>) scriptEntry.getObject(\"players\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"Player(s)\", players), action, quantity);\r\n        }\r\n        Economy eco = Depends.economy;\r\n        double amt = quantity.asDouble();\r\n        switch (Action.valueOf(action.asString().toUpperCase())) {\r\n            case GIVE:\r\n                for (PlayerTag player : players) {\r\n                    eco.depositPlayer(player.getOfflinePlayer(), amt);\r\n                }\r\n                break;\r\n            case TAKE:\r\n                for (PlayerTag player : players) {\r\n                    eco.withdrawPlayer(player.getOfflinePlayer(), amt);\r\n                }\r\n                break;\r\n            case SET:\r\n                for (PlayerTag player : players) {\r\n                    double balance = eco.getBalance(player.getOfflinePlayer());\r\n                    if (amt > balance) {\r\n                        eco.depositPlayer(player.getOfflinePlayer(), amt - balance);\r\n                    }\r\n                    else {\r\n                        eco.withdrawPlayer(player.getOfflinePlayer(), balance - amt);\r\n                    }\r\n                }\r\n                break;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/NarrateCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.ScriptFormattingContext;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.scripts.containers.core.FormatScriptContainer;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.ChatMessageType;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport org.bukkit.Bukkit;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic class NarrateCommand extends AbstractCommand {\r\n\r\n    public static final String NARRATE_FORMAT_TYPE = ScriptFormattingContext.registerFormatType(\"narrate\");\r\n\r\n    public NarrateCommand() {\r\n        setName(\"narrate\");\r\n        setSyntax(\"narrate [<text>] (targets:<player>|...) (format:<script>) (per_player) (from:<uuid>)\");\r\n        setRequiredArguments(1, 5);\r\n        setParseArgs(false);\r\n        isProcedural = true;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Narrate\r\n    // @Syntax narrate [<text>] (targets:<player>|...) (format:<script>) (per_player) (from:<uuid>)\r\n    // @Required 1\r\n    // @Maximum 5\r\n    // @Short Shows some text to the player.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Prints some text into the target's chat area. If no target is specified it will default to the attached player or the console.\r\n    //\r\n    // You can format the text with <@link language Format Script Containers> using the 'format' argument, or with the \"narrate\" format type (see <@link language Script Formats>).\r\n    //\r\n    // Optionally use 'per_player' with a list of player targets, to have the tags in the text input be reparsed for each and every player.\r\n    // So, for example, \"- narrate 'hello <player.name>' targets:<server.online_players>\"\r\n    // would normally say \"hello bob\" to every player (every player sees the exact same name in the text, ie bob sees \"hello bob\", steve also sees \"hello bob\", etc)\r\n    // but if you use \"per_player\", each player online would see their own name (so bob sees \"hello bob\", steve sees \"hello steve\", etc).\r\n    //\r\n    // Optionally, specify 'from:<uuid>' to indicate that message came from a specific UUID (used for things like the vanilla client social interaction block option).\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to narrate text to the player.\r\n    // - narrate \"Hello World!\"\r\n    //\r\n    // @Usage\r\n    // Use to narrate text to a list of players.\r\n    // - narrate \"Hello there.\" targets:<[player]>|<[someplayer]>|<[thatplayer]>\r\n    //\r\n    // @Usage\r\n    // Use to narrate text to a unique message to every player on the server.\r\n    // - narrate \"Hello <player.name>, your secret code is <util.random.duuid>.\" targets:<server.online_players> per_player\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : ArgumentHelper.interpret(scriptEntry, scriptEntry.getOriginalArguments())) {\r\n            if (!scriptEntry.hasObject(\"format\")\r\n                    && arg.matchesPrefix(\"format\", \"f\")) {\r\n                String formatStr = TagManager.tag(arg.getValue(), scriptEntry.getContext());\r\n                FormatScriptContainer format = ScriptRegistry.getScriptContainer(formatStr);\r\n                if (format == null) {\r\n                    Debug.echoError(\"Could not find format script matching '\" + formatStr + \"'\");\r\n                    return;\r\n                }\r\n                scriptEntry.addObject(\"format\", new ScriptTag(format));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"targets\")\r\n                    && arg.matchesPrefix(\"target\", \"targets\", \"t\")) {\r\n                scriptEntry.addObject(\"targets\", ListTag.getListFor(TagManager.tagObject(arg.getValue(), scriptEntry.getContext()), scriptEntry.getContext()).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"from\")\r\n                    && arg.matchesPrefix(\"from\")) {\r\n                scriptEntry.addObject(\"from\", TagManager.tagObject(arg.getValue(), scriptEntry.getContext()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"per_player\")\r\n                    && arg.matches(\"per_player\")) {\r\n                scriptEntry.addObject(\"per_player\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"text\")) {\r\n                scriptEntry.addObject(\"text\", arg.getRawElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"targets\")) {\r\n            scriptEntry.addObject(\"targets\", (Utilities.entryHasPlayer(scriptEntry) ? Collections.singletonList(Utilities.getEntryPlayer(scriptEntry)) : null));\r\n        }\r\n        if (!scriptEntry.hasObject(\"text\")) {\r\n            throw new InvalidArgumentsException(\"Missing any text!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        if (scriptEntry.getResidingQueue().procedural) {\r\n            Debug.echoError(\"'Narrate' should not be used in a procedure script. Consider the 'debug' command instead.\");\r\n        }\r\n        List<PlayerTag> targets = (List<PlayerTag>) scriptEntry.getObject(\"targets\");\r\n        String text = scriptEntry.getElement(\"text\").asString();\r\n        ScriptTag formatObj = scriptEntry.getObjectTag(\"format\");\r\n        ElementTag perPlayerObj = scriptEntry.getElement(\"per_player\");\r\n        ElementTag from = scriptEntry.getElement(\"from\");\r\n        boolean perPlayer = perPlayerObj != null && perPlayerObj.asBoolean();\r\n        BukkitTagContext context = (BukkitTagContext) scriptEntry.getContext();\r\n        if (!perPlayer || targets == null) {\r\n            text = TagManager.tag(text, context);\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"Narrating\", text), db(\"Targets\", targets), formatObj, perPlayerObj, from);\r\n        }\r\n        UUID fromId = null;\r\n        if (from != null) {\r\n            if (from.asString().startsWith(\"p@\")) {\r\n                fromId = UUID.fromString(from.asString().substring(\"p@\".length()));\r\n            }\r\n            else {\r\n                fromId = UUID.fromString(from.asString());\r\n            }\r\n        }\r\n        ScriptFormattingContext formattingContext;\r\n        if (formatObj != null) {\r\n            formattingContext = ((FormatScriptContainer) formatObj.getContainer()).getAsFormattingContext();\r\n        }\r\n        else {\r\n            ScriptContainer scriptContainer = scriptEntry.getScriptContainer();\r\n            formattingContext = scriptContainer != null ? scriptContainer.getFormattingContext() : null;\r\n        }\r\n        if (targets == null) {\r\n            Bukkit.getServer().getConsoleSender().spigot().sendMessage(FormattedTextHelper.parse(formattingContext != null ? formattingContext.format(NARRATE_FORMAT_TYPE, text, scriptEntry) : text, ChatColor.WHITE));\r\n            return;\r\n        }\r\n        for (PlayerTag player : targets) {\r\n            if (player != null) {\r\n                if (!player.isOnline()) {\r\n                    Debug.echoDebug(scriptEntry, \"Player is offline, can't narrate to them. Skipping.\");\r\n                    continue;\r\n                }\r\n                String personalText = text;\r\n                if (perPlayer) {\r\n                    context.player = player;\r\n                    personalText = TagManager.tag(personalText, context);\r\n                }\r\n                BaseComponent[] component = FormattedTextHelper.parse(formattingContext != null ? formattingContext.format(NARRATE_FORMAT_TYPE, personalText, scriptEntry) : personalText, ChatColor.WHITE);\r\n                if (fromId == null) {\r\n                    player.getPlayerEntity().spigot().sendMessage(component);\r\n                }\r\n                else {\r\n                    player.getPlayerEntity().spigot().sendMessage(ChatMessageType.CHAT, fromId, component);\r\n                }\r\n            }\r\n            else {\r\n                Debug.echoError(\"Narrated to non-existent player!?\");\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/OpenTradesCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.objects.TradeTag;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.inventory.Merchant;\r\nimport org.bukkit.inventory.MerchantRecipe;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class OpenTradesCommand extends AbstractCommand {\r\n\r\n    public OpenTradesCommand() {\r\n        setName(\"opentrades\");\r\n        setSyntax(\"opentrades [<entity>/<trade>|...] (title:<title>) (players:<player>|...)\");\r\n        setRequiredArguments(1, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name OpenTrades\r\n    // @Syntax opentrades [<entity>/<trade>|...] (title:<title>) (players:<player>|...)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Short Opens the specified villager entity's trading inventory or a list of trades.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Forces a player to open a villager's trading inventory or a virtual trading inventory.\r\n    // If an entity is specified, only one player can be specified.\r\n    // Otherwise, if a list of trades is specified, more than one player can be specified.\r\n    // If the title is not specified, no title will be applied to the virtual trading inventory.\r\n    // If no player is specified, by default the attached player will be forced to trade.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.selected_trade_index>\r\n    // <EntityTag.is_trading>\r\n    // <EntityTag.trades>\r\n    // <EntityTag.trading_with>\r\n    //\r\n    // @Usage\r\n    // Use to open an unusable trade.\r\n    // - opentrades trade\r\n    //\r\n    // @Usage\r\n    // Use to open a list of trades with an optional title.\r\n    // - opentrades trade[result=stone;inputs=stone;max_uses=9999]|trade[inputs=barrier;result=barrier] \"title:Useless Trades\"\r\n    //\r\n    // @Usage\r\n    // Use to force a player to trade with a villager.\r\n    // - opentrades <[villager_entity]>\r\n    // -->\r\n\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"trades\")\r\n                    && !scriptEntry.hasObject(\"entity\")\r\n                    && arg.matchesArgumentList(TradeTag.class)) {\r\n                scriptEntry.addObject(\"trades\", arg.asType(ListTag.class).filter(TradeTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"trades\")\r\n                    && !scriptEntry.hasObject(\"entity\")\r\n                    && arg.matchesArgumentType(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entity\", arg.asType(EntityTag.class));\r\n            }\r\n            else if (arg.matchesPrefix(\"title\")) {\r\n                scriptEntry.addObject(\"title\", arg.asElement());\r\n            }\r\n            else if (arg.matchesPrefix(\"players\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"players\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"trades\") && !scriptEntry.hasObject(\"entity\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a villager entity or a list of trades for the player(s) to trade with!\");\r\n        }\r\n        scriptEntry.defaultObject(\"title\", new ElementTag(\"\"))\r\n                .defaultObject(\"players\", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry)));\r\n    }\r\n\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag title = scriptEntry.getElement(\"title\");\r\n        EntityTag entity = scriptEntry.getObjectTag(\"entity\");\r\n        List<TradeTag> trades = (List<TradeTag>) scriptEntry.getObject(\"trades\");\r\n        List<PlayerTag> players = (List<PlayerTag>) scriptEntry.getObject(\"players\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), entity, db(\"trades\", trades), title, db(\"players\", players));\r\n        }\r\n        if (entity != null) {\r\n            if (players.size() > 1) {\r\n                Debug.echoError(\"No more than one player can access the same entity!\");\r\n                return;\r\n            }\r\n            if (entity.getBukkitEntity() instanceof Merchant) {\r\n                PlayerTag player = players.get(0);\r\n                if (player.isValid() && player.isOnline()) {\r\n                    player.getPlayerEntity().openMerchant((Merchant) entity.getBukkitEntity(), true);\r\n                }\r\n                else {\r\n                    Debug.echoError(\"Tried to make a nonexistent or offline player trade with a villager entity!\");\r\n                }\r\n                return;\r\n            }\r\n            Debug.echoError(\"The specified entity isn't a merchant!\");\r\n            return;\r\n        }\r\n        List<MerchantRecipe> recipes = new ArrayList<>();\r\n        for (TradeTag trade : trades) {\r\n            recipes.add(trade.getRecipe());\r\n        }\r\n        for (PlayerTag player : players) {\r\n            if (player.isValid() && player.isOnline()) {\r\n                Merchant merchant = PaperAPITools.instance.createMerchant(title.asString());\r\n                merchant.setRecipes(recipes);\r\n                player.getPlayerEntity().openMerchant(merchant, true);\r\n            }\r\n            else {\r\n                Debug.echoError(\"Tried to make a nonexistent or offline player view a virtual trading inventory!\");\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/OxygenCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\n\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\nimport com.denizenscript.denizencore.objects.Argument;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\n\npublic class OxygenCommand extends AbstractCommand {\n\n    public OxygenCommand() {\n        setName(\"oxygen\");\n        setSyntax(\"oxygen [<#>] (type:{remaining}/maximum) (mode:{set}/add/remove)\");\n        setRequiredArguments(1, 3);\n        isProcedural = false;\n    }\n\n    // <--[command]\n    // @Name Oxygen\n    // @Syntax oxygen [<#>] (type:{remaining}/maximum) (mode:{set}/add/remove)\n    // @Required 1\n    // @Maximum 3\n    // @Short Gives or takes breath from the player.\n    // @Group player\n    //\n    // @Description\n    // Used to add to, remove from or set the amount of current oxygen of a player.\n    // Also allows for the changing of the player's maximum oxygen level.\n    // Value is in ticks, so 30 equals 1 bubble.\n    //\n    // @Tags\n    // <PlayerTag.oxygen>\n    // <PlayerTag.max_oxygen>\n    //\n    // @Usage\n    // Use to set the player's current oxygen level to 5 bubbles.\n    // - oxygen 150\n    //\n    // @Usage\n    // Use to add 1 bubble to the player's current oxygen level.\n    // - oxygen 30 mode:add\n    //\n    // @Usage\n    // Use to set the player's maximum oxygen level to 20 bubbles.\n    // - oxygen 600 type:maximum\n    // -->\n\n    public enum Type {MAXIMUM, REMAINING}\n\n    public enum Mode {SET, ADD, REMOVE}\n\n    @Override\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\n        for (Argument arg : scriptEntry) {\n            if (!scriptEntry.hasObject(\"type\")\n                    && arg.matchesPrefix(\"type\", \"t\")\n                    && arg.matchesEnum(Type.class)) {\n                scriptEntry.addObject(\"type\", arg.asElement());\n            }\n            else if (!scriptEntry.hasObject(\"mode\")\n                    && arg.matchesPrefix(\"mode\", \"m\")\n                    && arg.matchesEnum(Mode.class)) {\n                scriptEntry.addObject(\"mode\", arg.asElement());\n            }\n            else if (!scriptEntry.hasObject(\"amount\")\n                    && arg.matchesInteger()) {\n                scriptEntry.addObject(\"amount\", arg.asElement());\n            }\n        }\n        if (!Utilities.entryHasPlayer(scriptEntry) || !Utilities.getEntryPlayer(scriptEntry).isValid()) {\n            throw new InvalidArgumentsException(\"Must have player context!\");\n        }\n        if (!scriptEntry.hasObject(\"amount\")) {\n            throw new InvalidArgumentsException(\"Must specify a valid amount!\");\n        }\n        scriptEntry.defaultObject(\"type\", new ElementTag(\"REMAINING\")).defaultObject(\"mode\", new ElementTag(\"SET\"));\n\n    }\n\n    @Override\n    public void execute(ScriptEntry scriptEntry) {\n        ElementTag type = scriptEntry.getElement(\"type\");\n        ElementTag mode = scriptEntry.getElement(\"mode\");\n        ElementTag amount = scriptEntry.getElement(\"amount\");\n        if (scriptEntry.dbCallShouldDebug()) {\n            Debug.report(scriptEntry, getName(), type, mode, amount);\n        }\n        PlayerTag player = Utilities.getEntryPlayer(scriptEntry);\n        switch (Type.valueOf(type.asString().toUpperCase())) {\n            case MAXIMUM:\n                switch (Mode.valueOf(mode.asString().toUpperCase())) {\n                    case SET:\n                        player.setMaximumAir(amount.asInt());\n                        break;\n                    case ADD:\n                        player.setMaximumAir(player.getMaximumAir() + amount.asInt());\n                        break;\n                    case REMOVE:\n                        player.setMaximumAir(player.getMaximumAir() - amount.asInt());\n                        break;\n                }\n                break;\n            case REMAINING:\n                switch (Mode.valueOf(mode.asString().toUpperCase())) {\n                    case SET:\n                        player.setRemainingAir(amount.asInt());\n                        break;\n                    case ADD:\n                        player.setRemainingAir(player.getRemainingAir() + amount.asInt());\n                        break;\n                    case REMOVE:\n                        player.setRemainingAir(player.getRemainingAir() - amount.asInt());\n                        break;\n                }\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/PermissionCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.OfflinePlayer;\r\nimport org.bukkit.World;\r\n\r\npublic class PermissionCommand extends AbstractCommand {\r\n\r\n    public PermissionCommand() {\r\n        setName(\"permission\");\r\n        setSyntax(\"permission [add/remove] [permission] (group:<name>) (<world>)\");\r\n        setRequiredArguments(2, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Permission\r\n    // @Syntax permission [add/remove] [permission] (group:<name>) (<world>)\r\n    // @Required 2\r\n    // @Maximum 4\r\n    // @Short Gives or takes a permission node to/from the player or group.\r\n    // @Group player\r\n    // @Plugin Vault\r\n    //\r\n    // @Description\r\n    // Adds or removes a permission node from a player or group.\r\n    // Accepts a world for world-based permissions plugins.\r\n    // By default changes the attached player's permissions.\r\n    // Accepts the 'group:<name>' argument to change a group's permission nodes rather than a player's.\r\n    // Note: This requires a permissions plugin and Vault.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.has_permission[permission.node]>\r\n    // <PlayerTag.has_permission[permission.node].global>\r\n    // <PlayerTag.has_permission[permission.node].world[<world>]>\r\n    // <server.has_permissions>\r\n    //\r\n    // @Usage\r\n    // Use to give the player a permissions node.\r\n    // - permission add bukkit.version\r\n    //\r\n    // @Usage\r\n    // Use to remove a permissions node from a player.\r\n    // - permission remove bukkit.version\r\n    //\r\n    // @Usage\r\n    // Use to give the group 'Members' a permission node.\r\n    // - permission add bukkit.version group:Members\r\n    //\r\n    // @Usage\r\n    // Use to remove a permissions node from the group 'Members' in the Creative world.\r\n    // - permission remove bukkit.version group:Members Creative\r\n    // -->\r\n\r\n    private enum Action {ADD, REMOVE}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        if (Depends.permissions == null) {\r\n            throw new InvalidArgumentsException(\"Permissions not linked - is Vault improperly installed, or is there no permissions plugin?\");\r\n        }\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matchesEnum(Action.class)) {\r\n                scriptEntry.addObject(\"action\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"group\")\r\n                    && arg.matchesPrefix(\"group\")) {\r\n                scriptEntry.addObject(\"group\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"world\")\r\n                    && arg.matchesArgumentType(WorldTag.class)) {\r\n                scriptEntry.addObject(\"world\", arg.asType(WorldTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"permission\")) {\r\n                scriptEntry.addObject(\"permission\", arg.asElement());\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"group\") && (!Utilities.entryHasPlayer(scriptEntry) || !Utilities.getEntryPlayer(scriptEntry).isValid())) {\r\n            throw new InvalidArgumentsException(\"Must have player context or a valid group!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"action\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a valid action!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"permission\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a permission!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag action = scriptEntry.getElement(\"action\");\r\n        ElementTag permission = scriptEntry.getElement(\"permission\");\r\n        ElementTag group = scriptEntry.getElement(\"group\");\r\n        WorldTag world = scriptEntry.getObjectTag(\"world\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), action, permission, group, world);\r\n        }\r\n        World bukkitWorld = null;\r\n        if (world != null) {\r\n            bukkitWorld = world.getWorld();\r\n        }\r\n        OfflinePlayer player = Utilities.entryHasPlayer(scriptEntry) ? Utilities.getEntryPlayer(scriptEntry).getOfflinePlayer() : null;\r\n        switch (Action.valueOf(action.asString().toUpperCase())) {\r\n            case ADD:\r\n                if (group != null) {\r\n                    if (Depends.permissions.groupHas(bukkitWorld, group.asString(), permission.asString())) {\r\n                        Debug.echoDebug(scriptEntry, \"Group \" + group + \" already has permission \" + permission);\r\n                    }\r\n                    else {\r\n                        Depends.permissions.groupAdd(bukkitWorld, group.asString(), permission.asString());\r\n                    }\r\n                }\r\n                else {\r\n                    if (Depends.permissions.playerHas(bukkitWorld == null ? null : bukkitWorld.getName(), player, permission.asString())) {\r\n                        Debug.echoDebug(scriptEntry, \"Player \" + player.getName() + \" already has permission \" + permission);\r\n                    }\r\n                    else {\r\n                        Depends.permissions.playerAdd(bukkitWorld == null ? null : bukkitWorld.getName(), player, permission.asString());\r\n                    }\r\n                }\r\n                break;\r\n            case REMOVE:\r\n                if (group != null) {\r\n                    if (!Depends.permissions.groupHas(bukkitWorld, group.asString(), permission.asString())) {\r\n                        Debug.echoDebug(scriptEntry, \"Group \" + group + \" does not have access to permission \" + permission);\r\n                    }\r\n                    else {\r\n                        Depends.permissions.groupRemove(bukkitWorld, group.asString(), permission.asString());\r\n                    }\r\n                }\r\n                else {\r\n                    if (!Depends.permissions.playerHas(bukkitWorld == null ? null : bukkitWorld.getName(), player, permission.asString())) {\r\n                        Debug.echoDebug(scriptEntry, \"Player \" + player.getName() + \" does not have access to permission \" + permission);\r\n                    }\r\n                    else {\r\n                        Depends.permissions.playerRemove(bukkitWorld == null ? null : bukkitWorld.getName(), player, permission.asString());\r\n                    }\r\n                }\r\n                break;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ResourcePackCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgDefaultNull;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgName;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgPrefixed;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgSubType;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class ResourcePackCommand extends AbstractCommand {\r\n\r\n    public ResourcePackCommand() {\r\n        setName(\"resourcepack\");\r\n        setSyntax(\"resourcepack [url:<url>] [hash:<hash>] (forced) (prompt:<text>) (targets:<player>|...)\");\r\n        setRequiredArguments(2, 5);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name ResourcePack\r\n    // @Syntax resourcepack [url:<url>] [hash:<hash>] (forced) (prompt:<text>) (targets:<player>|...)\r\n    // @Required 2\r\n    // @Maximum 5\r\n    // @Short Prompts a player to download a server resource pack.\r\n    // @group player\r\n    //\r\n    // @Description\r\n    // Sets the current resource pack by specifying a valid URL to a resource pack.\r\n    //\r\n    // The player will be prompted to download the pack, with the optional prompt text or a default vanilla message.\r\n    // Once a player says \"yes\" once, all future packs will be automatically downloaded. If the player selects \"no\" once, all future packs will automatically be rejected.\r\n    // Players can change the automatic setting from their server list in the main menu.\r\n    //\r\n    // Use \"hash:\" to specify a 40-character (20 byte) hexadecimal SHA-1 hash value (without '0x') for the resource pack to prevent redownloading cached data.\r\n    // Specifying a hash is required, though you can get away with copy/pasting a fake value if you don't care for the consequences.\r\n    // There are a variety of tools to generate the real hash, such as the `sha1sum` command on Linux, or using the 7-Zip GUI's Checksum option on Windows.\r\n    //\r\n    // Specify \"forced\" to tell the vanilla client they must accept the pack or quit the server. Hacked clients may still bypass this requirement.\r\n    //\r\n    // \"Forced\" and \"prompt\" inputs only work on Paper servers.\r\n    //\r\n    // Optionally specify players to send the pack to. If unspecified, will use the linked player.\r\n    //\r\n    // See also <@link event resource pack status>.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to send a resource pack with a pre-known hash.\r\n    // - resourcepack url:https://example.com/pack.zip hash:0102030405060708090a0b0c0d0e0f1112131415\r\n    //\r\n    // -->\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"url\") @ArgPrefixed String url,\r\n                                   @ArgName(\"hash\") @ArgPrefixed String hash,\r\n                                   @ArgName(\"prompt\") @ArgPrefixed @ArgDefaultNull String prompt,\r\n                                   @ArgName(\"targets\") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> targets,\r\n                                   @ArgName(\"forced\") boolean forced) {\r\n        if (targets == null) {\r\n            if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                throw new InvalidArgumentsRuntimeException(\"Must specify an online player!\");\r\n            }\r\n            targets = Collections.singletonList(Utilities.getEntryPlayer(scriptEntry));\r\n        }\r\n        if (hash.length() != 40) {\r\n            Debug.echoError(\"Invalid resource_pack hash. Should be 40 characters of hexadecimal data.\");\r\n            return;\r\n        }\r\n        for (PlayerTag player : targets) {\r\n            if (!player.isOnline()) {\r\n                Debug.echoDebug(scriptEntry, \"Player is offline, can't send resource pack to them. Skipping.\");\r\n                continue;\r\n            }\r\n            PaperAPITools.instance.sendResourcePack(player.getPlayerEntity(), url, hash, forced, prompt);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ShowFakeCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizen.utilities.command.TabCompleteHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class ShowFakeCommand extends AbstractCommand {\r\n\r\n    public ShowFakeCommand() {\r\n        setName(\"showfake\");\r\n        setSyntax(\"showfake [<material>|.../cancel] [<location>|...] (players:<player>|...) (d:<duration>{10s})\");\r\n        setRequiredArguments(2, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name ShowFake\r\n    // @Syntax showfake [<material>|.../cancel] [<location>|...] (players:<player>|...) (d:<duration>{10s})\r\n    // @Required 2\r\n    // @Maximum 4\r\n    // @Short Makes the player see a block change that didn't actually happen.\r\n    // @Synonyms FakeBlock\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Makes the player see a block change that didn't actually happen.\r\n    // This means that the server will still register the block being what it was before the command,\r\n    // and players not included in the command will still see the original block.\r\n    //\r\n    // You must specify a location (or list of locations), and a material (or list of materials).\r\n    // The material list does not have to be of the same size as the location list (materials will be repeated automatically).\r\n    //\r\n    // Optionally, specify a list of players to show the change to.\r\n    // If unspecified, will default to the linked player.\r\n    //\r\n    // Optionally, specify how long the fake block should remain for.\r\n    // If unspecified, will default to 10 seconds.\r\n    // After the duration is up, the block will revert back to whatever it really is (on the server-side).\r\n    //\r\n    // Note that while the player will see the block as though it were real, the server will have no knowledge of this.\r\n    // This means that if the player, for example, stands atop a fake block that the server sees as air, that player will be seen as flying.\r\n    // The reverse applies as well: if a player walks through fake air (that is actually solid), the server will see a player walking through walls.\r\n    // This can easily lead to players getting kicked by anti-cheat systems or similar results.\r\n    // You can enable the player to walk through fake air via <@link mechanism PlayerTag.noclip>.\r\n    // Note as well that some clientside block effects may occur (eg fake fire may appear momentarily to actually ignite things, but won't actually damage them).\r\n    //\r\n    // Warning: extremely complex chunks (those with a significant variety of block types in a small area) might not be able to retain fake blocks over time properly.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.fake_block_locations>\r\n    // <PlayerTag.fake_block[<location>]>\r\n    //\r\n    // @Usage\r\n    // Use to place a fake gold block at where the player is looking\r\n    // - showfake gold_block <player.cursor_on>\r\n    //\r\n    // @Usage\r\n    // Use to place a stone block right on player's head, that only stays for a second.\r\n    // - showfake stone <player.location.add[0,1,0]> duration:1s\r\n    //\r\n    // @Usage\r\n    // Use to place fake lava that the player is standing in, for all the server to see\r\n    // - showfake lava <player.location> players:<server.online_players>\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        TabCompleteHelper.tabCompleteBlockMaterials(tab);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"players\")\r\n                    && arg.matchesPrefix(\"to\", \"players\")) {\r\n                scriptEntry.addObject(\"players\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else if (arg.matchesPrefix(\"d\", \"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (arg.matches(\"cancel\")) {\r\n                scriptEntry.addObject(\"cancel\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"materials\")\r\n                && arg.matchesArgumentList(MaterialTag.class)) {\r\n                scriptEntry.addObject(\"materials\", arg.asType(ListTag.class).filter(MaterialTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"locations\")\r\n                    && arg.matchesArgumentList(LocationTag.class)) {\r\n                scriptEntry.addObject(\"locations\", arg.asType(ListTag.class).filter(LocationTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"players\") && Utilities.entryHasPlayer(scriptEntry)) {\r\n            scriptEntry.defaultObject(\"players\", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry)));\r\n        }\r\n        if (!scriptEntry.hasObject(\"locations\")) {\r\n            throw new InvalidArgumentsException(\"Must specify at least one valid location!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"players\")) {\r\n            throw new InvalidArgumentsException(\"Must have a valid, online player attached!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"materials\") && !scriptEntry.hasObject(\"cancel\")) {\r\n            throw new InvalidArgumentsException(\"Must specify valid material(s)!\");\r\n        }\r\n        scriptEntry.defaultObject(\"duration\", new DurationTag(10));\r\n        scriptEntry.defaultObject(\"cancel\", new ElementTag(false));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        NetworkInterceptHelper.enable();\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        ElementTag cancel = scriptEntry.getElement(\"cancel\");\r\n        List<MaterialTag> materials = (List<MaterialTag>) scriptEntry.getObject(\"materials\");\r\n        List<LocationTag> locations = (List<LocationTag>) scriptEntry.getObject(\"locations\");\r\n        List<PlayerTag> players = (List<PlayerTag>) scriptEntry.getObject(\"players\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), duration, cancel, db(\"materials\", materials), db(\"locations\", locations), db(\"players\", players));\r\n        }\r\n        boolean shouldCancel = cancel.asBoolean();\r\n        int i = 0;\r\n        for (LocationTag loc : locations) {\r\n            if (!shouldCancel) {\r\n                FakeBlock.showFakeBlockTo(players, loc.getBlockLocation(), materials.get(i % materials.size()), duration, locations.size() < 5);\r\n            }\r\n            else {\r\n                FakeBlock.stopShowingTo(players, loc.getBlockLocation());\r\n            }\r\n            i++;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/SidebarCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerQuitEvent;\r\n\r\nimport java.util.*;\r\n\r\npublic class SidebarCommand extends AbstractCommand {\r\n\r\n    public SidebarCommand() {\r\n        setName(\"sidebar\");\r\n        setSyntax(\"sidebar (add/remove/{set}/set_line) (title:<title>) (scores:<#>|...) (values:<line>|...) (start:<#>/{num_of_lines}) (increment:<#>/{-1}) (players:<player>|...) (per_player)\");\r\n        setRequiredArguments(1, 8);\r\n        setParseArgs(false);\r\n        Denizen.getInstance().getServer().getPluginManager().registerEvents(new SidebarEvents(), Denizen.getInstance());\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Sidebar\r\n    // @Syntax sidebar (add/remove/{set}/set_line) (title:<title>) (scores:<#>|...) (values:<line>|...) (start:<#>/{num_of_lines}) (increment:<#>/{-1}) (players:<player>|...) (per_player)\r\n    // @Required 1\r\n    // @Maximum 8\r\n    // @Short Controls clientside-only sidebars.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // This command was created as a simpler replacement for using the Scoreboard command to display per-player sidebars.\r\n    // By using packets and dummies, it enables you to have non-flickering, fully functional sidebars,\r\n    // without wasting processing speed and memory on creating new Scoreboards for  every single player.\r\n    //\r\n    // Using this command, you can add, remove, or set lines on the scoreboard.\r\n    //\r\n    // To set the title of the sidebar, use the 'title:' parameter in any case where the action is 'set'.\r\n    //\r\n    // By default, the score numbers descend from the total line count to 1.\r\n    // To customize the automatic score values, use the 'start:' and 'increment:' arguments in any case where the action is 'set'.\r\n    // 'Start' is the score where the first line will be shown with. The default 'start' value is determined by how many items are specified in 'values:'.\r\n    // 'Increment' is the difference between each score and the default is -1.\r\n    //\r\n    // To instead set entirely custom numbers, use the 'scores:' input with a list of numbers,\r\n    // where each number is the score to use with the value at the same place in the 'values:' list.\r\n    //\r\n    // You can remove by line value text, or by score number.\r\n    //\r\n    // The per_player argument is also available, and helps to reduce the number of loops required for updating multiple players' sidebars.\r\n    // When it is specified, all tags in the command will fill based on each individual player in the players list.\r\n    // So, for example, you could have <player.name> on a line and it will show each player specified their name on that line.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.sidebar_lines>\r\n    // <PlayerTag.sidebar_title>\r\n    // <PlayerTag.sidebar_scores>\r\n    //\r\n    // @Usage\r\n    // Use to show all online players a sidebar.\r\n    // - sidebar set \"title:Hello World!\" \"values:This is|My Message!|Wee!\" players:<server.online_players>\r\n    //\r\n    // @Usage\r\n    // Use to show a few players their ping.\r\n    // - sidebar set title:Info \"values:Ping<&co> <player.ping>\" players:<[someplayer]>|<[player]>|<[aplayer]> per_player\r\n    //\r\n    // @Usage\r\n    // Use to set a sidebar with the score values indicating information to the user.\r\n    // - sidebar set scores:<server.online_players.size>|<server.max_players> \"values:Players online|Players allowed\"\r\n    //\r\n    // @Usage\r\n    // Use to change a specific line of a sidebar.\r\n    // - sidebar set_line scores:5 \"values:Better message!\"\r\n    //\r\n    // @Usage\r\n    // Use to add a line to the bottom of the sidebar.\r\n    // - sidebar add \"values:This is the bottom!\"\r\n    //\r\n    // @Usage\r\n    // Use to remove multiple lines from the sidebar.\r\n    // - sidebar remove scores:2|4|6\r\n    //\r\n    // @Usage\r\n    // Use to stop showing the sidebar.\r\n    // - sidebar remove\r\n    // -->\r\n\r\n    // TODO: Clean me!\r\n\r\n    private enum Action {ADD, REMOVE, SET, SET_LINE}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        Action action = Action.SET;\r\n        for (Argument arg : ArgumentHelper.interpret(scriptEntry, scriptEntry.getOriginalArguments())) {\r\n            if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matchesEnum(Action.class)) {\r\n                action = Action.valueOf(arg.getValue().toUpperCase());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"title\")\r\n                    && arg.matchesPrefix(\"title\", \"t\", \"objective\", \"obj\", \"o\")) {\r\n                scriptEntry.addObject(\"title\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"scores\")\r\n                    && arg.matchesPrefix(\"scores\", \"score\", \"lines\", \"line\", \"l\")) {\r\n                scriptEntry.addObject(\"scores\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"value\")\r\n                    && arg.matchesPrefix(\"value\", \"values\", \"val\", \"v\")) {\r\n                scriptEntry.addObject(\"value\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"increment\")\r\n                    && arg.matchesPrefix(\"increment\", \"inc\", \"i\")) {\r\n                scriptEntry.addObject(\"increment\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"start\")\r\n                    && arg.matchesPrefix(\"start\", \"s\")) {\r\n                scriptEntry.addObject(\"start\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"players\")\r\n                    && arg.matchesPrefix(\"players\", \"player\", \"p\")) {\r\n                scriptEntry.addObject(\"players\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"per_player\")\r\n                    && arg.matches(\"per_player\")) {\r\n                scriptEntry.addObject(\"per_player\", new ElementTag(true));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (action == Action.ADD && !scriptEntry.hasObject(\"value\")) {\r\n            throw new InvalidArgumentsException(\"Must specify value(s) for that action!\");\r\n        }\r\n        if (action == Action.SET && !scriptEntry.hasObject(\"value\") && !scriptEntry.hasObject(\"title\")\r\n                && !scriptEntry.hasObject(\"increment\") && !scriptEntry.hasObject(\"start\")) {\r\n            throw new InvalidArgumentsException(\"Must specify at least one of: value(s), title, increment, or start for that action!\");\r\n        }\r\n        if (action == Action.SET && scriptEntry.hasObject(\"scores\") && !scriptEntry.hasObject(\"value\")) {\r\n            throw new InvalidArgumentsException(\"Must specify value(s) when setting scores!\");\r\n        }\r\n        scriptEntry.addObject(\"action\", new ElementTag(action));\r\n        scriptEntry.defaultObject(\"per_player\", new ElementTag(false));\r\n        scriptEntry.defaultObject(\"players\", new ElementTag(Utilities.entryHasPlayer(scriptEntry) ? Utilities.getEntryPlayer(scriptEntry).identify() : \"li@\"));\r\n    }\r\n\r\n    public static boolean hasScoreAlready(List<Sidebar.SidebarLine> lines, int score) {\r\n        for (Sidebar.SidebarLine line : lines) {\r\n            if (line.score == score) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag action = scriptEntry.getElement(\"action\");\r\n        ElementTag elTitle = scriptEntry.getElement(\"title\");\r\n        ElementTag elScores = scriptEntry.getElement(\"scores\");\r\n        ElementTag elValue = scriptEntry.getElement(\"value\");\r\n        ElementTag elIncrement = scriptEntry.getElement(\"increment\");\r\n        ElementTag elStart = scriptEntry.getElement(\"start\");\r\n        ElementTag elPlayers = scriptEntry.getElement(\"players\");\r\n        ElementTag elPerPlayer = scriptEntry.getElement(\"per_player\");\r\n        ListTag players = ListTag.valueOf(TagManager.tag(elPlayers.asString(), scriptEntry.getContext()), scriptEntry.getContext());\r\n        boolean per_player = elPerPlayer.asBoolean();\r\n        String perTitle = null;\r\n        String perScores = null;\r\n        String perValue = null;\r\n        String perIncrement = null;\r\n        String perStart = null;\r\n        ElementTag title = null;\r\n        ListTag scores = null;\r\n        ListTag value = null;\r\n        ElementTag increment = null;\r\n        ElementTag start = null;\r\n        if (per_player) {\r\n            if (elTitle != null) {\r\n                perTitle = elTitle.asString();\r\n            }\r\n            if (elScores != null) {\r\n                perScores = elScores.asString();\r\n            }\r\n            if (elValue != null) {\r\n                perValue = elValue.asString();\r\n            }\r\n            if (elIncrement != null) {\r\n                perIncrement = elIncrement.asString();\r\n            }\r\n            if (elStart != null) {\r\n                perStart = elStart.asString();\r\n            }\r\n            if (scriptEntry.dbCallShouldDebug()) {\r\n                Debug.report(scriptEntry, getName(), action, elTitle, elScores, elValue, elIncrement, elStart, db(\"players\", players));\r\n            }\r\n        }\r\n        else {\r\n            BukkitTagContext context = (BukkitTagContext) scriptEntry.getContext();\r\n            if (elTitle != null) {\r\n                title = new ElementTag(TagManager.tag(elTitle.asString(), context));\r\n            }\r\n            if (elScores != null) {\r\n                scores = ListTag.getListFor(TagManager.tagObject(elScores.asString(), context), context);\r\n            }\r\n            if (elValue != null) {\r\n                value = ListTag.getListFor(TagManager.tagObject(elValue.asString(), context), context);\r\n            }\r\n            if (elIncrement != null) {\r\n                increment = new ElementTag(TagManager.tag(elIncrement.asString(), context));\r\n            }\r\n            if (elStart != null) {\r\n                start = new ElementTag(TagManager.tag(elStart.asString(), context));\r\n            }\r\n            if (scriptEntry.dbCallShouldDebug()) {\r\n                Debug.report(scriptEntry, getName(), action, title, scores, value, increment, start, db(\"players\", players));\r\n            }\r\n        }\r\n        switch (Action.valueOf(action.asString())) {\r\n            case ADD:\r\n                for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                    if (player == null || !player.isValid()) {\r\n                        Debug.echoError(\"Invalid player!\");\r\n                        continue;\r\n                    }\r\n                    Sidebar sidebar = createSidebar(player);\r\n                    if (sidebar == null) {\r\n                        continue;\r\n                    }\r\n                    List<Sidebar.SidebarLine> current = sidebar.getLines();\r\n                    if (per_player) {\r\n                        TagContext context = new BukkitTagContext(player, Utilities.getEntryNPC(scriptEntry),\r\n                                scriptEntry, scriptEntry.shouldDebug(), scriptEntry.getScript());\r\n                        value = ListTag.getListFor(TagManager.tagObject(perValue, context), context);\r\n                        if (perScores != null) {\r\n                            scores = ListTag.getListFor(TagManager.tagObject(perScores, context), context);\r\n                        }\r\n                    }\r\n                    try {\r\n                        int index = start != null ? start.asInt() : (current.size() > 0 ? current.get(current.size() - 1).score : value.size());\r\n                        int incr = increment != null ? increment.asInt() : -1;\r\n                        for (int i = 0; i < value.size(); i++, index += incr) {\r\n                            int score = (scores != null && i < scores.size()) ? Integer.parseInt(scores.get(i)) : index;\r\n                            while (hasScoreAlready(current, score)) {\r\n                                score += (incr == 0 ? 1 : incr);\r\n                            }\r\n                            current.add(new Sidebar.SidebarLine(value.get(i), score));\r\n                        }\r\n                    }\r\n                    catch (Exception e) {\r\n                        Debug.echoError(e);\r\n                        continue;\r\n                    }\r\n                    sidebar.setLines(current);\r\n                    sidebar.sendUpdate();\r\n                }\r\n                break;\r\n            case REMOVE:\r\n                for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                    if (player == null || !player.isValid()) {\r\n                        Debug.echoError(\"Invalid player!\");\r\n                        continue;\r\n                    }\r\n                    Sidebar sidebar = createSidebar(player);\r\n                    if (sidebar == null) {\r\n                        continue;\r\n                    }\r\n                    List<Sidebar.SidebarLine> current = sidebar.getLines();\r\n                    if (per_player) {\r\n                        TagContext context = new BukkitTagContext(player, Utilities.getEntryNPC(scriptEntry),\r\n                                scriptEntry, scriptEntry.shouldDebug(), scriptEntry.getScript());\r\n                        if (perValue != null) {\r\n                            value = ListTag.getListFor(TagManager.tagObject(perValue, context), context);\r\n                        }\r\n                        if (perScores != null) {\r\n                            scores = ListTag.getListFor(TagManager.tagObject(perScores, context), context);\r\n                        }\r\n                    }\r\n                    boolean removedAny = false;\r\n                    if (scores != null) {\r\n                        try {\r\n                            for (String scoreString : scores) {\r\n                                int score = Integer.parseInt(scoreString);\r\n                                for (int i = 0; i < current.size(); i++) {\r\n                                    if (current.get(i).score == score) {\r\n                                        current.remove(i--);\r\n                                    }\r\n                                }\r\n                            }\r\n                        }\r\n                        catch (Exception e) {\r\n                            Debug.echoError(e);\r\n                            continue;\r\n                        }\r\n                        sidebar.setLines(current);\r\n                        sidebar.sendUpdate();\r\n                        removedAny = true;\r\n                    }\r\n                    if (value != null) {\r\n                        for (String line : value) {\r\n                            for (int i = 0; i < current.size(); i++) {\r\n                                if (current.get(i).text.equalsIgnoreCase(line)) {\r\n                                    current.remove(i--);\r\n                                }\r\n                            }\r\n                        }\r\n                        sidebar.setLines(current);\r\n                        sidebar.sendUpdate();\r\n                        removedAny = true;\r\n                    }\r\n                    if (!removedAny) {\r\n                        sidebar.remove();\r\n                        sidebars.remove(player.getPlayerEntity().getUniqueId());\r\n                    }\r\n                }\r\n                break;\r\n            case SET_LINE:\r\n                for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                    if (player == null || !player.isValid()) {\r\n                        Debug.echoError(\"Invalid player!\");\r\n                        continue;\r\n                    }\r\n                    if ((scores == null || scores.isEmpty()) && perScores == null) {\r\n                        Debug.echoError(\"Missing or invalid 'scores' parameter.\");\r\n                        return;\r\n                    }\r\n                    if ((value == null || value.size() != scores.size()) && perValue == null) {\r\n                        Debug.echoError(\"Missing or invalid 'values' parameter.\");\r\n                        return;\r\n                    }\r\n                    Sidebar sidebar = createSidebar(player);\r\n                    if (sidebar == null) {\r\n                        continue;\r\n                    }\r\n                    List<Sidebar.SidebarLine> current = sidebar.getLines();\r\n                    if (per_player) {\r\n                        TagContext context = new BukkitTagContext(player, Utilities.getEntryNPC(scriptEntry),\r\n                                scriptEntry, scriptEntry.shouldDebug(), scriptEntry.getScript());\r\n                        if (perValue != null) {\r\n                            value = ListTag.getListFor(TagManager.tagObject(perValue, context), context);\r\n                        }\r\n                        if (perScores != null) {\r\n                            scores = ListTag.getListFor(TagManager.tagObject(perScores, context), context);\r\n                        }\r\n                    }\r\n                    try {\r\n                        for (int i = 0; i < value.size(); i++) {\r\n                            if (!ArgumentHelper.matchesInteger(scores.get(i))) {\r\n                                Debug.echoError(\"Sidebar command scores input contains not-a-valid-number: \" + scores.get(i));\r\n                                return;\r\n                            }\r\n                            int score = Integer.parseInt(scores.get(i));\r\n                            if (hasScoreAlready(current, score)) {\r\n                                for (Sidebar.SidebarLine line : current) {\r\n                                    if (line.score == score) {\r\n                                        line.text = value.get(i);\r\n                                        break;\r\n                                    }\r\n                                }\r\n                            }\r\n                            else {\r\n                                current.add(new Sidebar.SidebarLine(value.get(i), score));\r\n                            }\r\n                        }\r\n                    }\r\n                    catch (Exception e) {\r\n                        Debug.echoError(e);\r\n                        continue;\r\n                    }\r\n                    sidebar.setLines(current);\r\n                    sidebar.sendUpdate();\r\n                }\r\n                break;\r\n            case SET:\r\n                for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                    if (player == null || !player.isValid()) {\r\n                        Debug.echoError(\"Invalid player!\");\r\n                        continue;\r\n                    }\r\n                    Sidebar sidebar = createSidebar(player);\r\n                    if (sidebar == null) {\r\n                        continue;\r\n                    }\r\n                    List<Sidebar.SidebarLine> current = new ArrayList<>();\r\n                    if (per_player) {\r\n                        TagContext context = new BukkitTagContext(player, Utilities.getEntryNPC(scriptEntry),\r\n                                scriptEntry, scriptEntry.shouldDebug(), scriptEntry.getScript());\r\n                        if (perValue != null) {\r\n                            value = ListTag.getListFor(TagManager.tagObject(perValue, context), context);\r\n                        }\r\n                        if (perScores != null) {\r\n                            scores = ListTag.getListFor(TagManager.tagObject(perScores, context), context);\r\n                        }\r\n                        if (perStart != null) {\r\n                            start = new ElementTag(TagManager.tag(perStart, context));\r\n                        }\r\n                        if (perIncrement != null) {\r\n                            increment = new ElementTag(TagManager.tag(perIncrement, context));\r\n                        }\r\n                        if (perTitle != null) {\r\n                            title = new ElementTag(TagManager.tag(perTitle, context));\r\n                        }\r\n                    }\r\n                    if (value != null) {\r\n                        try {\r\n                            int index = start != null ? start.asInt() : value.size();\r\n                            int incr = increment != null ? increment.asInt() : -1;\r\n                            for (int i = 0; i < value.size(); i++, index += incr) {\r\n                                int score = (scores != null && i < scores.size()) ? Integer.parseInt(scores.get(i)) : index;\r\n                                current.add(new Sidebar.SidebarLine(value.get(i), score));\r\n                            }\r\n                        }\r\n                        catch (Exception e) {\r\n                            Debug.echoError(e);\r\n                            continue;\r\n                        }\r\n                        sidebar.setLines(current);\r\n                    }\r\n                    if (title != null) {\r\n                        sidebar.setTitle(title.asString());\r\n                    }\r\n                    sidebar.sendUpdate();\r\n                }\r\n                break;\r\n        }\r\n    }\r\n\r\n    private static final Map<UUID, Sidebar> sidebars = new HashMap<>();\r\n\r\n    private static Sidebar createSidebar(PlayerTag denizenPlayer) {\r\n        if (!denizenPlayer.isOnline()) {\r\n            return null;\r\n        }\r\n        Player player = denizenPlayer.getPlayerEntity();\r\n        UUID uuid = player.getUniqueId();\r\n        if (!sidebars.containsKey(uuid)) {\r\n            sidebars.put(uuid, NMSHandler.instance.createSidebar(player));\r\n        }\r\n        return sidebars.get(player.getUniqueId());\r\n    }\r\n\r\n    public static Sidebar getSidebar(PlayerTag denizenPlayer) {\r\n        if (!denizenPlayer.isOnline()) {\r\n            return null;\r\n        }\r\n        return sidebars.get(denizenPlayer.getPlayerEntity().getUniqueId());\r\n    }\r\n\r\n    public static class SidebarEvents implements Listener {\r\n        @EventHandler\r\n        public void onPlayerQuit(PlayerQuitEvent event) {\r\n            UUID uuid = event.getPlayer().getUniqueId();\r\n            sidebars.remove(uuid);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/StatisticCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.Statistic;\r\nimport org.bukkit.entity.EntityType;\r\n\r\npublic class StatisticCommand extends AbstractCommand {\r\n\r\n    public StatisticCommand() {\r\n        setName(\"statistic\");\r\n        setSyntax(\"statistic [<statistic>] [add/take/set] (<#>) (qualifier:<material>/<entity>)\");\r\n        setRequiredArguments(2, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Statistic\r\n    // @Syntax statistic [<statistic>] [add/take/set] (<#>) (qualifier:<material>/<entity>)\r\n    // @Required 2\r\n    // @Maximum 4\r\n    // @Short Changes the specified statistic value for a player.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Changes the specified statistic for the player.\r\n    // For more info on statistics, see <@link url https://minecraft.wiki/w/Statistics>\r\n    // For statistic names, see <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Statistic.html>\r\n    //\r\n    // You can add, take, or set a numeric value to the statistic for the linked player.\r\n    // Works with offline players.\r\n    //\r\n    // Some statistics are unique per a material or entity - for those, use the \"qualifier\" argument.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.statistic[<statistic>]>\r\n    // <PlayerTag.statistic[<statistic>].qualifier[<material>/<entity>]>\r\n    //\r\n    // @Usage\r\n    // Use to hide the evidence of all the animal breeding you've done.\r\n    // - statistic animals_bred set 0\r\n    //\r\n    // @Usage\r\n    // Use to pretend you just ran a 5k.\r\n    // - statistic walk_one_cm add 500000\r\n    //\r\n    // @Usage\r\n    // Use to make it look like that challenge course wasn't even hard for you at all.\r\n    // - statistic deaths take 200\r\n    //\r\n    // -->\r\n\r\n    private enum Action {ADD, TAKE, SET}\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.add(Statistic.values());\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        boolean specified_players = false;\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matchesEnum(Action.class)) {\r\n                scriptEntry.addObject(\"action\", arg.asElement());\r\n            }\r\n            else if (arg.matchesPrefix(\"players\")\r\n                    && !scriptEntry.hasObject(\"players\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"players\", arg.asType(ListTag.class));\r\n                specified_players = true;\r\n            }\r\n            else if (!scriptEntry.hasObject(\"statistic\")\r\n                    && arg.matchesEnum(Statistic.class)) {\r\n                scriptEntry.addObject(\"statistic\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"amount\")\r\n                    && arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"amount\", arg.asElement());\r\n            }\r\n            else if (arg.matchesPrefix(\"qualifier\", \"q\")\r\n                    && !scriptEntry.hasObject(\"material\")\r\n                    && !scriptEntry.hasObject(\"entity\")) {\r\n                if (arg.matchesArgumentType(MaterialTag.class)) {\r\n                    scriptEntry.addObject(\"material\", arg.asType(MaterialTag.class));\r\n                }\r\n                else if (arg.matchesArgumentType(EntityTag.class)) {\r\n                    scriptEntry.addObject(\"entity\", arg.asType(EntityTag.class));\r\n                }\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"action\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a valid action!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"statistic\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a valid Statistic!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"amount\")) {\r\n            scriptEntry.addObject(\"amount\", new ElementTag(1));\r\n        }\r\n        Statistic.Type type = Statistic.valueOf(scriptEntry.getElement(\"statistic\").asString().toUpperCase()).getType();\r\n        if (type != Statistic.Type.UNTYPED) {\r\n            if ((type == Statistic.Type.BLOCK || type == Statistic.Type.ITEM) && !scriptEntry.hasObject(\"material\")) {\r\n                throw new InvalidArgumentsException(\"Must specify a valid \" + type.name() + \" MATERIAL!\");\r\n            }\r\n            else if (type == Statistic.Type.ENTITY && !scriptEntry.hasObject(\"entity\")) {\r\n                throw new InvalidArgumentsException(\"Must specify a valid ENTITY!\");\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"players\") && Utilities.entryHasPlayer(scriptEntry) && !specified_players) {\r\n            scriptEntry.addObject(\"players\", new ListTag(Utilities.getEntryPlayer(scriptEntry)));\r\n        }\r\n        if (!scriptEntry.hasObject(\"players\")) {\r\n            throw new InvalidArgumentsException(\"Must specify valid players!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag action = scriptEntry.getElement(\"action\");\r\n        ListTag players = scriptEntry.getObjectTag(\"players\");\r\n        ElementTag statistic = scriptEntry.getElement(\"statistic\");\r\n        ElementTag amount = scriptEntry.getElement(\"amount\");\r\n        MaterialTag material = scriptEntry.getObjectTag(\"material\");\r\n        EntityTag entity = scriptEntry.getObjectTag(\"entity\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), action, statistic, amount, players, material, entity);\r\n        }\r\n        Action act = Action.valueOf(action.asString().toUpperCase());\r\n        Statistic stat = Statistic.valueOf(statistic.asString().toUpperCase());\r\n        int amt = amount.asInt();\r\n        switch (stat.getType()) {\r\n            case BLOCK:\r\n            case ITEM:\r\n                Material mat = material.getMaterial();\r\n                switch (act) {\r\n                    case ADD:\r\n                        for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                            player.incrementStatistic(stat, mat, amt);\r\n                        }\r\n                        break;\r\n                    case TAKE:\r\n                        for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                            player.decrementStatistic(stat, mat, amt);\r\n                        }\r\n                        break;\r\n                    case SET:\r\n                        for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                            player.setStatistic(stat, mat, amt);\r\n                        }\r\n                        break;\r\n                }\r\n                break;\r\n            case ENTITY:\r\n                EntityType ent = entity.getBukkitEntityType();\r\n                switch (act) {\r\n                    case ADD:\r\n                        for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                            player.incrementStatistic(stat, ent, amt);\r\n                        }\r\n                        break;\r\n                    case TAKE:\r\n                        for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                            player.decrementStatistic(stat, ent, amt);\r\n                        }\r\n                        break;\r\n                    case SET:\r\n                        for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                            player.setStatistic(stat, ent, amt);\r\n                        }\r\n                        break;\r\n                }\r\n                break;\r\n            case UNTYPED:\r\n                switch (act) {\r\n                    case ADD:\r\n                        for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                            player.incrementStatistic(stat, amt);\r\n                        }\r\n                        break;\r\n                    case TAKE:\r\n                        for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                            player.decrementStatistic(stat, amt);\r\n                        }\r\n                        break;\r\n                    case SET:\r\n                        for (PlayerTag player : players.filter(PlayerTag.class, scriptEntry)) {\r\n                            player.setStatistic(stat, amt);\r\n                        }\r\n                        break;\r\n                }\r\n                break;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TablistCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.nms.interfaces.PlayerHelper;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgDefaultNull;\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgName;\nimport com.denizenscript.denizencore.scripts.commands.generator.ArgPrefixed;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport org.bukkit.GameMode;\nimport org.bukkit.entity.Player;\n\nimport java.util.EnumSet;\nimport java.util.UUID;\n\npublic class TablistCommand extends AbstractCommand {\n\n    public TablistCommand() {\n        setName(\"tablist\");\n        setSyntax(\"tablist [add/remove/update] (name:<name>) (display:<display>) (uuid:<uuid>) (skin_blob:<blob>) (latency:<#>) (gamemode:creative/survival/adventure/spectator) (listed:true/false)\");\n        setRequiredArguments(2, 8);\n        isProcedural = false;\n        autoCompile();\n    }\n\n    // <--[command]\n    // @Name TabList\n    // @Syntax tablist [add/remove/update] (name:<name>) (display:<display>) (uuid:<uuid>) (skin_blob:<blob>) (latency:<#>) (gamemode:creative/survival/adventure/spectator) (listed:true/false)\n    // @Required 2\n    // @Maximum 8\n    // @Short Modifies values in the player's tablist.\n    // @Group player\n    //\n    // @Description\n    // Adds, removes, or updates a player profile entry in the player's tab-list view.\n    //\n    // Using 'add' will add a new entry to the client's player list.\n    // 'name' must be specified.\n    // 'display' if unspecified will be the same as the name.\n    // 'uuid' if unspecified will be randomly generated.\n    // 'skin_blob' if unspecified will be a default Minecraft skin. Skin blob should be in format \"texture;signature\" (separated by semicolon).\n    // 'latency' is a number representing the players ping, if unspecified will be 0. 0 renders as full ping, -1 renders an \"X\", 500 renders orange (3 bars), 1000 renders red (1 bar).\n    // 'gamemode' if unspecified will be creative. 'spectator' renders as faded and is pushed below all non-spectator entries.\n    // 'listed' determines whether the entry will show up in the tab list, defaults to 'true'.\n    //\n    // Using 'remove' will remove an entry from the tab list.\n    // 'uuid' must be specified.\n    //\n    // Using 'update' will update an existing entry in the tab list.\n    // 'uuid' must be specified.\n    // Only 'display', 'latency', 'gamemode', and 'listed' can be updated.\n    //\n    // Usage of display names that are not empty requires enabling Denizen/config.yml option \"Allow restricted actions\".\n    // Using this tool to add entries that look like real players (but aren't) is forbidden.\n    //\n    // @Tags\n    // None\n    //\n    // @Usage\n    // Use to add a new empty entry to the player's tab list to fill space.\n    // - tablist add name:<empty> display:<empty> gamemode:spectator\n    //\n    // @Usage\n    // Use to update an existing entry\n    // - tablist update uuid:<[uuid]> gamemode:spectator latency:200\n    //\n    // -->\n\n\n    @Override\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\n        tab.addWithPrefix(\"gamemode:\", GameMode.values());\n    }\n\n    public enum Mode { ADD, REMOVE, UPDATE }\n\n    public static void autoExecute(ScriptEntry scriptEntry,\n                                   @ArgName(\"mode\") Mode mode,\n                                   @ArgDefaultNull @ArgPrefixed @ArgName(\"name\") String name,\n                                   @ArgDefaultNull @ArgPrefixed @ArgName(\"display\") String display,\n                                   @ArgDefaultNull @ArgPrefixed @ArgName(\"uuid\") String uuid,\n                                   @ArgDefaultNull @ArgPrefixed @ArgName(\"skin_blob\") String skinBlob,\n                                   @ArgDefaultNull @ArgPrefixed @ArgName(\"latency\") ElementTag latency,\n                                   @ArgDefaultNull @ArgPrefixed @ArgName(\"gamemode\") GameMode gamemode,\n                                   @ArgDefaultNull @ArgPrefixed @ArgName(\"listed\") ElementTag listed) {\n        if (!Utilities.entryHasPlayer(scriptEntry)) {\n            Debug.echoError(\"Must have a linked player!\");\n            return;\n        }\n        Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity();\n        if (listed != null && !listed.isBoolean()) {\n            Debug.echoError(\"Invalid input '\" + listed + \"' to 'listed': must be a boolean\");\n            return;\n        }\n        UUID id;\n        if (uuid != null) {\n            try {\n                id = UUID.fromString(uuid);\n            }\n            catch (IllegalArgumentException ex) {\n                Debug.echoError(\"Invalid UUID '\" + uuid + \"'\");\n                return;\n            }\n        }\n        else {\n            id = UUID.randomUUID();\n        }\n        String texture = null, signature = null;\n        if (skinBlob != null) {\n            int semicolon = skinBlob.indexOf(';');\n            if (semicolon == -1) {\n                Debug.echoError(\"Invalid skinblob '\" + skinBlob + \"'\");\n                return;\n            }\n            texture = skinBlob.substring(0, semicolon);\n            signature = skinBlob.substring(semicolon + 1);\n        }\n        if (latency != null && !latency.isInt()) {\n            Debug.echoError(\"Invalid latency, not a number '\" + latency + \"'\");\n            return;\n        }\n        int latencyNum = latency == null ? 0 : latency.asInt();\n        switch (mode) {\n            case ADD -> {\n                if (name == null) {\n                    throw new InvalidArgumentsRuntimeException(\"'name' wasn't specified but is required for 'add'\");\n                }\n                if ((display == null || display.length() > 0) && !CoreConfiguration.allowRestrictedActions) {\n                    Debug.echoError(\"Cannot use 'tablist add' to add a non-empty display-named entry: 'Allow restricted actions' is disabled in Denizen config.yml.\");\n                    return;\n                }\n                if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) {\n                    NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, EnumSet.of(PlayerHelper.ProfileEditMode.ADD), name, display, id, texture, signature, latencyNum, gamemode == null ? GameMode.CREATIVE : gamemode, false);\n                    return;\n                }\n                EnumSet<PlayerHelper.ProfileEditMode> editModes = EnumSet.of(PlayerHelper.ProfileEditMode.ADD);\n                boolean listedBool = listed == null || listed.asBoolean();\n                if (listedBool) {\n                    editModes.add(PlayerHelper.ProfileEditMode.UPDATE_LISTED);\n                }\n                if (display != null) {\n                    editModes.add(PlayerHelper.ProfileEditMode.UPDATE_DISPLAY);\n                }\n                if (latency != null) {\n                    editModes.add(PlayerHelper.ProfileEditMode.UPDATE_LATENCY);\n                }\n                if (gamemode != null) {\n                    editModes.add(PlayerHelper.ProfileEditMode.UPDATE_GAME_MODE);\n                }\n                NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, editModes, name, display, id, texture, signature, latencyNum, gamemode, listedBool);\n            }\n            case REMOVE -> {\n                if (uuid == null) {\n                    throw new InvalidArgumentsRuntimeException(\"'uuid' wasn't specified but is required for 'remove'\");\n                }\n                NMSHandler.playerHelper.sendPlayerInfoRemovePacket(player, id);\n            }\n            case UPDATE -> {\n                if (uuid == null) {\n                    throw new InvalidArgumentsRuntimeException(\"'uuid' wasn't specified but is required for 'update'\");\n                }\n                if ((display == null || display.length() > 0) && !CoreConfiguration.allowRestrictedActions) {\n                    Debug.echoError(\"Cannot use 'tablist update' to create a non-empty named entry: 'Allow restricted actions' is disabled in Denizen config.yml.\");\n                    return;\n                }\n                if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) {\n                    if (display != null) {\n                        NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, EnumSet.of(PlayerHelper.ProfileEditMode.UPDATE_DISPLAY), name, display, id, texture, signature, latencyNum, gamemode, false);\n                    }\n                    if (latency != null) {\n                        NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, EnumSet.of(PlayerHelper.ProfileEditMode.UPDATE_LATENCY), name, display, id, texture, signature, latencyNum, gamemode, false);\n                    }\n                    return;\n                }\n                EnumSet<PlayerHelper.ProfileEditMode> editModes = EnumSet.noneOf(PlayerHelper.ProfileEditMode.class);\n                if (display != null) {\n                    editModes.add(PlayerHelper.ProfileEditMode.UPDATE_DISPLAY);\n                }\n                if (latency != null) {\n                    editModes.add(PlayerHelper.ProfileEditMode.UPDATE_LATENCY);\n                }\n                if (gamemode != null) {\n                    editModes.add(PlayerHelper.ProfileEditMode.UPDATE_GAME_MODE);\n                }\n                if (listed != null) {\n                    editModes.add(PlayerHelper.ProfileEditMode.UPDATE_LISTED);\n                }\n                NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, editModes, name, display, id, texture, signature, latencyNum, gamemode, listed == null || listed.asBoolean());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TeamCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.ScoreboardHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.scoreboard.Scoreboard;\r\nimport org.bukkit.scoreboard.Team;\r\n\r\npublic class TeamCommand extends AbstractCommand {\r\n\r\n    public TeamCommand() {\r\n        setName(\"team\");\r\n        setSyntax(\"team (id:<scoreboard>/{main}) [name:<team>] (add:<entry>|...) (remove:<entry>|...) (prefix:<prefix>) (suffix:<suffix>) (option:<type> status:<status>) (color:<color>)\");\r\n        setRequiredArguments(2, 9);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Team\r\n    // @Syntax team (id:<scoreboard>/{main}) [name:<team>] (add:<entry>|...) (remove:<entry>|...) (prefix:<prefix>) (suffix:<suffix>) (option:<type> status:<status>) (color:<color>)\r\n    // @Required 2\r\n    // @Maximum 9\r\n    // @Short Controls scoreboard teams.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // The Team command allows you to control a scoreboard team.\r\n    //\r\n    // Use the \"prefix\" or \"suffix\" arguments to modify a team's playername prefix and suffix.\r\n    //\r\n    // The \"entry\" value can be a player's name to affect that player, or an entity's UUID to affect that entity.\r\n    // You can alternately input a raw PlayerTag or EntityTag, and they will be automatically translated to the name/UUID internally.\r\n    //\r\n    // Use the \"color\" argument to set the team color (for glowing, names, etc). Must be from <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/ChatColor.html>.\r\n    //\r\n    // Use the \"add\" and \"remove\" arguments to add or remove players by name to/from the team.\r\n    //\r\n    // Use the \"option\" and \"status\" arguments together to set a team option's status.\r\n    // Option can be \"COLLISION_RULE\", \"DEATH_MESSAGE_VISIBILITY\", or \"NAME_TAG_VISIBILITY\", with status \"ALWAYS\", \"FOR_OTHER_TEAMS\", \"FOR_OWN_TEAM\", or \"NEVER\".\r\n    // Option can instead be \"FRIENDLY_FIRE\" or \"SEE_INVISIBLE\", only allowing status \"ALWAYS\" or \"NEVER\".\r\n    //\r\n    // @Tags\r\n    // <server.scoreboard[(<board>)].team[<team>].members>\r\n    //\r\n    // @Usage\r\n    // Use to add a player to a team.\r\n    // - team name:red add:<player>\r\n    //\r\n    // @Usage\r\n    // Use to add some mob to a team.\r\n    // - team name:blue add:<player.location.find_entities[monster].within[10]>\r\n    //\r\n    // @Usage\r\n    // Use to change the prefix for a team.\r\n    // - team name:red \"prefix:[<red>Red Team<reset>]\"\r\n    //\r\n    // @Usage\r\n    // Use to hide nameplates for members of a team.\r\n    // - team name:red option:name_tag_visibility status:never\r\n    // -->\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"id\") @ArgPrefixed @ArgDefaultText(\"main\") ElementTag id,\r\n                                   @ArgName(\"name\") @ArgPrefixed @ArgDefaultNull ElementTag name,\r\n                                   @ArgName(\"add\") @ArgPrefixed @ArgDefaultNull ListTag addEntities,\r\n                                   @ArgName(\"remove\") @ArgPrefixed @ArgDefaultNull ListTag removeEntities,\r\n                                   @ArgName(\"prefix\") @ArgPrefixed @ArgDefaultNull ElementTag prefix,\r\n                                   @ArgName(\"suffix\") @ArgPrefixed @ArgDefaultNull ElementTag suffix,\r\n                                   @ArgName(\"option\") @ArgPrefixed @ArgDefaultNull ElementTag option,\r\n                                   @ArgName(\"status\") @ArgPrefixed @ArgDefaultNull Team.OptionStatus status,\r\n                                   @ArgName(\"color\") @ArgPrefixed @ArgDefaultNull ChatColor color) {\r\n        if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18) && name.asString().length() > 16) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify a team name between 1 and 16 characters!\");\r\n        }\r\n        if (addEntities == null && removeEntities == null && option == null && color == null && prefix == null && suffix == null) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify something to do with the team!\");\r\n        }\r\n        if ((option == null) != (status == null)) {\r\n            throw new InvalidArgumentsRuntimeException(\"Option and Status arguments must go together!\");\r\n        }\r\n        Scoreboard board;\r\n        if (id.asString().equalsIgnoreCase(\"main\")) {\r\n            board = ScoreboardHelper.getMain();\r\n        }\r\n        else {\r\n            if (ScoreboardHelper.hasScoreboard(id.asString())) {\r\n                board = ScoreboardHelper.getScoreboard(id.asString());\r\n            }\r\n            else {\r\n                board = ScoreboardHelper.createScoreboard(id.asString());\r\n            }\r\n        }\r\n        Team team = board.getTeam(name.asString());\r\n        if (team == null) {\r\n            String low = name.asLowerString();\r\n            team = board.getTeams().stream().filter(t -> CoreUtilities.toLowerCase(t.getName()).equals(low)).findFirst().orElse(null);\r\n            if (team == null) {\r\n                team = board.registerNewTeam(name.asString());\r\n            }\r\n        }\r\n        if (removeEntities != null) {\r\n            for (ObjectTag obj : removeEntities.objectForms) {\r\n                String remove = translateEntry(obj, scriptEntry.context);\r\n                if (remove != null) {\r\n                    team.removeEntry(remove);\r\n                }\r\n            }\r\n        }\r\n        if (addEntities != null) {\r\n            for (ObjectTag obj : addEntities.objectForms) {\r\n                String add = translateEntry(obj, scriptEntry.context);\r\n                if (add != null) {\r\n                    team.addEntry(add);\r\n                }\r\n            }\r\n        }\r\n        if (option != null) {\r\n            switch (option.asLowerString()) {\r\n                case \"friendly_fire\" -> {\r\n                    team.setAllowFriendlyFire(status == Team.OptionStatus.ALWAYS);\r\n                }\r\n                case \"see_invisible\" -> {\r\n                    team.setCanSeeFriendlyInvisibles(status == Team.OptionStatus.ALWAYS);\r\n                }\r\n                default -> {\r\n                    if (option.matchesEnum(Team.Option.class)) {\r\n                        team.setOption(option.asEnum(Team.Option.class), status);\r\n                    }\r\n                    else {\r\n                        throw new InvalidArgumentsRuntimeException(\"Option doesn't exist!\");\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (prefix != null) {\r\n            PaperAPITools.instance.setTeamPrefix(team, prefix.asString());\r\n        }\r\n        if (suffix != null) {\r\n            PaperAPITools.instance.setTeamSuffix(team, suffix.asString());\r\n        }\r\n        if (color != null) {\r\n            team.setColor(color);\r\n        }\r\n        if (team.getEntries().isEmpty()) {\r\n            team.unregister();\r\n        }\r\n    }\r\n\r\n    public static String translateEntry(ObjectTag obj, TagContext context) {\r\n        if (obj.shouldBeType(PlayerTag.class)) {\r\n            return obj.asType(PlayerTag.class, context).getName();\r\n        }\r\n        else if (obj.shouldBeType(EntityTag.class)) {\r\n            return obj.asType(EntityTag.class, context).getUUID().toString();\r\n        }\r\n        else {\r\n            return obj.toString();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TitleCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.tags.ParseableTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\n\r\nimport java.util.List;\r\n\r\npublic class TitleCommand extends AbstractCommand {\r\n\r\n    public TitleCommand() {\r\n        setName(\"title\");\r\n        setSyntax(\"title (title:<text>) (subtitle:<text>) (fade_in:<duration>/{1s}) (stay:<duration>/{3s}) (fade_out:<duration>/{1s}) (targets:<player>|...) (per_player)\");\r\n        setRequiredArguments(1, 7);\r\n        addRemappedPrefixes(\"targets\", \"target\");\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Title\r\n    // @Syntax title (title:<text>) (subtitle:<text>) (fade_in:<duration>/{1s}) (stay:<duration>/{3s}) (fade_out:<duration>/{1s}) (targets:<player>|...) (per_player)\r\n    // @Required 1\r\n    // @Maximum 7\r\n    // @Short Displays a title to specified players.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Shows the players a large, noticeable wall of text in the center of the screen.\r\n    // You can also show a \"subtitle\" below that title.\r\n    // You may add timings for fading in, staying there, and fading out.\r\n    // The defaults for these are: 1 second, 3 seconds, and 1 second, respectively.\r\n    //\r\n    // Optionally use 'per_player' with a list of player targets, to have the tags in the text input be reparsed for each and every player.\r\n    // So, for example, \"- title 'title:hello <player.name>' targets:<server.online_players>\"\r\n    // would normally say \"hello bob\" to every player (every player sees the exact same name in the text, ie bob sees \"hello bob\", steve also sees \"hello bob\", etc)\r\n    // but if you use \"per_player\", each player online would see their own name (so bob sees \"hello bob\", steve sees \"hello steve\", etc).\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to alert players of impending server restart.\r\n    // - title \"title:<red>Server Restarting\" \"subtitle:<red>In 1 minute!\" stay:1m targets:<server.online_players>\r\n    //\r\n    // @Usage\r\n    // Use to inform the player about the area they have just entered.\r\n    // - title \"title:<green>Tatooine\" \"subtitle:<gold>What a desolate place this is.\"\r\n    // -->\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"title\") @ArgPrefixed @ArgUnparsed @ArgDefaultNull String strTitle,\r\n                                   @ArgName(\"subtitle\") @ArgPrefixed @ArgUnparsed @ArgDefaultNull String strSubTitle,\r\n                                   @ArgName(\"fade_in\") @ArgPrefixed @ArgDefaultText(\"1s\") DurationTag fadeIn,\r\n                                   @ArgName(\"stay\") @ArgPrefixed @ArgDefaultText(\"3s\") DurationTag stay,\r\n                                   @ArgName(\"fade_out\") @ArgPrefixed @ArgDefaultText(\"1s\") DurationTag fadeOut,\r\n                                   @ArgName(\"targets\") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> players,\r\n                                   @ArgName(\"per_player\") boolean perPlayer) {\r\n        if (strTitle == null && strSubTitle == null) {\r\n            Debug.echoError(\"Must have a title or subtitle!\");\r\n            return;\r\n        }\r\n        if (players == null) {\r\n            if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                Debug.echoError(\"Must specify target(s).\");\r\n                return;\r\n            }\r\n            players = List.of(Utilities.getEntryPlayer(scriptEntry));\r\n        }\r\n        BukkitTagContext context = perPlayer ? new BukkitTagContext((BukkitTagContext) scriptEntry.getContext()) : (BukkitTagContext) scriptEntry.getContext();\r\n        ParseableTag parseableTitle = TagManager.parseTextToTag(strTitle, context);\r\n        ParseableTag parseableSubTitle = TagManager.parseTextToTag(strSubTitle, context);\r\n        String parsedTitle = perPlayer ? null : parse(parseableTitle, context);\r\n        String parsedSubTitle = perPlayer ? null : parse(parseableSubTitle, context);\r\n        for (PlayerTag player : players) {\r\n            if (!player.isOnline()) {\r\n                Debug.echoDebug(scriptEntry, \"Player is offline, can't send title to them. Skipping.\");\r\n                continue;\r\n            }\r\n            if (perPlayer) {\r\n                context.player = player;\r\n                parsedTitle = parse(parseableTitle, context);\r\n                parsedSubTitle = parse(parseableSubTitle, context);\r\n            }\r\n            NMSHandler.packetHelper.showTitle(player.getPlayerEntity(), parsedTitle, parsedSubTitle, fadeIn.getTicksAsInt(), stay.getTicksAsInt(), fadeOut.getTicksAsInt());\r\n        }\r\n    }\r\n\r\n    public static String parse(ParseableTag tag, BukkitTagContext context) {\r\n        return tag == null ? \"\" : tag.parse(context).toString();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/ToastCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.player;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.util.Advancement;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic class ToastCommand extends AbstractCommand {\r\n\r\n    public ToastCommand() {\r\n        setName(\"toast\");\r\n        setSyntax(\"toast [<text>] (targets:<player>|...) (icon:<item>) (frame:{task}/challenge/goal)\");\r\n        setRequiredArguments(1, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Toast\r\n    // @Syntax toast [<text>] (targets:<player>|...) (icon:<item>) (frame:{task}/challenge/goal)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Short Shows the player a custom advancement toast.\r\n    // @Group player\r\n    //\r\n    // @Description\r\n    // Displays a client-side custom advancement \"toast\" notification popup to the player(s).\r\n    // If no target is specified it will default to the attached player.\r\n    // The icon argument changes the icon displayed in the toast pop-up notification.\r\n    // The frame argument changes the type of advancement.\r\n    //\r\n    // As of MC 1.20, an icon is required. Dirt will be used if it is missing.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Welcomes the player with an advancement toast.\r\n    // - toast \"Welcome <player.name>!\"\r\n    //\r\n    // @Usage\r\n    // Sends the player an advancement toast with a custom icon.\r\n    // - toast \"Diggy Diggy Hole\" icon:iron_spade\r\n    //\r\n    // @Usage\r\n    // Sends the player a \"Challenge Complete!\" type advancement toast.\r\n    // - toast \"You finished a challenge!\" frame:challenge icon:diamond\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"targets\")\r\n                    && arg.matchesPrefix(\"target\", \"targets\", \"t\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"targets\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"icon\")\r\n                    && arg.matchesPrefix(\"icon\", \"i\")\r\n                    && arg.matchesArgumentType(ItemTag.class)) {\r\n                scriptEntry.addObject(\"icon\", arg.asType(ItemTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"frame\")\r\n                    && arg.matchesPrefix(\"frame\", \"f\")\r\n                    && arg.matchesEnum(Advancement.Frame.class)) {\r\n                scriptEntry.addObject(\"frame\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"text\")) {\r\n                scriptEntry.addObject(\"text\", arg.getRawElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"text\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a message!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"targets\")) {\r\n            if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                throw new InvalidArgumentsException(\"Must specify valid player targets!\");\r\n            }\r\n            else {\r\n                scriptEntry.addObject(\"targets\", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry)));\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"icon\", new ItemTag(Material.AIR));\r\n        scriptEntry.defaultObject(\"frame\", new ElementTag(\"TASK\"));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag text = scriptEntry.getElement(\"text\");\r\n        ElementTag frame = scriptEntry.getElement(\"frame\");\r\n        ItemTag icon = scriptEntry.getObjectTag(\"icon\");\r\n        final List<PlayerTag> targets = (List<PlayerTag>) scriptEntry.getObject(\"targets\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, name, text, frame, icon, db(\"targets\", targets));\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && icon.getBukkitMaterial().isAir()) {\r\n            icon = new ItemTag(Material.DIRT);\r\n        }\r\n        final Advancement advancement = new Advancement(true,\r\n                new NamespacedKey(Denizen.getInstance(), UUID.randomUUID().toString()), null,\r\n                icon.getItemStack(), text.asString(), \"\", null,\r\n                Advancement.Frame.valueOf(frame.asString().toUpperCase()), true, false, true, 0, 0, 1);\r\n        for (PlayerTag target : targets) {\r\n            Player player = target.getPlayerEntity();\r\n            if (player != null) {\r\n                NMSHandler.advancementHelper.grant(advancement, player);\r\n                NMSHandler.advancementHelper.revoke(advancement, player);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/server/AnnounceCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.server;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\nimport com.denizenscript.denizencore.objects.Argument;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.ScriptFormattingContext;\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\nimport com.denizenscript.denizencore.scripts.containers.core.FormatScriptContainer;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.md_5.bungee.api.ChatColor;\nimport org.bukkit.Bukkit;\nimport org.bukkit.entity.Player;\n\npublic class AnnounceCommand extends AbstractCommand {\n\n    public static final String ANNOUNCE_FORMAT_TYPE = ScriptFormattingContext.registerFormatType(\"announce\");\n\n    public AnnounceCommand() {\n        setName(\"announce\");\n        setSyntax(\"announce [<text>] (to_ops/to_console/to_flagged:<flag_name>/to_permission:<node>) (format:<script>)\");\n        setRequiredArguments(1, 3);\n        isProcedural = true;\n    }\n\n    // <--[command]\n    // @Name Announce\n    // @Syntax announce [<text>] (to_ops/to_console/to_flagged:<flag_name>/to_permission:<node>) (format:<script>)\n    // @Required 1\n    // @Maximum 3\n    // @Short Announces a message for everyone online to read.\n    // @Group server\n    //\n    // @Description\n    // Announce sends a raw message to players.\n    // Simply using announce with text will send the message to all online players using the Spigot broadcast system.\n    // Specifying the 'to_ops' argument will narrow down the players in which the message is sent to ops only.\n    // Alternatively, using the 'to_permission' argument will send the message to only players that have the specified permission node.\n    // Or, using the 'to_flagged' argument will send the message to only players that have the specified flag.\n    // You can also use the 'to_console' argument to make it so it only shows in the server console.\n    //\n    // You can format the announcement with <@link language Format Script Containers> using the 'format' argument, or with the \"announce\" format type (see <@link language Script Formats>).\n    //\n    // Note that the default announce mode (that shows for all players) relies on the Spigot broadcast system, which requires the permission \"bukkit.broadcast.user\" to see broadcasts.\n    //\n    // @Tags\n    // None\n    //\n    // @Usage\n    // Use to send an important message to your players.\n    // - announce 'Warning! This server will restart in 5 minutes!'\n    //\n    // @Usage\n    // Use to send a message to a specific 'group' of players.\n    // - announce to_flagged:clan_subang '[<player.name>] Best clan ever!'\n    //\n    // @Usage\n    // Use to easily send a message to all online ops.\n    // - announce to_ops '<player.name> requires help!'\n    //\n    // @Usage\n    // Use to send a message to just the console (Primarily for debugging / logging).\n    // - announce to_console 'Warning- <player.name> broke a mob spawner at location <player.location>'\n    // -->\n\n    enum AnnounceType {ALL, TO_OPS, TO_FLAGGED, TO_CONSOLE, TO_PERMISSION}\n\n    @Override\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\n        for (Argument arg : scriptEntry) {\n            if (!scriptEntry.hasObject(\"type\")\n                    && arg.matches(\"to_ops\")) {\n                scriptEntry.addObject(\"type\", AnnounceType.TO_OPS);\n            }\n            else if (!scriptEntry.hasObject(\"type\")\n                    && arg.matches(\"to_console\")) {\n                scriptEntry.addObject(\"type\", AnnounceType.TO_CONSOLE);\n            }\n            else if (!scriptEntry.hasObject(\"type\")\n                    && arg.matchesPrefix(\"to_flagged\")) {\n                scriptEntry.addObject(\"type\", AnnounceType.TO_FLAGGED);\n                scriptEntry.addObject(\"flag\", arg.asElement());\n            }\n            else if (!scriptEntry.hasObject(\"type\")\n                    && arg.matchesPrefix(\"to_permission\")) {\n                scriptEntry.addObject(\"type\", AnnounceType.TO_PERMISSION);\n                scriptEntry.addObject(\"flag\", arg.asElement());\n            }\n            else if (!scriptEntry.hasObject(\"format\")\n                    && arg.matchesPrefix(\"format\")) {\n                FormatScriptContainer format;\n                String formatStr = arg.getValue();\n                format = ScriptRegistry.getScriptContainer(formatStr);\n                if (format == null) {\n                    Debug.echoError(\"Could not find format script matching '\" + formatStr + '\\'');\n                }\n                scriptEntry.addObject(\"format\", format);\n            }\n            else if (!scriptEntry.hasObject(\"text\")) {\n                scriptEntry.addObject(\"text\", arg.getRawElement());\n            }\n            else {\n                arg.reportUnhandled();\n            }\n        }\n        if (!scriptEntry.hasObject(\"text\")) {\n            throw new InvalidArgumentsException(\"Missing text argument!\");\n        }\n        scriptEntry.defaultObject(\"type\", AnnounceType.ALL);\n    }\n\n    @Override\n    public void execute(ScriptEntry scriptEntry) {\n        if (scriptEntry.getResidingQueue().procedural) {\n            Debug.echoError(\"'Announce' should not be used in a procedure script. Consider the 'debug' command instead.\");\n        }\n        ElementTag text = scriptEntry.getElement(\"text\");\n        AnnounceType type = (AnnounceType) scriptEntry.getObject(\"type\");\n        FormatScriptContainer format = (FormatScriptContainer) scriptEntry.getObject(\"format\");\n        ElementTag flag = scriptEntry.getElement(\"flag\");\n        if (scriptEntry.dbCallShouldDebug()) {\n            Debug.report(scriptEntry, getName(), db(\"message\", text), (format != null ? db(\"format\", format.getName()) : \"\"), db(\"type\", type.name()), flag);\n        }\n        String message;\n        if (format != null) {\n            message = format.getFormattedText(text.asString(), scriptEntry);\n        }\n        else {\n            ScriptContainer scriptContainer = scriptEntry.getScriptContainer();\n            message = scriptContainer != null && scriptContainer.getFormattingContext() != null ? scriptContainer.getFormattingContext().format(ANNOUNCE_FORMAT_TYPE, text.asString(), scriptEntry) : text.asString();\n        }\n        // Use Bukkit to broadcast the message to everybody in the server.\n        switch (type) {\n            case ALL:\n                Denizen.getInstance().getServer().spigot().broadcast(FormattedTextHelper.parse(message, ChatColor.WHITE));\n                break;\n            case TO_OPS:\n                for (Player player : Bukkit.getOnlinePlayers()) {\n                    if (player.isOp()) {\n                        player.spigot().sendMessage(FormattedTextHelper.parse(message, ChatColor.WHITE));\n                    }\n                }\n                break;\n            case TO_PERMISSION:\n                for (Player player : Bukkit.getOnlinePlayers()) {\n                    if (player.hasPermission(flag.asString())) {\n                        player.spigot().sendMessage(FormattedTextHelper.parse(message, ChatColor.WHITE));\n                    }\n                }\n            case TO_FLAGGED:\n                for (Player player : Bukkit.getOnlinePlayers()) {\n                    if (new PlayerTag(player).getFlagTracker().hasFlag(flag.asString())) {\n                        player.spigot().sendMessage(FormattedTextHelper.parse(message, ChatColor.WHITE));\n                    }\n                }\n                break;\n            case TO_CONSOLE:\n                Bukkit.getServer().getConsoleSender().spigot().sendMessage(FormattedTextHelper.parse(message, ChatColor.WHITE));\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/server/BanCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.server;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.TimeTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport org.bukkit.BanList;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.Date;\r\nimport java.util.List;\r\n\r\npublic class BanCommand extends AbstractCommand {\r\n\r\n    public BanCommand() {\r\n        setName(\"ban\");\r\n        setSyntax(\"ban ({add}/remove) [<player>|.../addresses:<address>|.../names:<name>|...] (reason:<text>) (expire:<time>) (source:<text>)\");\r\n        setRequiredArguments(1, 5);\r\n        isProcedural = false;\r\n        autoCompile();\r\n        addRemappedPrefixes(\"addresses\", \"address\");\r\n        addRemappedPrefixes(\"expire\", \"duration\", \"time\", \"d\", \"expiration\");\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Ban\r\n    // @Syntax ban ({add}/remove) [<player>|.../addresses:<address>|.../names:<name>|...] (reason:<text>) (expire:<time>) (source:<text>)\r\n    // @Required 1\r\n    // @Maximum 5\r\n    // @Short Ban or un-ban players or ip addresses.\r\n    // @Group server\r\n    //\r\n    // @Description\r\n    // Add or remove player or ip address bans from the server. Banning a player will also kick them from the server.\r\n    // You may specify a list of player names instead of <@link ObjectType PlayerTag>s, which should only ever be used for special cases (such as banning players that haven't joined the server yet).\r\n    //\r\n    // You may optionally specify both a list of players and list of addresses.\r\n    //\r\n    // Additional options are:\r\n    // reason: Sets the ban reason.\r\n    // expire: Sets the expire time of the temporary ban, as a TimeTag or a DurationTag. This will be a permanent ban if not specified.\r\n    // source: Sets the source of the ban.\r\n    //\r\n    // @Tags\r\n    // <PlayerTag.is_banned>\r\n    // <PlayerTag.ban_reason>\r\n    // <PlayerTag.ban_expiration_time>\r\n    // <PlayerTag.ban_created_time>\r\n    // <PlayerTag.ban_source>\r\n    // <server.is_banned[<address>]>\r\n    // <server.ban_info[<address>].expiration_time>\r\n    // <server.ban_info[<address>].reason>\r\n    // <server.ban_info[<address>].created_time>\r\n    // <server.ban_info[<address>].source>\r\n    // <server.banned_addresses>\r\n    // <server.banned_players>\r\n    //\r\n    // @Usage\r\n    // Use to ban a player.\r\n    // - ban <[player]>\r\n    //\r\n    // @Usage\r\n    // Use to ban a list of players with a reason.\r\n    // - ban <[player]>|<[someplayer]> \"reason:Didn't grow enough potatoes.\"\r\n    //\r\n    // @Usage\r\n    // Use to ban a list of players for 10 minutes with a reason.\r\n    // - ban <[player]>|<[someplayer]> \"reason:Didn't grow enough potatoes.\" expire:10m\r\n    //\r\n    // @Usage\r\n    // Use to ban a player with a source.\r\n    // - ban <[aplayer]> \"reason:Grew too many potatoes.\" source:<player.name>\r\n    //\r\n    // @Usage\r\n    // Use to ban an ip address.\r\n    // - ban addresses:127.0.0.1\r\n    //\r\n    // @Usage\r\n    // Use to temporarily ip ban all online players.\r\n    // - ban addresses:<server.online_players.parse[ip]> expire:5m\r\n    //\r\n    // @Usage\r\n    // Use to unban a list of players.\r\n    // - ban remove <[player]>|<[someplayer]>\r\n    //\r\n    // @Usage\r\n    // Use to unban an ip address.\r\n    // - ban remove addresses:127.0.0.1\r\n    // -->\r\n\r\n    public enum Actions { ADD, REMOVE }\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                               @ArgName(\"action\") @ArgDefaultText(\"add\") Actions action,\r\n                               @ArgName(\"targets\") @ArgLinear @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> targets,\r\n                               @ArgName(\"addresses\") @ArgPrefixed @ArgDefaultNull ListTag addresses,\r\n                               @ArgName(\"names\") @ArgPrefixed @ArgDefaultNull ListTag names,\r\n                               @ArgName(\"reason\") @ArgPrefixed @ArgDefaultNull String reason,\r\n                               @ArgName(\"expire\") @ArgPrefixed @ArgDefaultNull ObjectTag rawExpire,\r\n                               @ArgName(\"source\") @ArgPrefixed @ArgDefaultNull String source) {\r\n        if ((targets == null || targets.isEmpty()) && (addresses == null || addresses.isEmpty()) && (names == null || names.isEmpty())) {\r\n            throw new InvalidArgumentsRuntimeException(\"Must specify valid players, addresses or names to ban.\");\r\n        }\r\n        Date expiration = null;\r\n        if (rawExpire != null) {\r\n            if (rawExpire.canBeType(DurationTag.class)) {\r\n                DurationTag banDuration = rawExpire.asType(DurationTag.class, scriptEntry.context);\r\n                if (banDuration.getSeconds() > 0) {\r\n                    expiration = new Date(TimeTag.now().millis() + banDuration.getMillis());\r\n                }\r\n            }\r\n            else {\r\n                TimeTag expirationTime = rawExpire.asType(TimeTag.class, scriptEntry.context);\r\n                if (expirationTime == null) {\r\n                    throw new InvalidArgumentsRuntimeException(\"Invalid 'expire:' input, must be a DurationTag or a TimeTag.\");\r\n                }\r\n                expiration = new Date(expirationTime.millis());\r\n            }\r\n        }\r\n        switch (action) {\r\n            case ADD -> {\r\n                if (targets != null) {\r\n                    for (PlayerTag player : targets) {\r\n                        if (player.isValid()) {\r\n                            Bukkit.getBanList(BanList.Type.NAME).addBan(player.getName(), reason, expiration, source);\r\n                            if (player.isOnline()) {\r\n                                player.getPlayerEntity().kickPlayer(reason);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                if (addresses != null) {\r\n                    for (String address : addresses) {\r\n                        Bukkit.getBanList(BanList.Type.IP).addBan(address, reason, expiration, source);\r\n                    }\r\n                }\r\n                if (names != null) {\r\n                    for (String name : names) {\r\n                        Bukkit.getBanList(BanList.Type.NAME).addBan(name, reason, expiration, source);\r\n                        Player player = Bukkit.getPlayerExact(name);\r\n                        if (player != null) {\r\n                            player.kickPlayer(reason);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            case REMOVE -> {\r\n                if (targets != null) {\r\n                    for (PlayerTag player : targets) {\r\n                        if (player.isValid()) {\r\n                            if (player.getOfflinePlayer().isBanned()) {\r\n                                Bukkit.getBanList(BanList.Type.NAME).pardon(player.getName());\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                if (addresses != null) {\r\n                    addresses.forEach(Bukkit.getBanList(BanList.Type.IP)::pardon);\r\n                }\r\n                if (names != null) {\r\n                    names.forEach(Bukkit.getBanList(BanList.Type.NAME)::pardon);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/server/BossBarCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.server;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.boss.BarColor;\r\nimport org.bukkit.boss.BarFlag;\r\nimport org.bukkit.boss.BarStyle;\r\nimport org.bukkit.boss.BossBar;\r\n\r\nimport java.util.*;\r\n\r\npublic class BossBarCommand extends AbstractCommand {\r\n\r\n    public BossBarCommand() {\r\n        setName(\"bossbar\");\r\n        setSyntax(\"bossbar ({auto}/create/update/remove) [<id>] (players:<player>|...) (title:<title>) (progress:<#.#>) (color:<color>) (style:<style>) (options:<option>|...) (uuid:<uuid>)\");\r\n        setRequiredArguments(1, 9);\r\n        isProcedural = false;\r\n        autoCompile();\r\n        addRemappedPrefixes(\"title\", \"t\");\r\n        addRemappedPrefixes(\"progress\", \"health\", \"p\", \"h\");\r\n        addRemappedPrefixes(\"style\", \"s\");\r\n        addRemappedPrefixes(\"options\", \"option\", \"opt\", \"o\", \"flags\", \"flag\", \"f\");\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name BossBar\r\n    // @Syntax bossbar ({auto}/create/update/remove) [<id>] (players:<player>|...) (title:<title>) (progress:<#.#>) (color:<color>) (style:<style>) (options:<option>|...) (uuid:<uuid>)\r\n    // @Required 1\r\n    // @Maximum 9\r\n    // @Short Shows players a boss bar.\r\n    // @Group server\r\n    //\r\n    // @Description\r\n    // Displays a boss bar at the top of the screen of the specified player(s).\r\n    // You can also update the values and remove the bar.\r\n    //\r\n    // You can CREATE a new bossbar, UPDATE an existing one, or REMOVE an existing one.\r\n    // The default 'auto' will either 'create' or 'update' depending on whether it already exists.\r\n    //\r\n    // Requires an ID.\r\n    //\r\n    // Progress must be between 0 and 1.\r\n    //\r\n    // Valid colors: BLUE, GREEN, PINK, PURPLE, RED, WHITE, YELLOW.\r\n    // Valid styles: SEGMENTED_10, SEGMENTED_12, SEGMENTED_20, SEGMENTED_6, SOLID.\r\n    // Valid options: CREATE_FOG, DARKEN_SKY, PLAY_BOSS_MUSIC.\r\n    //\r\n    // The UUID can optionally be specified, and will be sent to the client. Be careful to not overlap multiple bars with the same UUID.\r\n    // If not specified, it will be random.\r\n    //\r\n    // @Tags\r\n    // <server.current_bossbars>\r\n    // <server.bossbar_viewers[<bossbar_id>]>\r\n    // <PlayerTag.bossbar_ids>\r\n    // <entry[saveName].bar_uuid> returns the bossbar's UUID.\r\n    //\r\n    // @Usage\r\n    // Shows a message to all online players.\r\n    // - bossbar MyMessageID players:<server.online_players> \"title:HI GUYS\" color:red\r\n    //\r\n    // @Usage\r\n    // Update the boss bar's color and progress.\r\n    // - bossbar update MyMessageID color:blue progress:0.2\r\n    //\r\n    // @Usage\r\n    // Add more players to the boss bar.\r\n    // - bossbar update MyMessageID players:<server.flag[new_players]>\r\n    //\r\n    // @Usage\r\n    // Remove a player from the boss bar.\r\n    // - bossbar remove MyMessageID players:<[player]>\r\n    //\r\n    // @Usage\r\n    // Delete the boss bar.\r\n    // - bossbar remove MyMessageID\r\n    // -->\r\n\r\n    public enum Action {\r\n        AUTO, CREATE, UPDATE, REMOVE\r\n    }\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addWithPrefix(\"id:\", bossBarMap.keySet());\r\n        tab.addWithPrefix(\"style:\", BarStyle.values());\r\n        tab.addWithPrefix(\"color:\", BarColor.values());\r\n        tab.addWithPrefix(\"options:\", BarFlag.values());\r\n    }\r\n\r\n    public final static Map<String, BossBar> bossBarMap = new HashMap<>();\r\n\r\n    public static void autoExecute(ScriptEntry scriptEntry,\r\n                                   @ArgName(\"action\") @ArgDefaultText(\"auto\") Action action,\r\n                                   @ArgName(\"id\") @ArgLinear ElementTag id,\r\n                                   @ArgName(\"players\") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> players,\r\n                                   @ArgName(\"title\") @ArgPrefixed @ArgDefaultNull String title,\r\n                                   @ArgName(\"progress\") @ArgPrefixed @ArgDefaultNull ElementTag progress,\r\n                                   @ArgName(\"color\") @ArgPrefixed @ArgDefaultText(\"white\") BarColor color,\r\n                                   @ArgName(\"style\") @ArgPrefixed @ArgDefaultText(\"solid\") BarStyle style,\r\n                                   @ArgName(\"options\") @ArgPrefixed @ArgDefaultNull @ArgSubType(BarFlag.class) List<BarFlag> options,\r\n                                   @ArgName(\"uuid\") @ArgPrefixed @ArgDefaultNull String uuid) {\r\n        String idString = id.asLowerString();\r\n        if (action == Action.AUTO) {\r\n            action = bossBarMap.containsKey(idString) ? Action.UPDATE : Action.CREATE;\r\n        }\r\n        if (players == null && action == Action.CREATE) {\r\n            if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                throw new InvalidArgumentsRuntimeException(\"Missing player input!\");\r\n            }\r\n            players = Collections.singletonList(Utilities.getEntryPlayer(scriptEntry));\r\n        }\r\n        BossBar bossBar = null;\r\n        switch (action) {\r\n            case CREATE: {\r\n                if (bossBarMap.containsKey(idString)) {\r\n                    Debug.echoError(\"BossBar '\" + idString + \"' already exists!\");\r\n                    return;\r\n                }\r\n                List<PlayerTag> barPlayers = players;\r\n                double barProgress = progress != null ? progress.asDouble() : 1D;\r\n                if (title == null) {\r\n                    title = \"\";\r\n                }\r\n                bossBar = Bukkit.createBossBar(title, color, style, options == null ? new BarFlag[0] : options.toArray(new BarFlag[0]));\r\n                NMSHandler.playerHelper.setBossBarTitle(bossBar, title);\r\n                bossBar.setProgress(barProgress);\r\n                if (uuid != null) {\r\n                    NMSHandler.instance.setBossbarUUID(bossBar, UUID.fromString(uuid));\r\n                }\r\n                for (PlayerTag player : barPlayers) {\r\n                    if (!player.isOnline()) {\r\n                        Debug.echoError(\"Player must be online to show a BossBar to them!\");\r\n                        continue;\r\n                    }\r\n                    bossBar.addPlayer(player.getPlayerEntity());\r\n                }\r\n                bossBar.setVisible(true);\r\n                bossBarMap.put(idString, bossBar);\r\n                break;\r\n            }\r\n            case UPDATE: {\r\n                if (!bossBarMap.containsKey(idString)) {\r\n                    Debug.echoError(\"BossBar '\" + idString + \"' does not exist!\");\r\n                    return;\r\n                }\r\n                bossBar = bossBarMap.get(idString);\r\n                if (title != null) {\r\n                    NMSHandler.playerHelper.setBossBarTitle(bossBar, title);\r\n                }\r\n                if (progress != null) {\r\n                    bossBar.setProgress(progress.asDouble());\r\n                }\r\n                if (color != null) {\r\n                    bossBar.setColor(color);\r\n                }\r\n                if (style != null) {\r\n                    bossBar.setStyle(style);\r\n                }\r\n                if (options != null) {\r\n                    HashSet<BarFlag> oldFlags = new HashSet<>(Arrays.asList(BarFlag.values()));\r\n                    HashSet<BarFlag> newFlags = new HashSet<>(options.size());\r\n                    for (BarFlag flag : options) {\r\n                        newFlags.add(flag);\r\n                        oldFlags.remove(flag);\r\n                    }\r\n                    for (BarFlag flag : oldFlags) {\r\n                        bossBar.removeFlag(flag);\r\n                    }\r\n                    for (BarFlag flag : newFlags) {\r\n                        bossBar.addFlag(flag);\r\n                    }\r\n                }\r\n                if (players != null) {\r\n                    for (PlayerTag player : players) {\r\n                        bossBar.addPlayer(player.getPlayerEntity());\r\n                    }\r\n                }\r\n                break;\r\n            }\r\n            case REMOVE: {\r\n                bossBar = bossBarMap.get(idString);\r\n                if (bossBar == null) {\r\n                    Debug.echoError(\"BossBar '\" + idString + \"' does not exist!\");\r\n                    return;\r\n                }\r\n                if (players != null) {\r\n                    for (PlayerTag player : players) {\r\n                        bossBar.removePlayer(player.getPlayerEntity());\r\n                    }\r\n                    break;\r\n                }\r\n                bossBar.setVisible(false);\r\n                bossBarMap.remove(idString);\r\n                break;\r\n            }\r\n        }\r\n        if (bossBar != null) {\r\n            UUID actualUuid = NMSHandler.instance.getBossbarUUID(bossBar);\r\n            if (actualUuid != null) {\r\n                scriptEntry.saveObject(\"bar_uuid\", new ElementTag(actualUuid.toString()));\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/server/ExecuteCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.server;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.command.scripted.DenizenCommandSender;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.player.PlayerCommandPreprocessEvent;\r\nimport org.bukkit.event.server.ServerCommandEvent;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic class ExecuteCommand extends AbstractCommand {\r\n\r\n    public ExecuteCommand() {\r\n        setName(\"execute\");\r\n        setSyntax(\"execute [as_player/as_op/as_npc/as_server] [<Bukkit-command>] (silent)\");\r\n        setRequiredArguments(2, 3);\r\n        isProcedural = false;\r\n        setBooleansHandled(\"silent\");\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Execute\r\n    // @Syntax execute [as_player/as_op/as_npc/as_server] [<Bukkit-command>] (silent)\r\n    // @Required 2\r\n    // @Maximum 3\r\n    // @Short Executes an arbitrary server command as if the player, NPC, or server typed it in.\r\n    // @Group server\r\n    //\r\n    // @Description\r\n    // Allows the execution of server commands through a Denizen script.\r\n    // Commands can be executed as the server, as an npc, as an opped player, or as a player, as though it was typed by the respective source.\r\n    //\r\n    // Note that you should generally avoid using 'as_op', which is only meant for very specific special cases. 'as_server' is usually a better option.\r\n    //\r\n    // Note: do not include the slash at the start. A slash at the start will be interpreted equivalent to typing two slashes at the front in-game.\r\n    //\r\n    // Note that this is a Denizen script command that executes Bukkit commands.\r\n    // This can be considered the inverse of '/ex' (a Bukkit command that executes Denizen script commands).\r\n    //\r\n    // The 'silent' option can be specified with 'as_server' to hide the output. Note that 'silent' might or might not work with different plugins depending on how they operate.\r\n    // It can also be used with 'as_player' or 'as_op' to use network interception to silence the command output to player chat.\r\n    //\r\n    // Generally, you should never use this to execute a vanilla command, there is almost always a script command that should be used instead.\r\n    // Usually the 'execute' command should be reserved for interacting with external plugins.\r\n    //\r\n    // @Tags\r\n    // <entry[saveName].output> returns the output to an as_server sender.\r\n    //\r\n    // @Usage\r\n    // Use to execute the save-all command as the server.\r\n    // - execute as_server \"save-all\"\r\n    //\r\n    // @Usage\r\n    // Use to make the linked (non-op) player execute a command that normally only ops can use. Generally avoid ever doing this.\r\n    // - execute as_op \"staffsay hi\"\r\n    // -->\r\n\r\n    enum Type {AS_SERVER, AS_NPC, AS_PLAYER, AS_OP}\r\n\r\n    public DenizenCommandSender dcs = new DenizenCommandSender();\r\n    public static final List<UUID> silencedPlayers = new ArrayList<>();\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matches(\"asplayer\", \"as_player\")\r\n                    && !scriptEntry.hasObject(\"type\")) {\r\n                if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                    throw new InvalidArgumentsException(\"Must have a Player link when using AS_PLAYER.\");\r\n                }\r\n                scriptEntry.addObject(\"type\", new ElementTag(\"AS_PLAYER\"));\r\n            }\r\n            else if (arg.matches(\"asop\", \"as_op\")\r\n                    && !scriptEntry.hasObject(\"type\")) {\r\n                if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                    throw new InvalidArgumentsException(\"Must have a Player link when using AS_OP.\");\r\n                }\r\n                scriptEntry.addObject(\"type\", new ElementTag(\"AS_OP\"));\r\n            }\r\n            else if (arg.matches(\"asnpc\", \"as_npc\")\r\n                    && !scriptEntry.hasObject(\"type\")) {\r\n                if (!Utilities.entryHasNPC(scriptEntry)) {\r\n                    throw new InvalidArgumentsException(\"Must have a NPC link when using AS_NPC.\");\r\n                }\r\n                scriptEntry.addObject(\"type\", new ElementTag(\"AS_NPC\"));\r\n            }\r\n            else if (arg.matches(\"asserver\", \"as_server\")\r\n                    && !scriptEntry.hasObject(\"type\")) {\r\n                scriptEntry.addObject(\"type\", new ElementTag(\"AS_SERVER\"));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"command\")) {\r\n                scriptEntry.addObject(\"command\", arg.getRawElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"type\")) {\r\n            throw new InvalidArgumentsException(\"Missing execution type!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"command\")) {\r\n            throw new InvalidArgumentsException(\"Missing command text!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag cmd = scriptEntry.getElement(\"command\");\r\n        ElementTag type = scriptEntry.getElement(\"type\");\r\n        boolean silent = scriptEntry.argAsBoolean(\"silent\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), type, cmd, db(\"silent\", silent));\r\n        }\r\n        String command = cmd.asString();\r\n        switch (Type.valueOf(type.asString())) {\r\n            case AS_PLAYER:\r\n                try {\r\n                    PlayerCommandPreprocessEvent pcpe = new PlayerCommandPreprocessEvent(Utilities.getEntryPlayer(scriptEntry).getPlayerEntity(), \"/\" + command);\r\n                    Bukkit.getPluginManager().callEvent(pcpe);\r\n                    if (!pcpe.isCancelled()) {\r\n                        Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity();\r\n                        if (silent) {\r\n                            NetworkInterceptHelper.enable();\r\n                            silencedPlayers.add(player.getUniqueId());\r\n                        }\r\n                        player.performCommand(pcpe.getMessage().startsWith(\"/\") ?\r\n                                pcpe.getMessage().substring(1) : pcpe.getMessage());\r\n                        if (silent) {\r\n                            silencedPlayers.remove(player.getUniqueId());\r\n                        }\r\n                    }\r\n                }\r\n                catch (Throwable e) {\r\n                    Debug.echoError(scriptEntry, \"Exception while executing command as player.\");\r\n                    Debug.echoError(scriptEntry, e);\r\n                }\r\n                break;\r\n            case AS_OP:\r\n                if (CoreUtilities.equalsIgnoreCase(command, \"stop\")) {\r\n                    Debug.echoError(\"Please use as_server to execute 'stop'.\");\r\n                    return;\r\n                }\r\n                Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity();\r\n                boolean isOp = player.isOp();\r\n                if (!isOp) {\r\n                    NMSHandler.playerHelper.setTemporaryOp(player, true);\r\n                }\r\n                try {\r\n                    PlayerCommandPreprocessEvent pcpe = new PlayerCommandPreprocessEvent(player, \"/\" + command);\r\n                    Bukkit.getPluginManager().callEvent(pcpe);\r\n                    if (!pcpe.isCancelled()) {\r\n                        if (silent) {\r\n                            NetworkInterceptHelper.enable();\r\n                            silencedPlayers.add(player.getUniqueId());\r\n                        }\r\n                        player.performCommand(pcpe.getMessage().startsWith(\"/\") ?\r\n                                pcpe.getMessage().substring(1) : pcpe.getMessage());\r\n                        if (silent) {\r\n                            silencedPlayers.remove(player.getUniqueId());\r\n                        }\r\n                    }\r\n                }\r\n                catch (Throwable e) {\r\n                    Debug.echoError(scriptEntry, \"Exception while executing command as OP.\");\r\n                    Debug.echoError(scriptEntry, e);\r\n                }\r\n                if (!isOp) {\r\n                    NMSHandler.playerHelper.setTemporaryOp(player, false);\r\n                }\r\n                break;\r\n            case AS_NPC:\r\n                if (!Utilities.getEntryNPC(scriptEntry).isSpawned()) {\r\n                    Debug.echoError(scriptEntry, \"Cannot EXECUTE AS_NPC unless the NPC is Spawned.\");\r\n                    return;\r\n                }\r\n                if (Utilities.getEntryNPC(scriptEntry).getEntity().getType() != EntityType.PLAYER) {\r\n                    Debug.echoError(scriptEntry, \"Cannot EXECUTE AS_NPC unless the NPC is Player-Type.\");\r\n                    return;\r\n                }\r\n                Utilities.getEntryNPC(scriptEntry).getEntity().setOp(true);\r\n                try {\r\n                    ((Player) Utilities.getEntryNPC(scriptEntry).getEntity()).performCommand(command);\r\n                }\r\n                catch (Throwable e) {\r\n                    Debug.echoError(scriptEntry, \"Exception while executing command as NPC-OP.\");\r\n                    Debug.echoError(scriptEntry, e);\r\n                }\r\n                Utilities.getEntryNPC(scriptEntry).getEntity().setOp(false);\r\n                break;\r\n            case AS_SERVER:\r\n                dcs.clearOutput();\r\n                dcs.silent = silent;\r\n                ServerCommandEvent sce = new ServerCommandEvent(dcs, command);\r\n                Bukkit.getPluginManager().callEvent(sce);\r\n                Denizen.getInstance().getServer().dispatchCommand(dcs, sce.getCommand());\r\n                scriptEntry.saveObject(\"output\", new ListTag(dcs.getOutput()));\r\n                break;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/server/ScoreboardCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.server;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.ScoreboardHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.scoreboard.DisplaySlot;\r\nimport org.bukkit.scoreboard.Objective;\r\nimport org.bukkit.scoreboard.RenderType;\r\nimport org.bukkit.scoreboard.Scoreboard;\r\n\r\nimport java.util.List;\r\n\r\npublic class ScoreboardCommand extends AbstractCommand {\r\n\r\n    public ScoreboardCommand() {\r\n        setName(\"scoreboard\");\r\n        setSyntax(\"scoreboard ({add}/remove) (viewers:<player>|...) (lines:<player>/<text>|...) (id:<value>/player/{main}) (objective:<value>) (criteria:<criteria>/{dummy}) (score:<#>) (displayslot:<value>/{sidebar}/none) (displayname:<name>) (rendertype:<type>)\");\r\n        setRequiredArguments(1, 10);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Scoreboard\r\n    // @Syntax scoreboard ({add}/remove) (viewers:<player>|...) (lines:<player>/<text>|...) (id:<value>/player/{main}) (objective:<value>) (criteria:<criteria>/{dummy}) (score:<#>) (displayslot:<value>/{sidebar}/none) (displayname:<name>) (rendertype:<type>)\r\n    // @Required 1\r\n    // @Maximum 10\r\n    // @Short Add or removes viewers, objectives and scores from scoreboards.\r\n    // @Group server\r\n    //\r\n    // @Description\r\n    // Lets you make players see a certain scoreboard and then a certain objective in that scoreboard.\r\n    // Please note that a 'scoreboard' is NOT a 'sidebar' - if you want that thing where text is on the right side of the screen, use <@link command sidebar>.\r\n    // 'Scoreboard' is the underlying internal system that powers sidebars, below_name plates, team prefixing, and a lot of other systems. Generally, you should avoid using this command directly.\r\n    //\r\n    // The ID can be 'main' for the global default scoreboard, 'player' for the attached player's current scoreboard, or any other value for a custom named scoreboard.\r\n    //\r\n    // There are currently three slots where objectives can be displayed:\r\n    // in the sidebar on the right of the screen, below player names and in the player list that shows up when you press Tab.\r\n    // The names of these slots can be found here: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/scoreboard/DisplaySlot.html>\r\n    //\r\n    // Every objective has several lines of scores.\r\n    // Technically, the lines track players, but fake player names can be used by Denizen to let you call the lines anything you want.\r\n    //\r\n    // When using the sidebar as the display slot, all the scores set for an objective will be displayed there,\r\n    // but you will need to put actual player names in the lines to be able to use\r\n    // the below_name display slot (which displays each player's score underneath his/her name) and\r\n    // the player_list display slot (which displays each player's score to the right of his/her name in the player list).\r\n    //\r\n    // If you do not specify a display slot, the sidebar will be used. You can also use \"none\" as the\r\n    // display slot if you want to add a hidden objective without automatically making it get displayed.\r\n    // If the object already exists, and you don't specify the display slot, it will use the existing setting.\r\n    //\r\n    // When setting an objective, you can also optionally set the display name by using the \"displayname:\" argument.\r\n    //\r\n    // \"Rendertype\" can be \"INTEGER\" or \"HEARTS\". Defaults to integer.\r\n    //\r\n    // You can set scores manually, or you can use different Minecraft criteria that set and update the scores automatically.\r\n    // A list of these criteria can be found here: <@link url https://minecraft.wiki/w/Scoreboard#Objectives>\r\n    // If the object already exists, and you don't specify the criteria, it will use the existing setting.\r\n    //\r\n    // You can use the \"remove\" argument to remove different parts of scoreboards.\r\n    // The more arguments you use with it, the more specific your removal will be.\r\n    // For example, if you only use the \"remove\" argument and the \"id\" argument, you will completely remove all the objectives in a scoreboard,\r\n    // but if you specify an objective as well, you will only delete that one objective from that scoreboard,\r\n    // and if you also specify certain lines, you will only delete those specific lines from that objective.\r\n    // Similarly, if you use the \"remove\" argument along with the \"id\" and \"viewers\" arguments, you will only remove those viewers from the scoreboard, not the entire scoreboard.\r\n    //\r\n    // @Tags\r\n    // <server.scoreboard[(<board>)].exists>\r\n    // <server.scoreboard[(<board>)].team[<team>].members>\r\n    // <PlayerTag.scoreboard_id>\r\n    //\r\n    // @Usage\r\n    // Add a score for the defined player to the default scoreboard under the objective \"cookies\" and let him see it\r\n    // - scoreboard add obj:cookies lines:joe score:1000 viewers:<[aplayer]>\r\n    //\r\n    // @Usage\r\n    // Add a new current objective called \"food\" to the \"test\" scoreboard with 3 lines that each have a score of 50:\r\n    // - scoreboard add id:test obj:food lines:Cookies|Donuts|Cake score:50\r\n    //\r\n    // @Usage\r\n    // Make a list of players see the scoreboard that has the id \"test\":\r\n    // - scoreboard add viewers:<[player]>|<[aplayer]>|<[thatplayer]> id:test\r\n    //\r\n    // @Usage\r\n    // Change the value of one of the scores in the \"food\" objective:\r\n    // - scoreboard add id:test obj:food lines:Cake score:9000\r\n    //\r\n    // @Usage\r\n    // Remove one of the lines from the \"food\" objective in the \"test\" scoreboard\r\n    // - scoreboard remove obj:food lines:Donuts\r\n    //\r\n    // @Usage\r\n    // Remove one of the viewers of the \"test\" scoreboard:\r\n    // - scoreboard remove viewers:<[aplayer]>\r\n    //\r\n    // @Usage\r\n    // Make the defined player see the health of other players below their names\r\n    // - scoreboard add viewers:<[player]> id:test obj:anything criteria:health displayslot:below_name\r\n    //\r\n    // @Usage\r\n    // Make all the players on the world \"survival\" see each other's number of entity kills in the player list when pressing Tab\r\n    // - scoreboard add viewers:<world[survival].players> id:test obj:anything criteria:totalKillCount displayslot:player_list\r\n    // -->\r\n\r\n    private enum Action {ADD, REMOVE}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matchesEnum(Action.class)) {\r\n                scriptEntry.addObject(\"action\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"lines\")\r\n                    && arg.matchesPrefix(\"lines\", \"l\")) {\r\n                scriptEntry.addObject(\"lines\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"id\")\r\n                    && arg.matchesPrefix(\"id\")) {\r\n                scriptEntry.addObject(\"id\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"objective\")\r\n                    && arg.matchesPrefix(\"objective\", \"obj\", \"o\")) {\r\n                scriptEntry.addObject(\"objective\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"criteria\")\r\n                    && arg.matchesPrefix(\"criteria\", \"c\")) {\r\n                scriptEntry.addObject(\"criteria\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"score\")\r\n                    && arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"score\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"displayslot\")\r\n                    && (arg.matchesEnum(DisplaySlot.class) ||\r\n                    arg.matches(\"none\"))) {\r\n                scriptEntry.addObject(\"displayslot\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"displayslot\")\r\n                    && arg.matchesPrefix(\"displayname\")) {\r\n                scriptEntry.addObject(\"displayname\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"rendertype\")\r\n                    && arg.matchesPrefix(\"rendertype\")\r\n                    && arg.matchesEnum(RenderType.class)) {\r\n                scriptEntry.addObject(\"rendertype\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"viewers\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"viewers\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"action\", new ElementTag(\"add\"));\r\n        scriptEntry.defaultObject(\"id\", new ElementTag(\"main\"));\r\n    }\r\n\r\n    public static String checkLine(ObjectTag obj) {\r\n        if (obj instanceof PlayerTag) {\r\n            return ((PlayerTag) obj).getName();\r\n        }\r\n        else if (obj instanceof EntityTag && ((EntityTag) obj).isSpawned()) {\r\n            Entity ent = ((EntityTag) obj).getBukkitEntity();\r\n            if (ent instanceof Player) {\r\n                return ent.getName();\r\n            }\r\n            return ent.getUniqueId().toString();\r\n        }\r\n        String raw = obj.toString();\r\n        if (raw.startsWith(\"p@\")) {\r\n            PlayerTag player = PlayerTag.valueOf(raw, CoreUtilities.noDebugContext);\r\n            if (player != null) {\r\n                return player.getName();\r\n            }\r\n        }\r\n        return raw;\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        List<PlayerTag> viewers = (List<PlayerTag>) scriptEntry.getObject(\"viewers\");\r\n        ListTag lines = scriptEntry.hasObject(\"lines\") ? ListTag.valueOf(scriptEntry.getElement(\"lines\").asString(), scriptEntry.getContext()) : new ListTag();\r\n        ElementTag action = scriptEntry.getElement(\"action\");\r\n        ElementTag id = scriptEntry.getElement(\"id\");\r\n        ElementTag objective = scriptEntry.getElement(\"objective\");\r\n        ElementTag criteria = scriptEntry.getElement(\"criteria\");\r\n        ElementTag score = scriptEntry.getElement(\"score\");\r\n        ElementTag displaySlot = scriptEntry.getElement(\"displayslot\");\r\n        ElementTag displayName = scriptEntry.getElement(\"displayname\");\r\n        ElementTag renderType = scriptEntry.getElement(\"rendertype\");\r\n        Action act = Action.valueOf(action.asString().toUpperCase());\r\n        boolean hadCriteria = criteria != null;\r\n        boolean hadDisplaySlot = displaySlot != null;\r\n        if (!hadCriteria) {\r\n            criteria = new ElementTag(\"dummy\");\r\n        }\r\n        if (!hadDisplaySlot) {\r\n            displaySlot = new ElementTag(\"sidebar\");\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), action, id, db(\"viewers\", viewers), objective, lines, score, objective, displaySlot, criteria, displayName, renderType);\r\n        }\r\n        Scoreboard board = null;\r\n        // Get the main scoreboard by default\r\n        if (id.asString().equalsIgnoreCase(\"main\")) {\r\n            board = ScoreboardHelper.getMain();\r\n        }\r\n        else if (id.asString().equalsIgnoreCase(\"player\")) {\r\n            board = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity().getScoreboard();\r\n        }\r\n        else {\r\n            // If this scoreboard already exists, get it\r\n            if (ScoreboardHelper.hasScoreboard(id.asString())) {\r\n                board = ScoreboardHelper.getScoreboard(id.asString());\r\n            }\r\n            // Else, create a new one if Action is ADD\r\n            else if (act.equals(Action.ADD)) {\r\n                board = ScoreboardHelper.createScoreboard(id.asString());\r\n            }\r\n        }\r\n        // Don't progress if we ended up with a null board\r\n        if (board == null) {\r\n            Debug.echoError(scriptEntry, \"Scoreboard \" + id.asString() + \" does not exist!\");\r\n            return;\r\n        }\r\n        Objective obj;\r\n        if (act.equals(Action.ADD)) {\r\n            if (objective != null) {\r\n                // Try getting the objective from the board\r\n                obj = board.getObjective(objective.asString());\r\n                boolean existedAlready = obj != null;\r\n                // Create the objective if it does not already exist\r\n                if (obj == null) {\r\n                    obj = board.registerNewObjective(objective.asString(), criteria.asString());\r\n                }\r\n                // If a different criteria has been set for this objective,\r\n                // recreate the objective\r\n                else if (hadCriteria && !obj.getCriteria().equals(criteria.asString())) {\r\n                    obj.unregister();\r\n                    obj = board.registerNewObjective(objective.asString(), criteria.asString());\r\n                }\r\n                // Change the objective's display slot\r\n                if ((!existedAlready || hadDisplaySlot) && !displaySlot.asString().equalsIgnoreCase(\"none\")) {\r\n                    obj.setDisplaySlot(DisplaySlot.valueOf(displaySlot.asString().toUpperCase()));\r\n                }\r\n                if (renderType != null) {\r\n                    obj.setRenderType(RenderType.valueOf(renderType.asString().toUpperCase()));\r\n                }\r\n                if (displayName != null) {\r\n                    obj.setDisplayName(displayName.asString());\r\n                }\r\n                else if (!existedAlready) {\r\n                    obj.setDisplayName(objective.asString());\r\n                }\r\n                if (!lines.isEmpty()) {\r\n                    // If we've gotten this far, but the score is null,\r\n                    // use a score of 0\r\n                    if (score == null) {\r\n                        score = new ElementTag(0);\r\n                    }\r\n\r\n                    for (ObjectTag line : lines.objectForms) {\r\n                        ScoreboardHelper.addScore(obj, checkLine(line), score.asInt());\r\n                    }\r\n                }\r\n            }\r\n            // If there is no objective and no viewers, but there are some lines,\r\n            // the command cannot do anything at all, so print a message about that\r\n            else if (viewers == null && !lines.isEmpty()) {\r\n                Debug.echoDebug(scriptEntry, \"Cannot add lines without specifying an objective!\");\r\n            }\r\n        }\r\n        else if (act.equals(Action.REMOVE)) {\r\n            if (objective != null) {\r\n                // Try getting the objective from the board\r\n                obj = board.getObjective(objective.asString());\r\n                if (obj != null) {\r\n                    // Remove the entire objective if no lines have been specified\r\n                    if (lines.isEmpty()) {\r\n                        Debug.echoDebug(scriptEntry, \"Removing objective \" + obj.getName() +\r\n                                \" from scoreboard \" + id.asString());\r\n                        obj.unregister();\r\n                    }\r\n                    else {\r\n                        for (ObjectTag line : lines.objectForms) {\r\n                            ScoreboardHelper.removeScore(obj, checkLine(line));\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    Debug.echoError(scriptEntry, \"Objective \" + objective.asString() +\r\n                            \" does not exist in scoreboard \" + id.asString());\r\n                }\r\n            }\r\n            // If lines were specified, but an objective was not, remove the\r\n            // lines from every objective\r\n            else if (!lines.isEmpty()) {\r\n                Debug.echoDebug(scriptEntry, \"Removing lines \" + lines.identify() +\r\n                        \" from all objectives in scoreboard \" + id.asString());\r\n\r\n                for (ObjectTag line : lines.objectForms) {\r\n                    ScoreboardHelper.removePlayer(id.asString(), checkLine(line));\r\n                }\r\n            }\r\n            // Only remove all objectives from scoreboard if viewers\r\n            // argument was not specified (because if it was, a list\r\n            // of viewers should be removed instead)\r\n            else if (viewers == null) {\r\n                Debug.echoDebug(scriptEntry, \"Removing scoreboard \" + id.asString());\r\n                ScoreboardHelper.deleteScoreboard(id.asString());\r\n            }\r\n        }\r\n        if (viewers != null) {\r\n            for (PlayerTag viewer : viewers) {\r\n                // Add viewers for this scoreboard\r\n                if (act.equals(Action.ADD)) {\r\n                    // If this isn't the main scoreboard, add this viewer\r\n                    // to the map of viewers saved by Denizen\r\n                    if (!id.asString().equalsIgnoreCase(\"main\")) {\r\n                        ScoreboardHelper.viewerMap.put(viewer.getUUID(), id.asString());\r\n                    }\r\n                    // Make this player view the scoreboard if he/she\r\n                    // is already online\r\n                    if (viewer.isOnline()) {\r\n                        viewer.getPlayerEntity().setScoreboard(board);\r\n                    }\r\n                }\r\n                // Remove viewers for this scoreboard\r\n                else if (act.equals(Action.REMOVE)) {\r\n                    // Take this player out of the map of viewers\r\n                    ScoreboardHelper.viewerMap.remove(viewer.getUUID());\r\n\r\n                    // Make the player view a blank scoreboard if he/she\r\n                    // is online (in lieu of a scoreboard-removing method\r\n                    // provided by Bukkit)\r\n                    if (viewer.isOnline()) {\r\n                        viewer.getPlayerEntity().setScoreboard(ScoreboardHelper.createScoreboard());\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/AdjustBlockCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.data.BlockData;\r\n\r\nimport java.util.List;\r\n\r\npublic class AdjustBlockCommand extends AbstractCommand {\r\n\r\n    public AdjustBlockCommand() {\r\n        setName(\"adjustblock\");\r\n        setSyntax(\"adjustblock [<location>|...] [<mechanism>](:<value>) (no_physics)\");\r\n        setRequiredArguments(2, 3);\r\n        isProcedural = false;\r\n        allowedDynamicPrefixes = true;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name AdjustBlock\r\n    // @Syntax adjustblock [<location>|...] [<mechanism>](:<value>) (no_physics)\r\n    // @Required 2\r\n    // @Maximum 3\r\n    // @Short Adjusts a mechanism on the material of a block at the location.\r\n    // @Group core\r\n    // @Guide https://guide.denizenscript.com/guides/basics/mechanisms.html\r\n    //\r\n    // @Description\r\n    // Adjusts a mechanism on the material of a block at the location.\r\n    // That is, an equivalent to <@link command adjust>, but that directly applies a \"MaterialTag\" mechanism onto a block.\r\n    //\r\n    // Input a location or list of locations, and the mechanism to apply.\r\n    //\r\n    // Use the \"no_physics\" argument to indicate that the change should not apply a physics update.\r\n    // If not specified, physics will apply to the block and nearby blocks.\r\n    //\r\n    // @Tags\r\n    // <LocationTag.material>\r\n    //\r\n    // @Usage\r\n    // Use to put snow on the block at the player's feet.\r\n    // - adjustblock <player.location.below> snowy:true\r\n    //\r\n    // @Usage\r\n    // Use to switch on the lever that the player is looking at, without actually providing redstone power.\r\n    // - adjustblock <player.cursor_on> switched:true no_physics\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.add(PropertyParser.propertiesByClass.get(MaterialTag.class).propertiesByMechanism.keySet());\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"locations\")\r\n                    && arg.matchesArgumentList(LocationTag.class)) {\r\n                scriptEntry.addObject(\"locations\", arg.asType(ListTag.class).filter(LocationTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"no_physics\")\r\n                && arg.matches(\"no_physics\")) {\r\n                scriptEntry.addObject(\"no_physics\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"mechanism\")) {\r\n                if (arg.hasPrefix()) {\r\n                    scriptEntry.addObject(\"mechanism\", new ElementTag(arg.getPrefix().getValue()));\r\n                    scriptEntry.addObject(\"mechanism_value\", arg.object);\r\n                }\r\n                else {\r\n                    scriptEntry.addObject(\"mechanism\", arg.asElement());\r\n                }\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"locations\")) {\r\n            throw new InvalidArgumentsException(\"You must specify a location!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"mechanism\")) {\r\n            throw new InvalidArgumentsException(\"You must specify a mechanism!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag mechanismName = scriptEntry.getElement(\"mechanism\");\r\n        ObjectTag value = scriptEntry.getObjectTag(\"mechanism_value\");\r\n        ElementTag noPhysics = scriptEntry.getElement(\"no_physics\");\r\n        List<LocationTag> locations = (List<LocationTag>) scriptEntry.getObject(\"locations\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"locations\", locations), mechanismName, noPhysics, value);\r\n        }\r\n        boolean doPhysics = noPhysics == null || !noPhysics.asBoolean();\r\n        for (LocationTag location : locations) {\r\n            Block block = location.getBlock();\r\n            BlockData data = block.getBlockData();\r\n            MaterialTag specialMaterial = new MaterialTag(data);\r\n            Mechanism mechanism = new Mechanism(mechanismName.asString(), value, scriptEntry.getContext());\r\n            specialMaterial.safeAdjust(mechanism);\r\n            if (doPhysics) {\r\n                block.setBlockData(data, false);\r\n                applyPhysicsAt(location);\r\n            }\r\n            else {\r\n                ModifyBlockCommand.setBlock(block.getLocation(), specialMaterial, false, null, 0);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void applyPhysicsAt(Location location) {\r\n        NMSHandler.blockHelper.applyPhysics(location);\r\n        NMSHandler.blockHelper.applyPhysics(location.clone().add(1, 0, 0));\r\n        NMSHandler.blockHelper.applyPhysics(location.clone().add(-1, 0, 0));\r\n        NMSHandler.blockHelper.applyPhysics(location.clone().add(0, 0, 1));\r\n        NMSHandler.blockHelper.applyPhysics(location.clone().add(0, 0, -1));\r\n        NMSHandler.blockHelper.applyPhysics(location.clone().add(0, 1, 0));\r\n        NMSHandler.blockHelper.applyPhysics(location.clone().add(0, -1, 0));\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/AnimateChestCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Sound;\r\nimport org.bukkit.SoundCategory;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class AnimateChestCommand extends AbstractCommand {\r\n\r\n    public AnimateChestCommand() {\r\n        setName(\"animatechest\");\r\n        setSyntax(\"animatechest [<location>] ({open}/close) (sound:{true}/false) (<player>|...)\");\r\n        setRequiredArguments(1, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name AnimateChest\r\n    // @Syntax animatechest [<location>] ({open}/close) (sound:{true}/false) (<player>|...)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Short Makes a chest appear to open or close.\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // This command animates a chest at a specified location in the world opening or closing.\r\n    // By default, the chest will animate opening.\r\n    // Optionally, specify whether to play a sound with the animation. By default this will play.\r\n    // Optionally, specify a player or list of players that the animation should be visible to.\r\n    // By default, only the linked player can see the animation.\r\n    //\r\n    // Note that this uses a generic 'block action' packet internally,\r\n    // which means other block types may also react to this command.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to animate a chest opening, which only the linked player will see.\r\n    // - animatechest <context.location>\r\n    //\r\n    // @Usage\r\n    // Use to then animate a chest closing, which only the linked player will see.\r\n    // - animatechest <context.location> close\r\n    //\r\n    // @Usage\r\n    // Use to animate a chest opening with no sound, which only the linked player will see.\r\n    // - animatechest <context.location> sound:false\r\n    //\r\n    // @Usage\r\n    // Use to animate a chest opening that only a single specific player will see.\r\n    // - animatechest <context.location> sound:false <[someplayer]>\r\n    //\r\n    // @Usage\r\n    // Use to animate a chest opening that only a list of specific players will see.\r\n    // - animatechest <context.location> sound:false <[someplayer]>|<[player]>|<[thatplayer]>\r\n    // -->\r\n\r\n    enum ChestAction {OPEN, CLOSE}\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addNotesOfType(LocationTag.class);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matchesEnum(ChestAction.class)) {\r\n                scriptEntry.addObject(\"action\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"sound\")\r\n                    && arg.matchesPrefix(\"sound\")\r\n                    && arg.matchesBoolean()) {\r\n                scriptEntry.addObject(\"sound\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"players\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"players\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a location!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"action\")) {\r\n            scriptEntry.addObject(\"action\", new ElementTag(\"OPEN\"));\r\n        }\r\n        if (!scriptEntry.hasObject(\"sound\")) {\r\n            scriptEntry.addObject(\"sound\", new ElementTag(true));\r\n        }\r\n        if (!scriptEntry.hasObject(\"players\")) {\r\n            if (Utilities.entryHasPlayer(scriptEntry)) {\r\n                scriptEntry.addObject(\"players\", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry)));\r\n            }\r\n            else // TODO: Perhaps instead add all players in sight range?\r\n            {\r\n                throw new InvalidArgumentsException(\"Missing 'players' argument!\");\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        ElementTag action = scriptEntry.getElement(\"action\");\r\n        ElementTag sound = scriptEntry.getElement(\"sound\");\r\n        List<PlayerTag> players = (List<PlayerTag>) scriptEntry.getObject(\"players\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), location,  db(\"block type\", location.getBlock().getType().name()), action, sound, db(\"players\", players));\r\n        }\r\n        switch (ChestAction.valueOf(action.asString().toUpperCase())) {\r\n            case OPEN:\r\n                for (PlayerTag player : players) {\r\n                    Player ent = player.getPlayerEntity();\r\n                    if (sound.asBoolean()) {\r\n                        player.getPlayerEntity().playSound(location, Sound.BLOCK_CHEST_OPEN, SoundCategory.BLOCKS, 1, 1);\r\n                    }\r\n                    NMSHandler.packetHelper.showBlockAction(ent, location, 1, 1);\r\n                }\r\n                break;\r\n            case CLOSE:\r\n                for (PlayerTag player : players) {\r\n                    Player ent = player.getPlayerEntity();\r\n                    if (sound.asBoolean()) {\r\n                        player.getPlayerEntity().playSound(location, Sound.BLOCK_CHEST_CLOSE, SoundCategory.BLOCKS, 1, 1);\r\n                    }\r\n                    NMSHandler.packetHelper.showBlockAction(ent, location, 1, 0);\r\n                }\r\n                break;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/ChunkLoadCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.ChunkTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.citizensnpcs.api.event.DespawnReason;\r\nimport net.citizensnpcs.api.event.NPCDespawnEvent;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\n\r\nimport java.util.Collections;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class ChunkLoadCommand extends AbstractCommand implements Listener {\r\n\r\n    public ChunkLoadCommand() {\r\n        setName(\"chunkload\");\r\n        setSyntax(\"chunkload ({add}/remove/removeall) [<chunk>|...] (duration:<value>)\");\r\n        setRequiredArguments(1, 3);\r\n        Bukkit.getPluginManager().registerEvents(this, Denizen.getInstance());\r\n        if (Depends.citizens != null) {\r\n            Bukkit.getPluginManager().registerEvents(new ChunkLoadCommandNPCEvents(), Denizen.getInstance());\r\n        }\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name ChunkLoad\r\n    // @Syntax chunkload ({add}/remove/removeall) [<chunk>|...] (duration:<value>)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Short Keeps a chunk actively loaded and allowing activity.\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Forces a chunk to load and stay loaded in the world for the duration specified or until removed.\r\n    // This will not persist over server restarts.\r\n    // If no duration is specified it defaults to 0 (forever).\r\n    // While a chunk is loaded all normal activity such as crop growth and npc activity continues,\r\n    // other than activity that requires a nearby player.\r\n    //\r\n    // @Tags\r\n    // <WorldTag.loaded_chunks>\r\n    // <ChunkTag.is_loaded>\r\n    // <ChunkTag.force_loaded>\r\n    //\r\n    // @Usage\r\n    // Use to load a chunk.\r\n    // - chunkload <[some_chunk]>\r\n    //\r\n    // @Usage\r\n    // Use to temporarily load a chunk.\r\n    // - chunkload <player.location.add[5000,0,0].chunk> duration:5m\r\n    //\r\n    // @Usage\r\n    // Use to stop loading a chunk.\r\n    // - chunkload remove <[some_chunk]>\r\n    //\r\n    // @Usage\r\n    // Use to stop loading all chunks.\r\n    // - chunkload removeall\r\n    // -->\r\n\r\n    /*\r\n     * Keeps a chunk loaded\r\n     */\r\n\r\n    private enum Action {ADD, REMOVE, REMOVEALL}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matchesEnum(Action.class)\r\n                    && !scriptEntry.hasObject(\"action\")) {\r\n                scriptEntry.addObject(\"action\", new ElementTag(arg.getValue().toUpperCase()));\r\n                if (arg.getValue().equalsIgnoreCase(\"removeall\")) {\r\n                    scriptEntry.addObject(\"location\", new ListTag(Collections.singletonList(new LocationTag(Bukkit.getWorlds().get(0), 0, 0, 0))));\r\n                }\r\n            }\r\n            else if (arg.matchesArgumentList(ChunkTag.class)\r\n                    && !scriptEntry.hasObject(\"location\")) {\r\n                scriptEntry.addObject(\"location\", arg.asType(ListTag.class));\r\n            }\r\n            else if (arg.matchesArgumentList(LocationTag.class)\r\n                    && !scriptEntry.hasObject(\"location\")) {\r\n                scriptEntry.addObject(\"location\", arg.asType(ListTag.class));\r\n            }\r\n            else if (arg.matchesArgumentType(DurationTag.class)\r\n                    && !scriptEntry.hasObject(\"duration\")) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Missing location argument!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"action\")) {\r\n            scriptEntry.addObject(\"action\", new ElementTag(\"ADD\"));\r\n        }\r\n        if (!scriptEntry.hasObject(\"duration\")) {\r\n            scriptEntry.addObject(\"duration\", new DurationTag(0));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        ElementTag action = scriptEntry.getElement(\"action\");\r\n        ListTag chunklocs = scriptEntry.getObjectTag(\"location\");\r\n        DurationTag length = scriptEntry.getObjectTag(\"duration\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), action, chunklocs, length);\r\n        }\r\n        for (String chunkText : chunklocs) {\r\n            Chunk chunk;\r\n            if (ChunkTag.matches(chunkText)) {\r\n                chunk = ChunkTag.valueOf(chunkText, scriptEntry.context).getChunk();\r\n            }\r\n            else if (LocationTag.matches(chunkText)) {\r\n                chunk = LocationTag.valueOf(chunkText, scriptEntry.context).getChunk();\r\n            }\r\n            else {\r\n                Debug.echoError(\"Chunk input '\" + chunkText + \"' is invalid.\");\r\n                return;\r\n            }\r\n            ChunkCoordinate coord = new ChunkCoordinate(chunk);\r\n            switch (Action.valueOf(action.asString())) {\r\n                case ADD:\r\n                    if (length.getSeconds() != 0) {\r\n                        chunkDelays.put(coord, CoreUtilities.monotonicMillis() + length.getMillis());\r\n                    }\r\n                    else {\r\n                        chunkDelays.put(coord, (long) 0);\r\n                    }\r\n                    Debug.echoDebug(scriptEntry, \"...added chunk \" + chunk.getX() + \", \" + chunk.getZ() + \" with a delay of \" + length.getSeconds() + \" seconds.\");\r\n                    if (!chunk.isLoaded()) {\r\n                        chunk.load();\r\n                    }\r\n                    chunk.addPluginChunkTicket(Denizen.getInstance());\r\n                    if (length.getSeconds() > 0) {\r\n                        Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> {\r\n                            if (chunkDelays.containsKey(coord) && chunkDelays.get(coord) <= CoreUtilities.monotonicMillis()) {\r\n                                chunk.removePluginChunkTicket(Denizen.getInstance());\r\n                                chunkDelays.remove(coord);\r\n                            }\r\n                        }, length.getTicks() + 20);\r\n                    }\r\n                    break;\r\n                case REMOVE:\r\n                    if (chunkDelays.containsKey(coord)) {\r\n                        chunkDelays.remove(coord);\r\n                        chunk.removePluginChunkTicket(Denizen.getInstance());\r\n                        Debug.echoDebug(scriptEntry, \"...allowing unloading of chunk \" + chunk.getX() + \", \" + chunk.getZ());\r\n                    }\r\n                    else {\r\n                        Debug.echoDebug(scriptEntry, \"Chunk '\" + coord + \"' was not on the load list, ignoring.\");\r\n                    }\r\n                    break;\r\n                case REMOVEALL:\r\n                    Debug.echoDebug(scriptEntry, \"...allowing unloading of all stored chunks\");\r\n                    for (ChunkCoordinate loopCoord : chunkDelays.keySet()) {\r\n                        loopCoord.getChunk().getChunk().removePluginChunkTicket(Denizen.getInstance());\r\n                    }\r\n                    chunkDelays.clear();\r\n                    break;\r\n            }\r\n        }\r\n    }\r\n\r\n    // Map of chunks with delays\r\n    Map<ChunkCoordinate, Long> chunkDelays = new HashMap<>();\r\n\r\n    public class ChunkLoadCommandNPCEvents implements Listener {\r\n        @EventHandler\r\n        public void stopDespawn(NPCDespawnEvent e) {\r\n            if (e.getNPC() == null || !e.getNPC().isSpawned() || e.getReason() != DespawnReason.CHUNK_UNLOAD) {\r\n                return;\r\n            }\r\n            Chunk chnk = e.getNPC().getEntity().getLocation().getChunk();\r\n            ChunkCoordinate coord = new ChunkCoordinate(chnk);\r\n            if (chunkDelays.containsKey(coord)) {\r\n                if (chunkDelays.get(coord) == 0) {\r\n                    e.setCancelled(true);\r\n                }\r\n                else if (CoreUtilities.monotonicMillis() < chunkDelays.get(coord)) {\r\n                    e.setCancelled(true);\r\n                }\r\n                else {\r\n                    chunkDelays.remove(coord);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CopyBlockCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.blocks.FullBlockData;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.*;\r\nimport org.bukkit.inventory.InventoryHolder;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class CopyBlockCommand extends AbstractCommand {\r\n\r\n    public CopyBlockCommand() {\r\n        setName(\"copyblock\");\r\n        setSyntax(\"copyblock [<location>] [to:<location>] (remove_original)\");\r\n        setRequiredArguments(2, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name CopyBlock\r\n    // @Syntax copyblock [<location>] [to:<location>] (remove_original)\r\n    // @Required 2\r\n    // @Maximum 3\r\n    // @Short Copies a block to another location, keeping metadata when possible.\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Copies a block to another location.\r\n    // You may also use the 'remove_original' argument to delete the original block.\r\n    // This effectively moves the block to the target location.\r\n    //\r\n    // @Tags\r\n    // <LocationTag.material>\r\n    //\r\n    // @Usage\r\n    // Use to copy the block the player is looking at to their current location\r\n    // - copyblock <player.cursor_on> to:<player.location>\r\n    //\r\n    // @Usage\r\n    // Use to move the block the player is looking at to their current location (removing it from its original location)\r\n    // - copyblock <player.cursor_on> to:<player.location> remove_original\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matchesArgumentType(LocationTag.class)\r\n                    && !scriptEntry.hasObject(\"location\")\r\n                    && !arg.matchesPrefix(\"t\", \"to\")) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (arg.matchesArgumentType(LocationTag.class)\r\n                    && arg.matchesPrefix(\"t\", \"to\")) {\r\n                scriptEntry.addObject(\"destination\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (arg.matches(\"remove_original\")) {\r\n                scriptEntry.addObject(\"remove\", new ElementTag(true));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a source location.\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"destination\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a destination location.\");\r\n        }\r\n        // Set defaults\r\n        scriptEntry.defaultObject(\"remove\", new ElementTag(false));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        LocationTag copy_location = scriptEntry.getObjectTag(\"location\");\r\n        LocationTag destination = scriptEntry.getObjectTag(\"destination\");\r\n        ElementTag remove_original = scriptEntry.getElement(\"remove\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), copy_location, destination, remove_original);\r\n        }\r\n        List<Location> locations = new ArrayList<>();\r\n        if (copy_location != null) {\r\n            locations.add(copy_location);\r\n        }\r\n        for (Location loc : locations) {\r\n            Block source = loc.getBlock();\r\n            BlockState sourceState = source.getState();\r\n            Block update = destination.getBlock();\r\n            FullBlockData block = new FullBlockData(source);\r\n            block.set(update, false);\r\n            BlockState updateState = update.getState();\r\n            // Note: only a BlockState, not a Block, is actually an instance\r\n            // of InventoryHolder\r\n            if (sourceState instanceof InventoryHolder) {\r\n                ((InventoryHolder) updateState).getInventory()\r\n                        .setContents(((InventoryHolder) sourceState).getInventory().getContents());\r\n            }\r\n            else if (sourceState instanceof Sign) {\r\n                int n = 0;\r\n                for (String line : ((Sign) sourceState).getLines()) {\r\n                    PaperAPITools.instance.setSignLine(((Sign) updateState), n++, line);\r\n                }\r\n            }\r\n            else if (sourceState instanceof Skull) {\r\n                ((Skull) updateState).setSkullType(((Skull) sourceState).getSkullType());\r\n                ((Skull) updateState).setOwner(((Skull) sourceState).getOwner());\r\n                ((Skull) updateState).setRotation(((Skull) sourceState).getRotation());\r\n            }\r\n            else if (sourceState instanceof Jukebox) {\r\n                ((Jukebox) updateState).setPlaying(((Jukebox) sourceState).getPlaying());\r\n            }\r\n            else if (sourceState instanceof Banner) {\r\n                ((Banner) updateState).setBaseColor(((Banner) sourceState).getBaseColor());\r\n                ((Banner) updateState).setPatterns(((Banner) sourceState).getPatterns());\r\n            }\r\n            else if (sourceState instanceof CommandBlock) {\r\n                ((CommandBlock) updateState).setName(((CommandBlock) sourceState).getName());\r\n                ((CommandBlock) updateState).setCommand(((CommandBlock) sourceState).getCommand());\r\n            }\r\n            else if (sourceState instanceof CreatureSpawner) {\r\n                ((CreatureSpawner) updateState).setSpawnedType(((CreatureSpawner) sourceState).getSpawnedType());\r\n                ((CreatureSpawner) updateState).setDelay(((CreatureSpawner) sourceState).getDelay());\r\n            }\r\n            updateState.update();\r\n            if (remove_original.asBoolean()) {\r\n                loc.getBlock().setType(Material.AIR);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.utilities.Settings;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\nimport com.denizenscript.denizencore.objects.Argument;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport com.denizenscript.denizencore.scripts.commands.Holdable;\nimport com.denizenscript.denizencore.utilities.AsciiMatcher;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport org.bukkit.Bukkit;\nimport org.bukkit.World;\nimport org.bukkit.WorldCreator;\nimport org.bukkit.WorldType;\n\nimport java.io.File;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.function.Supplier;\n\npublic class CreateWorldCommand extends AbstractCommand implements Holdable {\n\n    public CreateWorldCommand() {\n        setName(\"createworld\");\n        setSyntax(\"createworld [<name>] (generator:<id>) (worldtype:<type>) (environment:<environment>) (copy_from:<world>) (seed:<seed>) (settings:<json>) (generate_structures:true/false)\");\n        setRequiredArguments(1, 8);\n        setPrefixesHandled(\"generator\", \"worldtype\", \"environment\", \"copy_from\", \"seed\", \"settings\", \"generate_structures\");\n        isProcedural = false;\n    }\n\n    // <--[command]\n    // @Name CreateWorld\n    // @Syntax createworld [<name>] (generator:<id>) (worldtype:<type>) (environment:<environment>) (copy_from:<world>) (seed:<seed>) (settings:<json>) (generate_structures:true/false)\n    // @Required 1\n    // @Maximum 8\n    // @Short Creates a new world, or loads an existing world.\n    // @Synonyms LoadWorld\n    // @Group world\n    //\n    // @Description\n    // This command creates a new minecraft world with the specified name, or loads an existing world by that name.\n    //\n    // Optionally specify a plugin-based world generator by its generator ID.\n    // If you want an empty void world with a void biome, you can use \"denizen:void\".\n    // If you want an empty void world with vanilla biomes, you can use \"denizen:void_biomes\".\n    //\n    // Optionally specify additional generator settings as JSON input.\n    //\n    // Optionally specify a world type which can be specified with 'worldtype:' (defaults to NORMAL).\n    // For all world types, see: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/WorldType.html>\n    //\n    // Optionally specify an environment (defaults to NORMAL, can also be NETHER, THE_END).\n    //\n    // Optionally choose whether to generate structures in this world.\n    //\n    // Optionally specify an existing world to copy files from.\n    // The 'copy_from' argument is ~waitable. Refer to <@link language ~waitable>.\n    //\n    // It's often ideal to put this command inside <@link event server prestart>.\n    //\n    // @Tags\n    // <server.world_types>\n    // <server.worlds>\n    //\n    // @Usage\n    // Use to create a normal world with name 'survival'\n    // - createworld survival\n    //\n    // @Usage\n    // Use to create a flat world with the name 'superflat'\n    // - createworld superflat worldtype:FLAT\n    //\n    // @Usage\n    // Use to create an end world with the name 'space'\n    // - createworld space environment:THE_END\n    //\n    // @Usage\n    // Use to create a new world named 'dungeon3' as a copy of an existing world named 'dungeon_template'.\n    // - ~createworld dungeon3 copy_from:dungeon_template\n    // -->\n\n    @Override\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\n        tab.addWithPrefix(\"environment:\", World.Environment.values());\n        tab.addWithPrefix(\"worldtype:\", WorldType.values());\n    }\n\n    @Override\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\n        for (Argument arg : scriptEntry) {\n            if (!scriptEntry.hasObject(\"world_name\")) {\n                scriptEntry.addObject(\"world_name\", arg.asElement());\n            }\n            else {\n                arg.reportUnhandled();\n            }\n        }\n        if (!scriptEntry.hasObject(\"world_name\")) {\n            throw new InvalidArgumentsException(\"Must specify a world name.\");\n        }\n    }\n\n    public static HashSet<String> excludedExtensionsForCopyFrom = new HashSet<>(Collections.singleton(\"lock\"));\n\n    public static AsciiMatcher forbiddenSymbols = new AsciiMatcher(\"\");\n\n    static {\n        for (int i = 0; i < 256; i++) {\n            forbiddenSymbols.accepted[i] = !((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || (i == '_') || (i == '-') || (i == ' '));\n        }\n    }\n\n    @Override\n    public void execute(ScriptEntry scriptEntry) {\n        ElementTag worldName = scriptEntry.getElement(\"world_name\");\n        ElementTag generator = scriptEntry.argForPrefixAsElement(\"generator\", null);\n        ElementTag worldType = scriptEntry.argForPrefixAsElement(\"worldtype\", \"NORMAL\");\n        ElementTag environment = scriptEntry.argForPrefixAsElement(\"environment\", \"NORMAL\");\n        ElementTag copy_from = scriptEntry.argForPrefixAsElement(\"copy_from\", null);\n        ElementTag settings = scriptEntry.argForPrefixAsElement(\"settings\", null);\n        ElementTag seed = scriptEntry.argForPrefixAsElement(\"seed\", null);\n        ElementTag generateStructures = scriptEntry.argForPrefixAsElement(\"generate_structures\", null);\n        if (scriptEntry.dbCallShouldDebug()) {\n            Debug.report(scriptEntry, getName(), worldName, generator, environment, copy_from, settings, worldType, seed, generateStructures);\n        }\n        if (Bukkit.getWorld(worldName.asString()) != null) {\n            Debug.echoDebug(scriptEntry, \"CreateWorld doing nothing, world by that name already loaded.\");\n            scriptEntry.setFinished(true);\n            return;\n        }\n        if (!Settings.cache_createWorldSymbols) {\n            if (forbiddenSymbols.containsAnyMatch(worldName.asString())) {\n                Debug.echoError(\"Cannot use world names with non-alphanumeric symbols due to security settings in Denizen/config.yml.\");\n                scriptEntry.setFinished(true);\n                return;\n            }\n        }\n        else if (!Settings.cache_createWorldWeirdPaths) {\n            String cleaned = worldName.asLowerString().replace('\\\\', '/');\n            while (cleaned.contains(\"//\")) {\n                cleaned = cleaned.replace(\"//\", \"/\");\n            }\n            if (cleaned.startsWith(\"/\")) {\n                cleaned = cleaned.substring(1);\n            }\n            if (cleaned.startsWith(\"plugins/\")) {\n                Debug.echoError(\"CreateWorld cannot create a world inside the plugins folder due to security settings in Denizen/config.yml.\");\n                scriptEntry.setFinished(true);\n                return;\n            }\n            if (cleaned.startsWith(\"..\")) {\n                Debug.echoError(\"CreateWorld cannot create a world with a raised path (contains '..') due to security settings in Denizen/config.yml.\");\n                scriptEntry.setFinished(true);\n                return;\n            }\n        }\n        final File newFolder = new File(Bukkit.getWorldContainer(), worldName.asString());\n        if (!Utilities.canWriteToFile(newFolder)) {\n            Debug.echoError(\"Cannot copy to that new folder path due to security settings in Denizen/config.yml.\");\n            scriptEntry.setFinished(true);\n            return;\n        }\n        WorldType enumWorldType;\n        World.Environment enumEnvironment;\n        try {\n            enumWorldType = WorldType.valueOf(worldType.asString().toUpperCase());\n            enumEnvironment = World.Environment.valueOf(environment.asString().toUpperCase());\n        }\n        catch (IllegalArgumentException ex) {\n            Debug.echoError(\"Invalid worldtype or environment: \" + ex.getMessage());\n            scriptEntry.setFinished(true);\n            return;\n        }\n        if (copy_from != null && !Settings.cache_createWorldSymbols && forbiddenSymbols.containsAnyMatch(copy_from.asString())) {\n            Debug.echoError(\"Cannot use copy_from world names with non-alphanumeric symbols due to security settings in Denizen/config.yml.\");\n            scriptEntry.setFinished(true);\n            return;\n        }\n        Supplier<Boolean> copyRunnable = () -> {\n            try {\n                File folder = new File(Bukkit.getWorldContainer(), copy_from.asString().replace(\"w@\", \"\"));\n                if (!Utilities.canReadFile(folder)) {\n                    Debug.echoError(scriptEntry, \"Cannot copy from that folder path due to security settings in Denizen/config.yml.\");\n                    return false;\n                }\n                if (!folder.exists() || !folder.isDirectory()) {\n                    Debug.echoError(scriptEntry, \"Invalid copy from world folder - does not exist!\");\n                    return false;\n                }\n                if (newFolder.exists()) {\n                    Debug.echoError(scriptEntry, \"Cannot copy to new world - that folder already exists.\");\n                    return false;\n                }\n                CoreUtilities.copyDirectory(folder, newFolder, excludedExtensionsForCopyFrom);\n                Debug.echoDebug(scriptEntry, \"Copied \" + folder.getName() + \" to \" + newFolder.getName());\n                File file = new File(Bukkit.getWorldContainer(), worldName.asString() + \"/uid.dat\");\n                if (file.exists()) {\n                    file.delete();\n                }\n                File file2 = new File(Bukkit.getWorldContainer(), worldName.asString() + \"/session.lock\");\n                if (file2.exists()) {\n                    file2.delete();\n                }\n            }\n            catch (Throwable ex) {\n                Debug.echoError(scriptEntry, ex);\n                return false;\n            }\n            return true;\n        };\n        Runnable createRunnable = () -> {\n            World world;\n            WorldCreator worldCreator = WorldCreator.name(worldName.asString())\n                    .environment(enumEnvironment)\n                    .type(enumWorldType);\n            if (generateStructures != null) {\n                worldCreator.generateStructures(generateStructures.asBoolean());\n            }\n            if (generator != null) {\n                worldCreator.generator(generator.asString());\n            }\n            if (seed != null) {\n                worldCreator.seed(seed.asLong());\n            }\n            if (settings != null) {\n                worldCreator.generatorSettings(settings.asString());\n            }\n            world = Bukkit.getServer().createWorld(worldCreator);\n            if (world == null) {\n                Debug.echoError(\"World is null, something went wrong in creation!\");\n            }\n            else {\n                Debug.echoDebug(scriptEntry, \"Created new world \" + world.getName());\n            }\n            scriptEntry.setFinished(true);\n        };\n        if (scriptEntry.shouldWaitFor() && copy_from != null) {\n            Bukkit.getScheduler().runTaskAsynchronously(Denizen.getInstance(), () -> {\n                if (!copyRunnable.get()) {\n                    scriptEntry.setFinished(true);\n                    return;\n                }\n                Bukkit.getScheduler().runTask(Denizen.getInstance(), createRunnable);\n            });\n        }\n        else {\n            if (copy_from != null && !copyRunnable.get()) {\n                return;\n            }\n            createRunnable.run();\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/DropCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.command.TabCompleteHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.ExperienceOrb;\r\nimport org.bukkit.entity.Item;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class DropCommand extends AbstractCommand {\r\n\r\n    public DropCommand() {\r\n        setName(\"drop\");\r\n        setSyntax(\"drop [<entity_type>/xp/<item>|...] (<location>) (quantity:<#>) (speed:<#.#>) (delay:<duration>)\");\r\n        setRequiredArguments(1, 5);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Drop\r\n    // @Syntax drop [<entity_type>/xp/<item>|...] (<location>) (quantity:<#>) (speed:<#.#>) (delay:<duration>)\r\n    // @Required 1\r\n    // @Maximum 5\r\n    // @Short Drops an item, entity, or experience orb on a location.\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // To drop an item, just specify a valid item object. To drop an entity, specify a generic entity object.\r\n    // Drop can also reward players with experience orbs by using the 'xp' argument.\r\n    //\r\n    // For all three usages, you can optionally specify an integer with 'quantity:' prefix to drop multiple items/entities/xp.\r\n    //\r\n    // For items, you can add 'speed:' to modify the launch velocity.\r\n    // You can also add 'delay:' to set the pickup delay of the item.\r\n    //\r\n    // @Tags\r\n    // <entry[saveName].dropped_entities> returns a list of entities that were dropped.\r\n    // <entry[saveName].dropped_entity> returns a single entity that was dropped (if only one).\r\n    // <EntityTag.item>\r\n    // <EntityTag.experience>\r\n    //\r\n    // @Usage\r\n    // Use to drop some loot around the player.\r\n    // - drop gold_nugget <cuboid[<player.location.add[-2,-2,-2]>|<player.location.add[2,2,2]>].spawnable_blocks.random>\r\n    //\r\n    // @Usage\r\n    // Use to reward a player with 500 xp.\r\n    // - drop xp quantity:500 <player.location>\r\n    //\r\n    // @Usage\r\n    // Use to drop a nasty surprise (exploding TNT).\r\n    // - drop primed_tnt <player.location>\r\n    //\r\n    // @Usage\r\n    // Use to drop an item with a pickup delay at the player's location.\r\n    // - drop diamond_sword <player.location> delay:20s\r\n    // -->\r\n\r\n    enum Action {DROP_ITEM, DROP_EXP, DROP_ENTITY}\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        TabCompleteHelper.tabCompleteItems(tab);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matches(\"experience\", \"exp\", \"xp\")) {\r\n                scriptEntry.addObject(\"action\", new ElementTag(Action.DROP_EXP.toString()).setPrefix(\"action\"));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"speed\")\r\n                    && arg.matchesPrefix(\"speed\")\r\n                    && arg.matchesFloat()) {\r\n                scriptEntry.addObject(\"speed\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"delay\") && arg.matchesArgumentType(DurationTag.class)\r\n                    && arg.matchesPrefix(\"delay\", \"d\")) {\r\n                scriptEntry.addObject(\"delay\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"quantity\")\r\n                    && arg.matchesInteger()\r\n                    && arg.matchesPrefix(\"quantity\", \"q\", \"qty\", \"a\", \"amt\", \"amount\")) {\r\n                if (arg.matchesPrefix(\"q\", \"qty\")) {\r\n                    BukkitImplDeprecations.qtyTags.warn(scriptEntry);\r\n                }\r\n                scriptEntry.addObject(\"quantity\", arg.asElement().setPrefix(\"quantity\"));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matchesArgumentList(ItemTag.class)) {\r\n                scriptEntry.addObject(\"action\", new ElementTag(Action.DROP_ITEM.toString()).setPrefix(\"action\"));\r\n                scriptEntry.addObject(\"item\", arg.asType(ListTag.class).filter(ItemTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"action\")\r\n                    && arg.matchesArgumentType(EntityTag.class)) {\r\n                scriptEntry.addObject(\"action\", new ElementTag(Action.DROP_ENTITY.toString()).setPrefix(\"action\"));\r\n                scriptEntry.addObject(\"entity\", arg.asType(EntityTag.class).setPrefix(\"entity\"));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class).setPrefix(\"location\"));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"action\")) {\r\n            throw new InvalidArgumentsException(\"Must specify something to drop!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            if (Utilities.getEntryPlayer(scriptEntry) != null && Utilities.getEntryPlayer(scriptEntry).isOnline()) {\r\n                scriptEntry.addObject(\"location\", Utilities.getEntryPlayer(scriptEntry).getLocation().setPrefix(\"location\"));\r\n                Debug.echoDebug(scriptEntry, \"Did not specify a location, assuming Player's location.\");\r\n            }\r\n            else {\r\n                throw new InvalidArgumentsException(\"Must specify a location!\");\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"quantity\")) {\r\n            scriptEntry.addObject(\"quantity\", new ElementTag(\"1\"));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        ElementTag quantity = scriptEntry.getElement(\"quantity\");\r\n        ElementTag action = scriptEntry.getElement(\"action\");\r\n        ElementTag speed = scriptEntry.getElement(\"speed\");\r\n        List<ItemTag> items = (List<ItemTag>) scriptEntry.getObject(\"item\");\r\n        EntityTag entity = scriptEntry.getObjectTag(\"entity\");\r\n        DurationTag delay = scriptEntry.getObjectTag(\"delay\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), action, location, quantity, db(\"items\", items), entity, speed, delay);\r\n        }\r\n        ListTag entityList = new ListTag();\r\n        switch (Action.valueOf(action.asString())) {\r\n            case DROP_EXP:\r\n                EntityTag orb = new EntityTag(location.getWorld().spawnEntity(location, EntityType.EXPERIENCE_ORB));\r\n                ((ExperienceOrb) orb.getBukkitEntity()).setExperience(quantity.asInt());\r\n                entityList.addObject(orb);\r\n                break;\r\n            case DROP_ITEM:\r\n                for (ItemTag item : items) {\r\n                    if (item.getMaterial().getMaterial() == Material.AIR) {\r\n                        continue;\r\n                    }\r\n                    if (quantity.asInt() > 1 && item.isUnique()) {\r\n                        Debug.echoDebug(scriptEntry, \"Cannot drop multiples of this item because it is Unique!\");\r\n                    }\r\n                    for (int x = 0; x < quantity.asInt(); x++) {\r\n                        EntityTag e = new EntityTag(location.getWorld().dropItem(location, item.getItemStack()));\r\n                        if (e.isValid()) {\r\n                            e.setVelocity(e.getVelocity().multiply(speed != null ? speed.asDouble() : 1d));\r\n                            if (delay != null) {\r\n                                ((Item) e.getBukkitEntity()).setPickupDelay(delay.getTicksAsInt());\r\n                            }\r\n                        }\r\n                        entityList.addObject(e);\r\n                    }\r\n                }\r\n                break;\r\n            case DROP_ENTITY:\r\n                if (quantity.asInt() > 1 && entity.isUnique()) {\r\n                    Debug.echoDebug(scriptEntry, \"Cannot drop multiples of this entity because it is Unique!\");\r\n                    entity.spawnAt(location);\r\n                    entityList.addObject(entity);\r\n                    break;\r\n                }\r\n                for (int x = 0; x < quantity.asInt(); x++) {\r\n                    ArrayList<Mechanism> mechanisms = new ArrayList<>();\r\n                    for (Mechanism mechanism : entity.getWaitingMechanisms()) {\r\n                        mechanisms.add(new Mechanism(mechanism.getName(), mechanism.value, scriptEntry.context));\r\n                    }\r\n                    EntityTag ent = new EntityTag(entity.getEntityType(), mechanisms);\r\n                    ent.spawnAt(location);\r\n                    entityList.addObject(ent);\r\n                }\r\n                break;\r\n        }\r\n        scriptEntry.saveObject(\"dropped_entities\", entityList);\r\n        if (entityList.size() == 1) {\r\n            scriptEntry.saveObject(\"dropped_entity\", entityList.getObject(0));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/ExplodeCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\npublic class ExplodeCommand extends AbstractCommand {\r\n\r\n    public ExplodeCommand() {\r\n        setName(\"explode\");\r\n        setSyntax(\"explode (power:<#.#>) (<location>) (fire) (breakblocks) (source:<entity>)\");\r\n        setRequiredArguments(0, 5);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Explode\r\n    // @Syntax explode (power:<#.#>) (<location>) (fire) (breakblocks) (source:<entity>)\r\n    // @Required 0\r\n    // @Maximum 5\r\n    // @Short Causes an explosion at the location.\r\n    // @Synonyms Detonate,Bomb,TNT\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // This command causes an explosion at the location specified (or the npc / player location).\r\n    // By default, this will not destroy blocks or set fire to blocks within the explosion.\r\n    //\r\n    // Specify the 'fire' argument to set blocks on fire within the explosion radius.\r\n    //\r\n    // Specify the 'breakblocks' argument to cause the explosion to break blocks within the power radius.\r\n    //\r\n    // If no power is specified, the default power will be 1.\r\n    //\r\n    // If no location is given, the default will be the linked NPC or player's location.\r\n    // It is highly recommended you specify a location to be safe.\r\n    //\r\n    // Optionally specify a source entity that will be tracked as the damage cause.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to create an explosion at a player's location.\r\n    // - explode <player.location>\r\n    //\r\n    // @Usage\r\n    // Use to create an explosion at a player, which breaks blocks and causes fire with a power of 5.\r\n    // - explode power:5 <player.location> fire breakblocks\r\n    //\r\n    // @Usage\r\n    // Use to create an explosion with a power radius of 3 at an NPC's location.\r\n    // - explode power:3 <npc.location>\r\n    //\r\n    // @Usage\r\n    // Use to create an explosion with a power radius of 3 at a related location which breaks blocks.\r\n    // - explode power:3 <context.location> breakblocks\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addNotesOfType(LocationTag.class);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"power\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"power\", \"p\")) {\r\n                scriptEntry.addObject(\"power\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"source\")\r\n                    && arg.matchesArgumentType(EntityTag.class)\r\n                    && arg.matchesPrefix(\"source\")) {\r\n                scriptEntry.addObject(\"source\", arg.asType(EntityTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"breakblocks\")\r\n                    && arg.matches(\"breakblocks\")) {\r\n                scriptEntry.addObject(\"breakblocks\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"fire\")\r\n                    && arg.matches(\"fire\")) {\r\n                scriptEntry.addObject(\"fire\", new ElementTag(true));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"power\", new ElementTag(1.0));\r\n        scriptEntry.defaultObject(\"fire\", new ElementTag(false));\r\n        scriptEntry.defaultObject(\"breakblocks\", new ElementTag(false));\r\n        scriptEntry.defaultObject(\"location\", Utilities.entryDefaultLocation(scriptEntry, false));\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Missing location argument!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        ElementTag power = scriptEntry.getElement(\"power\");\r\n        ElementTag breakblocks = scriptEntry.getElement(\"breakblocks\");\r\n        ElementTag fire = scriptEntry.getElement(\"fire\");\r\n        EntityTag source = scriptEntry.getObjectTag(\"source\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), location, source, power, breakblocks, fire);\r\n        }\r\n        location.getWorld().createExplosion(location, power.asFloat(), fire.asBoolean(), breakblocks.asBoolean(), source == null ? null : source.getBukkitEntity());\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/FireworkCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\r\nimport com.denizenscript.denizen.utilities.Conversion;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Color;\r\nimport org.bukkit.FireworkEffect;\r\nimport org.bukkit.FireworkEffect.Builder;\r\nimport org.bukkit.entity.Firework;\r\nimport org.bukkit.inventory.meta.FireworkMeta;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class FireworkCommand extends AbstractCommand {\r\n\r\n    public FireworkCommand() {\r\n        setName(\"firework\");\r\n        setSyntax(\"firework (<location>) (power:<#>) (<type>/random) (primary:<color>|...) (fade:<color>|...) (flicker) (trail) (life:<duration>)\");\r\n        setRequiredArguments(0, 8);\r\n        isProcedural = false;\r\n        setPrefixesHandled(\"life\", \"power\", \"primary\", \"fade\");\r\n        setBooleansHandled(\"flicker\", \"trail\");\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Firework\r\n    // @Syntax firework (<location>) (power:<#>) (<type>/random) (primary:<color>|...) (fade:<color>|...) (flicker) (trail) (life:<duration>)\r\n    // @Required 0\r\n    // @Maximum 8\r\n    // @Short Launches a firework with customizable style.\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // This command launches a firework from the specified location.\r\n    //\r\n    // If no location is given, the linked NPC or player's location will be used by default.\r\n    //\r\n    // The power option, which defaults to 1 if left empty, specifies the 'power' integer of the firework, which mainly controls how high the firework will go before exploding.\r\n    // Alternately, the \"life\" option allows you to manually specify a specific duration.\r\n    //\r\n    // The type option which specifies the shape the firework will explode with. If unspecified, 'ball' will be used.\r\n    // Can be any of: ball, ball_large, star, burst, or creeper\r\n    //\r\n    // The primary option specifies what color the firework explosion will start with, as a ColorTag. If unspecified, 'yellow' will be used.\r\n    //\r\n    // The fade option specifies what color the firework explosion will fade into, as a ColorTag.\r\n    //\r\n    // The trail option means the firework will leave a trail behind it.\r\n    //\r\n    // The flicker option means the firework will explode with a flicker effect.\r\n    //\r\n    // @Tags\r\n    // <EntityTag.firework_item>\r\n    // <ItemTag.is_firework>\r\n    // <ItemTag.firework>\r\n    // <entry[saveName].launched_firework> returns a EntityTag of the firework that was launched.\r\n    //\r\n    // @Usage\r\n    // Use to launch a star firework which explodes yellow and fades to white afterwards at the player's location.\r\n    // - firework <player.location> star primary:yellow fade:white\r\n    //\r\n    // @Usage\r\n    // Use to make the firework launch double the height before exploding.\r\n    // - firework <player.location> power:2 star primary:yellow fade:white\r\n    //\r\n    // @Usage\r\n    // Use to launch a firework which leaves a trail.\r\n    // - firework <player.location> random trail\r\n    //\r\n    // @Usage\r\n    // Use to launch a firework which leaves a trail and explodes with a flicker effect at related location.\r\n    // - firework <context.location> random trail flicker\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addNotesOfType(LocationTag.class);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"type\")\r\n                    && arg.matches(\"random\")) {\r\n                scriptEntry.addObject(\"type\", new ElementTag(FireworkEffect.Type.values()[CoreUtilities.getRandom().nextInt(FireworkEffect.Type.values().length)]));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"type\")\r\n                    && arg.matchesEnum(FireworkEffect.Type.class)) {\r\n                scriptEntry.addObject(\"type\", arg.asElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"location\", Utilities.entryDefaultLocation(scriptEntry, false));\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Missing location!\");\r\n        }\r\n        scriptEntry.defaultObject(\"type\", new ElementTag(\"ball\"));\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        final LocationTag location = (LocationTag) scriptEntry.getObject(\"location\");\r\n        ElementTag type = scriptEntry.getElement(\"type\");\r\n        List<ColorTag> primary = scriptEntry.argForPrefixList(\"primary\", ColorTag.class, true);\r\n        if (primary == null) {\r\n            primary = Collections.singletonList(BukkitColorExtensions.fromColor(Color.YELLOW));\r\n        }\r\n        List<ColorTag> fade = scriptEntry.argForPrefixList(\"fade\", ColorTag.class, true);\r\n        boolean flicker = scriptEntry.argAsBoolean(\"flicker\");\r\n        boolean trail = scriptEntry.argAsBoolean(\"trail\");\r\n        ElementTag power = scriptEntry.argForPrefixAsElement(\"power\", \"1\");\r\n        DurationTag life = scriptEntry.argForPrefix(\"life\", DurationTag.class, true);\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), location, type, power, life, db(\"flicker\", flicker), db(\"trail\", trail), db(\"primary colors\", primary), db(\"fade colors\", fade));\r\n        }\r\n        Firework firework = location.getWorld().spawn(location, Firework.class);\r\n        FireworkMeta fireworkMeta = firework.getFireworkMeta();\r\n        fireworkMeta.setPower(power.asInt());\r\n        Builder fireworkBuilder = FireworkEffect.builder();\r\n        fireworkBuilder.with(FireworkEffect.Type.valueOf(type.asString().toUpperCase()));\r\n        fireworkBuilder.withColor(Conversion.convertColors(primary));\r\n        if (fade != null) {\r\n            fireworkBuilder.withFade(Conversion.convertColors(fade));\r\n        }\r\n        if (flicker) {\r\n            fireworkBuilder.withFlicker();\r\n        }\r\n        if (trail) {\r\n            fireworkBuilder.withTrail();\r\n        }\r\n        fireworkMeta.addEffects(fireworkBuilder.build());\r\n        firework.setFireworkMeta(fireworkMeta);\r\n        if (life != null) {\r\n            NMSHandler.entityHelper.setFireworkLifetime(firework, life.getTicksAsInt());\r\n        }\r\n        scriptEntry.saveObject(\"launched_firework\", new EntityTag(firework));\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/GameRuleCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\n\nimport com.denizenscript.denizen.objects.WorldTag;\nimport com.denizenscript.denizen.utilities.world.GameRuleReflect;\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\nimport com.denizenscript.denizencore.objects.Argument;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\nimport org.bukkit.Bukkit;\nimport org.bukkit.GameRule;\nimport org.bukkit.generator.WorldInfo;\n\npublic class GameRuleCommand extends AbstractCommand {\n\n    public GameRuleCommand() {\n        setName(\"gamerule\");\n        setSyntax(\"gamerule [<world>] [<rule>] [<value>]\");\n        setRequiredArguments(3, 3);\n        isProcedural = false;\n    }\n\n    // <--[command]\n    // @Name Gamerule\n    // @Syntax gamerule [<world>] [<rule>] [<value>]\n    // @Required 3\n    // @Maximum 3\n    // @Short Sets a gamerule on the world.\n    // @Group world\n    //\n    // @Description\n    // Sets a gamerule on the world. A list of valid gamerules can be found here: <@link url https://minecraft.wiki/w/Game_rule>\n    //\n    // @Tags\n    // <WorldTag.gamerule[<gamerule>]>\n    //\n    // @Usage\n    // Use to disable fire spreading in world \"Adventure\".\n    // - gamerule Adventure fire_spread_radius_around_player 0\n    //\n    // @Usage\n    // Use to avoid mobs from destroying blocks (creepers, endermen...) and picking items up (zombies, skeletons...) in world \"Adventure\".\n    // - gamerule Adventure mob_griefing false\n    // -->\n\n    @Override\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\n        for (GameRule<?> gameRule : GameRuleReflect.values()) {\n            tab.add(GameRuleReflect.getName(gameRule));\n        }\n        tab.add(Bukkit.getWorlds().stream().map(WorldInfo::getName).toList());\n    }\n\n    @Override\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\n        for (Argument arg : scriptEntry) {\n            if (!scriptEntry.hasObject(\"world\")\n                    && arg.matchesArgumentType(WorldTag.class)) {\n                scriptEntry.addObject(\"world\", arg.asType(WorldTag.class));\n            }\n            else if (!scriptEntry.hasObject(\"gamerule\")) {\n                scriptEntry.addObject(\"gamerule\", arg.asElement());\n            }\n            else if (!scriptEntry.hasObject(\"value\")) {\n                scriptEntry.addObject(\"value\", arg.asElement());\n            }\n            else {\n                arg.reportUnhandled();\n            }\n        }\n        if (!scriptEntry.hasObject(\"world\")) {\n            throw new InvalidArgumentsException(\"Must specify a world!\");\n        }\n        if (!scriptEntry.hasObject(\"gamerule\")) {\n            throw new InvalidArgumentsException(\"Must specify a gamerule!\");\n        }\n        if (!scriptEntry.hasObject(\"value\")) {\n            throw new InvalidArgumentsException(\"Must specify a value!\");\n        }\n    }\n\n    @Override\n    public void execute(ScriptEntry scriptEntry) {\n        WorldTag world = scriptEntry.getObjectTag(\"world\");\n        ElementTag gameRuleInput = scriptEntry.getElement(\"gamerule\");\n        ElementTag valueInput = scriptEntry.getElement(\"value\");\n        if (scriptEntry.dbCallShouldDebug()) {\n            Debug.report(scriptEntry, getName(), world, gameRuleInput, valueInput);\n        }\n        GameRule gameRule = GameRuleReflect.getByName(gameRuleInput.asString());\n        if (gameRule == null) {\n            Debug.echoError(\"Invalid game rule specified: \" + gameRuleInput.asString() + '.');\n            return;\n        }\n        Class<?> gameRuleType = GameRuleReflect.getType(gameRule);\n        Object convertedValue;\n        if (gameRuleType == Integer.class) {\n            if (!valueInput.isInt()) {\n                Debug.echoError(\"Invalid value specified: must be a number.\");\n                return;\n            }\n            convertedValue = valueInput.asInt();\n        }\n        else if (gameRuleType == Boolean.class) {\n            if (!valueInput.isBoolean()) {\n                Debug.echoError(\"Invalid value specified: must be a boolean.\");\n                return;\n            }\n            convertedValue = valueInput.asBoolean();\n        }\n        else {\n            Debug.echoError(\"Unrecognized game rule type '\" + DebugInternals.getFullClassNameOpti(gameRuleType) + \"'! Please report this to the developers.\");\n            return;\n        }\n        if (!world.getWorld().setGameRule(gameRule, convertedValue)) {\n            Debug.echoError(scriptEntry, \"Invalid gamerule!\");\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/LightCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\npublic class LightCommand extends AbstractCommand {\r\n\r\n    public LightCommand() { // TODO: Deprecate? Incompatible with modern paper, and likely not worth the effort of completely remaking.\r\n        setName(\"light\");\r\n        setSyntax(\"light [<location>] [<#>/reset] (duration:<duration>)\");\r\n        setRequiredArguments(2, 3);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Light\r\n    // @Syntax light [<location>] [<#>/reset] (duration:<duration>)\r\n    // @Required 2\r\n    // @Maximum 3\r\n    // @Short Creates a light source at the location with a specified brightness.\r\n    // @Group world\r\n    //\r\n    // @Warning May cause lag spikes, use carefully.\r\n    // @Warning Incompatible with Paper in 1.17+. Use Spigot, or use vanilla Light blocks.\r\n    //\r\n    // @Description\r\n    // This command can create and reset a light source at a specified location, regardless of the type of block.\r\n    // It will be shown to all players near the location until it is reset.\r\n    // The brightness must be between 0 and 15, inclusive.\r\n    // Optionally, specify the amount of time the light should exist before being removed.\r\n    //\r\n    // Note that lights do not persist across server restarts, but will still be visible in the world after a restart until there is a block change near the location (to reset the light).\r\n    //\r\n    // @Tags\r\n    // <LocationTag.light>\r\n    // <LocationTag.light.blocks>\r\n    //\r\n    // @Usage\r\n    // Use to create a bright light at a noted location.\r\n    // - light MyFancyLightOfWool 15\r\n    //\r\n    // @Usage\r\n    // Use to reset the brightness of the location to its original state.\r\n    // - light MyFancyLightOfWool reset\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addNotesOfType(LocationTag.class);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"light\")\r\n                    && arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"light\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"reset\")\r\n                    && arg.matches(\"reset\")) {\r\n                scriptEntry.addObject(\"reset\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"duration\")\r\n                    && arg.matchesPrefix(\"d\", \"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\") ||\r\n                (!scriptEntry.hasObject(\"light\") && !scriptEntry.hasObject(\"reset\"))) {\r\n            throw new InvalidArgumentsException(\"Must specify a valid location and light level.\");\r\n        }\r\n        scriptEntry.defaultObject(\"reset\", new ElementTag(false));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        ElementTag light = scriptEntry.getElement(\"light\");\r\n        ElementTag reset = scriptEntry.getElement(\"reset\");\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), location, reset, light, duration);\r\n        }\r\n        if (!Utilities.isLocationYSafe(location)) {\r\n            Debug.echoError(scriptEntry, \"Invalid light location!\");\r\n            return;\r\n        }\r\n        for (int x = -1; x <= 1; x++) {\r\n            for (int z = -1; z <= 1; z++) {\r\n                location.clone().add(x * 16, 0, z * 16).getChunk().load();\r\n            }\r\n        }\r\n        if (!reset.asBoolean()) {\r\n            int brightness = light.asInt();\r\n            if (brightness < 0 || brightness > 15) {\r\n                Debug.echoError(\"Light brightness must be between 0 and 15, inclusive!\");\r\n                return;\r\n            }\r\n            NMSHandler.instance.createBlockLight(location, brightness, duration == null ? 0 : duration.getTicks());\r\n        }\r\n        else {\r\n            BlockLight.removeLight(location);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/MidiCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.midi.MidiUtil;\r\nimport com.denizenscript.denizen.utilities.midi.NoteBlockReceiver;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.Holdable;\r\n\r\nimport java.io.File;\r\nimport java.util.List;\r\n\r\npublic class MidiCommand extends AbstractCommand implements Holdable {\r\n\r\n    public MidiCommand() {\r\n        setName(\"midi\");\r\n        setSyntax(\"midi [cancel/<file> (tempo:<#.#>) (volume:<#.#>)] (<location>/<entity>|...)\");\r\n        setRequiredArguments(1, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Midi\r\n    // @Syntax midi [cancel/<file> (tempo:<#.#>) (volume:<#.#>)] (<location>/<entity>|...)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Short Plays a midi file at a given location or to a list of players using note block sounds.\r\n    // @Synonyms Music\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // This will fully load a midi song file stored in the '../plugins/Denizen/midi/' folder.\r\n    // The file must be a valid midi file with the extension '.mid'.\r\n    // It will continuously play the song as noteblock songs at the given location or group of players until the song ends.\r\n    // If no location or entity is specified, by default this will play for the attached player.\r\n    //\r\n    // Also, an example Midi song file has been included: \"Denizen\" by Black Coyote. He made it just for us!\r\n    // Check out more of his amazing work at: http://www.youtube.com/user/BlaCoyProductions\r\n    //\r\n    // The midi command is ~waitable. Refer to <@link language ~waitable>.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to play a midi song file on the current player.\r\n    // - midi file:Denizen\r\n    //\r\n    // @Usage\r\n    // Use to play a midi song file at a given location.\r\n    // - midi file:Denizen <player.location>\r\n    //\r\n    // @Usage\r\n    // Use to play a midi song file at a given location to the specified player(s), and wait for it to finish.\r\n    // - ~midi file:Denizen <server.online_players>\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"cancel\")\r\n                    && (arg.matches(\"cancel\") || arg.matches(\"stop\"))) {\r\n                scriptEntry.addObject(\"cancel\", \"true\");\r\n            }\r\n            else if (!scriptEntry.hasObject(\"location\") &&\r\n                    arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\") &&\r\n                    arg.matchesArgumentList(EntityTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(EntityTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"volume\") &&\r\n                    arg.matchesFloat() &&\r\n                    arg.matchesPrefix(\"volume\", \"vol\", \"v\")) {\r\n                scriptEntry.addObject(\"volume\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"tempo\") &&\r\n                    arg.matchesFloat()) {\r\n                scriptEntry.addObject(\"tempo\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"file\")) {\r\n                scriptEntry.addObject(\"file\", arg.asElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"file\")\r\n                && !scriptEntry.hasObject(\"cancel\")) {\r\n            throw new InvalidArgumentsException(\"Missing file (Midi name) argument!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            scriptEntry.defaultObject(\"entities\", Utilities.entryDefaultEntityList(scriptEntry, true));\r\n        }\r\n        scriptEntry.defaultObject(\"tempo\", new ElementTag(1)).defaultObject(\"volume\", new ElementTag(10));\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        boolean cancel = scriptEntry.hasObject(\"cancel\");\r\n        ElementTag filePath = scriptEntry.getElement(\"file\");\r\n        List<EntityTag> entities = (List<EntityTag>) scriptEntry.getObject(\"entities\");\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        float tempo = scriptEntry.getElement(\"tempo\").asFloat();\r\n        float volume = scriptEntry.getElement(\"volume\").asFloat();\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), (cancel ? db(\"cancel\", true) : \"\"), filePath, db(\"entities\", entities), location, db(\"tempo\", tempo), db(\"volume\", volume));\r\n        }\r\n        // Play the midi\r\n        if (!cancel) {\r\n            String fName = scriptEntry.getElement(\"file\").asString();\r\n            if (!fName.endsWith(\".mid\")) {\r\n                fName += \".mid\";\r\n            }\r\n            File file = new File(Denizen.getInstance().getDataFolder(), \"/midi/\" + fName);\r\n            if (!Utilities.canReadFile(file)) {\r\n                Debug.echoError(\"Cannot read from that file path due to security settings in Denizen/config.yml.\");\r\n                return;\r\n            }\r\n            if (!file.exists()) {\r\n                Debug.echoError(scriptEntry, \"Invalid file \" + filePath.asString());\r\n                return;\r\n            }\r\n            NoteBlockReceiver rec;\r\n            if (location != null) {\r\n                rec = MidiUtil.playMidi(file, tempo, volume, location);\r\n            }\r\n            else {\r\n                rec = MidiUtil.playMidi(file, tempo, volume, entities);\r\n            }\r\n            if (rec == null) {\r\n                Debug.echoError(scriptEntry, \"Something went wrong playing a midi!\");\r\n                scriptEntry.setFinished(true);\r\n            }\r\n            else {\r\n                rec.onFinish = () -> scriptEntry.setFinished(true);\r\n            }\r\n        }\r\n        else {\r\n            if (location != null) {\r\n                MidiUtil.stopMidi(location.identify());\r\n            }\r\n            else {\r\n                MidiUtil.stopMidi(entities);\r\n            }\r\n            scriptEntry.setFinished(true);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/ModifyBlockCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.command.TabCompleteHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.Holdable;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ScriptUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.World;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.ExperienceOrb;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.Cancellable;\r\nimport org.bukkit.event.Event;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockBreakEvent;\r\nimport org.bukkit.event.block.BlockPhysicsEvent;\r\nimport org.bukkit.event.block.BlockPlaceEvent;\r\nimport org.bukkit.event.entity.EntityChangeBlockEvent;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.HashSet;\r\nimport java.util.List;\r\n\r\npublic class ModifyBlockCommand extends AbstractCommand implements Listener, Holdable {\r\n\r\n    public ModifyBlockCommand() {\r\n        setName(\"modifyblock\");\r\n        setSyntax(\"modifyblock [<location>|.../<ellipsoid>/<cuboid>] [<material>|...] (no_physics/naturally:<tool>) (delayed) (<script>) (<percent chance>|...) (source:<player>) (max_delay_ms:<#>)\");\r\n        setRequiredArguments(2, 8);\r\n        Bukkit.getPluginManager().registerEvents(this, Denizen.getInstance());\r\n        // Keep the list empty automatically - we don't want to still block physics so much later that something else edited the block!\r\n        Bukkit.getScheduler().scheduleSyncRepeatingTask(Denizen.getInstance(), () -> {\r\n            tick++;\r\n            if (physitick < tick - 1) {\r\n                block_physics.clear();\r\n            }\r\n        }, 2, 2);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name ModifyBlock\r\n    // @Syntax modifyblock [<location>|.../<ellipsoid>/<cuboid>] [<material>|...] (no_physics/naturally:<tool>) (delayed) (<script>) (<percent chance>|...) (source:<player>) (max_delay_ms:<#>)\r\n    // @Required 2\r\n    // @Maximum 8\r\n    // @Short Modifies blocks.\r\n    // @Synonyms SetBlock,ChangeBlock,PlaceBlock,BreakBlock\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Changes blocks in the world based on the criteria given.\r\n    //\r\n    // Use 'no_physics' to place the blocks without physics taking over the modified blocks.\r\n    // This is useful for block types such as portals or water. This does NOT control physics for an extended period of time.\r\n    //\r\n    // Specify (<percent chance>|...) to give a chance of each material being placed (in any material at all).\r\n    //\r\n    // Use 'naturally:' when setting a block to air to break it naturally, meaning that it will drop items. Specify the tool item that should be used for calculating drops.\r\n    //\r\n    // Use 'delayed' to make the modifyblock slowly edit blocks at a time pace roughly equivalent to the server's limits.\r\n    // Optionally, specify 'max_delay_ms' to control how many milliseconds the 'delayed' set can run for in any given tick (defaults to 50).\r\n    //\r\n    // Note that specifying a list of locations will take more time in parsing than in the actual block modification.\r\n    //\r\n    // Optionally, specify a script to be ran after the delayed edits finish. (Doesn't fire if delayed is not set.)\r\n    //\r\n    // Optionally, specify a source player. When set, Bukkit events will fire that identify that player as the source of a change, and potentially cancel the change.\r\n    // The source argument might cause weird interoperation with other plugins, use with caution.\r\n    //\r\n    // The modifyblock command is ~waitable. Refer to <@link language ~waitable>.\r\n    //\r\n    // @Tags\r\n    // <LocationTag.material>\r\n    //\r\n    // @Usage\r\n    // Use to change the block a player is looking at to stone.\r\n    // - modifyblock <player.cursor_on> stone\r\n    //\r\n    // @Usage\r\n    // Use to modify an entire cuboid to half stone, half dirt.\r\n    // - modifyblock <player.location.to_cuboid[<player.cursor_on>]> stone|dirt\r\n    //\r\n    // @Usage\r\n    // Use to modify an entire cuboid to some stone, some dirt, and some left as it is.\r\n    // - modifyblock <player.location.to_cuboid[<player.cursor_on>]> stone|dirt 25|25\r\n    //\r\n    // @Usage\r\n    // Use to modify the ground beneath the player's feet.\r\n    // - modifyblock <player.location.add[2,-1,2].to_cuboid[<player.location.add[-2,-1,-2]>]> RED_WOOL\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        TabCompleteHelper.tabCompleteBlockMaterials(tab);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (arg.matchesArgumentType(CuboidTag.class)\r\n                    && !scriptEntry.hasObject(\"locations\")\r\n                    && !scriptEntry.hasObject(\"location_list\")\r\n                    && (arg.startsWith(\"cu@\") || !arg.getRawValue().contains(\"|\"))) {\r\n                scriptEntry.addObject(\"locations\", arg.asType(CuboidTag.class).getBlockLocationsUnfiltered(false));\r\n            }\r\n            else if (arg.matchesArgumentType(EllipsoidTag.class)\r\n                    && !scriptEntry.hasObject(\"locations\")\r\n                    && !scriptEntry.hasObject(\"location_list\")\r\n                    && (arg.startsWith(\"ellipsoid@\") || !arg.getRawValue().contains(\"|\"))) {\r\n                scriptEntry.addObject(\"locations\", arg.asType(EllipsoidTag.class).getBlockLocationsUnfiltered(false));\r\n            }\r\n            else if (arg.matchesArgumentList(LocationTag.class)\r\n                    && !scriptEntry.hasObject(\"locations\")\r\n                    && !scriptEntry.hasObject(\"location_list\")) {\r\n                scriptEntry.addObject(\"location_list\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"materials\")\r\n                    && arg.matchesArgumentList(MaterialTag.class)) {\r\n                scriptEntry.addObject(\"materials\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"radius\")\r\n                    && arg.matchesPrefix(\"radius\", \"r\")\r\n                    && arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"radius\", new ElementTag(arg.getValue()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"height\")\r\n                    && arg.matchesPrefix(\"height\", \"h\")\r\n                    && arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"height\", new ElementTag(arg.getValue()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"depth\")\r\n                    && arg.matchesPrefix(\"depth\", \"d\")\r\n                    && arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"depth\", new ElementTag(arg.getValue()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"source\")\r\n                    && arg.matchesPrefix(\"source\")\r\n                    && arg.matchesArgumentType(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"source\", arg.asType(PlayerTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"physics\")\r\n                    && arg.matches(\"no_physics\")) {\r\n                scriptEntry.addObject(\"physics\", new ElementTag(false));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"natural\")\r\n                    && arg.matches(\"naturally\")) {\r\n                scriptEntry.addObject(\"natural\", new ItemTag(new ItemStack(Material.AIR)));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"natural\")\r\n                    && arg.matchesPrefix(\"naturally\")\r\n                    && arg.matchesArgumentType(ItemTag.class)) {\r\n                scriptEntry.addObject(\"natural\", arg.asType(ItemTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"max_delay_ms\")\r\n                    && arg.matchesPrefix(\"max_delay_ms\")\r\n                    && arg.matchesInteger()) {\r\n                scriptEntry.addObject(\"max_delay_ms\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"delayed\")\r\n                    && arg.matches(\"delayed\")) {\r\n                scriptEntry.addObject(\"delayed\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"script\")\r\n                    && arg.limitToOnlyPrefix(\"script\")\r\n                    && arg.matchesArgumentType(ScriptTag.class)) {\r\n                scriptEntry.addObject(\"script\", arg.asType(ScriptTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"percents\")\r\n                    && arg.limitToOnlyPrefix(\"percents\")) {\r\n                scriptEntry.addObject(\"percents\", arg.asType(ListTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"materials\")) {\r\n            throw new InvalidArgumentsException(\"Missing material argument!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"locations\") && !scriptEntry.hasObject(\"location_list\")) {\r\n            throw new InvalidArgumentsException(\"Missing location argument!\");\r\n        }\r\n        scriptEntry.defaultObject(\"radius\", new ElementTag(0))\r\n                .defaultObject(\"max_delay_ms\", new ElementTag(50))\r\n                .defaultObject(\"height\", new ElementTag(0))\r\n                .defaultObject(\"depth\", new ElementTag(0))\r\n                .defaultObject(\"physics\", new ElementTag(true))\r\n                .defaultObject(\"delayed\", new ElementTag(false));\r\n    }\r\n\r\n    public static LocationTag getLocAt(ListTag list, int index, ScriptEntry entry) {\r\n        ObjectTag obj = list.getObject(index);\r\n        if (obj instanceof LocationTag) {\r\n            return (LocationTag) obj;\r\n        }\r\n        else {\r\n            return LocationTag.valueOf(obj.toString(), entry.context);\r\n        }\r\n    }\r\n\r\n    public static boolean isLocationBad(ScriptEntry entry, LocationTag loc) {\r\n        if (loc == null) {\r\n            Debug.echoError(entry, \"Input is not a valid LocationTag\");\r\n            return true;\r\n        }\r\n        if (loc.getWorld() == null) {\r\n            Debug.echoError(entry, \"Input '\" + loc + \"' is missing a world value\");\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        final ListTag materials = scriptEntry.getObjectTag(\"materials\");\r\n        final List<LocationTag> locations = (List<LocationTag>) scriptEntry.getObject(\"locations\");\r\n        final ListTag location_list = scriptEntry.getObjectTag(\"location_list\");\r\n        final ElementTag physics = scriptEntry.getElement(\"physics\");\r\n        final ItemTag natural = scriptEntry.getObjectTag(\"natural\");\r\n        final ElementTag delayed = scriptEntry.getElement(\"delayed\");\r\n        final ElementTag maxDelayMs = scriptEntry.getElement(\"max_delay_ms\");\r\n        final ElementTag radiusElement = scriptEntry.getElement(\"radius\");\r\n        final ElementTag heightElement = scriptEntry.getElement(\"height\");\r\n        final ElementTag depthElement = scriptEntry.getElement(\"depth\");\r\n        final ScriptTag script = scriptEntry.getObjectTag(\"script\");\r\n        final PlayerTag source = scriptEntry.getObjectTag(\"source\");\r\n        ListTag percents = scriptEntry.getObjectTag(\"percents\");\r\n        if (percents != null && percents.size() != materials.size()) {\r\n            Debug.echoError(scriptEntry, \"Percents length != materials length\");\r\n            percents = null;\r\n        }\r\n        final List<MaterialTag> materialList = materials.filter(MaterialTag.class, scriptEntry);\r\n        for (MaterialTag mat : materialList) {\r\n            if (!mat.getMaterial().isBlock()) {\r\n                Debug.echoError(\"Material '\" + mat.getMaterial().name() + \"' is not a block material\");\r\n                scriptEntry.setFinished(true);\r\n                return;\r\n            }\r\n        }\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), materials, physics, radiusElement, heightElement, depthElement, natural,\r\n                    delayed, maxDelayMs, script, percents, source, (locations == null ? location_list : db(\"locations\", locations)));\r\n        }\r\n        Player sourcePlayer = source == null ? null : source.getPlayerEntity();\r\n        final boolean doPhysics = physics.asBoolean();\r\n        final int radius = radiusElement.asInt();\r\n        final int height = heightElement.asInt();\r\n        final int depth = depthElement.asInt();\r\n        List<Float> percentages = null;\r\n        if (percents != null) {\r\n            percentages = new ArrayList<>();\r\n            for (String str : percents) {\r\n                percentages.add(new ElementTag(str).asFloat());\r\n            }\r\n        }\r\n        final List<Float> percs = percentages;\r\n        if (locations == null && location_list == null) {\r\n            Debug.echoError(\"Must specify a valid location!\");\r\n            scriptEntry.setFinished(true);\r\n            return;\r\n        }\r\n        if ((location_list != null && location_list.isEmpty()) || (locations != null && locations.isEmpty())) {\r\n            scriptEntry.setFinished(true);\r\n            return;\r\n        }\r\n        if (materialList.isEmpty()) {\r\n            Debug.echoError(\"Must specify a valid material!\");\r\n            scriptEntry.setFinished(true);\r\n            return;\r\n        }\r\n        no_physics = !doPhysics;\r\n        if (delayed.asBoolean()) {\r\n            final long maxDelay = maxDelayMs.asLong();\r\n            new BukkitRunnable() {\r\n                int index = 0;\r\n                @Override\r\n                public void run() {\r\n                    try {\r\n                        long start = CoreUtilities.monotonicMillis();\r\n                        LocationTag loc;\r\n                        if (locations != null) {\r\n                            loc = locations.get(0);\r\n                        }\r\n                        else {\r\n                            loc = getLocAt(location_list, 0, scriptEntry);\r\n                        }\r\n                        if (isLocationBad(scriptEntry, loc)) {\r\n                            scriptEntry.setFinished(true);\r\n                            cancel();\r\n                            return;\r\n                        }\r\n                        boolean was_static = preSetup(loc);\r\n                        while ((locations != null && locations.size() > index) || (location_list != null && location_list.size() > index)) {\r\n                            LocationTag nLoc;\r\n                            if (locations != null) {\r\n                                nLoc = locations.get(index);\r\n                            }\r\n                            else {\r\n                                nLoc = getLocAt(location_list, index, scriptEntry);\r\n                            }\r\n                            if (isLocationBad(scriptEntry, nLoc)) {\r\n                                scriptEntry.setFinished(true);\r\n                                cancel();\r\n                                return;\r\n                            }\r\n                            handleLocation(nLoc, index, materialList, doPhysics, natural, radius, height, depth, percs, sourcePlayer, scriptEntry);\r\n                            index++;\r\n                            if (CoreUtilities.monotonicMillis() - start > maxDelay) {\r\n                                break;\r\n                            }\r\n                        }\r\n                        postComplete(loc, was_static);\r\n                        if ((locations != null && locations.size() == index) || (location_list != null && location_list.size() == index)) {\r\n                            if (script != null) {\r\n                                ScriptUtilities.createAndStartQueue(script.getContainer(), null, scriptEntry.entryData, null, null, null, null, null, scriptEntry);\r\n                            }\r\n                            scriptEntry.setFinished(true);\r\n                            cancel();\r\n                        }\r\n                    }\r\n                    catch (Throwable ex) {\r\n                        Debug.echoError(ex);\r\n                    }\r\n                }\r\n            }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n        }\r\n        else {\r\n            LocationTag loc;\r\n            if (locations != null) {\r\n                loc = locations.get(0);\r\n            }\r\n            else {\r\n                loc = getLocAt(location_list, 0, scriptEntry);\r\n            }\r\n            if (isLocationBad(scriptEntry, loc)) {\r\n                scriptEntry.setFinished(true);\r\n                return;\r\n            }\r\n            boolean was_static = preSetup(loc);\r\n            int index = 0;\r\n            if (locations != null) {\r\n                for (LocationTag obj : locations) {\r\n                    if (isLocationBad(scriptEntry, obj)) {\r\n                        scriptEntry.setFinished(true);\r\n                        return;\r\n                    }\r\n                    handleLocation(obj, index, materialList, doPhysics, natural, radius, height, depth, percentages, sourcePlayer, scriptEntry);\r\n                    index++;\r\n                }\r\n            }\r\n            else {\r\n                for (int i = 0; i < location_list.size(); i++) {\r\n                    LocationTag obj = getLocAt(location_list, i, scriptEntry);\r\n                    if (isLocationBad(scriptEntry, obj)) {\r\n                        scriptEntry.setFinished(true);\r\n                        return;\r\n                    }\r\n                    handleLocation(obj, index, materialList, doPhysics, natural, radius, height, depth, percentages, sourcePlayer, scriptEntry);\r\n                    index++;\r\n                }\r\n            }\r\n            postComplete(loc, was_static);\r\n            scriptEntry.setFinished(true);\r\n        }\r\n    }\r\n\r\n    boolean preSetup(LocationTag loc0) {\r\n        // Freeze the first world in the list.\r\n        World world = loc0.getWorld();\r\n        boolean was_static = NMSHandler.worldHelper.isStatic(world);\r\n        if (no_physics) {\r\n            NMSHandler.worldHelper.setStatic(world, true);\r\n        }\r\n        return was_static;\r\n    }\r\n\r\n    void postComplete(Location loc, boolean was_static) {\r\n        // Unfreeze the first world in the list.\r\n        if (no_physics) {\r\n            NMSHandler.worldHelper.setStatic(loc.getWorld(), was_static);\r\n        }\r\n        no_physics = false;\r\n    }\r\n\r\n    <T extends Event & Cancellable> boolean callEvent(T event, ScriptEntry scriptEntry) {\r\n        Bukkit.getPluginManager().callEvent(event);\r\n        if (event.isCancelled()) {\r\n            Debug.echoDebug(scriptEntry, \"Source event cancelled, not changing block.\");\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    void handleLocation(LocationTag location, int index, List<MaterialTag> materialList, boolean doPhysics,\r\n                        ItemTag natural, int radius, int height, int depth, List<Float> percents, Player source, ScriptEntry entry) {\r\n        MaterialTag material;\r\n        if (percents == null) {\r\n            material = materialList.get(index % materialList.size());\r\n        }\r\n        else {\r\n            material = null;\r\n            for (int i = 0; i < materialList.size(); i++) {\r\n                float perc = percents.get(i) / 100f;\r\n                if (CoreUtilities.getRandom().nextDouble() <= perc) {\r\n                    material = materialList.get(i);\r\n                    break;\r\n                }\r\n            }\r\n            if (material == null) {\r\n                return;\r\n            }\r\n        }\r\n        location = location.getBlockLocation();\r\n        World world = location.getWorld();\r\n        Block block = location.getBlock();\r\n        int xpToDrop = natural != null && material.getMaterial() == Material.AIR ? NMSHandler.blockHelper.getExpDrop(block, natural.getItemStack()) : 0;\r\n        if (source != null) {\r\n            if (material.getMaterial() == Material.AIR) {\r\n                BlockBreakEvent breakEvent = new BlockBreakEvent(block, source);\r\n                breakEvent.setExpToDrop(xpToDrop);\r\n                if (callEvent(breakEvent, entry)) {\r\n                    return;\r\n                }\r\n                setBlock(location, material, doPhysics, breakEvent.isDropItems() ? natural : null, breakEvent.getExpToDrop());\r\n            }\r\n            else {\r\n                BlockState originalState = block.getState();\r\n                setBlock(location, material, doPhysics, natural, xpToDrop);\r\n                BlockPlaceEvent placeEvent = new BlockPlaceEvent(block, originalState, block, new ItemStack(material.getMaterial()), source, true, EquipmentSlot.HAND);\r\n                if (callEvent(placeEvent, entry) || !placeEvent.canBuild()) {\r\n                    originalState.update(true, doPhysics);\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n        else {\r\n            setBlock(location, material, doPhysics, natural, xpToDrop);\r\n        }\r\n        if (radius != 0) {\r\n            for (int x = 0; x < 2 * radius + 1; x++) {\r\n                for (int z = 0; z < 2 * radius + 1; z++) {\r\n                    setBlock(new Location(world, location.getX() + x - radius, location.getY(), location.getZ() + z - radius), material, doPhysics, natural, xpToDrop);\r\n                }\r\n            }\r\n        }\r\n        if (height != 0) {\r\n            for (int x = 0; x < 2 * radius + 1; x++) {\r\n                for (int z = 0; z < 2 * radius + 1; z++) {\r\n                    for (int y = 1; y < height + 1; y++) {\r\n                        setBlock(new Location(world, location.getX() + x - radius, location.getY() + y, location.getZ() + z - radius), material, doPhysics, natural, xpToDrop);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (depth != 0) {\r\n            for (int x = 0; x < 2 * radius + 1; x++) {\r\n                for (int z = 0; z < 2 * radius + 1; z++) {\r\n                    for (int y = 1; y < depth + 1; y++) {\r\n                        setBlock(new Location(world, location.getX() + x - radius, location.getY() - y, location.getZ() + z - radius), material, doPhysics, natural, xpToDrop);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void setBlock(Location location, MaterialTag material, boolean physics, ItemTag natural, int xpToDrop) {\r\n        if (physics) {\r\n            block_physics.remove(location);\r\n        }\r\n        else {\r\n            block_physics.add(location);\r\n            physitick = tick;\r\n        }\r\n        if (!Utilities.isLocationYSafe(location)) {\r\n            Debug.echoError(\"Invalid modifyblock location: \" + new LocationTag(location));\r\n            return;\r\n        }\r\n        if (xpToDrop > 0) {\r\n            location.getWorld().spawn(location, ExperienceOrb.class, orb -> orb.setExperience(xpToDrop));\r\n        }\r\n        if (natural != null && material.getMaterial() == Material.AIR) {\r\n            location.getBlock().breakNaturally(natural.getItemStack());\r\n        }\r\n        else {\r\n            location.getBlock().setBlockData(material.getModernData(), physics);\r\n        }\r\n    }\r\n\r\n    public static boolean no_physics = false;\r\n\r\n    public static final HashSet<Location> block_physics = new HashSet<>();\r\n\r\n    public static long tick = 0;\r\n\r\n    public static long physitick = 0;\r\n\r\n    @EventHandler\r\n    public void blockPhysics(BlockPhysicsEvent event) {\r\n        if (no_physics) {\r\n            event.setCancelled(true);\r\n        }\r\n        if (block_physics.contains(event.getBlock().getLocation())) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void blockChanges(EntityChangeBlockEvent event) {\r\n        if (event.getEntity().getType() != EntityType.FALLING_BLOCK) {\r\n            return;\r\n        }\r\n        if (no_physics) {\r\n            event.setCancelled(true);\r\n        }\r\n        if (block_physics.contains(event.getBlock().getLocation())) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/PlayEffectCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.LegacyParticleNaming;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.*;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\nimport java.util.Random;\r\n\r\npublic class PlayEffectCommand extends AbstractCommand {\r\n\r\n    public static final List<Particle> VISIBLE_PARTICLES = new ArrayList<>(Arrays.asList(Particle.values()));\r\n\r\n    static {\r\n        VISIBLE_PARTICLES.removeAll(List.of(Particle.valueOf(\"SUSPENDED\"), Particle.valueOf(\"SUSPENDED_DEPTH\"), Particle.valueOf(\"WATER_BUBBLE\")));\r\n    }\r\n\r\n    public PlayEffectCommand() {\r\n        setName(\"playeffect\");\r\n        setSyntax(\"playeffect [effect:<name>] [at:<location>|...] (data:<#.#>) (special_data:<map>) (visibility:<#.#>) (quantity:<#>) (offset:<#.#>,<#.#>,<#.#>) (targets:<player>|...) (velocity:<vector>)\");\r\n        setRequiredArguments(2, 8);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[language]\r\n    // @name Particle Effects\r\n    // @group Useful Lists\r\n    // @description\r\n    // All the effects listed here can be used by <@link command PlayEffect> to display visual effects or play sounds\r\n    //\r\n    // Effects:\r\n    // - Everything on <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html>\r\n    // - Everything on <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Effect.html>\r\n    // - RANDOM (chooses a random visual effect from the Particle list)\r\n    // -->\r\n\r\n    // <--[command]\r\n    // @Name PlayEffect\r\n    // @Syntax playeffect [effect:<name>] [at:<location>|...] (data:<#.#>) (special_data:<map>) (visibility:<#.#>) (quantity:<#>) (offset:<#.#>,<#.#>,<#.#>) (targets:<player>|...) (velocity:<vector>)\r\n    // @Required 2\r\n    // @Maximum 8\r\n    // @Short Plays a visible or audible effect at the location.\r\n    // @Synonyms Particle\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Allows the playing of particle effects anywhere without the need of the source it comes from originally.\r\n    // The particles you may use, can come from sources such as a potion effect or a portal/Enderman with their particles respectively.\r\n    // Some particles have different data which may include different behavior depending on the data. Default data is 0\r\n    // Specifying a visibility value changes the sight radius of the effect. For example if visibility is 15; Targeted players won't see it unless they are 15 blocks or closer.\r\n    // You can add a quantity value that allow multiple of the same effect played at the same time. If an offset is set, each particle will be played at a different location in the offset area.\r\n    // Everyone will see the particle effects unless a target has been specified.\r\n    // See <@link language Particle Effects> for a list of valid effect names.\r\n    //\r\n    // Version change note: The original PlayEffect command raised all location inputs 1 block-height upward to avoid effects playing underground when played at eg a player's location.\r\n    // This was found to cause too much confusion, so it is no longer on by default. However, it will still happen for older commands.\r\n    // The distinction is in whether you include the (now expected to use) \"at:\" prefix on your location argument.\r\n    // If you do not have this prefix, the system will assume your command is older, and will apply the 1-block height offset.\r\n    //\r\n    // Some particles will require input to the \"special_data\" argument. The data input is a MapTag with different keys per particle.\r\n    // - For DUST particles, the input is of format [size=<size>;color=<color>], e.g. \"[size=1.2;color=red]\".\r\n    // - For DUST_COLOR_TRANSITION particles, the input is of format [size=<size>;from=<color>;to=<color>], e.g \"[size=1.2;from=red;to=blue]\".\r\n    // - For BLOCK, BLOCK_CRUMBLE, BLOCK_MARKER, DUST_PILLAR, or FALLING_DUST particles, the input is of format [material=<material>], e.g. [material=stone]\r\n    // - For VIBRATION particles, the input is of format [origin=<location>;destination=<location/entity>;duration=<duration>], for example: [origin=<player.location>;destination=<player.cursor_on>;duration=5s]\r\n    // - For ITEM particles, the input is of format [item=<item>], e.g. \"[item=stick]\".\r\n    // - For TRAIL particles, the input is of format [color=<color>;target=<location>;duration=<duration>], e.g. \"[color=red;target=<player.cursor_on>;duration=20s]\".\r\n    // - For ENTITY_EFFECT particles, the input is of format [color=<color>], e.g. \"[color=green]\".\r\n    // - For SHRIEK particles, the input is of format [duration=<duration>], e.g. \"[duration=1m]\".\r\n    // - For SCULK_CHARGE particles, the input is of format [radians=<element>], e.g. \"[radians=<element[90].to_radians>]\".\r\n    //\r\n    // Optionally specify a velocity vector for standard particles to move. Note that this ignores the 'data' input if used.\r\n    //\r\n    // @Tags\r\n    // <server.effect_types>\r\n    // <server.particle_types>\r\n    //\r\n    // @Usage\r\n    // Use to create a fake explosion.\r\n    // - playeffect effect:EXPLOSION_HUGE at:<player.location> visibility:500 quantity:10 offset:2.0\r\n    //\r\n    // @Usage\r\n    // Use to play a cloud effect.\r\n    // - playeffect effect:CLOUD at:<player.location.add[0,5,0]> quantity:20 data:1 offset:0.0\r\n    //\r\n    // @Usage\r\n    // Use to play some effects at spawn.\r\n    // - playeffect effect:FIREWORKS_SPARK at:<world[world].spawn_location> visibility:100 quantity:375 data:0 offset:50.0\r\n    //\r\n    // @Usage\r\n    // Use to spawn a cloud of rainbow-colored ENTITY_EFFECT particles around yourself.\r\n    // - foreach <util.color_names> as:color:\r\n    //     - playeffect effect:ENTITY_EFFECT at:<player.eye_location> quantity:25 special_data:[color=<[color]>]\r\n    //\r\n    // @Usage\r\n    // Use to shoot particles in to the direction you're looking at.\r\n    // - repeat 10:\r\n    //     - playeffect effect:TRAIL at:<player.eye_location> quantity:1 offset:0 special_data:[color=red;target=<player.eye_location.ray_trace[default=air]>;duration=5s]\r\n    //     - wait 1t\r\n    //\r\n    // @Usage\r\n    // Use to spawn a SCULK_CHARGE effect upside down.\r\n    // - playeffect effect:SCULK_CHARGE at:<player.eye_location.add[0,1,0]> quantity:1 offset:0 special_data:[radians=<element[180].to_radians>]\r\n    //\r\n    // @Usage\r\n    // Use to play a SHRIEK effect with a 5-second delay.\r\n    // - playeffect effect:SHRIEK at:<player.eye_location.add[0,1,0]> quantity:1 special_data:[duration=5s]\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addWithPrefix(\"effect:\", Particle.values());\r\n        tab.addWithPrefix(\"effect:\", Effect.values());\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentList(LocationTag.class)\r\n                    && (arg.matchesPrefix(\"at\") || !arg.hasPrefix())) {\r\n                if (arg.matchesPrefix(\"at\")) {\r\n                    scriptEntry.addObject(\"no_offset\", new ElementTag(true));\r\n                }\r\n                scriptEntry.addObject(\"location\", arg.asType(ListTag.class).filter(LocationTag.class, scriptEntry));\r\n                continue;\r\n            }\r\n            else if (!scriptEntry.hasObject(\"effect\") &&\r\n                    !scriptEntry.hasObject(\"particleeffect\") &&\r\n                    !scriptEntry.hasObject(\"iconcrack\")) {\r\n                String particleName = CoreUtilities.toUpperCase(arg.getValue());\r\n                Particle particle = Utilities.elementToEnumlike(new ElementTag(particleName), Particle.class);\r\n                if (particle != null) {\r\n                    scriptEntry.addObject(\"particleeffect\", particle);\r\n                    continue;\r\n                }\r\n                particle = LegacyParticleNaming.legacyParticleNames.get(particleName);\r\n                if (particle != null) {\r\n                    BukkitImplDeprecations.oldSpigotNames.warn(scriptEntry);\r\n                    scriptEntry.addObject(\"particleeffect\", particle);\r\n                    continue;\r\n                }\r\n                if (arg.matches(\"barrier\") && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n                    scriptEntry.addObject(\"particleeffect\", Particle.BLOCK_MARKER);\r\n                    scriptEntry.addObject(\"special_data\", new ElementTag(\"barrier\"));\r\n                    continue;\r\n                }\r\n                else if (arg.matches(\"random\")) {\r\n                    // Get another effect if \"RANDOM\" is used\r\n                    scriptEntry.addObject(\"particleeffect\", VISIBLE_PARTICLES.get(CoreUtilities.getRandom().nextInt(VISIBLE_PARTICLES.size())));\r\n                    continue;\r\n                }\r\n                else if (arg.startsWith(\"iconcrack_\")) {\r\n                    BukkitImplDeprecations.oldPlayEffectSpecials.warn(scriptEntry);\r\n                    // Allow iconcrack_[item] for item break effects (ex: iconcrack_stone)\r\n                    String shrunk = arg.getValue().substring(\"iconcrack_\".length());\r\n                    ItemTag item = ItemTag.valueOf(shrunk, scriptEntry.context);\r\n                    if (item != null) {\r\n                        scriptEntry.addObject(\"iconcrack\", item);\r\n                    }\r\n                    else {\r\n                        Debug.echoError(\"Invalid iconcrack_[item]. Must be a valid ItemTag!\");\r\n                    }\r\n                    continue;\r\n                }\r\n                else if (arg.matchesEnum(Effect.class)) {\r\n                    scriptEntry.addObject(\"effect\", Effect.valueOf(arg.getValue().toUpperCase()));\r\n                    continue;\r\n                }\r\n            }\r\n            if (!scriptEntry.hasObject(\"radius\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"visibility\", \"v\", \"radius\", \"r\")) {\r\n                scriptEntry.addObject(\"radius\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"data\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"data\", \"d\")) {\r\n                scriptEntry.addObject(\"data\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"special_data\")\r\n                    && arg.matchesPrefix(\"special_data\")) {\r\n                scriptEntry.addObject(\"special_data\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"quantity\")\r\n                    && arg.matchesInteger()\r\n                    && arg.matchesPrefix(\"qty\", \"q\", \"quantity\")) {\r\n                if (arg.matchesPrefix(\"q\", \"qty\")) {\r\n                    BukkitImplDeprecations.qtyTags.warn(scriptEntry);\r\n                }\r\n                scriptEntry.addObject(\"quantity\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"offset\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"offset\", \"o\")) {\r\n                double offset = arg.asElement().asDouble();\r\n                scriptEntry.addObject(\"offset\", new LocationTag(null, offset, offset, offset));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"offset\")\r\n                    && arg.matchesArgumentType(LocationTag.class)\r\n                    && arg.matchesPrefix(\"offset\", \"o\")) {\r\n                scriptEntry.addObject(\"offset\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"velocity\")\r\n                    && arg.matchesArgumentType(LocationTag.class)\r\n                    && arg.matchesPrefix(\"velocity\")) {\r\n                scriptEntry.addObject(\"velocity\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"targets\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)\r\n                    && arg.matchesPrefix(\"targets\", \"target\", \"t\")) {\r\n                scriptEntry.addObject(\"targets\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        scriptEntry.defaultObject(\"data\", new ElementTag(0));\r\n        scriptEntry.defaultObject(\"radius\", new ElementTag(15));\r\n        scriptEntry.defaultObject(\"quantity\", new ElementTag(1));\r\n        scriptEntry.defaultObject(\"offset\", new LocationTag(null, 0.5, 0.5, 0.5));\r\n        if (!scriptEntry.hasObject(\"effect\") &&\r\n                !scriptEntry.hasObject(\"particleeffect\") &&\r\n                !scriptEntry.hasObject(\"iconcrack\")) {\r\n            throw new InvalidArgumentsException(\"Missing effect argument!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Missing location argument!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        List<LocationTag> locations = (List<LocationTag>) scriptEntry.getObject(\"location\");\r\n        List<PlayerTag> targets = (List<PlayerTag>) scriptEntry.getObject(\"targets\");\r\n        Effect effect = (Effect) scriptEntry.getObject(\"effect\");\r\n        Particle particleEffect = (Particle) scriptEntry.getObject(\"particleeffect\");\r\n        ItemTag iconcrack = scriptEntry.getObjectTag(\"iconcrack\");\r\n        ElementTag radius = scriptEntry.getElement(\"radius\");\r\n        ElementTag data = scriptEntry.getElement(\"data\");\r\n        ElementTag quantity = scriptEntry.getElement(\"quantity\");\r\n        ElementTag no_offset = scriptEntry.getElement(\"no_offset\");\r\n        boolean should_offset = no_offset == null || !no_offset.asBoolean();\r\n        LocationTag offset = scriptEntry.getObjectTag(\"offset\");\r\n        ElementTag special_data = scriptEntry.getElement(\"special_data\");\r\n        LocationTag velocity = scriptEntry.getObjectTag(\"velocity\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), (effect != null ? db(\"effect\", effect.name()) : particleEffect != null ? db(\"special effect\", particleEffect.name()) : iconcrack),\r\n                    db(\"locations\", locations), db(\"targets\", targets), radius, data, quantity, offset, special_data, velocity, (should_offset ? db(\"note\", \"Location will be offset 1 block-height upward (see documentation)\") : \"\"));\r\n        }\r\n        for (LocationTag location : locations) {\r\n            if (should_offset) {\r\n                // Slightly increase the location's Y so effects don't seem to come out of the ground\r\n                location = new LocationTag(location.clone().add(0, 1, 0));\r\n            }\r\n            // Play the Bukkit effect the number of times specified\r\n            if (effect != null) {\r\n                for (int n = 0; n < quantity.asInt(); n++) {\r\n                    if (targets != null) {\r\n                        for (PlayerTag player : targets) {\r\n                            if (player.isValid() && player.isOnline()) {\r\n                                player.getPlayerEntity().playEffect(location, effect, data.asInt());\r\n                            }\r\n                        }\r\n                    }\r\n                    else {\r\n                        location.getWorld().playEffect(location, effect, data.asInt(), radius.asInt());\r\n                    }\r\n                }\r\n            }\r\n            // Play a ParticleEffect\r\n            else if (particleEffect != null) {\r\n                List<Player> players = new ArrayList<>();\r\n                if (targets == null) {\r\n                    float rad = radius.asFloat();\r\n                    for (Player player : location.getWorld().getPlayers()) {\r\n                        if (player.getLocation().distanceSquared(location) < rad * rad) {\r\n                            players.add(player);\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    for (PlayerTag player : targets) {\r\n                        if (player.isValid() && player.isOnline()) {\r\n                            players.add(player.getPlayerEntity());\r\n                        }\r\n                    }\r\n                }\r\n                Class<?> clazz = particleEffect.getDataType() == Void.class ? null : particleEffect.getDataType();\r\n                Object dataObject = null;\r\n                if (clazz != null) {\r\n                    if (special_data == null) {\r\n                        Debug.echoError(\"Missing required special data for particle: \" + particleEffect.name());\r\n                        return;\r\n                    }\r\n                    MapTag dataMap = MapTag.valueOf(special_data.asString(), scriptEntry.getContext());\r\n                    if (dataMap == null) {\r\n                        ListTag dataList = ListTag.valueOf(special_data.asString(), scriptEntry.getContext());\r\n                        BukkitImplDeprecations.playEffectSpecialDataListInput.warn(scriptEntry.getContext());\r\n                        if (clazz == Particle.DustOptions.class) {\r\n                            if (dataList.size() != 2) {\r\n                                Debug.echoError(\"DustOptions special_data must have 2 list entries for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            float size = Float.parseFloat(dataList.get(0));\r\n                            ColorTag color = ColorTag.valueOf(dataList.get(1), scriptEntry.context);\r\n                            dataObject = new Particle.DustOptions(BukkitColorExtensions.getColor(color), size);\r\n                        }\r\n                        else if (clazz == BlockData.class) {\r\n                            MaterialTag blockMaterial = MaterialTag.valueOf(special_data.asString(), scriptEntry.getContext());\r\n                            dataObject = blockMaterial.getModernData();\r\n                        }\r\n                        else if (clazz == ItemStack.class) {\r\n                            ItemTag itemType = ItemTag.valueOf(special_data.asString(), scriptEntry.getContext());\r\n                            dataObject = itemType.getItemStack();\r\n                        }\r\n                        else if (clazz == Particle.DustTransition.class) {\r\n                            if (dataList.size() != 3) {\r\n                                Debug.echoError(\"DustTransition special_data must have 3 list entries for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            else {\r\n                                float size = Float.parseFloat(dataList.get(0));\r\n                                ColorTag fromColor = ColorTag.valueOf(dataList.get(1), scriptEntry.context);\r\n                                ColorTag toColor = ColorTag.valueOf(dataList.get(2), scriptEntry.context);\r\n                                dataObject = new Particle.DustTransition(BukkitColorExtensions.getColor(fromColor), BukkitColorExtensions.getColor(toColor), size);\r\n                            }\r\n                        }\r\n                        else if (clazz == Vibration.class) {\r\n                            if (dataList.size() != 3) {\r\n                                Debug.echoError(\"Vibration special_data must have 3 list entries for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            else {\r\n                                DurationTag duration = dataList.getObject(0).asType(DurationTag.class, scriptEntry.context);\r\n                                LocationTag origin = dataList.getObject(1).asType(LocationTag.class, scriptEntry.context);\r\n                                ObjectTag destination = dataList.getObject(2);\r\n                                Vibration.Destination destObj;\r\n                                if (destination.shouldBeType(EntityTag.class)) {\r\n                                    destObj = new Vibration.Destination.EntityDestination(destination.asType(EntityTag.class, scriptEntry.context).getBukkitEntity());\r\n                                }\r\n                                else {\r\n                                    destObj = new Vibration.Destination.BlockDestination(destination.asType(LocationTag.class, scriptEntry.context));\r\n                                }\r\n                                dataObject = new Vibration(origin, destObj, duration.getTicksAsInt());\r\n                            }\r\n                        }\r\n                        else {\r\n                            Debug.echoError(\"Unknown particle data type: \" + clazz.getCanonicalName() + \" for particle: \" + particleEffect.name() + \". Are you sure it exists and are not using a legacy format?\");\r\n                            return;\r\n                        }\r\n                    }\r\n                    else {\r\n                        if (clazz == Particle.DustOptions.class) {\r\n                            ElementTag size = dataMap.getObjectAs(\"size\", ElementTag.class, scriptEntry.context);\r\n                            if (size == null || !size.isFloat()) {\r\n                                Debug.echoError(\"special_data input must have a 'size' key with a valid number, for particle \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            ColorTag color = dataMap.getObjectAs(\"color\", ColorTag.class, scriptEntry.context);\r\n                            if (color == null) {\r\n                                Debug.echoError(\"special_data input must have a 'color' key with a valid ColorTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            dataObject = new Particle.DustOptions(BukkitColorExtensions.getColor(color), size.asFloat());\r\n                        }\r\n                        else if (clazz == BlockData.class) {\r\n                            MaterialTag blockMaterial = dataMap.getObjectAs(\"material\", MaterialTag.class, scriptEntry.context);\r\n                            if (blockMaterial == null) {\r\n                                Debug.echoError(\"special_data input must have a 'material' key with a valid MaterialTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            dataObject = blockMaterial.getModernData();\r\n                        }\r\n                        else if (clazz == ItemStack.class) {\r\n                            ItemTag itemType = dataMap.getObjectAs(\"item\", ItemTag.class, scriptEntry.context);\r\n                            if (itemType == null) {\r\n                                Debug.echoError(\"special_data input must have a 'item' key with a valid ItemTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            dataObject = itemType.getItemStack();\r\n                        }\r\n                        else if (clazz == Particle.DustTransition.class) {\r\n                            ElementTag size = dataMap.getObjectAs(\"size\", ElementTag.class, scriptEntry.context);\r\n                            if (size == null || !size.isFloat()) {\r\n                                Debug.echoError(\"special_data input must have a 'size' key with a valid number, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            ColorTag fromColor = dataMap.getObjectAs(\"from\", ColorTag.class, scriptEntry.context);\r\n                            ColorTag toColor = dataMap.getObjectAs(\"to\", ColorTag.class, scriptEntry.context);\r\n                            if (fromColor == null || toColor == null) {\r\n                                Debug.echoError(\"special_data input must have a 'to' and 'size' key with a valid ColorTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            dataObject = new Particle.DustTransition(BukkitColorExtensions.getColor(fromColor), BukkitColorExtensions.getColor(toColor), size.asFloat());\r\n                        }\r\n                        else if (clazz == Vibration.class) {\r\n                            DurationTag duration = dataMap.getObjectAs(\"duration\", DurationTag.class, scriptEntry.context);\r\n                            if (duration == null) {\r\n                                Debug.echoError(\"special_data input must have a 'duration' key with a valid LocationTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            LocationTag origin = dataMap.getObjectAs(\"origin\", LocationTag.class, scriptEntry.context);\r\n                            if (origin == null) {\r\n                                Debug.echoError(\"special_data input must have a 'origin' key with a valid LocationTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            ObjectTag destination = dataMap.getObjectAs(\"destination\", ObjectTag.class, scriptEntry.context);\r\n                            Vibration.Destination destObj;\r\n                            if (destination.shouldBeType(EntityTag.class)) {\r\n                                destObj = new Vibration.Destination.EntityDestination(destination.asType(EntityTag.class, scriptEntry.context).getBukkitEntity());\r\n                            }\r\n                            else if (destination.shouldBeType(LocationTag.class)) {\r\n                                destObj = new Vibration.Destination.BlockDestination(destination.asType(LocationTag.class, scriptEntry.context));\r\n                            }\r\n                            else {\r\n                                Debug.echoError(\"special_data input must have a 'destination' key with a valid LocationTag or EntityTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            dataObject = new Vibration(origin, destObj, duration.getTicksAsInt());\r\n                        }\r\n                        else if (clazz == Particle.Trail.class) {\r\n                            ColorTag color = dataMap.getObjectAs(\"color\", ColorTag.class, scriptEntry.context);\r\n                            if (color == null) {\r\n                                Debug.echoError(\"special_data input must have a 'color' key with a valid ColorTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            LocationTag target = dataMap.getObjectAs(\"target\", LocationTag.class, scriptEntry.context);\r\n                            if (target == null) {\r\n                                Debug.echoError(\"special_data input must have a 'target' key with a valid LocationTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            DurationTag duration = dataMap.getObjectAs(\"duration\", DurationTag.class, scriptEntry.context);\r\n                            if (duration == null) {\r\n                                Debug.echoError(\"special_data input must have a 'duration' key with a valid DurationTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            dataObject = new Particle.Trail(target, BukkitColorExtensions.getColor(color), duration.getTicksAsInt());\r\n                        }\r\n                        else if (clazz == Color.class) {\r\n                            ColorTag color = dataMap.getObjectAs(\"color\", ColorTag.class, scriptEntry.context);\r\n                            if (!dataMap.getObject(\"color\").canBeType(ColorTag.class)) {\r\n                                Debug.echoError(\"special_data input must have a 'color' key with a valid ColorTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            dataObject = BukkitColorExtensions.getColor(color);\r\n                        }\r\n                        else if (clazz == Integer.class) {\r\n                            DurationTag duration = dataMap.getObjectAs(\"duration\", DurationTag.class, scriptEntry.context);\r\n                            if (duration == null) {\r\n                                Debug.echoError(\"special_data input must have a 'duration' key with a valid DurationTag, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            dataObject = duration.getTicksAsInt();\r\n                        }\r\n                        else if (clazz == Float.class) {\r\n                            ElementTag radians = dataMap.getObjectAs(\"radians\", ElementTag.class, scriptEntry.context);\r\n                            if (radians == null || !radians.isFloat()) {\r\n                                Debug.echoError(\"special_data input must have a 'radians' key with a valid number, for particle: \" + particleEffect.name());\r\n                                return;\r\n                            }\r\n                            dataObject = radians.asFloat();\r\n                        }\r\n                        else {\r\n                            Debug.echoError(\"Unknown particle data type: \" + clazz.getCanonicalName() + \" for particle: \" + particleEffect.name());\r\n                            return;\r\n                        }\r\n                    }\r\n                }\r\n                else if (special_data != null) {\r\n                    Debug.echoError(\"Particles of type '\" + particleEffect.name() + \"' cannot take special_data as input.\");\r\n                    return;\r\n                }\r\n                Random random = CoreUtilities.getRandom();\r\n                int quantityInt = quantity.asInt();\r\n                for (Player player : players) {\r\n                    if (velocity == null) {\r\n                        player.spawnParticle(particleEffect, location, quantityInt, offset.getX(), offset.getY(), offset.getZ(), data.asDouble(), dataObject);\r\n                    }\r\n                    else {\r\n                        for (int i = 0; i < quantityInt; i++) {\r\n                            LocationTag singleLocation = location.clone().add((random.nextDouble() - 0.5) * offset.getX(),\r\n                                    (random.nextDouble() - 0.5) * offset.getY(),\r\n                                    (random.nextDouble() - 0.5) * offset.getZ());\r\n                            player.spawnParticle(particleEffect, singleLocation, 0, velocity.getX(), velocity.getY(), velocity.getZ(), 1, dataObject);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            // Play an iconcrack (item break) effect\r\n            else {\r\n                List<Player> players = new ArrayList<>();\r\n                if (targets == null) {\r\n                    float rad = radius.asFloat();\r\n                    for (Player player : location.getWorld().getPlayers()) {\r\n                        if (player.getLocation().distanceSquared(location) < rad * rad) {\r\n                            players.add(player);\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    for (PlayerTag player : targets) {\r\n                        if (player.isValid() && player.isOnline()) {\r\n                            players.add(player.getPlayerEntity());\r\n                        }\r\n                    }\r\n                }\r\n                if (iconcrack != null) {\r\n                    ItemStack itemStack = iconcrack.getItemStack();\r\n                    Particle particle = Particle.valueOf(\"ITEM_CRACK\");\r\n                    for (Player player : players) {\r\n                        player.spawnParticle(particle, location, quantity.asInt(), offset.getX(), offset.getY(), offset.getZ(), data.asFloat(), itemStack);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/PlaySoundCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Sound;\r\nimport org.bukkit.SoundCategory;\r\n\r\nimport java.util.List;\r\n\r\npublic class PlaySoundCommand extends AbstractCommand {\r\n\r\n    public PlaySoundCommand() {\r\n        setName(\"playsound\");\r\n        setSyntax(\"playsound (<location>|...) (<player>|...) [sound:<name>] (volume:<#.#>) (pitch:<#.#>) (custom) (sound_category:<category_name>)\");\r\n        setRequiredArguments(2, 7);\r\n        isProcedural = false;\r\n        setBooleansHandled(\"custom\");\r\n        setPrefixesHandled(\"sound_category\", \"pitch\", \"volume\");\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name PlaySound\r\n    // @Syntax playsound (<location>|...) (<player>|...) [sound:<name>] (volume:<#.#>) (pitch:<#.#>) (custom) (sound_category:<category_name>)\r\n    // @Required 2\r\n    // @Maximum 7\r\n    // @Short Plays a sound at the location or to a list of players.\r\n    // @Synonyms Noise\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Plays a sound to a player or nearby players at a location.\r\n    // The sound is played through the player's client just like any other sounds in Minecraft.\r\n    //\r\n    // For a list of all sounds, check <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Sound.html>\r\n    //\r\n    // Sounds are by default played under their normal sound type (eg zombie sounds are under the type Mobs/Animals).\r\n    // You can optionally instead specify an alternate sound category to use.\r\n    // For a list of all valid sound categories, check <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/SoundCategory.html>\r\n    //\r\n    // Specifying a player or list of players will only play the sound for each player, from their own location (but will not follow them if they move).\r\n    // If a location is specified, it will play the sound for any players that are near the location specified.\r\n    // If both players and locations are specified, will play the sound for only those players at those locations.\r\n    //\r\n    // Optionally, specify 'custom' to play a custom sound added by a resource pack, changing the sound name to something like 'random.click'\r\n    //\r\n    // Optionally specify a pitch value (defaults to 1.0). A pitch from 0.0 to 1.0 will be deeper (sounds like a demon), and above 1.0 will be higher pitched (sounds like a fairy).\r\n    //\r\n    // Optionally specify a volume value (defaults to 1.0). A volume from 0.0 to 1.0 will be quieter than normal.\r\n    // A volume above 1.0 however will not be louder - instead it will be audible from farther (approximately 1 extra chunk of distance per value, eg 2.0 is 2 more chunks, 5.0 is 5 more chunks, etc.).\r\n    //\r\n    // @Tags\r\n    // <server.sound_types>\r\n    //\r\n    // @Usage\r\n    // Use to play a sound for a player\r\n    // - playsound <player> sound:ENTITY_EXPERIENCE_ORB_PICKUP pitch:1\r\n    // @Usage\r\n    // Use to play a sound at a location for all nearby\r\n    // - playsound <player.location> sound:ENTITY_PLAYER_LEVELUP\r\n    // @Usage\r\n    // Use to notify all players with a sound\r\n    // - playsound <server.online_players> sound:ENTITY_PLAYER_LEVELUP volume:0.5 pitch:0.8\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addWithPrefix(\"sound:\", Utilities.listTypes(Sound.class));\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"locations\")\r\n                    && arg.matchesArgumentList(LocationTag.class)) {\r\n                scriptEntry.addObject(\"locations\", arg.asType(ListTag.class).filter(LocationTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"entities\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"entities\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"sound\")\r\n                    && arg.limitToOnlyPrefix(\"sound\")) {\r\n                scriptEntry.addObject(\"sound\", arg.asElement());\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"sound\")) {\r\n            throw new InvalidArgumentsException(\"Missing sound argument!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"locations\") && !scriptEntry.hasObject(\"entities\")) {\r\n            throw new InvalidArgumentsException(\"Missing location argument!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        List<LocationTag> locations = (List<LocationTag>) scriptEntry.getObject(\"locations\");\r\n        List<PlayerTag> players = (List<PlayerTag>) scriptEntry.getObject(\"entities\");\r\n        ElementTag soundElement = scriptEntry.getElement(\"sound\");\r\n        ElementTag volumeElement = scriptEntry.argForPrefixAsElement(\"volume\", \"1\");\r\n        ElementTag pitchElement = scriptEntry.argForPrefixAsElement(\"pitch\", \"1\");\r\n        boolean custom = scriptEntry.argAsBoolean(\"custom\");\r\n        ElementTag sound_category = scriptEntry.argForPrefixAsElement(\"sound_category\", \"MASTER\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"locations\", locations), db(\"entities\", players), soundElement, volumeElement, pitchElement, db(\"custom\", custom));\r\n        }\r\n        String sound = soundElement.asString();\r\n        float volume = volumeElement.asFloat();\r\n        float pitch = pitchElement.asFloat();\r\n        String category = sound_category.asString().toUpperCase();\r\n        SoundCategory categoryEnum = category != null ? new ElementTag(category).asEnum(SoundCategory.class) : null;\r\n        if (categoryEnum == null) {\r\n            categoryEnum = SoundCategory.MASTER;\r\n        }\r\n        try {\r\n            if (players == null) {\r\n                if (custom) {\r\n                    for (LocationTag location : locations) {\r\n                        location.getWorld().playSound(location, sound, categoryEnum, volume, pitch);\r\n                    }\r\n                }\r\n                else {\r\n                    for (LocationTag location : locations) {\r\n                        location.getWorld().playSound(location, Utilities.elementToEnumlike(soundElement, Sound.class), categoryEnum, volume, pitch);\r\n                    }\r\n                }\r\n            }\r\n            else if (locations != null) {\r\n                for (LocationTag location : locations) {\r\n                    for (PlayerTag player : players) {\r\n                        if (custom) {\r\n                            player.getPlayerEntity().playSound(location, sound, categoryEnum, volume, pitch);\r\n                        }\r\n                        else {\r\n                            player.getPlayerEntity().playSound(location, Utilities.elementToEnumlike(soundElement, Sound.class), categoryEnum, volume, pitch);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            else {\r\n                for (PlayerTag player : players) {\r\n                    if (custom) {\r\n                        player.getPlayerEntity().playSound(player.getLocation(), sound, categoryEnum, volume, pitch);\r\n                    }\r\n                    else {\r\n                        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n                            player.getPlayerEntity().playSound(player.getPlayerEntity(), Utilities.elementToEnumlike(soundElement, Sound.class), categoryEnum, volume, pitch);\r\n                        }\r\n                        else {\r\n                            player.getPlayerEntity().playSound(player.getLocation(), Utilities.elementToEnumlike(soundElement, Sound.class), categoryEnum, volume, pitch);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoDebug(scriptEntry, \"Unable to play sound.\");\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SchematicCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.blocks.*;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.Holdable;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.ReplaceableTagEvent;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.tags.TagRunnable;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockPhysicsEvent;\r\n\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.FileOutputStream;\r\nimport java.io.InputStream;\r\nimport java.net.URLDecoder;\r\nimport java.util.HashMap;\r\nimport java.util.HashSet;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.function.Consumer;\r\n\r\npublic class SchematicCommand extends AbstractCommand implements Holdable, Listener {\r\n\r\n    public SchematicCommand() {\r\n        setName(\"schematic\");\r\n        setSyntax(\"schematic [create/load/unload/rotate/save/flip_x/flip_y/flip_z/paste (fake_to:<player>|... fake_duration:<duration>) (noair) (mask:<material_matcher>)] [name:<name>] (filename:<name>) (angle:<#>) (<location>) (area:<area>) (delayed) (max_delay_ms:<#>) (entities) (flags)\");\r\n        setRequiredArguments(2, 13);\r\n        TagManager.registerTagHandler(new TagRunnable.RootForm() {\r\n            @Override\r\n            public void run(ReplaceableTagEvent event) {\r\n                schematicTags(event);\r\n            }\r\n        }, \"schematic\");\r\n        schematics = new HashMap<>();\r\n        noPhys = false;\r\n        Bukkit.getPluginManager().registerEvents(this, Denizen.getInstance());\r\n        isProcedural = false;\r\n        setBooleansHandled(\"noair\", \"delayed\", \"entities\", \"flags\");\r\n        setPrefixesHandled(\"angle\", \"fake_duration\", \"mask\", \"name\", \"filename\", \"max_delay_ms\", \"fake_to\", \"area\");\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Schematic\r\n    // @Syntax schematic [create/load/unload/rotate/save/flip_x/flip_y/flip_z/paste (fake_to:<player>|... fake_duration:<duration>) (noair) (mask:<material_matcher>)] [name:<name>] (filename:<name>) (angle:<#>) (<location>) (area:<area>) (delayed) (max_delay_ms:<#>) (entities) (flags)\r\n    // @Group world\r\n    // @Required 2\r\n    // @Maximum 13\r\n    // @Short Creates, loads, pastes, and saves schematics (Sets of blocks).\r\n    //\r\n    // @Description\r\n    // Creates, loads, pastes, and saves schematics. Schematics are files containing info about blocks and the order of those blocks.\r\n    //\r\n    // Denizen offers a number of tools to manipulate and work with schematics.\r\n    // Schematics can be rotated, flipped, pasted with no air, or pasted with a delay.\r\n    //\r\n    // All schematic command usages must specify the \"name\" argument, which is a unique global identifier of the schematic in memory.\r\n    // This will be created by \"create\" or \"load\" options, and persist in memory until \"unload\" is used (or the server is restarted).\r\n    //\r\n    // The 'create' option requires an area and a center location as input.\r\n    // The area can be defined as any valid <@link ObjectType AreaObject>, such as a CuboidTag.\r\n    // Note that all schematics are internally tracked as cuboids, and other area shapes will only constrain the copy region.\r\n    // Note that the block boundaries of non-cuboid regions are defined by whether the region definition contains the center of a block.\r\n    // This will create a new schematic in memory based on world data.\r\n    //\r\n    // The \"rotate angle:#\" and \"flip_x/y/z\" options will apply the change to the copy of the schematic in memory, to later be pasted or saved.\r\n    // This will rotate the set of blocks itself, the relative origin, and any directional blocks inside the schematic.\r\n    // Rotation angles must be a multiple of 90 degrees.\r\n    //\r\n    // When using 'paste', you can specify 'angle:#' to have that paste rotated, without rotating the original schematic.\r\n    //\r\n    // The \"delayed\" option makes the command non-instant. This is recommended for large schematics.\r\n    // For 'save', 'load', and 'rotate', this processes async to prevent server lockup.\r\n    // For 'paste' and 'create', this delays how many blocks can be processed at once, spread over many ticks.\r\n    // Optionally, specify 'max_delay_ms' to control how many milliseconds the 'delayed' set can run for in any given tick (defaults to 50) (for create/paste only).\r\n    //\r\n    // The \"load\" option by default will load '.schem' files. If no '.schem' file is available, will attempt to load a legacy '.schematic' file instead.\r\n    //\r\n    // For load and save, the \"filename\" option is available to specify the name of the file to look for.\r\n    // If unspecified, the filename will default to the same as the \"name\" input.\r\n    //\r\n    // The \"noair\" option skips air blocks in the pasted schematics- this means those air blocks will not replace any blocks in the target location.\r\n    //\r\n    // The \"mask\" option can be specified to limit what block types the schematic will be pasted over.\r\n    // When using \"create\" and \"mask\", any block that doesn't match the mask will become a structure void.\r\n    //\r\n    // The \"fake_to\" option can be specified to cause the schematic paste to be a fake (packet-based, see <@link command showfake>)\r\n    // block set, instead of actually modifying the blocks in the world.\r\n    // This takes an optional duration as \"fake_duration\" for how long the fake blocks should remain.\r\n    //\r\n    // The \"create\" and \"paste\" options allow the \"entities\" argument to be specified - when used, entities will be copied or pasted.\r\n    // At current time, entity types included will be: Paintings, ItemFrames, ArmorStands.\r\n    //\r\n    // The \"create\" option allows the \"flags\" argument to be specified - when used, block location flags will be copied.\r\n    //\r\n    // The schematic command is ~waitable as an alternative to 'delayed' argument. Refer to <@link language ~waitable>.\r\n    //\r\n    // To delete a schematic file, use <@link mechanism server.delete_file>.\r\n    //\r\n    // @Tags\r\n    // <schematic[<name>].height>\r\n    // <schematic[<name>].length>\r\n    // <schematic[<name>].width>\r\n    // <schematic[<name>].block[<location>]>\r\n    // <schematic[<name>].origin>\r\n    // <schematic[<name>].blocks>\r\n    // <schematic[<name>].exists>\r\n    // <schematic[<name>].cuboid[<origin_location>]>\r\n    // <schematic.list>\r\n    //\r\n    // @Usage\r\n    // Use to create a new schematic from a cuboid and an origin location.\r\n    // - schematic create name:MySchematic area:<[my_cuboid]> <player.location>\r\n    //\r\n    // @Usage\r\n    // Use to load a schematic.\r\n    // - ~schematic load name:MySchematic\r\n    //\r\n    // @Usage\r\n    // Use to unload a schematic.\r\n    // - schematic unload name:MySchematic\r\n    //\r\n    // @Usage\r\n    // Use to paste a loaded schematic with no air blocks.\r\n    // - schematic paste name:MySchematic <player.location> noair\r\n    //\r\n    // @Usage\r\n    // Use to save a created schematic.\r\n    // - ~schematic save name:MySchematic\r\n    // -->\r\n\r\n    public static boolean noPhys = false;\r\n\r\n    @EventHandler\r\n    public void onBlockPhysics(BlockPhysicsEvent event) {\r\n        if (noPhys) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addWithPrefix(\"name:\", schematics.keySet());\r\n    }\r\n\r\n    private enum Type {CREATE, LOAD, UNLOAD, ROTATE, PASTE, SAVE, FLIP_X, FLIP_Y, FLIP_Z}\r\n\r\n    public static Map<String, CuboidBlockSet> schematics;\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"type\")\r\n                    && arg.matchesEnum(Type.class)) {\r\n                scriptEntry.addObject(\"type\", new ElementTag(arg.getRawValue().toUpperCase()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class).getBlockLocation());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"area\")\r\n                    && arg.matchesArgumentType(CuboidTag.class)) { // Historical input format\r\n                scriptEntry.addObject(\"area\", arg.asType(CuboidTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"type\")) {\r\n            throw new InvalidArgumentsException(\"Missing type argument!\");\r\n        }\r\n    }\r\n\r\n    public static void rotateSchem(CuboidBlockSet schematic, int angle, boolean delayed, Runnable callback) {\r\n        Runnable rotateRunnable = () -> {\r\n            try {\r\n                int ang = angle;\r\n                while (ang < 0) {\r\n                    ang = 360 + ang;\r\n                }\r\n                while (ang >= 360) {\r\n                    ang -= 360;\r\n                }\r\n                if (ang != 0) {\r\n                    ang = 360 - ang;\r\n                    while (ang > 0) {\r\n                        ang -= 90;\r\n                        schematic.rotateOne();\r\n                    }\r\n                }\r\n            }\r\n            finally {\r\n                if (delayed) {\r\n                    Bukkit.getScheduler().runTask(Denizen.instance, () -> schematic.isModifying = false);\r\n                }\r\n                if (callback != null) {\r\n                    if (delayed) {\r\n                        Bukkit.getScheduler().runTask(Denizen.instance, callback);\r\n                    }\r\n                    else {\r\n                        callback.run();\r\n                    }\r\n                }\r\n            }\r\n        };\r\n        if (delayed) {\r\n            schematic.isModifying = true;\r\n            Bukkit.getScheduler().runTaskAsynchronously(Denizen.instance, rotateRunnable);\r\n        }\r\n        else {\r\n            rotateRunnable.run();\r\n        }\r\n    }\r\n\r\n    public static void parseMask(ScriptEntry scriptEntry, String maskText, HashSet<Material> mask) {\r\n        if (maskText.startsWith(\"li@\")) { // Back-compat: input used to be a list of materials\r\n            for (MaterialTag material : ListTag.valueOf(maskText, scriptEntry.getContext()).filter(MaterialTag.class, scriptEntry)) {\r\n                mask.add(material.getMaterial());\r\n            }\r\n        }\r\n        else {\r\n            for (Material material : Material.values()) {\r\n                if (MaterialTag.advancedMatchesInternal(material, maskText, true)) {\r\n                    mask.add(material);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        ElementTag angle = scriptEntry.argForPrefixAsElement(\"angle\", null);\r\n        ElementTag type = scriptEntry.getElement(\"type\");\r\n        ElementTag name = scriptEntry.requiredArgForPrefixAsElement(\"name\");\r\n        ElementTag filename = scriptEntry.argForPrefixAsElement(\"filename\", null);\r\n        boolean noair = scriptEntry.argAsBoolean(\"noair\");\r\n        boolean delayed = scriptEntry.argAsBoolean(\"delayed\") || scriptEntry.shouldWaitFor();\r\n        ElementTag maxDelayMs = scriptEntry.argForPrefixAsElement(\"max_delay_ms\", \"50\");\r\n        boolean copyEntities = scriptEntry.argAsBoolean(\"entities\");\r\n        boolean flags = scriptEntry.argAsBoolean(\"flags\");\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        ElementTag mask = scriptEntry.argForPrefixAsElement(\"mask\", null);\r\n        List<PlayerTag> fakeTo = scriptEntry.argForPrefixList(\"fake_to\", PlayerTag.class, true);\r\n        DurationTag fakeDuration = scriptEntry.argForPrefix(\"fake_duration\", DurationTag.class, true);\r\n        CuboidTag legacyCuboid = scriptEntry.getObjectTag(\"area\");\r\n        AreaContainmentObject areaVal = null;\r\n        if (legacyCuboid != null) {\r\n            areaVal = legacyCuboid;\r\n        }\r\n        else {\r\n            Argument areaArg = scriptEntry.argForPrefix(\"area\");\r\n            if (areaArg != null) {\r\n                if (areaArg.object instanceof AreaContainmentObject) {\r\n                    areaVal = (AreaContainmentObject) areaArg.object;\r\n                }\r\n                else {\r\n                    ObjectTag reparsedArea = ObjectFetcher.pickObjectFor(areaArg.getValue(), scriptEntry.context);\r\n                    if (reparsedArea instanceof AreaContainmentObject) {\r\n                        areaVal = (AreaContainmentObject)  reparsedArea;\r\n                    }\r\n                    else {\r\n                        throw new InvalidArgumentsRuntimeException(\"Area input '\" + areaArg.getValue() + \"' is not a valid Area object\");\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        final AreaContainmentObject area = areaVal;\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), type, name, location, filename, area, angle, db(\"noair\", noair), db(\"delayed\", delayed),\r\n                    maxDelayMs, db(\"flags\", flags), db(\"entities\", copyEntities), mask, fakeDuration, db(\"fake_to\", fakeTo));\r\n        }\r\n        CuboidBlockSet set;\r\n        Type ttype = Type.valueOf(type.asString());\r\n        String fname = filename != null ? filename.asString() : name.asString();\r\n        switch (ttype) {\r\n            case CREATE: {\r\n                if (schematics.containsKey(name.asString().toUpperCase())) {\r\n                    Debug.echoError(scriptEntry, \"Schematic file \" + name.asString() + \" is already loaded.\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                if (area == null) {\r\n                    Debug.echoError(scriptEntry, \"Missing area argument!\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                if (location == null) {\r\n                    Debug.echoError(scriptEntry, \"Missing origin location argument!\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                try {\r\n                    HashSet<Material> maskSet = null;\r\n                    if (mask != null) {\r\n                        String maskText = mask.asString();\r\n                        maskSet = new HashSet<>();\r\n                        parseMask(scriptEntry, maskText, maskSet);\r\n                    }\r\n                    set = new CuboidBlockSet();\r\n                    if (delayed) {\r\n                        set.buildDelayed(area, location, maskSet, () -> {\r\n                            if (copyEntities) {\r\n                                set.buildEntities(area, location);\r\n                            }\r\n                            schematics.put(name.asString().toUpperCase(), set);\r\n                            scriptEntry.setFinished(true);\r\n                        }, maxDelayMs.asLong(), flags);\r\n                    }\r\n                    else {\r\n                        scriptEntry.setFinished(true);\r\n                        set.buildImmediate(area, location, maskSet, flags);\r\n                        if (copyEntities) {\r\n                            set.buildEntities(area, location);\r\n                        }\r\n                        schematics.put(name.asString().toUpperCase(), set);\r\n                    }\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(scriptEntry, \"Error creating schematic object \" + name.asString() + \".\");\r\n                    Debug.echoError(scriptEntry, ex);\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                break;\r\n            }\r\n            case LOAD: {\r\n                if (schematics.containsKey(name.asString().toUpperCase())) {\r\n                    Debug.echoError(scriptEntry, \"Schematic file \" + name.asString() + \" is already loaded.\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                File f = new File(Denizen.getInstance().getDataFolder(), \"schematics/\" + fname + \".schem\");\r\n                if (!Utilities.canReadFile(f)) {\r\n                    Debug.echoError(\"Cannot read from that file path due to security settings in Denizen/config.yml.\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                if (!f.exists()) {\r\n                    f = new File(Denizen.getInstance().getDataFolder(), \"schematics/\" + fname + \".schematic\");\r\n                    if (!f.exists()) {\r\n                        Debug.echoError(\"Schematic file \" + fname + \" does not exist. Are you sure it's in plugins/Denizen/schematics/?\");\r\n                        scriptEntry.setFinished(true);\r\n                        return;\r\n                    }\r\n                }\r\n                File schemFile = f;\r\n                Runnable loadRunnable = () -> {\r\n                    try {\r\n                        InputStream fs = new FileInputStream(schemFile);\r\n                        CuboidBlockSet newSet;\r\n                        newSet = SpongeSchematicHelper.fromSpongeStream(fs);\r\n                        fs.close();\r\n                        Runnable storeSchem = () -> {\r\n                            schematics.put(name.asString().toUpperCase(), newSet);\r\n                            scriptEntry.setFinished(true);\r\n                        };\r\n                        if (delayed) {\r\n                            Bukkit.getScheduler().runTask(Denizen.instance, storeSchem);\r\n                        }\r\n                        else {\r\n                            storeSchem.run();\r\n                        }\r\n                    }\r\n                    catch (Exception ex) {\r\n                        Runnable showError = () -> {\r\n                            Debug.echoError(scriptEntry, \"Error loading schematic file \" + name.asString() + \".\");\r\n                            Debug.echoError(scriptEntry, ex);\r\n                        };\r\n                        if (delayed) {\r\n                            Bukkit.getScheduler().runTask(Denizen.instance, showError);\r\n                        }\r\n                        else {\r\n                            showError.run();\r\n                        }\r\n                        scriptEntry.setFinished(true);\r\n                        return;\r\n                    }\r\n                };\r\n                if (delayed) {\r\n                    Bukkit.getScheduler().runTaskAsynchronously(Denizen.instance, loadRunnable);\r\n                }\r\n                else {\r\n                    loadRunnable.run();\r\n                    scriptEntry.setFinished(true);\r\n                }\r\n                break;\r\n            }\r\n            case UNLOAD: {\r\n                if (!schematics.containsKey(name.asString().toUpperCase())) {\r\n                    Debug.echoError(scriptEntry, \"Schematic file \" + name.asString() + \" is not loaded.\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                schematics.remove(name.asString().toUpperCase());\r\n                scriptEntry.setFinished(true);\r\n                break;\r\n            }\r\n            case ROTATE: {\r\n                if (!schematics.containsKey(name.asString().toUpperCase())) {\r\n                    Debug.echoError(scriptEntry, \"Schematic file \" + name.asString() + \" is not loaded.\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                if (angle == null) {\r\n                    Debug.echoError(scriptEntry, \"Missing angle argument!\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                final CuboidBlockSet schematic = schematics.get(name.asString().toUpperCase());\r\n                if (schematic.isModifying || schematic.readingProcesses > 0) {\r\n                    Debug.echoError(\"Cannot rotate schematic: schematic is currently processing another instruction.\");\r\n                    return;\r\n                }\r\n                rotateSchem(schematic, angle.asInt(), delayed, () -> scriptEntry.setFinished(true));\r\n                break;\r\n            }\r\n            case FLIP_X: {\r\n                if (!schematics.containsKey(name.asString().toUpperCase())) {\r\n                    Debug.echoError(scriptEntry, \"Schematic file \" + name.asString() + \" is not loaded.\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                final CuboidBlockSet schematic = schematics.get(name.asString().toUpperCase());\r\n                if (schematic.isModifying || schematic.readingProcesses > 0) {\r\n                    Debug.echoError(\"Cannot flip schematic: schematic is currently processing another instruction.\");\r\n                    return;\r\n                }\r\n                schematic.flipX();\r\n                scriptEntry.setFinished(true);\r\n                break;\r\n            }\r\n            case FLIP_Y: {\r\n                if (!schematics.containsKey(name.asString().toUpperCase())) {\r\n                    Debug.echoError(scriptEntry, \"Schematic file \" + name.asString() + \" is not loaded.\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                final CuboidBlockSet schematic = schematics.get(name.asString().toUpperCase());\r\n                if (schematic.isModifying || schematic.readingProcesses > 0) {\r\n                    Debug.echoError(\"Cannot flip schematic: schematic is currently processing another instruction.\");\r\n                    return;\r\n                }\r\n                schematic.flipY();\r\n                scriptEntry.setFinished(true);\r\n                break;\r\n            }\r\n            case FLIP_Z: {\r\n                if (!schematics.containsKey(name.asString().toUpperCase())) {\r\n                    Debug.echoError(scriptEntry, \"Schematic file \" + name.asString() + \" is not loaded.\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                final CuboidBlockSet schematic = schematics.get(name.asString().toUpperCase());\r\n                if (schematic.isModifying || schematic.readingProcesses > 0) {\r\n                    Debug.echoError(\"Cannot flip schematic: schematic is currently processing another instruction.\");\r\n                    return;\r\n                }\r\n                schematic.flipZ();\r\n                scriptEntry.setFinished(true);\r\n                break;\r\n            }\r\n            case PASTE: {\r\n                if (!schematics.containsKey(name.asString().toUpperCase())) {\r\n                    Debug.echoError(scriptEntry, \"Schematic file \" + name.asString() + \" is not loaded.\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                if (location == null) {\r\n                    Debug.echoError(scriptEntry, \"Missing location argument!\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                try {\r\n                    BlockSet.InputParams input = new BlockSet.InputParams();\r\n                    input.centerLocation = location;\r\n                    input.noAir = noair;\r\n                    input.fakeTo = fakeTo;\r\n                    if (fakeTo != null && copyEntities) {\r\n                        Debug.echoError(scriptEntry, \"Cannot fake paste entities currently.\");\r\n                        scriptEntry.setFinished(true);\r\n                        return;\r\n                    }\r\n                    if (fakeDuration == null) {\r\n                        fakeDuration = new DurationTag(0);\r\n                    }\r\n                    input.fakeDuration = fakeDuration;\r\n                    if (mask != null) {\r\n                        String maskText = mask.asString();\r\n                        input.mask = new HashSet<>();\r\n                        parseMask(scriptEntry, maskText, input.mask);\r\n                    }\r\n                    set = schematics.get(name.asString().toUpperCase());\r\n                    if (set.isModifying) {\r\n                        Debug.echoError(\"Cannot paste schematic: schematic is currently processing another instruction.\");\r\n                        return;\r\n                    }\r\n                    Consumer<CuboidBlockSet> pasteRunnable = (schematic) -> {\r\n                        if (delayed) {\r\n                            schematic.readingProcesses++;\r\n                            schematic.setBlocksDelayed(() -> {\r\n                                try {\r\n                                    if (copyEntities) {\r\n                                        schematic.pasteEntities(location);\r\n                                    }\r\n                                }\r\n                                finally {\r\n                                    scriptEntry.setFinished(true);\r\n                                    schematic.readingProcesses--;\r\n                                }\r\n                            }, input, maxDelayMs.asLong());\r\n                        }\r\n                        else {\r\n                            schematic.setBlocks(input);\r\n                            if (copyEntities) {\r\n                                schematic.pasteEntities(location);\r\n                            }\r\n                            scriptEntry.setFinished(true);\r\n                        }\r\n                    };\r\n                    if (angle != null) {\r\n                        final CuboidBlockSet newSet = set.duplicate();\r\n                        rotateSchem(newSet, angle.asInt(), delayed, () -> pasteRunnable.accept(newSet));\r\n                    }\r\n                    else {\r\n                        pasteRunnable.accept(set);\r\n                    }\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(scriptEntry, \"Exception pasting schematic file \" + name.asString() + \".\");\r\n                    Debug.echoError(scriptEntry, ex);\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                break;\r\n            }\r\n            case SAVE: {\r\n                if (!schematics.containsKey(name.asString().toUpperCase())) {\r\n                    Debug.echoError(scriptEntry, \"Schematic file \" + name.asString() + \" is not loaded.\");\r\n                    return;\r\n                }\r\n                set = schematics.get(name.asString().toUpperCase());\r\n                if (set.isModifying) {\r\n                    Debug.echoError(\"Cannot save schematic: schematic is currently processing another instruction.\");\r\n                    return;\r\n                }\r\n                String directory = URLDecoder.decode(System.getProperty(\"user.dir\"));\r\n                String extension = \".schem\";\r\n                File f = new File(directory + \"/plugins/Denizen/schematics/\" + fname + extension);\r\n                if (!Utilities.canWriteToFile(f)) {\r\n                    Debug.echoError(\"Cannot write to that file path due to security settings in Denizen/config.yml.\");\r\n                    scriptEntry.setFinished(true);\r\n                    return;\r\n                }\r\n                Runnable saveRunnable = () -> {\r\n                    try {\r\n                        f.getParentFile().mkdirs();\r\n                        FileOutputStream fs = new FileOutputStream(f);\r\n                        SpongeSchematicHelper.saveToSpongeStream(set, fs);\r\n                        fs.flush();\r\n                        fs.close();\r\n                    }\r\n                    catch (Exception ex) {\r\n                        Bukkit.getScheduler().runTask(Denizen.instance, () -> {\r\n                            Debug.echoError(scriptEntry, \"Error saving schematic file \" + fname + \".\");\r\n                            Debug.echoError(scriptEntry, ex);\r\n                        });\r\n                    }\r\n                    Bukkit.getScheduler().runTask(Denizen.instance, () -> {\r\n                        set.readingProcesses--;\r\n                        scriptEntry.setFinished(true);\r\n                    });\r\n                };\r\n                if (delayed) {\r\n                    set.readingProcesses++;\r\n                    Bukkit.getScheduler().runTaskAsynchronously(Denizen.instance, saveRunnable);\r\n                }\r\n                else {\r\n                    scriptEntry.setFinished(true);\r\n                    saveRunnable.run();\r\n                }\r\n                break;\r\n            }\r\n        }\r\n    }\r\n\r\n    public void schematicTags(ReplaceableTagEvent event) {\r\n        if (!event.matches(\"schematic\")) {\r\n            return;\r\n        }\r\n        Attribute attribute = event.getAttributes();\r\n        String id = attribute.hasParam() ? attribute.getParam().toUpperCase() : null;\r\n        attribute = attribute.fulfill(1);\r\n\r\n        // <--[tag]\r\n        // @attribute <schematic.list>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all loaded schematics.\r\n        // -->\r\n        if (attribute.startsWith(\"list\")) {\r\n            event.setReplacedObject(new ListTag(schematics.keySet()).getObjectAttribute(attribute.fulfill(1)));\r\n        }\r\n        if (id == null) {\r\n            return;\r\n        }\r\n        if (!schematics.containsKey(id)) {\r\n            // Meta below\r\n            if (attribute.startsWith(\"exists\")) {\r\n                event.setReplacedObject(new ElementTag(false)\r\n                        .getObjectAttribute(attribute.fulfill(1)));\r\n                return;\r\n            }\r\n            Debug.echoError(attribute.getScriptEntry(), \"Schematic file \" + id + \" is not loaded.\");\r\n            return;\r\n        }\r\n        CuboidBlockSet set = schematics.get(id);\r\n\r\n        // <--[tag]\r\n        // @attribute <schematic[<name>].exists>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the schematic exists.\r\n        // -->\r\n        if (attribute.startsWith(\"exists\")) {\r\n            event.setReplacedObject(new ElementTag(true)\r\n                    .getObjectAttribute(attribute.fulfill(1)));\r\n            return;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <schematic[<name>].height>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the height (Y) of the schematic.\r\n        // -->\r\n        if (attribute.startsWith(\"height\")) {\r\n            event.setReplacedObject(new ElementTag(set.y_length)\r\n                    .getObjectAttribute(attribute.fulfill(1)));\r\n            return;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <schematic[<name>].length>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the length (Z) of the schematic.\r\n        // -->\r\n        if (attribute.startsWith(\"length\")) {\r\n            event.setReplacedObject(new ElementTag(set.z_height)\r\n                    .getObjectAttribute(attribute.fulfill(1)));\r\n            return;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <schematic[<name>].width>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the width (X) of the schematic.\r\n        // -->\r\n        if (attribute.startsWith(\"width\")) {\r\n            event.setReplacedObject(new ElementTag(set.x_width)\r\n                    .getObjectAttribute(attribute.fulfill(1)));\r\n            return;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <schematic[<name>].block[<location>]>\r\n        // @returns MaterialTag\r\n        // @description\r\n        // Returns the material for the block at the location in the schematic.\r\n        // An input location of 0,0,0 corresponds to the minimum corner of the schematic.\r\n        // -->\r\n        if (attribute.startsWith(\"block\")) {\r\n            if (attribute.hasParam() && LocationTag.matches(attribute.getParam())) {\r\n                LocationTag location = attribute.paramAsType(LocationTag.class);\r\n                FullBlockData block = set.blockAt(location.getX(), location.getY(), location.getZ());\r\n                event.setReplacedObject(new MaterialTag(block.data)\r\n                        .getObjectAttribute(attribute.fulfill(1)));\r\n                return;\r\n            }\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <schematic[<name>].origin>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns the origin location of the schematic.\r\n        // -->\r\n        if (attribute.startsWith(\"origin\")) {\r\n            event.setReplacedObject(new LocationTag(null, set.center_x, set.center_y, set.center_z)\r\n                    .getObjectAttribute(attribute.fulfill(1)));\r\n            return;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <schematic[<name>].blocks>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the number of blocks in the schematic.\r\n        // -->\r\n        if (attribute.startsWith(\"blocks\")) {\r\n            event.setReplacedObject(new ElementTag(set.blocks.length)\r\n                    .getObjectAttribute(attribute.fulfill(1)));\r\n            return;\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <schematic[<name>].cuboid[<origin_location>]>\r\n        // @returns CuboidTag\r\n        // @description\r\n        // Returns a cuboid of where the schematic would be if it was pasted at an origin.\r\n        // -->\r\n        if (attribute.startsWith(\"cuboid\") && attribute.hasParam()) {\r\n            LocationTag origin = attribute.paramAsType(LocationTag.class);\r\n            event.setReplacedObject(set.getCuboid(origin)\r\n                    .getObjectAttribute(attribute.fulfill(1)));\r\n            return;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SignCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.properties.material.MaterialDirectional;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.block.Sign;\r\n\r\npublic class SignCommand extends AbstractCommand {\r\n\r\n    public SignCommand() {\r\n        setName(\"sign\");\r\n        setSyntax(\"sign (type:{automatic}/sign_post/wall_sign) (material:<material>) [<line>|...] [<location>] (direction:north/east/south/west)\");\r\n        setRequiredArguments(1, 5);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Sign\r\n    // @Syntax sign (type:{automatic}/sign_post/wall_sign) (material:<material>) [<line>|...] [<location>] (direction:north/east/south/west)\r\n    // @Required 1\r\n    // @Maximum 5\r\n    // @Short Modifies a sign.\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Modifies a sign that replaces the text shown on it. If no sign is at the location, it replaces the location with the modified sign.\r\n    //\r\n    // Specify 'automatic' as a type to use whatever sign type and direction is already placed there.\r\n    // If there is not already a sign there, defaults to a sign_post.\r\n    //\r\n    // Optionally specify a material to use. If not specified, will use an oak sign (unless the block is already a sign, and 'type' is 'automatic').\r\n    //\r\n    // The direction argument specifies which direction the sign should face.\r\n    // If a direction is not specified, and there is not already a sign there for 'automatic', the direction defaults to south.\r\n    // If a sign_post is placed, you can specify any specific blockface value as the direction, eg \"SOUTH_WEST\".\r\n    // See also <@link tag MaterialTag.valid_directions> (test in-game for example via \"/ex narrate <material[oak_sign].valid_directions>\").\r\n    //\r\n    // @Tags\r\n    // <LocationTag.sign_contents>\r\n    //\r\n    // @Usage\r\n    // Use to edit some text on an existing sign.\r\n    // - sign \"Hello|this is|some|text\" <context.location>\r\n    //\r\n    // @Usage\r\n    // Use to show the time on a sign and ensure that it points north.\r\n    // - sign \"I point|North.|System Time<&co>|<util.time_now.formatted>\" <[location]> direction:north\r\n    //\r\n    // @Usage\r\n    // Use to place a new wall_sign regardless of whether there is already a sign there.\r\n    // - sign type:wall_sign \"Player<&co>|<player.name>|Online Players<&co>|<server.online_players.size>\" <player.location>\r\n    //\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addNotesOfType(LocationTag.class);\r\n    }\r\n\r\n    private enum Type {AUTOMATIC, SIGN_POST, WALL_SIGN}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"type\")\r\n                    && arg.matchesEnum(Type.class)) {\r\n                scriptEntry.addObject(\"type\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class).setPrefix(\"location\"));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"direction\")\r\n                    && arg.matchesPrefix(\"direction\", \"dir\")) {\r\n                scriptEntry.addObject(\"direction\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"material\")\r\n                    && arg.matchesPrefix(\"material\")\r\n                    && arg.matchesArgumentType(MaterialTag.class)) {\r\n                scriptEntry.addObject(\"material\", arg.asType(MaterialTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"text\")) {\r\n                scriptEntry.addObject(\"text\", arg.asType(ListTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a Sign location!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"text\")) {\r\n            throw new InvalidArgumentsException(\"Must specify sign text!\");\r\n        }\r\n        scriptEntry.defaultObject(\"type\", new ElementTag(Type.AUTOMATIC));\r\n    }\r\n\r\n    public void setWallSign(Block sign, BlockFace bf, MaterialTag material) {\r\n        sign.setType(material == null ? Material.OAK_WALL_SIGN : material.getMaterial(), false);\r\n        MaterialTag signMaterial = new MaterialTag(sign);\r\n        MaterialDirectional.getFrom(signMaterial).setFacing(bf);\r\n        sign.setBlockData(signMaterial.getModernData());\r\n    }\r\n\r\n    public static boolean isStandingSign(Material material) {\r\n        switch (material) {\r\n            case CRIMSON_SIGN:\r\n            case WARPED_SIGN:\r\n            case ACACIA_SIGN:\r\n            case BIRCH_SIGN:\r\n            case DARK_OAK_SIGN:\r\n            case JUNGLE_SIGN:\r\n            case OAK_SIGN:\r\n            case SPRUCE_SIGN:\r\n                return true;\r\n            default:\r\n                return false;\r\n        }\r\n    }\r\n\r\n    public static boolean isWallSign(Material material) {\r\n        switch (material) {\r\n            case CRIMSON_WALL_SIGN:\r\n            case WARPED_WALL_SIGN:\r\n            case ACACIA_WALL_SIGN:\r\n            case BIRCH_WALL_SIGN:\r\n            case DARK_OAK_WALL_SIGN:\r\n            case JUNGLE_WALL_SIGN:\r\n            case OAK_WALL_SIGN:\r\n            case SPRUCE_WALL_SIGN:\r\n                return true;\r\n            default:\r\n                return false;\r\n        }\r\n    }\r\n\r\n    public static boolean isAnySign(Material material) {\r\n        return isStandingSign(material) || isWallSign(material);\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        String direction = scriptEntry.hasObject(\"direction\") ? ((ElementTag) scriptEntry.getObject(\"direction\")).asString() : null;\r\n        ElementTag typeElement = scriptEntry.getElement(\"type\");\r\n        ListTag text = scriptEntry.getObjectTag(\"text\");\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        MaterialTag material = scriptEntry.getObjectTag(\"material\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), typeElement, location, db(\"direction\", direction), material, text);\r\n        }\r\n        Type type = Type.valueOf(typeElement.asString().toUpperCase());\r\n        Block sign = location.getBlock();\r\n        if (type != Type.AUTOMATIC || !isAnySign(sign.getType())) {\r\n            if (type == Type.WALL_SIGN) {\r\n                BlockFace bf;\r\n                if (direction != null) {\r\n                    bf = Utilities.chooseSignRotation(direction);\r\n                }\r\n                else {\r\n                    bf = Utilities.chooseSignRotation(sign);\r\n                }\r\n                setWallSign(sign, bf, material);\r\n            }\r\n            else {\r\n                sign.setType(material == null ? Material.OAK_SIGN : material.getMaterial(), false);\r\n                if (direction != null) {\r\n                    Utilities.setSignRotation(sign.getState(), direction);\r\n                }\r\n            }\r\n        }\r\n        else if (!isAnySign(sign.getType())) {\r\n            if (sign.getRelative(BlockFace.DOWN).getType().isSolid()) {\r\n                sign.setType(material == null ? Material.OAK_SIGN : material.getMaterial(), false);\r\n            }\r\n            else {\r\n                BlockFace bf = Utilities.chooseSignRotation(sign);\r\n                setWallSign(sign, bf, material);\r\n            }\r\n        }\r\n        BlockState signState = sign.getState();\r\n        Utilities.setSignLines((Sign) signState, text.toArray(new String[4]));\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/StrikeCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\n\r\npublic class StrikeCommand extends AbstractCommand {\r\n\r\n    public StrikeCommand() {\r\n        setName(\"strike\");\r\n        setSyntax(\"strike [<location>] (no_damage) (silent)\");\r\n        setRequiredArguments(1, 3);\r\n        isProcedural = false;\r\n        setBooleansHandled(\"no_damage\", \"silent\");\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Strike\r\n    // @Syntax strike [<location>] (no_damage) (silent)\r\n    // @Required 1\r\n    // @Maximum 3\r\n    // @Short Strikes lightning down upon the location.\r\n    // @Synonyms Lightning, Electrocute\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Causes lightning to strike at the specified location, which can optionally have damage disabled.\r\n    //\r\n    // The lightning will still cause fires to start, even without the 'no_damage' argument.\r\n    //\r\n    // Lightning caused by this command will cause creepers to activate. Using the no_damage argument makes the\r\n    // lightning do no damage to the player or any other entities, and means creepers struck will not activate.\r\n    //\r\n    // Use 'silent' to remove the sound of the lightning strike.\r\n    // NOTE: The 'silent' option appears to have been removed in a Minecraft update and thus lightning will be audible until/unless Mojang re-adds it.\r\n    //\r\n    // @Tags\r\n    // None\r\n    //\r\n    // @Usage\r\n    // Use to cause lightning to strike the player.\r\n    // - strike <player.location>\r\n    //\r\n    // @Usage\r\n    // Use to strike the player with lightning causing no damage.\r\n    // - strike no_damage <player.location>\r\n    // -->\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addNotesOfType(LocationTag.class);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"location\")\r\n                    && arg.matchesArgumentType(LocationTag.class)) {\r\n                scriptEntry.addObject(\"location\", arg.asType(LocationTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"location\")) {\r\n            throw new InvalidArgumentsException(\"Missing location argument!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        LocationTag location = scriptEntry.getObjectTag(\"location\");\r\n        boolean noDamage = scriptEntry.argAsBoolean(\"no_damage\");\r\n        boolean silent = scriptEntry.argAsBoolean(\"silent\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), location, db(\"no_damage\", noDamage), db(\"silent\", silent));\r\n        }\r\n        if (noDamage) {\r\n            location.getWorld().spigot().strikeLightningEffect(location, silent);\r\n        }\r\n        else {\r\n            location.getWorld().spigot().strikeLightning(location, silent);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/SwitchCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.properties.material.MaterialSwitchable;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Bell;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.data.Bisected;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.type.TrapDoor;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class SwitchCommand extends AbstractCommand {\r\n\r\n    public SwitchCommand() {\r\n        setName(\"switch\");\r\n        setSyntax(\"switch [<location>|...] (state:{toggle}/on/off) (duration:<value>) (no_physics)\");\r\n        setRequiredArguments(1, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Switch\r\n    // @Syntax switch [<location>|...] (state:{toggle}/on/off) (duration:<value>) (no_physics)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Short Switches state of the block.\r\n    // @Synonyms Toggle,Lever,Activate,Power,Redstone\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Changes the state of a block at the given location, or list of blocks at the given locations.\r\n    //\r\n    // Optionally specify \"state:on\" to turn a block on (or open it, or whatever as applicable) or \"state:off\" to turn it off (or close it, etc).\r\n    // By default, will toggle the state (on to off, or off to on).\r\n    //\r\n    // Optionally specify the \"duration\" argument to set a length of time after which the block will return to the original state.\r\n    //\r\n    // Works on any interactable blocks, including:\r\n    // - the standard toggling levers, doors, gates...\r\n    // - Single-use interactables like buttons, note blocks, dispensers, droppers, ...\r\n    // - Redstone interactables like repeaters, ...\r\n    // - Special interactables like tripwires, ...\r\n    // - Bells as a special case will ring when you use 'switch' on them, ...\r\n    // - Almost any other block with an interaction handler.\r\n    //\r\n    // This will generally (but not always) function equivalently to a user right-clicking the block\r\n    // (so it will open and close doors, flip levers on and off, press and depress buttons, ...).\r\n    //\r\n    // Optionally specify 'no_physics' to not apply a physics update after switching.\r\n    //\r\n    // @Tags\r\n    // <LocationTag.switched>\r\n    // <MaterialTag.switched>\r\n    //\r\n    // @Usage\r\n    // At the player's location, switch the state of the block to on, no matter what state it was in before.\r\n    // - switch <player.location> state:on\r\n    //\r\n    // @Usage\r\n    // Opens a door that the player is looking at.\r\n    // - switch <player.cursor_on> state:on\r\n    //\r\n    // @Usage\r\n    // Toggle a block at the player's foot location.\r\n    // - switch <player.location>\r\n    //\r\n    // -->\r\n\r\n    private enum SwitchState {ON, OFF, TOGGLE}\r\n\r\n    private Map<Location, Integer> taskMap = new HashMap<>(32);\r\n\r\n    @Override\r\n    public void addCustomTabCompletions(TabCompletionsBuilder tab) {\r\n        tab.addNotesOfType(LocationTag.class);\r\n    }\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"no_physics\") &&\r\n                    arg.matches(\"no_physics\")) {\r\n                scriptEntry.addObject(\"no_physics\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"locations\") &&\r\n                    arg.matchesArgumentList(LocationTag.class)) {\r\n                scriptEntry.addObject(\"locations\", arg.asType(ListTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"duration\") &&\r\n                    arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"state\") &&\r\n                    arg.matchesEnum(SwitchState.class)) {\r\n                scriptEntry.addObject(\"switchstate\", new ElementTag(arg.getValue().toUpperCase()));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"locations\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a location!\");\r\n        }\r\n        scriptEntry.defaultObject(\"duration\", new DurationTag(0));\r\n        scriptEntry.defaultObject(\"switchstate\", new ElementTag(\"TOGGLE\"));\r\n    }\r\n\r\n    @Override\r\n    public void execute(final ScriptEntry scriptEntry) {\r\n        final ListTag interactLocations = scriptEntry.getObjectTag(\"locations\");\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        final SwitchState switchState = SwitchState.valueOf(scriptEntry.getElement(\"switchstate\").asString());\r\n        ElementTag noPhysics = scriptEntry.getElement(\"no_physics\");\r\n        // Switch the Block\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), interactLocations, duration, noPhysics, db(\"switchstate\", switchState.name()));\r\n        }\r\n        final boolean physics = noPhysics == null || !noPhysics.asBoolean();\r\n        for (final LocationTag interactLocation : interactLocations.filter(LocationTag.class, scriptEntry)) {\r\n            switchBlock(scriptEntry, interactLocation, switchState, physics);\r\n            // If duration set, schedule a delayed task.\r\n            if (duration.getTicks() > 0) {\r\n                // If this block already had a delayed task, cancel it.\r\n                if (taskMap.containsKey(interactLocation)) {\r\n                    try {\r\n                        Bukkit.getScheduler().cancelTask(taskMap.get(interactLocation));\r\n                    }\r\n                    catch (Exception e) {\r\n                    }\r\n                }\r\n                Debug.echoDebug(scriptEntry, \"Setting delayed task 'SWITCH' for \" + interactLocation.identify());\r\n                // Store new delayed task ID, for checking against, then schedule new delayed task.\r\n                taskMap.put(interactLocation, Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> switchBlock(scriptEntry, interactLocation, SwitchState.TOGGLE, physics), duration.getTicks()));\r\n            }\r\n        }\r\n    }\r\n\r\n    public static boolean switchState(Block b) {\r\n        MaterialSwitchable switchable = MaterialSwitchable.getFrom(new MaterialTag(b.getBlockData()));\r\n        if (switchable == null) {\r\n            return false;\r\n        }\r\n        return switchable.getState();\r\n    }\r\n\r\n    // Break off this portion of the code from execute() so it can be used in both execute and the delayed runnable\r\n    public void switchBlock(ScriptEntry scriptEntry, Location interactLocation, SwitchState switchState, boolean physics) {\r\n        Block block = interactLocation.getBlock();\r\n        BlockData data1 = block.getBlockData();\r\n        MaterialTag materialTag = new MaterialTag(data1);\r\n        MaterialSwitchable switchable = MaterialSwitchable.getFrom(materialTag);\r\n        if (switchable == null) {\r\n            Debug.echoError(\"Cannot switch block of type '\" + materialTag.getMaterial().name() + \"'\");\r\n            return;\r\n        }\r\n        if (materialTag.getMaterial() == Material.BELL) {\r\n            NMSHandler.blockHelper.ringBell((Bell) block.getState());\r\n            return;\r\n        }\r\n        boolean currentState = switchable.getState();\r\n        if ((switchState.equals(SwitchState.ON) && !currentState) || (switchState.equals(SwitchState.OFF) && currentState) || switchState.equals(SwitchState.TOGGLE)) {\r\n            switchable.setState(!currentState);\r\n            if (physics) {\r\n                block.setBlockData(switchable.material.getModernData());\r\n            }\r\n            else {\r\n                ModifyBlockCommand.setBlock(block.getLocation(), materialTag, false, null, 0);\r\n            }\r\n            if (data1 instanceof Bisected && !(data1 instanceof TrapDoor)) { // TrapDoor implements Bisected, but is not actually bisected???\r\n                Location other = interactLocation.clone();\r\n                if (((Bisected) data1).getHalf() == Bisected.Half.TOP) {\r\n                    other = other.add(0, -1, 0);\r\n                }\r\n                else {\r\n                    other = other.add(0, 1, 0);\r\n                }\r\n                BlockData data2 = other.getBlock().getBlockData();\r\n                if (data2.getMaterial() == data1.getMaterial()) {\r\n                    MaterialSwitchable switchable2 = MaterialSwitchable.getFrom(new MaterialTag(data2));\r\n                    switchable2.setState(!currentState);\r\n                    other.getBlock().setBlockData(switchable2.material.getModernData());\r\n                    if (physics) {\r\n                        AdjustBlockCommand.applyPhysicsAt(other);\r\n                    }\r\n                }\r\n            }\r\n            if (physics) {\r\n                AdjustBlockCommand.applyPhysicsAt(interactLocation);\r\n            }\r\n            if (scriptEntry.dbCallShouldDebug()) {\r\n                Debug.echoDebug(scriptEntry, \"Switched \" + block.getType() + \"! Current state now: \" + (switchState(block) ? \"ON\" : \"OFF\"));\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/TickCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.generator.*;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ServerTickManager;\r\n\r\npublic class TickCommand extends AbstractCommand {\r\n\r\n    // <--[command]\r\n    // @Name tick\r\n    // @Syntax tick [rate/step/sprint/freeze/reset] (amount:<#.#>/cancel)\r\n    // @Required 1\r\n    // @Maximum 2\r\n    // @Short Controls the server's tick rate.\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Controls the server's tick rate. MC versions 1.20+ only.\r\n    //\r\n    // The tick rate affects things such as entity movement (including player movement), entity animations, plant growth, etc.\r\n    // Be careful when setting the tick rate, as a high tick rate can overload the server.\r\n    // If tick rate is high, crops will grow faster, animations will speed up, and so forth.\r\n    // Conversely, if the tick rate is low, crops will grow slower, animations will slow down, and entity movement (including player movement) will slow down and appear to be in slow motion.\r\n    // For a list of all the things that the tick rate affects, see <@link url https://minecraft.wiki/w/Tick>\r\n    //\r\n    // To change the tick rate, use the 'rate' argument and input the amount using 'amount'. The tick rate must be a number between 1.0 and 10000.0 (inclusive).\r\n    // To reset the tick rate to the normal value (20.0), use the 'reset' argument.\r\n    //\r\n    // To freeze the tick rate, use the 'freeze' argument. To unfreeze, add the 'cancel' argument.\r\n    // Freezing the tick rate will cause all entities (except for players and entities that a player is riding) to stop ticking.\r\n    // This means that entity movement and animations will stop, as well as stop things like crop growth, daylight cycle, etc.\r\n    //\r\n    // To step the tick rate while the server is frozen for a specific amount of time, use the 'step' argument and input the amount of ticks using 'amount'.\r\n    // Tick rate stepping is allowing the tick rate to resume for a specific amount of ticks while the server is frozen.\r\n    // After the amount of specified ticks have passed, the tick rate will refreeze.\r\n    // Step input should not be a number less than one.\r\n    // If the server is not frozen when trying to use the 'step' argument, nothing will happen.\r\n    //\r\n    // To make the tick rate as fast as possible for a specific amount of time, use the 'sprint' argument and input the amount of ticks using 'amount'.\r\n    // The tick rate will increase as much as possible without overloading the server for the amount of specified ticks.\r\n    // Sprint input should be not be a number less than one.\r\n    // For example, if you want to sprint 200 ticks, the tick rate will run 200 ticks as fast as possible and then return to the previous tick rate.\r\n    // To stop stepping or sprinting early, use the 'cancel' argument.\r\n    //\r\n    // The tick rate resets to 20.0 on server restart.\r\n    // For information about tick rate arguments, see <@link url https://minecraft.wiki/w/Commands/tick>\r\n    //\r\n    // @Warning Be careful, this command will affect plugins that depend on tick rate and may cause them to break. For example, setting the tick rate to 1 will cause the <@link event tick> event to fire once per second.\r\n    //\r\n    // @Usage\r\n    // Use to set the tick rate to 30 ticks per second.\r\n    // - tick rate amount:30\r\n    //\r\n    // @Usage\r\n    // Use to stop stepping early if the server is currently stepping.\r\n    // - tick step cancel\r\n    //\r\n    // @Usage\r\n    // Use to reset tick rate.\r\n    // - tick reset\r\n    //\r\n    // @Usage\r\n    // Use to freeze the server.\r\n    // - tick freeze\r\n    //\r\n    // @Usage\r\n    // Use to unfreeze the server.\r\n    // - tick freeze cancel\r\n    // -->\r\n\r\n    public TickCommand() {\r\n        setName(\"tick\");\r\n        setSyntax(\"tick [rate/step/sprint/freeze/reset] (amount:<#.#>/cancel)\");\r\n        setRequiredArguments(1, 2);\r\n        isProcedural = false;\r\n        autoCompile();\r\n    }\r\n\r\n    public enum TickActions { RATE, STEP, SPRINT, FREEZE, RESET }\r\n\r\n    public static void autoExecute(@ArgName(\"action\") TickActions action,\r\n                                   @ArgName(\"amount\") @ArgPrefixed @ArgDefaultText(\"0\") float amount,\r\n                                   @ArgName(\"cancel\") boolean cancel) {\r\n        ServerTickManager tickManager = Bukkit.getServerTickManager();\r\n        switch (action) {\r\n            case RATE -> {\r\n                if (amount < 1 || amount > 10000) {\r\n                    throw new InvalidArgumentsRuntimeException(\"Invalid input! Tick rate must be a decimal number between 1.0 and 10000.0 (inclusive)!\");\r\n                }\r\n                tickManager.setTickRate(amount);\r\n            }\r\n            case STEP -> {\r\n                if (cancel) {\r\n                    tickManager.stopStepping();\r\n                    return;\r\n                }\r\n                if (amount < 1) {\r\n                    throw new InvalidArgumentsRuntimeException(\"The step action must have a number input not less than 1!\");\r\n                }\r\n                tickManager.stepGameIfFrozen((int)amount);\r\n            }\r\n            case SPRINT -> {\r\n                if (cancel) {\r\n                    tickManager.stopSprinting();\r\n                    return;\r\n                }\r\n                if (amount < 1) {\r\n                    throw new InvalidArgumentsRuntimeException(\"The sprint action must have a number input not less than 1!\");\r\n                }\r\n                tickManager.requestGameToSprint((int)amount);\r\n            }\r\n            case FREEZE -> tickManager.setFrozen(!cancel);\r\n            case RESET -> tickManager.setTickRate(20);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/TimeCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.UUID;\r\n\r\npublic class TimeCommand extends AbstractCommand {\r\n\r\n    public TimeCommand() {\r\n        setName(\"time\");\r\n        setSyntax(\"time ({global}/player) [<time-duration>/reset] (<world>) (reset:<duration>) (freeze)\");\r\n        setRequiredArguments(1, 5);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Time\r\n    // @Syntax time ({global}/player) [<time-duration>/reset] (<world>) (reset:<duration>) (freeze)\r\n    // @Required 1\r\n    // @Maximum 5\r\n    // @Short Changes the current time in the minecraft world.\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Changes the current time in a world or the time that a player sees the world in.\r\n    // If no world is specified, defaults to the NPCs world. If no NPC is available,\r\n    // defaults to the player's world. If no player is available, an error will be thrown.\r\n    //\r\n    // If 'player' is specified, this will change their personal time.\r\n    // This is separate from the global time, and does not affect other players.\r\n    // When that player logs off, their time will be reset to the global time.\r\n    // Additionally, you may instead specify 'reset' to return the player's time back to global time.\r\n    // If you specify a custom time, optionally specify 'reset:<duration>'\r\n    // to set a time after which the player's time will reset (if not manually changed again before then).\r\n    // Optionally specify 'freeze' to lock a player's time in at the specified time value.\r\n    //\r\n    // @Tags\r\n    // <WorldTag.time>\r\n    // <WorldTag.time.period>\r\n    //\r\n    // @Usage\r\n    // Use to set the time in the NPC or Player's world.\r\n    // - time 500t\r\n    //\r\n    // @Usage\r\n    // Use to make the player see a different time than everyone else.\r\n    // - time player 500t\r\n    //\r\n    // @Usage\r\n    // Use to make the player see a different time than everyone else, with the sun no longer moving.\r\n    // - time player 500t freeze\r\n    //\r\n    // @Usage\r\n    // Use to make the player see a different time than everyone else for the next 5 minutes.\r\n    // - time player 500t reset:5m\r\n    //\r\n    // @Usage\r\n    // Use to make the player see the global time again.\r\n    // - time player reset\r\n    //\r\n    // @Usage\r\n    // Use to set the time in a specific world.\r\n    // - time 500t myworld\r\n    //\r\n    // -->\r\n\r\n    private enum Type {GLOBAL, PLAYER}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"type\")\r\n                    && arg.matchesEnum(Type.class)) {\r\n                scriptEntry.addObject(\"type\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"value\")\r\n                    && !scriptEntry.hasObject(\"reset\")\r\n                    && !arg.matchesPrefix(\"reset\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"value\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"value\")\r\n                    && !scriptEntry.hasObject(\"reset\")\r\n                    && arg.matches(\"reset\")) {\r\n                scriptEntry.addObject(\"reset\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"freeze\")\r\n                    && arg.matches(\"freeze\")) {\r\n                scriptEntry.addObject(\"freeze\", new ElementTag(true));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"reset_after\")\r\n                    && arg.matchesPrefix(\"reset\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"reset_after\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"world\")\r\n                    && arg.matchesArgumentType(WorldTag.class)) {\r\n                scriptEntry.addObject(\"world\", arg.asType(WorldTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"value\") && !scriptEntry.hasObject(\"reset\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a value!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"world\")) {\r\n            scriptEntry.addObject(\"world\", Utilities.entryDefaultWorld(scriptEntry, false));\r\n        }\r\n        scriptEntry.defaultObject(\"type\", new ElementTag(\"GLOBAL\"));\r\n    }\r\n\r\n    public HashMap<UUID, Integer> resetTasks = new HashMap<>();\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        DurationTag value = scriptEntry.getObjectTag(\"value\");\r\n        DurationTag resetAfter = scriptEntry.getObjectTag(\"reset_after\");\r\n        WorldTag world = scriptEntry.getObjectTag(\"world\");\r\n        ElementTag type_element = scriptEntry.getElement(\"type\");\r\n        ElementTag reset = scriptEntry.getElement(\"reset\");\r\n        ElementTag freeze = scriptEntry.getElement(\"freeze\");\r\n        Type type = Type.valueOf(type_element.asString().toUpperCase());\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), type_element, reset, value, freeze, resetAfter, world);\r\n        }\r\n        if (type.equals(Type.GLOBAL)) {\r\n            world.getWorld().setTime(value.getTicks());\r\n        }\r\n        else {\r\n            if (!Utilities.entryHasPlayer(scriptEntry)) {\r\n                Debug.echoError(\"Must have a valid player link!\");\r\n            }\r\n            else {\r\n                Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity();\r\n                if (reset != null && reset.asBoolean()) {\r\n                    player.resetPlayerTime();\r\n                }\r\n                else {\r\n                    Integer existingTask = resetTasks.get(player.getUniqueId());\r\n                    if (existingTask != null) {\r\n                        Bukkit.getScheduler().cancelTask(existingTask);\r\n                        resetTasks.remove(player.getUniqueId());\r\n                    }\r\n                    if (freeze == null || !freeze.asBoolean()) {\r\n                        // sets a relative time, so subtract by the world time to keep the command absolute\r\n                        player.setPlayerTime(value.getTicks() - player.getWorld().getTime(), true);\r\n                    }\r\n                    else {\r\n                        player.setPlayerTime(value.getTicks(), false);\r\n                    }\r\n                    if (resetAfter != null) {\r\n                        int newTask = Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), player::resetPlayerTime, resetAfter.getTicks());\r\n                        resetTasks.put(player.getUniqueId(), newTask);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/WeatherCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.WeatherType;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.UUID;\r\n\r\npublic class WeatherCommand extends AbstractCommand {\r\n\r\n    public WeatherCommand() {\r\n        setName(\"weather\");\r\n        setSyntax(\"weather ({global}/player) [sunny/storm/thunder/reset] (<world>) (reset:<duration>)\");\r\n        setRequiredArguments(1, 4);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name Weather\r\n    // @Syntax weather ({global}/player) [sunny/storm/thunder/reset] (<world>) (reset:<duration>)\r\n    // @Required 1\r\n    // @Maximum 4\r\n    // @Short Changes the current weather in the minecraft world.\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // By default, changes the weather in the specified world.\r\n    //\r\n    // If you specify 'player', this will change the weather in that player's view.\r\n    // This is separate from the global weather, and does not affect other players.\r\n    // When that player logs off, their weather will be reset to the global weather.\r\n    // Additionally, you may instead specify 'reset' to return the player's weather back to global weather.\r\n    // If you specify a custom weather, optionally specify 'reset:<duration>'\r\n    // to set a time after which the player's weather will reset (if not manually changed again before then).\r\n    // Note that 'thunder' is no different from 'storm' when used on a single player.\r\n    //\r\n    // @Tags\r\n    // <BiomeTag.downfall_type>\r\n    // <PlayerTag.weather>\r\n    // <WorldTag.has_storm>\r\n    // <WorldTag.weather_duration>\r\n    // <WorldTag.thundering>\r\n    // <WorldTag.thunder_duration>\r\n    //\r\n    // @Usage\r\n    // Use to make the weather sunny.\r\n    // - weather sunny\r\n    //\r\n    // @Usage\r\n    // Use to start a storm in world \"cookies\".\r\n    // - weather storm cookies\r\n    //\r\n    // @Usage\r\n    // Use to start a storm that's only visible to the attached player.\r\n    // - weather player storm\r\n    //\r\n    // @Usage\r\n    // Use to make the player see a storm for 2 minutes.\r\n    // - weather player storm reset:2m\r\n    //\r\n    // @Usage\r\n    // Use to let the player see the global weather again.\r\n    // - weather player reset\r\n    //\r\n    // -->\r\n\r\n    private enum Type {GLOBAL, PLAYER}\r\n\r\n    private enum Value {SUNNY, STORM, THUNDER, RESET}\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"type\")\r\n                    && arg.matchesEnum(Type.class)) {\r\n                scriptEntry.addObject(\"type\", Type.valueOf(arg.getValue().toUpperCase()));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"world\")\r\n                    && arg.matchesArgumentType(WorldTag.class)) {\r\n                scriptEntry.addObject(\"world\", arg.asType(WorldTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"value\")\r\n                    && arg.matchesEnum(Value.class)) {\r\n                scriptEntry.addObject(\"value\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"reset_after\")\r\n                    && arg.matchesPrefix(\"reset\")\r\n                    && arg.matchesArgumentType(DurationTag.class)) {\r\n                scriptEntry.addObject(\"reset_after\", arg.asType(DurationTag.class));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"value\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a value!\");\r\n        }\r\n        scriptEntry.defaultObject(\"type\", Type.GLOBAL);\r\n        scriptEntry.defaultObject(\"world\", Utilities.entryDefaultWorld(scriptEntry, false));\r\n    }\r\n\r\n    public HashMap<UUID, Integer> resetTasks = new HashMap<>();\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        Value value = Value.valueOf((scriptEntry.getElement(\"value\")).asString().toUpperCase());\r\n        WorldTag world = scriptEntry.getObjectTag(\"world\");\r\n        Type type = (Type) scriptEntry.getObject(\"type\");\r\n        DurationTag resetAfter = scriptEntry.getObjectTag(\"reset_after\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), db(\"type\", type.name()),\r\n                    (type.name().equalsIgnoreCase(\"player\") ? db(\"player\", Utilities.getEntryPlayer(scriptEntry)) : \"\"),\r\n                    (type.name().equalsIgnoreCase(\"global\") ? db(\"world\", world) : \"\"), resetAfter, db(\"value\", value));\r\n        }\r\n        if (type.equals(Type.GLOBAL)) {\r\n            switch (value) {\r\n                case SUNNY:\r\n                    world.getWorld().setStorm(false);\r\n                    world.getWorld().setThundering(false);\r\n                    break;\r\n                case STORM:\r\n                    world.getWorld().setStorm(true);\r\n                    break;\r\n                case THUNDER:\r\n                    world.getWorld().setStorm(true);\r\n                    world.getWorld().setThundering(true);\r\n                    break;\r\n                case RESET:\r\n                    Debug.echoError(\"Cannot RESET global weather!\");\r\n                    break;\r\n            }\r\n        }\r\n        else {\r\n            Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity();\r\n            Integer existingTask = resetTasks.get(player.getUniqueId());\r\n            if (existingTask != null) {\r\n                Bukkit.getScheduler().cancelTask(existingTask);\r\n                resetTasks.remove(player.getUniqueId());\r\n            }\r\n            if (value == Value.SUNNY) {\r\n                player.setPlayerWeather(WeatherType.CLEAR);\r\n            }\r\n            else if (value == Value.STORM || value == Value.THUNDER) {\r\n                player.setPlayerWeather(WeatherType.DOWNFALL);\r\n            }\r\n            else if (value == Value.RESET) {\r\n                player.resetPlayerWeather();\r\n            }\r\n            if (resetAfter != null) {\r\n                int newTask = Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), player::resetPlayerWeather, resetAfter.getTicks());\r\n                resetTasks.put(player.getUniqueId(), newTask);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/WorldBorderCommand.java",
    "content": "package com.denizenscript.denizen.scripts.commands.world;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsException;\r\nimport com.denizenscript.denizencore.objects.*;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport org.bukkit.WorldBorder;\r\n\r\nimport java.util.List;\r\n\r\npublic class WorldBorderCommand extends AbstractCommand {\r\n\r\n    public WorldBorderCommand() {\r\n        setName(\"worldborder\");\r\n        setSyntax(\"worldborder [<world>/<player>|...] (center:<location>) (size:<#.#>) (current_size:<#.#>) (damage:<#.#>) (damagebuffer:<#.#>) (warningdistance:<#>) (warningtime:<duration>) (duration:<duration>) (reset)\");\r\n        setRequiredArguments(2, 10);\r\n        isProcedural = false;\r\n    }\r\n\r\n    // <--[command]\r\n    // @Name WorldBorder\r\n    // @Syntax worldborder [<world>/<player>|...] (center:<location>) (size:<#.#>) (current_size:<#.#>) (damage:<#.#>) (damagebuffer:<#.#>) (warningdistance:<#>) (warningtime:<duration>) (duration:<duration>) (reset)\r\n    // @Required 2\r\n    // @Maximum 10\r\n    // @Short Modifies a world border.\r\n    // @Group world\r\n    //\r\n    // @Description\r\n    // Modifies the world border of a specified world or a list of players.\r\n    // NOTE: Modifying player world borders is client-side and will reset on death, relog, or other actions.\r\n    // Options are:\r\n    // center: Sets the center of the world border.\r\n    // size: Sets the new size of the world border.\r\n    // current_size: Sets the initial size of the world border when resizing it over a duration.\r\n    // damage: Sets the amount of damage a player takes when outside the world border buffer radius.\r\n    // damagebuffer: Sets the radius a player may safely be outside the world border before taking damage.\r\n    // warningdistance: Causes the screen to be tinted red when the player is within the specified radius from the world border.\r\n    // warningtime: Causes the screen to be tinted red when a contracting world border will reach the player within the specified time.\r\n    // duration: Causes the world border to grow or shrink from its current size to its new size over the specified duration.\r\n    // reset: Resets the world border to its vanilla defaults for a world, or to the current world border for players.\r\n    //\r\n    // @Tags\r\n    // <LocationTag.is_within_border>\r\n    // <WorldTag.border_size>\r\n    // <WorldTag.border_center>\r\n    // <WorldTag.border_damage>\r\n    // <WorldTag.border_damage_buffer>\r\n    // <WorldTag.border_warning_distance>\r\n    // <WorldTag.border_warning_time>\r\n    //\r\n    // @Usage\r\n    // Use to set the size of a world border.\r\n    // - worldborder <player.location.world> size:4\r\n    //\r\n    // @Usage\r\n    // Use to update a world border's center, and then the size over the course of 10 seconds.\r\n    // - worldborder <[world]> center:<[world].spawn_location> size:100 duration:10s\r\n    //\r\n    // @Usage\r\n    // Use to show a client-side world border to the attached player.\r\n    // - worldborder <player> center:<player.location> size:10\r\n    // -->\r\n\r\n    @Override\r\n    public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {\r\n        for (Argument arg : scriptEntry) {\r\n            if (!scriptEntry.hasObject(\"center\")\r\n                    && arg.matchesArgumentType(LocationTag.class)\r\n                    && arg.matchesPrefix(\"center\")) {\r\n                scriptEntry.addObject(\"center\", arg.asType(LocationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"damage\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"damage\")) {\r\n                scriptEntry.addObject(\"damage\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"damagebuffer\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"damagebuffer\")) {\r\n                scriptEntry.addObject(\"damagebuffer\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"size\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"size\")) {\r\n                scriptEntry.addObject(\"size\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"current_size\")\r\n                    && arg.matchesFloat()\r\n                    && arg.matchesPrefix(\"current_size\")) {\r\n                scriptEntry.addObject(\"current_size\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"duration\")\r\n                    && arg.matchesArgumentType(DurationTag.class)\r\n                    && arg.matchesPrefix(\"duration\")) {\r\n                scriptEntry.addObject(\"duration\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"warningdistance\")\r\n                    && arg.matchesInteger()\r\n                    && arg.matchesPrefix(\"warningdistance\")) {\r\n                scriptEntry.addObject(\"warningdistance\", arg.asElement());\r\n            }\r\n            else if (!scriptEntry.hasObject(\"warningtime\")\r\n                    && arg.matchesArgumentType(DurationTag.class)\r\n                    && arg.matchesPrefix(\"warningtime\")) {\r\n                scriptEntry.addObject(\"warningtime\", arg.asType(DurationTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"world\")\r\n                    && arg.matchesArgumentType(WorldTag.class)) {\r\n                scriptEntry.addObject(\"world\", arg.asType(WorldTag.class));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"players\")\r\n                    && arg.matchesArgumentList(PlayerTag.class)) {\r\n                scriptEntry.addObject(\"players\", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));\r\n            }\r\n            else if (!scriptEntry.hasObject(\"reset\")\r\n                    && arg.matches(\"reset\")) {\r\n                scriptEntry.addObject(\"reset\", new ElementTag(\"true\"));\r\n            }\r\n            else {\r\n                arg.reportUnhandled();\r\n            }\r\n        }\r\n        if (!scriptEntry.hasObject(\"world\") && !scriptEntry.hasObject(\"players\")) {\r\n            throw new InvalidArgumentsException(\"Must specify a world or players!\");\r\n        }\r\n        if (!scriptEntry.hasObject(\"center\") && !scriptEntry.hasObject(\"size\")\r\n                && !scriptEntry.hasObject(\"damage\") && !scriptEntry.hasObject(\"damagebuffer\")\r\n                && !scriptEntry.hasObject(\"warningdistance\") && !scriptEntry.hasObject(\"warningtime\")\r\n                && !scriptEntry.hasObject(\"reset\")) {\r\n            throw new InvalidArgumentsException(\"Must specify at least one option!\");\r\n        }\r\n        scriptEntry.defaultObject(\"duration\", new DurationTag(0));\r\n        scriptEntry.defaultObject(\"reset\", new ElementTag(\"false\"));\r\n    }\r\n\r\n    @Override\r\n    public void execute(ScriptEntry scriptEntry) {\r\n        WorldTag world = scriptEntry.getObjectTag(\"world\");\r\n        List<PlayerTag> players = (List<PlayerTag>) scriptEntry.getObject(\"players\");\r\n        LocationTag center = scriptEntry.getObjectTag(\"center\");\r\n        ElementTag size = scriptEntry.getElement(\"size\");\r\n        ElementTag currSize = scriptEntry.getElement(\"current_size\");\r\n        ElementTag damage = scriptEntry.getElement(\"damage\");\r\n        ElementTag damagebuffer = scriptEntry.getElement(\"damagebuffer\");\r\n        DurationTag duration = scriptEntry.getObjectTag(\"duration\");\r\n        ElementTag warningdistance = scriptEntry.getElement(\"warningdistance\");\r\n        DurationTag warningtime = scriptEntry.getObjectTag(\"warningtime\");\r\n        ElementTag reset = scriptEntry.getObjectTag(\"reset\");\r\n        if (scriptEntry.dbCallShouldDebug()) {\r\n            Debug.report(scriptEntry, getName(), world, db(\"players\", players), center, size, currSize, damage, damagebuffer, warningdistance, warningtime, duration, reset);\r\n        }\r\n        if (players != null) {\r\n            if (reset.asBoolean()) {\r\n                for (PlayerTag player : players) {\r\n                    NMSHandler.packetHelper.resetWorldBorder(player.getPlayerEntity());\r\n                }\r\n                return;\r\n            }\r\n            WorldBorder wb;\r\n            for (PlayerTag player : players) {\r\n                wb = player.getWorld().getWorldBorder();\r\n                NMSHandler.packetHelper.setWorldBorder(\r\n                        player.getPlayerEntity(),\r\n                        (center != null ? center : wb.getCenter()),\r\n                        (size != null ? size.asDouble() : wb.getSize()),\r\n                        (currSize != null ? currSize.asDouble() : wb.getSize()),\r\n                        duration.getMillis(),\r\n                        (warningdistance != null ? warningdistance.asInt() : wb.getWarningDistance()),\r\n                        (warningtime != null ? warningtime.getSecondsAsInt() : wb.getWarningTime()));\r\n            }\r\n            return;\r\n        }\r\n        WorldBorder worldborder = world.getWorld().getWorldBorder();\r\n        if (reset.asBoolean()) {\r\n            worldborder.reset();\r\n            return;\r\n        }\r\n        if (center != null) {\r\n            worldborder.setCenter(center);\r\n        }\r\n        if (size != null) {\r\n            if (currSize != null) {\r\n                worldborder.setSize(currSize.asDouble());\r\n            }\r\n            worldborder.setSize(size.asDouble(), duration.getSecondsAsInt());\r\n        }\r\n        if (damage != null) {\r\n            worldborder.setDamageAmount(damage.asDouble());\r\n        }\r\n        if (damagebuffer != null) {\r\n            worldborder.setDamageBuffer(damagebuffer.asDouble());\r\n        }\r\n        if (warningdistance != null) {\r\n            worldborder.setWarningDistance(warningdistance.asInt());\r\n        }\r\n        if (warningtime != null) {\r\n            worldborder.setWarningTime(warningtime.getSecondsAsInt());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/ContainerRegistry.java",
    "content": "package com.denizenscript.denizen.scripts.containers;\r\n\r\nimport com.denizenscript.denizen.scripts.containers.core.*;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\n\r\npublic class ContainerRegistry {\r\n\r\n    public static void registerMainContainers() {\r\n        ScriptRegistry._registerType(\"assignment\", AssignmentScriptContainer.class);\r\n        ScriptRegistry._registerType(\"book\", BookScriptContainer.class);\r\n        ScriptRegistry._registerType(\"command\", CommandScriptContainer.class);\r\n        if (Depends.vault != null) {\r\n            ScriptRegistry._registerType(\"economy\", EconomyScriptContainer.class);\r\n        }\r\n        ScriptRegistry._registerType(\"enchantment\", EnchantmentScriptContainer.class);\r\n        ScriptRegistry._registerType(\"entity\", EntityScriptContainer.class);\r\n        ScriptRegistry._registerType(\"interact\", InteractScriptContainer.class);\r\n        ScriptRegistry._registerType(\"inventory\", InventoryScriptContainer.class);\r\n        ScriptRegistry._registerType(\"item\", ItemScriptContainer.class);\r\n        ScriptRegistry._registerType(\"map\", MapScriptContainer.class);\r\n        ScriptRegistry._registerType(\"version\", VersionScriptContainer.class);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/AssignmentScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\n\r\nimport java.util.List;\r\n\r\npublic class AssignmentScriptContainer extends ScriptContainer {\r\n\r\n    // <--[language]\r\n    // @name Assignment Script Containers\r\n    // @group Script Container System\r\n    // @description\r\n    // Assignment script-containers provide functionality to NPCs by 'assignment' of the container. Assignment\r\n    // scripts are meant to be used when customizing the normal behavior of NPCs. This can be used on a 'per-NPC' basis,\r\n    // but the encouraged approach is to design assignment scripts in a way that they can be used for multiple NPCs,\r\n    // perhaps with the use of constants or flags to determine specific information required by the scripts.\r\n    //\r\n    // Features unique to assignment script-containers include 'actions' and 'interact script' assignment.\r\n    // Like any script, the ability to run local utility scripts can be accomplished as well. This allows fully\r\n    // interactive NPCs to be built purely with Assignment Scripts, and for advanced situations, world scripts and\r\n    // interact scripts can provide more functionality.\r\n    // See also <@link language interact script containers>\r\n    //\r\n    // Assignments scripts can be automatically disabled by adding \"enabled: false\" as a root key (supports any load-time-parseable tags).\r\n    // This will disable any \"actions\" on the script (but not interact scripts steps - disable the interact for that).\r\n    //\r\n    // Basic structure of an assignment script:\r\n    // <code>\r\n    // Assignment_Script_Name:\r\n    //\r\n    //     type: assignment\r\n    //\r\n    //     # | All assignment scripts MUST have this key!\r\n    //     actions:\r\n    //         on <action>:\r\n    //         - ...\r\n    //\r\n    //     # | Most assignment scripts should exclude this key, but it's available.\r\n    //     default constants:\r\n    //         <constant_name>: <value>\r\n    //         <constant_list>:\r\n    //         - ...\r\n    //\r\n    //     # | MOST assignment scripts should have this key!\r\n    //     interact scripts:\r\n    //     - <interact_script_name>\r\n    // </code>\r\n    //\r\n    // Though note that almost always you should include the 'actions:' key, usually with the 'on assignment:' action (if using triggers).\r\n    // Refer to <@link action assignment>.\r\n    //\r\n    // -->\r\n\r\n    public AssignmentScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\r\n        super(configurationSection, scriptContainerName);\r\n        if (contains(\"interact scripts\", List.class)) {\r\n            List<String> names = getStringList(\"interact scripts\");\r\n            if (!names.isEmpty()) {\r\n                if (names.size() > 1) {\r\n                    Debug.echoError(\"Assignment script '\" + getName() + \"' invalid: assignment scripts should only have ONE interact script in modern Denizen, not multiple!\");\r\n                }\r\n                String name = names.get(0);\r\n                int space = name.indexOf(' ');\r\n                if (space != -1 && Character.isDigit(name.charAt(0))) {\r\n                    BukkitImplDeprecations.interactScriptPriority.warn(this);\r\n                    name = name.substring(space + 1).replace(\"^\", \"\");\r\n                }\r\n                interactName = name;\r\n            }\r\n        }\r\n    }\r\n\r\n    public String interactName;\r\n\r\n    public InteractScriptContainer interact;\r\n\r\n    public InteractScriptContainer getInteract() {\r\n        if (interact == null && interactName != null) {\r\n            interact = ScriptRegistry.getScriptContainer(interactName);\r\n            if (interact == null) {\r\n                Debug.echoError(\"'\" + interactName + \"' is not a valid Interact Script. Is there a duplicate script by this name, or is it missing?\");\r\n            }\r\n        }\r\n        return interact;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/BookScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.meta.BookMeta;\r\n\r\nimport java.util.List;\r\n\r\npublic class BookScriptContainer extends ScriptContainer {\r\n\r\n    // <--[language]\r\n    // @name Book Script Containers\r\n    // @group Script Container System\r\n    // @description\r\n    // Book script containers are similar to item script containers, except they are specifically\r\n    // for the book items. They work with the ItemTag object, and can be fetched\r\n    // with the Object Fetcher by using the ItemTag constructor book_script_name\r\n    // Example: - give <player> my_book\r\n    //\r\n    // <code>\r\n    // Book_Script_Name:\r\n    //\r\n    //     type: book\r\n    //\r\n    //     # The 'custom name' can be anything you wish.\r\n    //     # | All book scripts MUST have this key!\r\n    //     title: custom name\r\n    //\r\n    //     # The 'custom name' can be anything you wish.\r\n    //     # | All book scripts MUST have this key!\r\n    //     author: custom name\r\n    //\r\n    //     # Defaults to true. Set to false to spawn a 'book and quill' instead of a 'written book'.\r\n    //     # | Some book scripts might have this key!\r\n    //     signed: true/false\r\n    //\r\n    //     # Each -line in the text section represents an entire page.\r\n    //     # To create a newline, use the tag <n>. To create a paragraph, use <p>.\r\n    //     # | All book scripts MUST have this key!\r\n    //     text:\r\n    //     - page\r\n    //     - ...\r\n    // </code>\r\n    //\r\n    // -->\r\n    public BookScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\r\n        super(configurationSection, scriptContainerName);\r\n        canRunScripts = false;\r\n    }\r\n\r\n    public ItemTag getBookFrom(TagContext context) {\r\n        Material material = Material.WRITTEN_BOOK;\r\n        if (contains(\"signed\", String.class) && getString(\"signed\").equalsIgnoreCase(\"false\")) {\r\n            material = Material.WRITABLE_BOOK;\r\n        }\r\n        ItemTag stack = new ItemTag(material);\r\n        return writeBookTo(stack, context);\r\n    }\r\n\r\n    public ItemTag writeBookTo(ItemTag book, TagContext context) {\r\n        if (context == null) {\r\n            context = new BukkitTagContext(null, null, new ScriptTag(this));\r\n        }\r\n        else {\r\n            context = context.clone();\r\n            context.script = new ScriptTag(this);\r\n        }\r\n        if (contains(\"signed\", String.class)) {\r\n            Material target = getString(\"signed\").equalsIgnoreCase(\"false\") ? Material.WRITABLE_BOOK : Material.WRITTEN_BOOK;\r\n            if (book.getItemStack().getType() != target) {\r\n                book.getItemStack().setType(target);\r\n                book.setItemStack(book.getItemStack());\r\n            }\r\n        }\r\n        BookMeta bookInfo = (BookMeta) book.getItemMeta();\r\n        if (contains(\"title\", String.class)) {\r\n            String title = getString(\"title\");\r\n            title = TagManager.tag(title, context);\r\n            bookInfo.setTitle(title);\r\n        }\r\n        if (contains(\"author\", String.class)) {\r\n            String author = getString(\"author\");\r\n            author = TagManager.tag(author, context);\r\n            bookInfo.setAuthor(author);\r\n        }\r\n        if (contains(\"text\", List.class)) {\r\n            List<String> pages = getStringList(\"text\");\r\n            for (String page : pages) {\r\n                page = TagManager.tag(page, context);\r\n                bookInfo.spigot().addPage(FormattedTextHelper.parse(page, ChatColor.BLACK));\r\n            }\r\n        }\r\n        book.setItemMeta(bookInfo);\r\n        return book;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/BukkitWorldScriptHelper.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.world.TimeChangeScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.utilities.ScoreboardHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.flags.DataPersistenceFlagTracker;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.World;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.AsyncPlayerChatEvent;\r\nimport org.bukkit.event.player.PlayerJoinEvent;\r\nimport org.bukkit.event.player.PlayerLoginEvent;\r\nimport org.bukkit.event.player.PlayerQuitEvent;\r\nimport org.bukkit.event.world.ChunkLoadEvent;\r\nimport org.bukkit.scoreboard.Scoreboard;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class BukkitWorldScriptHelper implements Listener {\r\n\r\n    public BukkitWorldScriptHelper() {\r\n        Denizen.getInstance().getServer().getPluginManager().registerEvents(this, Denizen.getInstance());\r\n    }\r\n\r\n    /////////////////////\r\n    //   CUSTOM EVENTS\r\n    /////////////////\r\n\r\n    public void serverStartEvent() {\r\n        long ticks = Settings.worldScriptTimeEventFrequency().getTicks();\r\n        Bukkit.getScheduler().scheduleSyncRepeatingTask(Denizen.getInstance(),\r\n                this::timeEvent, ticks, ticks);\r\n    }\r\n\r\n    private final Map<String, Integer> current_time = new HashMap<>();\r\n\r\n    public void timeEvent() {\r\n        for (World world : Bukkit.getWorlds()) {\r\n            int hour = (int) (world.getTime() / 1000);\r\n            hour = hour + 6;\r\n            // Get the hour\r\n            if (hour >= 24) {\r\n                hour = hour - 24;\r\n            }\r\n            if (!current_time.containsKey(world.getName())\r\n                    || current_time.get(world.getName()) != hour) {\r\n                current_time.put(world.getName(), hour);\r\n                TimeChangeScriptEvent.instance.hour = hour;\r\n                TimeChangeScriptEvent.instance.world = new WorldTag(world);\r\n                TimeChangeScriptEvent.instance.fire();\r\n            }\r\n        }\r\n    }\r\n\r\n    /////////////////////\r\n    //   PLAYER EVENTS\r\n    /////////////////\r\n\r\n    @EventHandler(priority = EventPriority.MONITOR)\r\n    public void onPlayerJoins(PlayerJoinEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        String board = ScoreboardHelper.viewerMap.get(event.getPlayer().getUniqueId());\r\n        if (board != null) {\r\n            Scoreboard score = ScoreboardHelper.getScoreboard(board);\r\n            if (score != null) {\r\n                event.getPlayer().setScoreboard(score);\r\n            }\r\n        }\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.MONITOR)\r\n    public void onPlayerChat(final AsyncPlayerChatEvent event) {\r\n        Bukkit.getScheduler().runTaskLater(Denizen.instance, () -> {\r\n            // If currently recording debug information, add the chat message to debug output\r\n            if (CoreConfiguration.shouldRecordDebug) {\r\n                Debug.log(ChatColor.DARK_GREEN + \"CHAT: \" + event.getPlayer().getName() + \": \" + event.getMessage());\r\n            }\r\n        }, 1);\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.MONITOR)\r\n    public void playerLogin(PlayerLoginEvent event) {\r\n        if (EntityTag.isNPC(event.getPlayer())) {\r\n            return;\r\n        }\r\n        PlayerTag.notePlayer(event.getPlayer());\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerQuit(PlayerQuitEvent event) {\r\n        NMSHandler.packetHelper.removeNoCollideTeam(event.getPlayer(), null);\r\n    }\r\n\r\n    @EventHandler\r\n    public void chunkLoadEvent(ChunkLoadEvent event) {\r\n        if (CoreConfiguration.skipAllFlagCleanings || Settings.skipChunkFlagCleaning) {\r\n            return;\r\n        }\r\n        new DataPersistenceFlagTracker(event.getChunk()).doTotalClean();\r\n    }\r\n\r\n    public static void cleanAllWorldChunkFlags() {\r\n        for (World world : Bukkit.getWorlds()) {\r\n            for (Chunk chunk : world.getLoadedChunks()) {\r\n                new DataPersistenceFlagTracker(chunk).doTotalClean();\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/CommandScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.scripts.queues.ContextSource;\r\nimport com.denizenscript.denizencore.scripts.queues.ScriptQueue;\r\nimport com.denizenscript.denizencore.scripts.queues.core.InstantQueue;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class CommandScriptContainer extends ScriptContainer {\r\n\r\n    // <--[language]\r\n    // @name Command Script Containers\r\n    // @group Script Container System\r\n    // @description\r\n    // Command script containers allow you to register your own custom commands to the server.\r\n    // This also allows the command to show up in the '/help' command, with some info on the command.\r\n    //\r\n    // Note that existing names or aliases from other plugins will be overridden.\r\n    // If you want to run a script at the same time as an existing command, see <@link event on command>.\r\n    //\r\n    // The following is the format for the container.\r\n    //\r\n    // The required keys are 'name:', 'description:', 'usage:', and 'script:'\r\n    // All other keys can be excluded if unneeded.\r\n    // If you are not intentionally setting a specific value for the other keys, it is\r\n    // strongly recommended that you simply not include them at all.\r\n    //\r\n    // Please note that 'name:' is the true name of the command (written by users),\r\n    // and 'usage:' is for documentation in the '/help' command.\r\n    // These two options should almost always show the same name.\r\n    //\r\n    //\r\n    // Command scripts can be automatically disabled by adding \"enabled: false\" as a root key (supports any load-time-parseable tags).\r\n    //\r\n    // <code>\r\n    // # The name of the script doesn't matter, and will not affect the command in any way.\r\n    // Command_Script_Name:\r\n    //\r\n    //     type: command\r\n    //\r\n    //     # The name of the command. This will be the default method for running the command, and will show in '/help'.\r\n    //     # | All command scripts MUST have this key!\r\n    //     name: mycmd\r\n    //\r\n    //     # The description of the command. This will be shown in the '/help' command.\r\n    //     # Multiple lines are acceptable, via <&nl> (the tag for a new line), but you should\r\n    //     # make the first line a brief summary of what your command does.\r\n    //     # | All command scripts MUST have this key!\r\n    //     description: My command.\r\n    //\r\n    //     # Correct usage for the command. This will show in the '/help' command.\r\n    //     # This is NOT the name of the command, and it is NOT used to control input parsing. It is EXCLUSIVELY for '/help'.\r\n    //     # | All command scripts MUST have this key!\r\n    //     usage: /mycmd <&lt>myArg1<&gt>\r\n    //\r\n    //     # A list of aliases for the command. These will be used as alternative ways to trigger the command, and will show in the '/help' command.\r\n    //     # | Some command scripts might have this key, but it's optional.\r\n    //     aliases:\r\n    //     - myalias\r\n    //     - mycommand\r\n    //\r\n    //     # The permission node to check for permissions plugins. This will automatically\r\n    //     # block players without the permission from accessing the command and help for\r\n    //     # the command.\r\n    //     # Note that you can include multiple permission nodes (a player only needs to have any one permission from the list)\r\n    //     # by separating them with a semicolon, like: perm.one;perm.two;third.perm\r\n    //     # | Most command scripts should have this key!\r\n    //     permission: my.permission.node\r\n    //\r\n    //     # The message to send to the player when they try to use the command without\r\n    //     # permission. If this is not specified, the default Bukkit message will be sent.\r\n    //     # Has \"permission\" def available.\r\n    //     # | Most command scripts should NOT have this key, but it's available.\r\n    //     permission message: Sorry, <player.name>, you can't use my command because you don't have the permission '<[permission]>'!\r\n    //\r\n    //     # The procedure-based script that will be checked when a player or the console\r\n    //     # is trying to view help for this command. This must always be determined true\r\n    //     # or false. If there is no script, it's assumed that all players and the console\r\n    //     # should be allowed to view the help for this command.\r\n    //     # Available context: <context.server> returns whether the server is viewing the help (a player if false).\r\n    //     # | Most command scripts should NOT have this key, but it's available.\r\n    //     allowed help:\r\n    //     - determine <player.has_flag[special_allowed_help_flag]||<context.server>>\r\n    //\r\n    //     # You can optionally specify tab completions on a per-argument basis.\r\n    //     # Available context:\r\n    //     # <context.args> returns a list of input arguments.\r\n    //     # <context.raw_args> returns all the arguments as raw text.\r\n    //     # <context.server> returns whether the server is using tab completion (a player if false).\r\n    //     # <context.alias> returns the command alias being used.\r\n    //     # | This key is great to have when used well, but is not required.\r\n    //     tab completions:\r\n    //         # This will complete \"alpha\" and \"beta\" for the first argument\r\n    //         1: alpha|beta\r\n    //         # This will complete any online player name for the second argument\r\n    //         2: <server.online_players.parse[name]>\r\n    //         # This will allow flags \"-a\", \"-b\", or \"-c\" to be entered in the third, fourth, or fifth argument.\r\n    //         3 4 5: -a|-b|-c\r\n    //         # Any argument other than the ones explicitly listed will be handled here with a tab complete that just says 'StopTyping'.\r\n    //         default: StopTyping\r\n    //\r\n    //     # You can also optionally use the 'tab complete' key to build custom procedure-style tab complete logic\r\n    //     # if the simply numeric argument basis isn't sufficient.\r\n    //     # Has the same context available as 'tab completions'.\r\n    //     # | Most scripts should leave this key off, though it can be useful to some.\r\n    //     tab complete:\r\n    //     - determine some|dynamic|logic|here\r\n    //\r\n    //     # The script that will run when the command is executed.\r\n    //     # No, you do not need '- determine fulfilled' or anything of the sort, since the command is fully registered.\r\n    //     # Available context:\r\n    //     # <context.args> returns a list of input arguments.\r\n    //     # <context.raw_args> returns all the arguments as raw text.\r\n    //     # <context.source_type> returns the source of the command. Can be: PLAYER, SERVER, COMMAND_BLOCK, or COMMAND_MINECART.\r\n    //     # <context.alias> returns the command alias being used.\r\n    //     # <context.command_block_location> returns the command block's location (if the command was run from one).\r\n    //     # <context.command_minecart> returns the EntityTag of the command minecart (if the command was run from one).\r\n    //     # | All command scripts MUST have this key!\r\n    //     script:\r\n    //     - narrate Yay!\r\n    //     - narrate \"My command worked!\"\r\n    //     - narrate \"And I typed '/<context.alias> <context.raw_args>'!\"\r\n    // </code>\r\n    //\r\n    // -->\r\n\r\n    public CommandScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\r\n        super(configurationSection, scriptContainerName);\r\n        CommandScriptHelper.init();\r\n        CommandScriptHelper.commandScripts.put(getName(), this);\r\n        if (containsScriptSection(\"tab complete\")) {\r\n            hasProcStyleTabComplete = true;\r\n        }\r\n        if (contains(\"tab completions\", Map.class)) {\r\n            tabCompletionTaggables = new HashMap<>();\r\n            YamlConfiguration section = getConfigurationSection(\"tab completions\");\r\n            for (StringHolder key : section.getKeys(false)) {\r\n                String val = section.getString(key.str);\r\n                if (key.str.equals(\"default\")) {\r\n                    tabCompletionTaggables.put(-1, val);\r\n                }\r\n                else {\r\n                    try {\r\n                        for (String num : key.str.split(\" \")) {\r\n                            tabCompletionTaggables.put(Integer.parseInt(num), val);\r\n                        }\r\n                    }\r\n                    catch (NumberFormatException ex) {\r\n                        Debug.echoError(\"Invalid tab completion argument number key '\" + key.str + \"'.\");\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        handlePseudoTagBasesDeprecation(\"permission message\", \"<permission\");\r\n    }\r\n\r\n    public boolean hasProcStyleTabComplete = false;\r\n\r\n    public HashMap<Integer, String> tabCompletionTaggables;\r\n\r\n    public String getCommandName() {\r\n        String name = getString(\"name\", null);\r\n        if (name == null) {\r\n            Debug.echoError(\"Command script '\" + getName() + \"' is missing required 'name' key!\");\r\n            return null;\r\n        }\r\n        return CoreUtilities.toLowerCase(name);\r\n    }\r\n\r\n    public String getDescription() {\r\n        // Replace new lines with a space and a new line, to allow full brief descriptions in /help.\r\n        // Without this, \"line<n>line\"s brief description would be \"lin\", because who doesn't like\r\n        // random cutoff-\r\n        return getString(\"description\", \"\", true).replace(\"\\n\", \" \\n\");\r\n    }\r\n\r\n    public String getUsage() {\r\n        return getString(\"usage\", \"\", true);\r\n    }\r\n\r\n    public List<String> getAliases() {\r\n        List<String> aliases = getStringList(\"aliases\", true);\r\n        return aliases != null ? aliases : new ArrayList<>();\r\n    }\r\n\r\n    public String getPermission() {\r\n        return getString(\"permission\");\r\n    }\r\n\r\n    public String getPermissionMessage() {\r\n        return getString(\"permission message\");\r\n    }\r\n\r\n    public ScriptQueue runCommandScript(PlayerTag player, NPCTag npc, Map<String, ObjectTag> context) {\r\n        ScriptQueue queue = new InstantQueue(getName());\r\n        queue.addEntries(getBaseEntries(new BukkitScriptEntryData(player, npc)));\r\n        if (context != null) {\r\n            ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n            src.contexts = context;\r\n            queue.setContextSource(src);\r\n        }\r\n        queue.start();\r\n        return queue;\r\n    }\r\n\r\n    public boolean runAllowedHelpProcedure(PlayerTag player, NPCTag npc, Map<String, ObjectTag> context) {\r\n        List<ScriptEntry> entries = getEntries(new BukkitScriptEntryData(player, npc), \"allowed help\");\r\n\r\n        ScriptQueue queue = new InstantQueue(getName());\r\n        queue.addEntries(entries);\r\n        if (context != null) {\r\n            ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n            src.contexts = context;\r\n            queue.setContextSource(src);\r\n        }\r\n        queue.start();\r\n        return queue.determinations != null && queue.determinations.size() > 0 && queue.determinations.get(0).equalsIgnoreCase(\"true\");\r\n    }\r\n\r\n    public List<String> runTabCompleteProcedure(PlayerTag player, NPCTag npc, Map<String, ObjectTag> context, String[] originalArguments) {\r\n        BukkitTagContext tagContext = new BukkitTagContext(player, npc, new ScriptTag(this));\r\n        ContextSource contextSrc = null;\r\n        if (context != null) {\r\n            ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n            src.contexts = context;\r\n            tagContext.contextSource = src;\r\n            contextSrc = src;\r\n        }\r\n        List<String> list = new ArrayList<>();\r\n        if (tabCompletionTaggables != null) {\r\n            int argCount = Math.max(originalArguments.length, 1);\r\n            String taggable = tabCompletionTaggables.get(argCount);\r\n            if (taggable == null) {\r\n                taggable = tabCompletionTaggables.get(-1);\r\n            }\r\n            if (taggable != null) {\r\n                String argLow = originalArguments.length == 0 ? \"\" : CoreUtilities.toLowerCase(originalArguments[originalArguments.length - 1]);\r\n                for (String value : ListTag.getListFor(TagManager.tagObject(taggable, tagContext), tagContext)) {\r\n                    if (CoreUtilities.toLowerCase(value).startsWith(argLow)) {\r\n                        list.add(value);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (hasProcStyleTabComplete) {\r\n            List<ScriptEntry> entries = getEntries(new BukkitScriptEntryData(player, npc), \"tab complete\");\r\n            ScriptQueue queue = new InstantQueue(getName());\r\n            queue.addEntries(entries);\r\n            if (contextSrc != null) {\r\n                queue.setContextSource(contextSrc);\r\n            }\r\n            queue.start();\r\n            if (queue.determinations != null && queue.determinations.size() > 0) {\r\n                list.addAll(ListTag.getListFor(queue.determinations.getObject(0), tagContext));\r\n            }\r\n        }\r\n        return list;\r\n    }\r\n\r\n    public boolean hasAllowedHelpProcedure() {\r\n        return containsScriptSection(\"allowed help\");\r\n    }\r\n\r\n    public boolean hasTabCompleteProcedure() {\r\n        return hasProcStyleTabComplete || tabCompletionTaggables != null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/CommandScriptHelper.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.command.scripted.DenizenAliasHelpTopic;\r\nimport com.denizenscript.denizen.utilities.command.scripted.DenizenCommand;\r\nimport com.denizenscript.denizen.utilities.command.scripted.DenizenCommandHelpTopic;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.base.Predicate;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.events.bukkit.ScriptReloadEvent;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Server;\r\nimport org.bukkit.command.Command;\r\nimport org.bukkit.command.CommandMap;\r\nimport org.bukkit.command.defaults.HelpCommand;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.help.HelpMap;\r\nimport org.bukkit.help.HelpTopic;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.lang.reflect.Method;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class CommandScriptHelper implements Listener {\r\n\r\n    public static Map<String, DenizenCommand> denizenCommands = new HashMap<>();\r\n    public static Map<String, Command> overriddenCommands = new HashMap<>();\r\n    public static Map<String, HelpTopic> overriddenHelpTopics = new HashMap<>();\r\n    public static Map<String, Command> knownCommands = null;\r\n    public static Map<String, HelpTopic> helpTopics = null;\r\n    public static final Map<String, CommandScriptContainer> commandScripts = new HashMap<>();\r\n    public static boolean hasCommandInformation = true;\r\n    public static CommandScriptHelper instance;\r\n    public static boolean isInitialized = false;\r\n\r\n    public CommandScriptHelper() {\r\n        instance = this;\r\n        if (Settings.cache_commandScriptAutoInit) {\r\n            init();\r\n        }\r\n    }\r\n\r\n    public static void init() {\r\n        if (isInitialized) {\r\n            return;\r\n        }\r\n        isInitialized = true;\r\n        try {\r\n            Server server = Bukkit.getServer();\r\n\r\n            server.getPluginManager().registerEvents(instance, Denizen.getInstance());\r\n\r\n            // Get the CommandMap for the server\r\n            Field commandMapField = server.getClass().getDeclaredField(\"commandMap\");\r\n            commandMapField.setAccessible(true);\r\n            CommandMap commandMap = (CommandMap) commandMapField.get(server);\r\n\r\n            // Get the knownCommands for the server's CommandMap\r\n            Field kcf = null;\r\n            Class commandMapClass = commandMap.getClass();\r\n            while (kcf == null && commandMapClass != Object.class) {\r\n                try {\r\n                    kcf = commandMapClass.getDeclaredField(\"knownCommands\");\r\n                }\r\n                catch (NoSuchFieldException e) {\r\n                    commandMapClass = commandMapClass.getSuperclass();\r\n                }\r\n            }\r\n            final Field knownCommandsField = kcf;\r\n            knownCommandsField.setAccessible(true);\r\n            knownCommands = (Map<String, Command>) knownCommandsField.get(commandMap);\r\n\r\n            // Get the HelpMap for the server\r\n            HelpMap helpMap = server.getHelpMap();\r\n\r\n            // Get the helpTopics for the server's HelpMap\r\n            final Field helpTopicsField = helpMap.getClass().getDeclaredField(\"helpTopics\");\r\n            helpTopicsField.setAccessible(true);\r\n            helpTopics = (Map<String, HelpTopic>) helpTopicsField.get(helpMap);\r\n\r\n            // The Minecraft Help command doesn't like our added commands,\r\n            // so let's force the server to use Bukkit's version if it's running\r\n            // Mojang's version.\r\n            // TODO: figure out a different workaround?\r\n            if (Settings.overrideHelp()) {\r\n                new BukkitRunnable() {\r\n                    @Override\r\n                    public void run() {\r\n                        if (knownCommands.get(\"help\") instanceof HelpCommand) {\r\n                            return;\r\n                        }\r\n                        knownCommands.put(\"help\", knownCommands.get(\"bukkit:help\"));\r\n                        helpTopics.put(\"/help\", helpTopics.get(\"/bukkit:help\"));\r\n                    }\r\n                }.runTaskLater(Denizen.getInstance(), 1);\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(\"Error getting the server's command information! Are you running a non-CraftBukkit server?\");\r\n            Debug.echoError(\"Command scripts will not function!\");\r\n            //dB.echoError(e);\r\n            hasCommandInformation = false;\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void scriptReload(ScriptReloadEvent event) {\r\n        for (CommandScriptContainer script : commandScripts.values()) {\r\n            if (script.shouldEnable()) {\r\n                registerDenizenCommand(new DenizenCommand(script));\r\n            }\r\n        }\r\n        syncDenizenCommands();\r\n    }\r\n\r\n    public static final Method syncCommandsMethod;\r\n\r\n    static {\r\n        Method syncMethod = null;\r\n        try {\r\n            syncMethod = Bukkit.getServer().getClass().getDeclaredMethod(\"syncCommands\");\r\n            syncMethod.setAccessible(true);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(\"Failed to load helper to synchronize server commands.\");\r\n        }\r\n        syncCommandsMethod = syncMethod;\r\n    }\r\n\r\n    /**\r\n     * In 1.13+, commands are also sent to players client-side via packets.\r\n     * We need to sync them for tab completion to work.\r\n     */\r\n    public static void syncDenizenCommands() {\r\n        if (syncCommandsMethod != null) {\r\n            try {\r\n                syncCommandsMethod.invoke(Bukkit.getServer());\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(\"Failed to synchronize server commands.\");\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Removes all registered {@link DenizenCommand DenizenCommands} from CraftBukkit and restores any\r\n     * overridden Commands.\r\n     *\r\n     * @see #registerDenizenCommand(DenizenCommand)\r\n     */\r\n    public static void removeDenizenCommands() {\r\n        if (!hasCommandInformation || !isInitialized) {\r\n            return;\r\n        }\r\n        for (String command : denizenCommands.keySet()) {\r\n            knownCommands.remove(command);\r\n            helpTopics.remove(command);\r\n            if (overriddenCommands.containsKey(command)) {\r\n                knownCommands.put(command, overriddenCommands.get(command));\r\n                if (overriddenHelpTopics.containsKey(command)) {\r\n                    helpTopics.put(command, overriddenHelpTopics.get(command));\r\n                }\r\n            }\r\n        }\r\n        denizenCommands.clear();\r\n    }\r\n\r\n    /**\r\n     * Registers a {@link DenizenCommand} to CraftBukkit, including aliases and help command\r\n     * information. This will override any existing command aliases or names.\r\n     *\r\n     * @param command the command to register.\r\n     * @see #removeDenizenCommands()\r\n     */\r\n    public static void registerDenizenCommand(DenizenCommand command) {\r\n        if (!hasCommandInformation) {\r\n            return;\r\n        }\r\n        String name = command.getName();\r\n        // Existing Denizen commands take priority!\r\n        if (!denizenCommands.containsKey(name)) {\r\n            // Register the command\r\n            forceCommand(name, command, new DenizenCommandHelpTopic(command));\r\n            // Register each alias\r\n            for (String alias : command.getAliases()) {\r\n                if (denizenCommands.containsKey(alias)) {\r\n                    continue;\r\n                }\r\n                forceCommand(alias, command, new DenizenAliasHelpTopic(\"/\" + alias, name,\r\n                        Denizen.getInstance().getServer().getHelpMap()));\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Forces CraftBukkit to recognize DenizenCommands, and overrides any existing\r\n     * commands of the same name. This should be called for the name of the command\r\n     * and each alias of the command.\r\n     *\r\n     * @param name      name or alias of the command.\r\n     * @param command   the command.\r\n     * @param helpTopic the help topic for the command or command alias.\r\n     */\r\n    public static void forceCommand(String name, DenizenCommand command, HelpTopic helpTopic) {\r\n        // Override any existing non-DenizenCommand commands, but save them just in case\r\n        // TODO: use fallback prefixes for overridden commands instead?\r\n        if (knownCommands.containsKey(name)) {\r\n            overriddenCommands.put(name, knownCommands.get(name));\r\n            knownCommands.remove(name);\r\n            if (helpTopics.containsKey(name)) {\r\n                overriddenHelpTopics.put(name, helpTopics.get(name));\r\n                helpTopics.remove(name);\r\n            }\r\n        }\r\n        knownCommands.put(name, command);\r\n        helpTopics.put(helpTopic.getName(), helpTopic);\r\n        denizenCommands.put(name, command);\r\n    }\r\n\r\n    private static class IsCommandTopicPredicate implements Predicate<HelpTopic> {\r\n        public boolean test(HelpTopic topic) {\r\n            return apply(topic);\r\n        }\r\n\r\n        public boolean apply(HelpTopic topic) {\r\n            return topic.getName().charAt(0) == '/';\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/EconomyScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.scripts.queues.core.InstantQueue;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.DefinitionProvider;\r\nimport com.denizenscript.denizencore.utilities.SimpleDefinitionProvider;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.milkbowl.vault.economy.AbstractEconomy;\r\nimport net.milkbowl.vault.economy.Economy;\r\nimport net.milkbowl.vault.economy.EconomyResponse;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.OfflinePlayer;\r\nimport org.bukkit.plugin.ServicePriority;\r\n\r\nimport java.text.DecimalFormat;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\nimport java.util.concurrent.Future;\r\n\r\npublic class EconomyScriptContainer extends ScriptContainer {\r\n\r\n    // <--[language]\r\n    // @name Economy Script Containers\r\n    // @group Script Container System\r\n    // @plugin Vault\r\n    // @description\r\n    // Economy script containers\r\n    //\r\n    // Economy script containers provide a Vault economy, which can be used in scripts by\r\n    // <@link tag PlayerTag.money> and <@link command money>\r\n    // and as well by any other plugin that relies on economy functionality (such as shop plugins).\r\n    //\r\n    // Note that vault economy bank systems are not currently supported.\r\n    // Per-world economies are also not currently supported.\r\n    //\r\n    // Note that in most cases, you do not want to have multiple economy providers, as only one will actually be in use.\r\n    //\r\n    // ALL SCRIPT KEYS ARE REQUIRED.\r\n    //\r\n    // Economy scripts can be automatically disabled by adding \"enabled: false\" as a root key (supports any load-time-parseable tags).\r\n    //\r\n    // <code>\r\n    // # The script name will be shown to the economy provider as the name of the economy system.\r\n    // Economy_Script_Name:\r\n    //\r\n    //     type: economy\r\n    //\r\n    //     # The Bukkit service priority. Priorities are Lowest, Low, Normal, High, Highest.\r\n    //     priority: normal\r\n    //     # The name of the currency in the singular (such as \"dollar\" or \"euro\").\r\n    //     name single: scripto\r\n    //     # The name of the currency in the plural (such as \"dollars\" or \"euros\").\r\n    //     name plural: scriptos\r\n    //     # How many digits after the decimal to include. For example, '2' means '1.05' is a valid amount, but '1.005' will be rounded.\r\n    //     digits: 2\r\n    //     # Format the standard output for the money in human-readable format. Use \"<[amount]>\" for the actual amount to display.\r\n    //     # Fully supports tags.\r\n    //     format: $<[amount]>\r\n    //     # A tag that returns the balance of a linked player. Use a 'proc[]' tag if you need more complex logic.\r\n    //     # Must return a decimal number.\r\n    //     balance: <player.flag[money]>\r\n    //     # A tag that returns a boolean indicating whether the linked player has the amount specified by def \"<[amount]>\".\r\n    //     # Use a 'proc[]' tag if you need more complex logic.\r\n    //     # Must return 'true' or 'false'.\r\n    //     has: <player.flag[money].is[or_more].than[<[amount]>]>\r\n    //     # A script that removes the amount of money needed from a player.\r\n    //     # Note that it's generally up to the systems calling this script to verify that the amount can be safely withdrawn, not this script itself.\r\n    //     # However you may wish to verify that the player has the amount required within this script.\r\n    //     # The script may determine a failure message if the withdraw was refused. Determine nothing for no error.\r\n    //     # Use def 'amount' for the amount to withdraw.\r\n    //     withdraw:\r\n    //     - flag <player> money:-:<[amount]>\r\n    //     # A script that adds the amount of money needed to a player.\r\n    //     # The script may determine a failure message if the deposit was refused. Determine nothing for no error.\r\n    //     # Use def 'amount' for the amount to deposit.\r\n    //     deposit:\r\n    //     - flag <player> money:+:<[amount]>\r\n    //\r\n    // </code>\r\n    //\r\n    // -->\r\n\r\n    public static class DenizenEconomyProvider extends AbstractEconomy {\r\n\r\n        public EconomyScriptContainer backingScript;\r\n\r\n        public String autoTagAmount(String value, OfflinePlayer player, double amount) {\r\n            int digits = fractionalDigits();\r\n            String amountText;\r\n            if (digits <= 0) {\r\n                amountText = String.valueOf((long) amount);\r\n            }\r\n            else {\r\n                DecimalFormat d = new DecimalFormat(\"0.\" + new String(new char[digits]).replace('\\0', '0'), CoreUtilities.decimalFormatSymbols);\r\n                amountText = d.format(amount);\r\n            }\r\n            DefinitionProvider defProvider = new SimpleDefinitionProvider();\r\n            defProvider.addDefinition(\"amount\", new ElementTag(amountText));\r\n            return autoTag(value, player, defProvider);\r\n        }\r\n\r\n        public boolean validateThread() {\r\n            if (!Bukkit.isPrimaryThread()) {\r\n                if (Settings.allowAsyncPassThrough) {\r\n                    return false;\r\n                }\r\n                Debug.echoError(\"Warning: economy access from wrong thread, blocked. Inform the developer of whatever plugin tried to read eco data that it is forbidden to do so async.\"\r\n                        + \" You can use config option 'Scripts.Economy.Pass async to main thread' to enable dangerous access.\");\r\n                try {\r\n                    throw new RuntimeException(\"Stack reference\");\r\n                }\r\n                catch (RuntimeException ex) {\r\n                    Debug.echoError(ex);\r\n                }\r\n                return false;\r\n            }\r\n            return true;\r\n        }\r\n\r\n        public String autoTag(String value, OfflinePlayer player, DefinitionProvider defProvider) {\r\n            if (value == null) {\r\n                return null;\r\n            }\r\n            if (!validateThread()) {\r\n                if (!Settings.allowAsyncPassThrough) {\r\n                    return null;\r\n                }\r\n                try {\r\n                    Future<String> future = Bukkit.getScheduler().callSyncMethod(Denizen.instance, () -> autoTag(value, player, defProvider));\r\n                    return future.get();\r\n                }\r\n                catch (Throwable ex) {\r\n                    Debug.echoError(ex);\r\n                    return null;\r\n                }\r\n            }\r\n            BukkitTagContext context = new BukkitTagContext(player == null ? null : new PlayerTag(player), null, new ScriptTag(backingScript));\r\n            context.definitionProvider = defProvider;\r\n            return TagManager.tag(value, context);\r\n        }\r\n\r\n        public String runSubScript(String pathName, OfflinePlayer player, double amount) {\r\n            if (!validateThread()) {\r\n                if (!Settings.allowAsyncPassThrough) {\r\n                    return null;\r\n                }\r\n                try {\r\n                    Future<String> future = Bukkit.getScheduler().callSyncMethod(Denizen.instance, () -> runSubScript(pathName, player, amount));\r\n                    return future.get();\r\n                }\r\n                catch (Throwable ex) {\r\n                    Debug.echoError(ex);\r\n                    return null;\r\n                }\r\n            }\r\n            List<ScriptEntry> entries = backingScript.getEntries(new BukkitScriptEntryData(new PlayerTag(player), null), pathName);\r\n            InstantQueue queue = new InstantQueue(backingScript.getName());\r\n            queue.addEntries(entries);\r\n            queue.addDefinition(\"amount\", new ElementTag(amount));\r\n            queue.start();\r\n            if (queue.determinations != null && queue.determinations.size() > 0) {\r\n                return queue.determinations.get(0);\r\n            }\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public boolean isEnabled() {\r\n            return true;\r\n        }\r\n\r\n        @Override\r\n        public String getName() {\r\n            return backingScript.getName();\r\n        }\r\n\r\n        @Override\r\n        public boolean hasBankSupport() {\r\n            return false;\r\n        }\r\n\r\n        @Override\r\n        public int fractionalDigits() {\r\n            return Integer.parseInt(backingScript.getString(\"digits\", \"2\"));\r\n        }\r\n\r\n        @Override\r\n        public String format(double amount) {\r\n            return autoTagAmount(backingScript.getString(\"format\"), null, amount);\r\n        }\r\n\r\n        @Override\r\n        public String currencyNamePlural() {\r\n            return backingScript.getString(\"name plural\", \"moneys\");\r\n        }\r\n\r\n        @Override\r\n        public String currencyNameSingular() {\r\n            return backingScript.getString(\"name single\", \"money\");\r\n        }\r\n\r\n        @Override\r\n        public double getBalance(OfflinePlayer player) {\r\n            if (player == null) {\r\n                Debug.echoError(\"Economy attempted BALANCE-CHECK to NULL player.\");\r\n                return 0;\r\n            }\r\n            try {\r\n                return Double.parseDouble(autoTag(backingScript.getString(\"balance\"), player, null));\r\n            }\r\n            catch (NumberFormatException ex) {\r\n                Debug.echoError(\"Economy script '\" + getName() + \"' returned invalid balance for player '\" + new PlayerTag(player).debuggable() + \"': \" + ex.getMessage());\r\n                return 0;\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse withdrawPlayer(OfflinePlayer player, double amount) {\r\n            if (player == null) {\r\n                Debug.echoError(\"Economy attempted WITHDRAW to NULL player for \" + amount);\r\n                return null;\r\n            }\r\n            String determination = runSubScript(\"withdraw\", player, amount);\r\n            return new EconomyResponse(amount, getBalance(player), determination == null ?\r\n                    EconomyResponse.ResponseType.SUCCESS : EconomyResponse.ResponseType.FAILURE, determination);\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse depositPlayer(OfflinePlayer player, double amount) {\r\n            if (player == null) {\r\n                Debug.echoError(\"Economy attempted DEPOSIT to NULL player for \" + amount);\r\n                return null;\r\n            }\r\n            String determination = runSubScript(\"deposit\", player, amount);\r\n            return new EconomyResponse(amount, getBalance(player), determination == null ?\r\n                    EconomyResponse.ResponseType.SUCCESS : EconomyResponse.ResponseType.FAILURE, determination);\r\n        }\r\n\r\n        @Override\r\n        public boolean has(OfflinePlayer player, double amount) {\r\n            if (player == null) {\r\n                Debug.echoError(\"Economy attempted HAS-CHECK to NULL player for \" + amount);\r\n                return false;\r\n            }\r\n            return autoTagAmount(backingScript.getString(\"has\"), player, amount).equalsIgnoreCase(\"true\");\r\n        }\r\n\r\n        @Override\r\n        public boolean hasAccount(String playerName) {\r\n            return true;\r\n        }\r\n\r\n        @Override\r\n        public boolean hasAccount(String playerName, String worldName) {\r\n            return true;\r\n        }\r\n\r\n        public OfflinePlayer playerForName(String name) {\r\n            UUID id = PlayerTag.getAllPlayers().get(CoreUtilities.toLowerCase(name));\r\n            if (id == null) {\r\n                Debug.echoError(\"Economy attempted access to unknown player '\" + name + \"'\");\r\n                return null;\r\n            }\r\n            return Bukkit.getOfflinePlayer(id);\r\n        }\r\n\r\n        @Override\r\n        public double getBalance(String playerName) {\r\n            return getBalance(playerForName(playerName));\r\n        }\r\n\r\n        @Override\r\n        public double getBalance(String playerName, String worldName) {\r\n            return getBalance(playerName);\r\n        }\r\n\r\n        @Override\r\n        public double getBalance(OfflinePlayer player, String worldName) {\r\n            return getBalance(player);\r\n        }\r\n\r\n        @Override\r\n        public boolean has(String playerName, double amount) {\r\n            return has(playerForName(playerName), amount);\r\n        }\r\n\r\n        @Override\r\n        public boolean has(String playerName, String worldName, double amount) {\r\n            return has(playerName, amount);\r\n        }\r\n\r\n        @Override\r\n        public boolean has(OfflinePlayer player, String worldName, double amount) {\r\n            return has(player, amount);\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse withdrawPlayer(String playerName, double amount) {\r\n            return withdrawPlayer(playerForName(playerName), amount);\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse withdrawPlayer(String playerName, String worldName, double amount) {\r\n            return withdrawPlayer(playerName, amount);\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse withdrawPlayer(OfflinePlayer player, String worldName, double amount) {\r\n            return withdrawPlayer(player, amount);\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse depositPlayer(String playerName, double amount) {\r\n            return depositPlayer(playerForName(playerName), amount);\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse depositPlayer(String playerName, String worldName, double amount) {\r\n            return depositPlayer(playerName, amount);\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse depositPlayer(OfflinePlayer player, String worldName, double amount) {\r\n            return depositPlayer(player, amount);\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse createBank(String s, String s1) {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse deleteBank(String s) {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse bankBalance(String s) {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse bankHas(String s, double v) {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse bankWithdraw(String s, double v) {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse bankDeposit(String s, double v) {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse isBankOwner(String s, String s1) {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public EconomyResponse isBankMember(String s, String s1) {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public List<String> getBanks() {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public boolean createPlayerAccount(String s) {\r\n            return false;\r\n        }\r\n\r\n        @Override\r\n        public boolean createPlayerAccount(String s, String s1) {\r\n            return false;\r\n        }\r\n    }\r\n\r\n    public ServicePriority getPriority() {\r\n        String prioString = getString(\"priority\", \"normal\");\r\n        // Enumeration name casing is weird for ServicePriority.\r\n        for (ServicePriority prio : ServicePriority.values()) {\r\n            if (CoreUtilities.equalsIgnoreCase(prio.name(), prioString)) {\r\n                return prio;\r\n            }\r\n        }\r\n        return ServicePriority.Normal;\r\n    }\r\n\r\n    public DenizenEconomyProvider register() {\r\n        DenizenEconomyProvider provider = new DenizenEconomyProvider();\r\n        provider.backingScript = this;\r\n        Bukkit.getServer().getServicesManager().register(Economy.class, provider, Denizen.getInstance(), getPriority());\r\n        return provider;\r\n    }\r\n\r\n    public static void cleanup() {\r\n        for (DenizenEconomyProvider provider : providersRegistered) {\r\n            Bukkit.getServer().getServicesManager().unregister(provider);\r\n        }\r\n        providersRegistered.clear();\r\n    }\r\n\r\n    public static List<DenizenEconomyProvider> providersRegistered = new ArrayList<>();\r\n\r\n    public EconomyScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\r\n        super(configurationSection, scriptContainerName);\r\n        if (shouldEnable()) {\r\n            providersRegistered.add(register());\r\n        }\r\n        handlePseudoTagBasesDeprecation(\"format\", \"<amount\");\r\n        handlePseudoTagBasesDeprecation(\"has\", \"<amount\");\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/EnchantmentScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.scripts.queues.ContextSource;\r\nimport com.denizenscript.denizencore.scripts.queues.core.InstantQueue;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.AsciiMatcher;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\n\r\npublic class EnchantmentScriptContainer extends ScriptContainer {\r\n\r\n    // <--[language]\r\n    // @name Enchantment Script Containers\r\n    // @group Script Container System\r\n    // @description\r\n    // Enchantment script containers allow you to register custom item enchantments.\r\n    // For the most part, they work similarly to vanilla enchantments, albeit with some limitations.\r\n    // These can be attached to enchanted books and used in anvils,\r\n    // and can be generated by the enchanting table (requires discoverable: true and treasure_only: false).\r\n    //\r\n    // In current implementation, custom enchantments do not appear in lore on their own, and will need fake lore added in their place.\r\n    // This might be fixed in the future.\r\n    //\r\n    // It may be beneficial in some cases to restart your server after making changes to enchantments, rather than just reloading scripts.\r\n    // Rarity, Category, and Slots do not apply changes to an already-loaded script until the next restart (except when the script is newly added).\r\n    //\r\n    // Using these may cause unpredictable compatibility issues with external plugins.\r\n    //\r\n    // Enchantment scripts can be automatically disabled by adding \"enabled: false\" as a root key (supports any load-time-parseable tags).\r\n    //\r\n    // <code>\r\n    // Enchantment_Script_Name:\r\n    //\r\n    //     type: enchantment\r\n    //\r\n    //     # The ID is used as the internal registration key, and for lookups with things like the 'enchantments' mechanism.\r\n    //     # If unspecified, will use the script name.\r\n    //     # Limited to A-Z or _.\r\n    //     # | Most enchantment scripts should have this key!\r\n    //     id: my_id\r\n    //\r\n    //     # A list of slots this enchantment is valid in.\r\n    //     # | ALL enchantment scripts MUST have this key!\r\n    //     slots:\r\n    //     # Can be any of: mainhand, offhand, feet, legs, chest, head\r\n    //     - mainhand\r\n    //\r\n    //     # The rarity level of this enchantment. Can be any of: COMMON, UNCOMMON, RARE, VERY_RARE\r\n    //     # If unspecified, will use COMMON.\r\n    //     # | Most enchantment scripts should have this key!\r\n    //     rarity: common\r\n    //\r\n    //     # The category/type of this enchantment. Can be any of: ARMOR, ARMOR_FEET, ARMOR_LEGS, ARMOR_CHEST, ARMOR_HEAD,\r\n    //     # WEAPON, DIGGER, FISHING_ROD, TRIDENT, BREAKABLE, BOW, WEARABLE, CROSSBOW, VANISHABLE\r\n    //     # This is used internally by the enchanting table to determine which items to offer the enchantment for.\r\n    //     # If unspecified, will use WEAPON.\r\n    //     # | Most enchantment scripts should have this key!\r\n    //     category: weapon\r\n    //\r\n    //     # The per-level full name of this enchantment. Does not appear in lore automatically (but might in the future?).\r\n    //     # Can make use of \"<context.level>\" for the applicable level.\r\n    //     # | Most enchantment scripts should have this key!\r\n    //     full_name: My Enchantment <context.level>\r\n    //\r\n    //     # The minimum level of this enchantment.\r\n    //     # If unspecified, will use 1.\r\n    //     # | Most enchantment scripts can exclude this key.\r\n    //     min_level: 1\r\n    //\r\n    //     # The maximum level of this enchantment.\r\n    //     # If unspecified, will use 1.\r\n    //     # | Some enchantment scripts should have this key.\r\n    //     max_level: 1\r\n    //\r\n    //     # The per-level minimum XP cost of enchanting.\r\n    //     # Can make use of \"<context.level>\" for the applicable level.\r\n    //     # | Most enchantment scripts should have this key!\r\n    //     min_cost: <context.level.mul[1]>\r\n    //\r\n    //     # The per-level maximum XP cost of enchanting.\r\n    //     # Can make use of \"<context.level>\" for the applicable level.\r\n    //     # | Most enchantment scripts should have this key!\r\n    //     max_cost: <context.level.mul[1]>\r\n    //\r\n    //     # Whether this enchantment is only considered to be treasure. Treasure enchantments do not show in the enchanting table.\r\n    //     # If unspecified, will be false.\r\n    //     # | Most enchantment scripts can exclude this key.\r\n    //     treasure_only: false\r\n    //\r\n    //     # Whether this enchantment is only considered to be a curse. Curses are removed at grindstones, and spread from crafting table repairs.\r\n    //     # If unspecified, will be false.\r\n    //     # | Most enchantment scripts can exclude this key.\r\n    //     is_curse: false\r\n    //\r\n    //     # Whether this enchantment is only considered to be tradable. Villagers won't trade this enchantment if set to false.\r\n    //     # If unspecified, will be true.\r\n    //     # | Most enchantment scripts can exclude this key.\r\n    //     is_tradable: true\r\n    //\r\n    //     # Whether this enchantment is only considered to be discoverable.\r\n    //     # If true, this will spawn from vanilla sources like the enchanting table. If false, it can only be given directly by script.\r\n    //     # If unspecified, will be true.\r\n    //     # | Most enchantment scripts can exclude this key.\r\n    //     is_discoverable: true\r\n    //\r\n    //     # A tag that returns a boolean indicating whether this enchantment is compatible with another.\r\n    //     # Can make use of \"<context.enchantment_key>\" for the applicable enchantment's key, like \"minecraft:sharpness\".\r\n    //     # This is used internally by the enchanting table and the anvil to determine if this enchantment can be given alongside another.\r\n    //     # If unspecified, will default to always true.\r\n    //     # | Most enchantment scripts can exclude this key.\r\n    //     is_compatible: <context.enchantment_key.advanced_matches[minecraft:lure|minecraft:luck*]>\r\n    //\r\n    //     # A tag that returns a boolean indicating whether this enchantment can enchant a specific item.\r\n    //     # Can make use of \"<context.item>\" for the applicable ItemTag.\r\n    //     # This is used internally to decide whether survival players can copy the enchantment between items on an anvil, as well as for commands like \"/enchant\".\r\n    //     # If unspecified, will default to always true.\r\n    //     # | Most enchantment scripts can exclude this key.\r\n    //     can_enchant: <context.item.advanced_matches[*_sword|*_axe]>\r\n    //\r\n    //     # A tag that returns a decimal number indicating how much extra damage this item should deal.\r\n    //     # Can make use of \"<context.level>\" for the enchantment level,\r\n    //     # and \"<context.type>\" for the type of monster being fought: ARTHROPOD, ILLAGER, WATER, UNDEAD, or UNDEFINED\r\n    //     # If unspecified, will default to 0.0.\r\n    //     # | Most enchantment scripts can exclude this key.\r\n    //     damage_bonus: 0.0\r\n    //\r\n    //     # A tag that returns an integer number indicating how much this item should protection against damage.\r\n    //     # Can make use of \"<context.level>\" for the enchantment level, and \"<context.cause>\" for the applicable damage cause, using internal cause names (different from Spigot Damage Causes).\r\n    //     # Internal cause names: inFire, lightningBolt, onFire, lava, hotFloor, inWall, cramming, drown, starve, cactus, fall, flyIntoWall,\r\n    //     # outOfWorld, generic, magic, wither, anvil, fallingBlock, dragonBreath, dryout, sweetBerryBush, freeze, fallingStalactite, stalagmite\r\n    //     # Also \"<context.attacker>\" as an EntityTag if the cause has an attacker specified.\r\n    //     # If unspecified, will default to 0.\r\n    //     # | Most enchantment scripts can exclude this key.\r\n    //     damage_protection: 0\r\n    //\r\n    //     # Triggered after an enchanted weapon is used to attack an entity.\r\n    //     # Has <context.attacker> (EntityTag), <context.victim> (EntityTag), and <context.level>.\r\n    //     # May fire rapidly or multiple times per hit. Use a ratelimit to prevent this.\r\n    //     # | Some enchantment scripts should have this key.\r\n    //     after attack:\r\n    //     - narrate \"You attacked <context.victim.name> with a <context.level> enchantment!\"\r\n    //\r\n    //     # Triggered after an enchanted armor is used to defend against an attack.\r\n    //     # Also fires if an entity is holding a weapon with this enchantment while being hit.\r\n    //     # Has <context.attacker> (EntityTag), <context.victim> (EntityTag), and <context.level>.\r\n    //     # | Some enchantment scripts should have this key.\r\n    //     after hurt:\r\n    //     - narrate \"You got hurt by <context.attacker.name> and protected by a level <context.level> enchantment!\"\r\n    // </code>\r\n    //\r\n    // -->\r\n    public static AsciiMatcher descriptionCharsAllowed = new AsciiMatcher(AsciiMatcher.LETTERS_LOWER + \"_\");\r\n\r\n    public static HashMap<String, EnchantmentReference> registeredEnchantmentContainers = new HashMap<>();\r\n\r\n    public static class EnchantmentReference {\r\n        public EnchantmentScriptContainer script;\r\n    }\r\n\r\n    public EnchantmentScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\r\n        super(configurationSection, scriptContainerName);\r\n        canRunScripts = false;\r\n        id = descriptionCharsAllowed.trimToMatches(CoreUtilities.toLowerCase(getString(\"id\", scriptContainerName)));\r\n        descriptionId = \"enchantment.denizen.\" + id;\r\n        minLevel = Integer.parseInt(getString(\"min_level\", \"1\"));\r\n        maxLevel = Integer.parseInt(getString(\"max_level\", \"1\"));\r\n        isTreasureOnly = CoreUtilities.toLowerCase(getString(\"treasure_only\", \"false\")).equals(\"true\");\r\n        isCurse = CoreUtilities.toLowerCase(getString(\"is_curse\", \"false\")).equals(\"true\");\r\n        isTradable = CoreUtilities.toLowerCase(getString(\"is_tradable\", \"true\")).equals(\"true\");\r\n        isDiscoverable = CoreUtilities.toLowerCase(getString(\"is_discoverable\", \"true\")).equals(\"true\");\r\n        rarity = getString(\"rarity\", \"COMMON\").toUpperCase();\r\n        category = getString(\"category\", \"WEAPON\").toUpperCase();\r\n        slots = getStringList(\"slots\");\r\n        fullNameTaggable = getString(\"full_name\", \"\");\r\n        canEnchantTaggable = getString(\"can_enchant\", \"true\");\r\n        isCompatibleTaggable = getString(\"is_compatible\", \"true\");\r\n        minCostTaggable = getString(\"min_cost\", \"1\");\r\n        maxCostTaggable = getString(\"max_cost\", \"1\");\r\n        damageBonusTaggable = getString(\"damage_bonus\", \"0.0\");\r\n        damageProtectionTaggable = getString(\"damage_protection\", \"0\");\r\n        if (shouldEnable()) {\r\n            EnchantmentReference ref = registeredEnchantmentContainers.get(id);\r\n            boolean isNew = ref == null;\r\n            if (isNew) {\r\n                ref = new EnchantmentReference();\r\n            }\r\n            EnchantmentScriptContainer old = ref.script;\r\n            ref.script = this;\r\n            registeredEnchantmentContainers.put(id, ref);\r\n            if (isNew) {\r\n                enchantment = NMSHandler.enchantmentHelper.registerFakeEnchantment(ref);\r\n            }\r\n            else {\r\n                enchantment = old.enchantment;\r\n            }\r\n        }\r\n    }\r\n\r\n    public int minLevel, maxLevel;\r\n\r\n    public String id, rarity, category, descriptionId, fullNameTaggable, canEnchantTaggable, isCompatibleTaggable, minCostTaggable, maxCostTaggable, damageBonusTaggable, damageProtectionTaggable;\r\n\r\n    public boolean isTreasureOnly, isCurse, isTradable, isDiscoverable;\r\n\r\n    public List<String> slots;\r\n\r\n    public HashMap<Integer, BaseComponent[]> fullNamePerLevel = new HashMap<>();\r\n\r\n    public Enchantment enchantment;\r\n\r\n    public void validateThread() {\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            try {\r\n                throw new RuntimeException(\"Stack reference\");\r\n            }\r\n            catch (RuntimeException ex) {\r\n                Debug.echoError(\"Warning: enchantment access from wrong thread, errors will result\");\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    public String autoTag(String value, ContextSource src) {\r\n        if (value == null) {\r\n            return null;\r\n        }\r\n        validateThread();\r\n        TagContext context = new BukkitTagContext(null, new ScriptTag(this));\r\n        context.contextSource = src;\r\n        return TagManager.tag(value, context);\r\n    }\r\n\r\n    public String autoTagForLevel(String value, int level) {\r\n        ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n        src.contexts = new HashMap<>();\r\n        src.contexts.put(\"level\", new ElementTag(level));\r\n        return autoTag(value, src);\r\n    }\r\n\r\n    public boolean canEnchant(ItemStack item) {\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            return false;\r\n        }\r\n        ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n        src.contexts = new HashMap<>();\r\n        src.contexts.put(\"item\", new ItemTag(item));\r\n        String res = autoTag(canEnchantTaggable, src);\r\n        return CoreUtilities.toLowerCase(res).equals(\"true\");\r\n    }\r\n\r\n    public boolean isCompatible(Enchantment enchantment) {\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            return false; // NMS calls this method off-thread for level gen (mob equipment can have random enchants). Just say no to this for now.\r\n        }\r\n        ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n        src.contexts = new HashMap<>();\r\n        src.contexts.put(\"enchantment_key\", new ElementTag(enchantment.getKey().toString()));\r\n        String res = autoTag(isCompatibleTaggable, src);\r\n        return CoreUtilities.toLowerCase(res).equals(\"true\");\r\n    }\r\n\r\n    public BaseComponent[] getFullName(int level) {\r\n        BaseComponent[] result = fullNamePerLevel.get(level);\r\n        if (result != null) {\r\n            return result;\r\n        }\r\n        String tagged = autoTagForLevel(fullNameTaggable, level);\r\n        result = FormattedTextHelper.parse(tagged, ChatColor.GRAY);\r\n        fullNamePerLevel.put(level, result);\r\n        return result;\r\n    }\r\n\r\n    public int getDamageProtection(int level, String causeName, Entity attacker) {\r\n        ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n        src.contexts = new HashMap<>();\r\n        src.contexts.put(\"level\", new ElementTag(level));\r\n        src.contexts.put(\"cause\", new ElementTag(causeName));\r\n        if (attacker != null) {\r\n            src.contexts.put(\"attacker\", new EntityTag(attacker).getDenizenObject());\r\n        }\r\n        return Integer.parseInt(autoTag(damageProtectionTaggable, src));\r\n    }\r\n\r\n    public float getDamageBonus(int level, String type) {\r\n        ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n        src.contexts = new HashMap<>();\r\n        src.contexts.put(\"level\", new ElementTag(level));\r\n        src.contexts.put(\"type\", new ElementTag(type));\r\n        return Float.parseFloat(autoTag(damageBonusTaggable, src));\r\n    }\r\n\r\n    public void runSubScript(String pathName, Entity attacker, Entity victim, Entity primary, int level) {\r\n        validateThread();\r\n        List<ScriptEntry> entries = getEntries(new BukkitScriptEntryData(new EntityTag(primary)), pathName);\r\n        if (entries == null || entries.isEmpty()) {\r\n            return;\r\n        }\r\n        InstantQueue queue = new InstantQueue(getName());\r\n        queue.addEntries(entries);\r\n        ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n        src.contexts = new HashMap<>();\r\n        if (attacker != null) {\r\n            src.contexts.put(\"attacker\", new EntityTag(attacker).getDenizenObject());\r\n        }\r\n        if (victim != null) {\r\n            src.contexts.put(\"victim\", new EntityTag(victim).getDenizenObject());\r\n        }\r\n        src.contexts.put(\"level\", new ElementTag(level));\r\n        queue.contextSource = src;\r\n        queue.start();\r\n    }\r\n\r\n    public void doPostAttack(Entity attacker, Entity victim, int level) {\r\n        runSubScript(\"after attack\", attacker, victim, attacker, level);\r\n    }\r\n\r\n    public void doPostHurt(Entity victim, Entity attacker, int level) {\r\n        runSubScript(\"after hurt\", attacker, victim, victim, level);\r\n    }\r\n\r\n    public HashMap<Integer, Integer> minCosts = new HashMap<>(), maxCosts = new HashMap<>();\r\n\r\n    public int getMinCost(int level) {\r\n        Integer cost = minCosts.get(level);\r\n        if (cost != null) {\r\n            return cost;\r\n        }\r\n        cost = Integer.valueOf(autoTagForLevel(minCostTaggable, level));\r\n        minCosts.put(level, cost);\r\n        return cost;\r\n    }\r\n\r\n    public int getMaxCost(int level) {\r\n        Integer cost = maxCosts.get(level);\r\n        if (cost != null) {\r\n            return cost;\r\n        }\r\n        cost = Integer.valueOf(autoTagForLevel(maxCostTaggable, level));\r\n        maxCosts.put(level, cost);\r\n        return cost;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/EntityScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizencore.flags.MapTagFlagTracker;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\nimport java.util.Map;\r\nimport java.util.Set;\r\n\r\npublic class EntityScriptContainer extends ScriptContainer {\r\n\r\n    // <--[language]\r\n    // @name Entity Script Containers\r\n    // @group Script Container System\r\n    // @description\r\n    // Entity script containers are an easy way to pre-define custom entities for use within scripts. Entity\r\n    // scripts work with the EntityTag object, and can be fetched with the Object Fetcher by using the\r\n    // EntityTag constructor of simply the script name. Example: - spawn <player.location> MyEntity\r\n    //\r\n    // The following is the format for the container.\r\n    // Except for the 'entity_type' key (and the required 'type' key), all other keys are optional.\r\n    //\r\n    // You can also include a 'custom' key to hold any custom data attached to the script.\r\n    //\r\n    // <code>\r\n    // # The name of the entity script is the same name that you can use to construct a new\r\n    // # EntityTag based on this entity script. For example, an entity script named 'space_zombie'\r\n    // # can be referred to as 'space_zombie'.\r\n    // Entity_Script_Name:\r\n    //\r\n    //     type: entity\r\n    //\r\n    //     # Must be a valid EntityTag (EG 'zombie' or 'pig[age=baby]') See 'EntityTag' for more information.\r\n    //     # | All entity scripts MUST have this key!\r\n    //     entity_type: BASE_ENTITY_TYPE_HERE\r\n    //\r\n    //     # If you want custom data that won't be parsed, use the 'data' root key.\r\n    //     # | Some entity scripts should have this key!\r\n    //     data:\r\n    //         example_key: example value\r\n    //\r\n    //     # You can set flags on the entity when it spawns.\r\n    //     # | Some entity scripts should have this key!\r\n    //     flags:\r\n    //         my_flag: my value\r\n    //\r\n    //     # Specify any mechanisms to apply the entity when it spawns.\r\n    //     # | Some entity scripts should have this key!\r\n    //     mechanisms:\r\n    //\r\n    //         # Samples of mechanisms to use (any valid EntityTag mechanisms may be listed like this):\r\n    //\r\n    //         # Whether the entity has the default AI\r\n    //         # | Do not copy this line, it is only an example.\r\n    //         has_ai: true/false\r\n    //\r\n    //         # What age the entity is\r\n    //         # | Do not copy this line, it is only an example.\r\n    //         age: baby/adult/<#>\r\n    // </code>\r\n    //\r\n    // MORE MECHANISM OPTIONS ARE LISTED HERE: <@link url https://meta.denizenscript.com/Docs/Mechanisms/entitytag.>\r\n    //\r\n    // -->\r\n\r\n    public EntityScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\r\n        super(configurationSection, scriptContainerName);\r\n        EntityScriptHelper.scripts.put(CoreUtilities.toLowerCase(getName()), this);\r\n        canRunScripts = false;\r\n    }\r\n\r\n    public EntityTag getEntityFrom() {\r\n        return getEntityFrom(null, null);\r\n    }\r\n\r\n    public static HashSet<String> nonMechanismKeys = new HashSet<>(Arrays.asList(\"entity_type\", \"type\", \"debug\", \"custom\", \"data\", \"flags\", \"mechanisms\"));\r\n\r\n    public EntityTag getEntityFrom(PlayerTag player, NPCTag npc) {\r\n        EntityTag entity;\r\n        try {\r\n            TagContext context = new BukkitTagContext(player, npc, new ScriptTag(this));\r\n            if (contains(\"entity_type\", String.class)) {\r\n                String entityType = TagManager.tag((getString(\"entity_type\", \"\")), context);\r\n                entity = EntityTag.valueOf(entityType, context);\r\n            }\r\n            else {\r\n                throw new Exception(\"Missing entity_type argument!\");\r\n            }\r\n            if (contains(\"flags\", Map.class)) {\r\n                YamlConfiguration flagSection = getConfigurationSection(\"flags\");\r\n                MapTagFlagTracker tracker = new MapTagFlagTracker();\r\n                for (StringHolder key : flagSection.getKeys(false)) {\r\n                    tracker.setFlag(key.str, CoreUtilities.objectToTagForm(flagSection.get(key.str), context, true, true), null);\r\n                }\r\n                entity.safeAdjust(new Mechanism(\"flag_map\", tracker.map, context));\r\n            }\r\n            if (contains(\"mechanisms\", Map.class)) {\r\n                YamlConfiguration mechSection = getConfigurationSection(\"mechanisms\");\r\n                Set<StringHolder> strings = mechSection.getKeys(false);\r\n                for (StringHolder string : strings) {\r\n                    ObjectTag obj = CoreUtilities.objectToTagForm(mechSection.get(string.low), context, true, true);\r\n                    entity.safeAdjust(new Mechanism(string.low, obj, context));\r\n                }\r\n            }\r\n            boolean any = false;\r\n            Set<StringHolder> strings = getContents().getKeys(false);\r\n            for (StringHolder string : strings) {\r\n                if (!nonMechanismKeys.contains(string.low)) {\r\n                    any = true;\r\n                    ObjectTag obj = CoreUtilities.objectToTagForm(getContents().get(string.low), context, true, true);\r\n                    entity.safeAdjust(new Mechanism(string.low, obj, context));\r\n                }\r\n            }\r\n            if (any) {\r\n                BukkitImplDeprecations.entityMechanismsFormat.warn(this);\r\n            }\r\n            if (entity == null || entity.isUnique()) {\r\n                return null;\r\n            }\r\n            entity.setEntityScript(getName());\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(\"Woah! An exception has been called with this entity script!\");\r\n            Debug.echoError(e);\r\n            entity = null;\r\n        }\r\n\r\n        return entity;\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/EntityScriptHelper.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.utilities.DataPersistenceHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.events.entity.EntityDespawnScriptEvent;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityDeathEvent;\r\nimport org.bukkit.event.world.EntitiesUnloadEvent;\r\n\r\nimport java.util.HashMap;\r\n\r\npublic class EntityScriptHelper implements Listener {\r\n\r\n    public static HashMap<String, EntityScriptContainer> scripts = new HashMap<>();\r\n\r\n    public EntityScriptHelper() {\r\n        Denizen.getInstance().getServer().getPluginManager()\r\n                .registerEvents(this, Denizen.getInstance());\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.MONITOR)\r\n    public void onEntityDeath(EntityDeathEvent event) {\r\n        Entity entity = event.getEntity();\r\n        EntityTag.rememberEntity(entity);\r\n        EntityDespawnScriptEvent.instance.entity = new EntityTag(entity);\r\n        EntityDespawnScriptEvent.instance.cause = new ElementTag(\"DEATH\");\r\n        EntityDespawnScriptEvent.instance.fire();\r\n        EntityTag.forgetEntity(entity);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onChunkUnload(EntitiesUnloadEvent event) {\r\n        for (Entity ent : event.getEntities()) {\r\n            if (!(ent instanceof LivingEntity) || ((LivingEntity) ent).getRemoveWhenFarAway()) {\r\n                EntityTag.rememberEntity(ent);\r\n                EntityDespawnScriptEvent.instance.entity = new EntityTag(ent);\r\n                EntityDespawnScriptEvent.instance.cause = new ElementTag(\"CHUNK_UNLOAD\");\r\n                EntityDespawnScriptEvent.instance.fire();\r\n                EntityTag.forgetEntity(ent);\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Indicates whether a specified entity has a custom entity script.\r\n     */\r\n    public static boolean entityHasScript(Entity ent) {\r\n        return getEntityScript(ent) != null;\r\n    }\r\n\r\n    /**\r\n     * Returns the name of the entity script that defined this entity, or null if none.\r\n     */\r\n    public static String getEntityScript(Entity ent) {\r\n        if (ent == null) {\r\n            return null;\r\n        }\r\n        if (!DataPersistenceHelper.hasDenizenKey(ent, \"entity_script\")) {\r\n            return null;\r\n        }\r\n        ObjectTag scriptObject = DataPersistenceHelper.getDenizenKey(ent, \"entity_script\");\r\n        if (!(scriptObject instanceof ScriptTag)) {\r\n            return null;\r\n        }\r\n        return ((ScriptTag) scriptObject).getName();\r\n    }\r\n\r\n    /**\r\n     * Marks the entity as having been created by a specified script.\r\n     */\r\n    public static void setEntityScript(Entity ent, String script) {\r\n        if (ent == null || ent.getUniqueId() == null || script == null) {\r\n            return;\r\n        }\r\n        ScriptTag scriptObj = ScriptTag.valueOf(script, CoreUtilities.basicContext);\r\n        if (scriptObj == null) {\r\n            Debug.echoError(\"Can't set entity script to '\" + script + \"': not a valid script!\");\r\n        }\r\n        DataPersistenceHelper.setDenizenKey(ent, \"entity_script\", scriptObj);\r\n        return;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/InteractScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.scripts.triggers.AbstractTrigger;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\n\nimport java.util.*;\n\npublic class InteractScriptContainer extends ScriptContainer {\n\n    // <--[language]\n    // @name Interact Script Containers\n    // @group Script Container System\n    // @description\n    // Interact script containers are used to handle NPC triggers.\n    //\n    // Interact scripts must be referenced from an assignment script container to be of any use.\n    // See <@link language assignment script containers>.\n    //\n    // The only required key on an interact script container is the 'steps:' key.\n    //\n    // Within the steps key is a list of steps,\n    // where the first step is '1', 'default', or any step that contains a '*' symbol.\n    // After that, any steps must be 'zapped' to via the zap command: <@link command zap>.\n    //\n    // Each step contains a list of trigger types that it handles, and the relevant handling that the given\n    // trigger makes available.\n    //\n    // Refer to <@link language interact script triggers> for documentation about the triggers available.\n    // Any triggers used must be enabled in <@link action assignment> by <@link command trigger>.\n    //\n    // Note that script commands ran in interact scripts by default have a delay between each command.\n    // To override this delay, set 'speed: 0' on the container or change the relevant config setting.\n    //\n    // Interact scripts can be automatically disabled by adding \"enabled: false\" as a root key (supports any load-time-parseable tags).\n    //\n    // <code>\n    // Interact_Script_Name:\n    //\n    //     type: interact\n    //\n    //     # | All interact scripts MUST have this key!\n    //     steps:\n    //\n    //         # The first step\n    //         1:\n    //             # Any trigger type here\n    //             click trigger:\n    //                 script:\n    //                     # Handle what happens when the NPC is clicked during step 1\n    //                     - some commands\n    //             # Other triggers here\n    //         # other steps here\n    //\n    // </code>\n    //\n    // -->\n\n    // <--[language]\n    // @name Interact Script Triggers\n    // @group NPC Interact Scripts\n    // @description\n    // Interact script triggers are the most basic components of standard NPC scripting.\n    // They're very useful for NPCs that give quests or have other very basic interactions with players.\n    // While less powerful that other tools that Denizen provides, they can be very straightforward and clear to use in many simpler cases.\n    //\n    // Note that triggers have a default cooldown system built in to prevent users from clicking too rapidly.\n    // However these are very short cooldowns by default - when you need a longer cooldown, use\n    // <@link command cooldown> or <@link command engage>.\n    //\n    // Triggers go in <@link language interact script containers>.\n    //\n    // The available default trigger types are <@link language click triggers>,\n    // <@link language damage triggers>, <@link language chat triggers>, and <@link language proximity triggers>.\n    // -->\n\n    public InteractScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\n        super(configurationSection, scriptContainerName);\n\n        try {\n            // Find steps/default step in the script\n            Set<StringHolder> keys;\n            keys = getConfigurationSection(\"steps\").getKeys(false);\n            if (contains(\"requirements\", Object.class)) {\n                Debug.echoError(\"Interact script '\" + getName() + \"' is outdated: 'requirements' do not exist in modern Denizen!\");\n            }\n            if (keys.isEmpty()) {\n                throw new ExceptionInInitializerError(\"Could not find any STEPS in \" + getName() + \"! Is the type on this script correct?\");\n            }\n            for (StringHolder step1 : keys) {\n                String step = step1.str;\n                if (step.contains(\"*\")) {\n                    YamlConfiguration defaultStepSection = getConfigurationSection(\"steps.\" + step);\n                    step = step.replace(\"*\", \"\");\n                    set(\"steps.\" + step, defaultStepSection);\n                    set(\"steps.\" + step + \"*\", null);\n                    defaultStep = step;\n                }\n                if (step.equalsIgnoreCase(\"1\")) {\n                    defaultStep = step;\n                }\n                if (step.equalsIgnoreCase(\"default\")) {\n                    defaultStep = step;\n                }\n                steps.add(step);\n            }\n        }\n        catch (Exception e) {\n            Debug.echoError(e);\n        }\n        // Make default step the only step if there is only one step\n        if (defaultStep == null && steps.size() == 1) {\n            defaultStep = steps.get(0);\n        }\n        if (defaultStep == null) {\n            throw new ExceptionInInitializerError(\"Must specify a default step in '\" + getName() + \"'!\");\n        }\n    }\n\n    private String defaultStep = null;\n    private List<String> steps = new ArrayList<>();\n\n    /**\n     * <p>Gets the name of the default step for this interact script container. Default step\n     * is specified by a '*' character on the end of the step name.</p>\n     * <p/>\n     * <b>Example:</b>\n     * <tt>\n     * Example Interact Script: <br/>\n     * Type: interact <br/>\n     * Steps: <br/>\n     * Step Name*:   <--- Default step for this interact script <br/>\n     * ...  <br/>\n     * <br/>\n     * Another Step Name:  <--- Not the default step, must use ZAP <br/>\n     * ...  <br/>\n     * </tt>\n     * <p/>\n     * <p>Note: For the sake of compatibility with v0.76, a step named '1' can also\n     * be used to specify a default step.</p>\n     *\n     * @return name of the default step\n     */\n    public String getDefaultStepName() {\n        return defaultStep;\n    }\n\n    /**\n     * Checks if the step in this script contains an entry for the specified trigger.\n     *\n     * @param step    the name of the step to check\n     * @param trigger the trigger to check for\n     * @return true if the trigger is present in the step, false otherwise\n     */\n    public boolean containsTriggerInStep(String step, Class<? extends AbstractTrigger> trigger) {\n        String triggerName = Denizen.getInstance().triggerRegistry.get(trigger).getName();\n        return contains(\"steps.\" + step + \".\" + triggerName + \" trigger\", Map.class);\n    }\n\n    public List<ScriptEntry> getEntriesFor(Class<? extends AbstractTrigger> trigger, PlayerTag player, NPCTag npc, String id, boolean quiet) {\n        // Get the trigger name\n        String triggerName = Denizen.getInstance().triggerRegistry.get(trigger).getName();\n        // Check for entries\n        String key = \"steps.\" + InteractScriptHelper.getCurrentStep(player, getName()) + \".\"\n                + triggerName + \" trigger.\" + (id == null ? \"script\" : id + \".script\");\n        if (containsScriptSection(key)) {\n            // Entries exist, so get them and return the list of ScriptEntries\n            return getEntries(new BukkitScriptEntryData(player, npc), key);\n            // No entries, so just return an empty list to avoid NPEs\n        }\n        else {\n            if (!quiet) {\n                Debug.echoDebug(this, \"No entries in script for \" + key);\n            }\n            return Collections.emptyList();\n        }\n    }\n\n    /**\n     * Gets the available IDs with its trigger value in the form of a Map. The 'key' is\n     * the name of the ID and the value is the value of the 'Trigger' key that is owned\n     * by the ID key.\n     * <p/>\n     * <b>Example:</b>\n     * <tt>\n     * Example Interact Script: <br/>\n     * Type: interact <br/>\n     * Steps: <br/>\n     * Current Step:\n     * Click Trigger:           <--- obtained with the Trigger class <br/>\n     * <br/>\n     * id:                    <--- id of the specific trigger script/script options <br/>\n     * Trigger: iron_sword  <--- value of the id key <br/>\n     * Script: <br/>\n     * - ...\n     * - ... <br/>\n     * <br/>\n     * Script:                <--- since this is an id-less entry for the click trigger, <br/>\n     * - ...                       it will be ignored and not in the Map. <br/>\n     * - ... <br/>\n     * </tt>\n     * <p/>\n     * <p>Note: This is handled internally with the parse() method in AbstractTrigger, so for\n     * basic triggers, you probably don't need to even call this.</p>\n     *\n     * @param trigger The trigger involved\n     * @param player  The Denizen Player object for the player who triggered it\n     * @return A map of options in the trigger's script, excluding a plain 'script'\n     */\n    public Map<String, String> getIdMapFor(Class<? extends AbstractTrigger> trigger, PlayerTag player) {\n        // Get the trigger name\n        String triggerName = Denizen.getInstance().triggerRegistry.get(trigger).getName();\n        // Get the step\n        String step = InteractScriptHelper.getCurrentStep(player, getName());\n        // Check for entries\n        String keyBase = \"steps.\" + step + \".\" + triggerName + \" trigger\";\n        if (contains(keyBase, Map.class)) {\n            // Trigger exists in Player's current step, get ids.\n            Map<String, String> idMap = new LinkedHashMap<>();\n            // Iterate through IDs to build the idMap\n            try {\n                for (StringHolder id : getConfigurationSection(keyBase).getKeys(false)) {\n                    if (!id.str.equalsIgnoreCase(\"script\")) {\n                        idMap.put(id.str, getString(keyBase + \".\" + id.str + \".trigger\", \"\"));\n                    }\n                }\n            }\n            catch (Exception ex) {\n                Debug.echoError(\"Warning: improperly defined \" + DebugInternals.getClassNameOpti(trigger) + \" trigger for script '\" + getName() + \"' (basic formatting error?)!\");\n                Debug.echoError(ex);\n            }\n            return idMap;\n        }\n        // No entries, so just return an empty list to avoid NPEs\n        else {\n            return Collections.emptyMap();\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/InteractScriptHelper.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\n\nimport com.denizenscript.denizen.npc.traits.AssignmentTrait;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.scripts.commands.core.CooldownCommand;\nimport com.denizenscript.denizen.scripts.triggers.AbstractTrigger;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.TimeTag;\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\nimport com.denizenscript.denizencore.utilities.debugging.Debug.DebugElement;\nimport org.bukkit.ChatColor;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class InteractScriptHelper {\n\n    public static List<InteractScriptContainer> getInteractScripts(NPCTag npc) {\n        if (npc == null) {\n            return null;\n        }\n        AssignmentTrait trait = npc.getCitizen().getTraitNullable(AssignmentTrait.class);\n        if (trait == null) {\n            return null;\n        }\n        ArrayList<InteractScriptContainer> result = new ArrayList<>();\n        for (AssignmentScriptContainer container : trait.containerCache) {\n            if (container != null) {\n                InteractScriptContainer interact = container.getInteract();\n                if (interact != null && interact.shouldEnable()) {\n                    result.add(interact);\n                }\n            }\n        }\n        return result.isEmpty() ? null : result;\n    }\n\n    public static List<InteractScriptContainer> getInteractScripts(NPCTag npc, PlayerTag player, boolean showDebug, Class<? extends AbstractTrigger> trigger) {\n        if (player == null || trigger == null) {\n            return null;\n        }\n        List<InteractScriptContainer> interactScripts = getInteractScripts(npc);\n        if (interactScripts == null) {\n            return null;\n        }\n        for (int i = 0; i < interactScripts.size(); i++) {\n            InteractScriptContainer interactScript = interactScripts.get(i);\n            if (Debug.shouldDebug(interactScript) && showDebug) {\n                Debug.log(DebugElement.Header, \"Getting interact script: n@\" + npc.getName() + \"/p@\" + player.getName());\n            }\n            if (!CooldownCommand.checkCooldown(player, interactScript.getName())) {\n                if (Debug.shouldDebug(interactScript) && showDebug) {\n                    Debug.log(ChatColor.GOLD + interactScript.getName() + \" isn't cooled down yet! Skipping.\");\n                    interactScripts.remove(i--);\n                    continue;\n                }\n            }\n            if (Debug.shouldDebug(interactScript) && showDebug) {\n                Debug.log(\"Interact script is \" + interactScript.getName() + \". Current step for this script is: \" + getCurrentStep(player, interactScript.getName()));\n                Debug.log(DebugElement.Footer, \"\");\n            }\n        }\n        return interactScripts.isEmpty() ? null : interactScripts;\n    }\n\n    public static String getCurrentStep(PlayerTag player, String scriptName) {\n        if (scriptName == null) {\n            return null;\n        }\n        ObjectTag step = player.getFlagTracker().getFlagValue(\"__interact_step.\" + scriptName);\n        if (step != null) {\n            return step.toString().toUpperCase();\n        }\n        return ScriptRegistry.getScriptContainerAs(scriptName, InteractScriptContainer.class).getDefaultStepName().toUpperCase();\n    }\n\n    public static TimeTag getStepExpiration(PlayerTag player, String scriptName) {\n        if (scriptName == null) {\n            return null;\n        }\n        return player.getFlagTracker().getFlagExpirationTime(\"__interact_step.\" + scriptName);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/InventoryScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\n\nimport com.denizenscript.denizen.objects.InventoryTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\nimport com.denizenscript.denizencore.scripts.queues.core.InstantQueue;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.tags.TagManager;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\nimport org.bukkit.Material;\nimport org.bukkit.event.inventory.InventoryType;\nimport org.bukkit.inventory.ItemStack;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class InventoryScriptContainer extends ScriptContainer {\n\n    // <--[language]\n    // @name Inventory Script Containers\n    // @group Script Container System\n    // @description\n    // Inventory script containers are an easy way to pre-define custom inventories for use within scripts.\n    // Inventory scripts work with the InventoryTag object, and can be fetched with the Object Fetcher by using the\n    // InventoryTag constructor InventoryTag_script_name.\n    //\n    // Example: - inventory open d:MyInventoryScript\n    //\n    // The following is the format for the container.\n    //\n    // The 'inventory:' key is required, other keys vary based on the type.\n    // Some types will require you define either 'size:' or 'slots:' (or both).\n    // 'Procedural items:' and 'definitions:' are optional, and should only be defined if needed.\n    //\n    // <code>\n    // # The name of the script is the same name that you can use to construct a new\n    // # InventoryTag based on this inventory script. For example, an inventory script named 'Super_Cool_Inventory'\n    // # can be referred to as 'Super_Cool_Inventory'.\n    // Inventory_Script_Name:\n    //\n    //     type: inventory\n    //\n    //     # Must be a valid inventory type.\n    //     # Valid inventory types: ANVIL, BREWING, CHEST, DISPENSER, ENCHANTING, ENDER_CHEST, HOPPER, WORKBENCH\n    //     # | All inventory scripts MUST have this key!\n    //     inventory: inventory type\n    //\n    //     # The title can be anything you wish. Use color tags to make colored titles.\n    //     # Note that titles only work for some inventory types, including ANVIL, CHEST, DISPENSER, FURNACE, ENCHANTING, HOPPER, WORKBENCH\n    //     # | MOST inventory scripts should have this key!\n    //     title: custom title\n    //\n    //     # The size must be a multiple of 9. It is recommended to not go above 54, as it will not show correctly when a player looks into it.\n    //     # | Some inventory scripts should have this key! Most can exclude it if 'slots' is used.\n    //     size: 27\n    //\n    //     # Set 'gui' to 'true' to indicate that the inventory is a GUI, meaning it's a set of buttons to be clicked, not a container of items.\n    //     # This will prevent players from taking items out of or putting items into the inventory.\n    //     # | SOME inventory scripts should have this key!\n    //     gui: true\n    //\n    //     # You can use definitions to define items to use in the slots. These are not like normal script definitions, and do not need to be in a definition tag.\n    //     # | Some inventory scripts MAY have this key, but it is optional. Most scripts will just specify items directly.\n    //     definitions:\n    //         my item: ItemTag\n    //         other item: ItemTag\n    //\n    //     # Procedural items can be used to specify a list of ItemTags for the empty slots to be filled with.\n    //     # Each item in the list represents the next available empty slot.\n    //     # When the inventory has no more empty slots, it will discard any remaining items in the list.\n    //     # A slot is considered empty when it has no value specified in the slots section.\n    //     # If the slot is filled with air, it will no longer count as being empty.\n    //     # | Most inventory scripts should exclude this key, but it may be useful in some cases.\n    //     procedural items:\n    //     - define list <list>\n    //     - foreach <server.online_players>:\n    //         # Insert some form of complex doesn't-fit-in-just-a-tag logic here\n    //         - define item <[value].skull_item>\n    //         - define list:->:<[item]>\n    //     - determine <[list]>\n    //\n    //     # You can specify the items in the slots of the inventory. For empty spaces, simply put an empty \"slot\" value, like \"[]\".\n    //     # | Most inventory scripts SHOULD have this key!\n    //     slots:\n    //     - [] [] [] [my item] [ItemTag] [] [other item] [] []\n    //     - [my item] [] [] [] [] [ItemTag] [ItemTag] [] []\n    //     - [] [] [] [] [] [] [] [] [other item]\n    // </code>\n    //\n    // -->\n\n    public InventoryScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\n        super(configurationSection, scriptContainerName);\n        gui = CoreUtilities.equalsIgnoreCase(getString(\"gui\", \"false\"), \"true\");\n        InventoryScriptHelper.inventoryScripts.put(getName(), this);\n    }\n\n    public boolean gui;\n\n    public TagContext fixContext(TagContext context) {\n        context = (context == null ? CoreUtilities.basicContext : context).clone();\n        context.script = new ScriptTag(this);\n        context.debug = context.debug && shouldDebug();\n        return context;\n    }\n\n    public InventoryTag getInventoryFrom(TagContext context) {\n        InventoryTag inventory;\n        context = fixContext(context);\n        Debug.pushErrorContext(this);\n        try {\n            InventoryType type = InventoryType.CHEST;\n            if (contains(\"inventory\", String.class)) {\n                try {\n                    type = InventoryType.valueOf(getString(\"inventory\").toUpperCase());\n                }\n                catch (IllegalArgumentException ex) {\n                    Debug.echoError(this, \"Invalid inventory type specified. Assuming \\\"CHEST\\\" (\" + ex.getMessage() + \")\");\n                }\n            }\n            else {\n                Debug.echoError(this, \"Inventory script '\" + getName() + \"' does not specify an inventory type. Assuming \\\"CHEST\\\".\");\n            }\n            if (type == InventoryType.PLAYER) {\n                Debug.echoError(this, \"Inventory type 'player' is not valid for inventory scripts - defaulting to 'CHEST'.\");\n                type = InventoryType.CHEST;\n            }\n            int size = 0;\n            if (contains(\"size\", String.class)) {\n                if (type != InventoryType.CHEST) {\n                    Debug.echoError(this, \"You can only set the size of chest inventories!\");\n                }\n                else {\n                    String sizeText = TagManager.tag(getString(\"size\"), context);\n                    if (!ArgumentHelper.matchesInteger(sizeText)) {\n                        Debug.echoError(this, \"Invalid (not-a-number) size value.\");\n                    }\n                    else {\n                        size = Integer.parseInt(sizeText);\n                    }\n                    if (size == 0) {\n                        Debug.echoError(this, \"Inventory size can't be 0. Assuming default of inventory type...\");\n                    }\n                    if (size % 9 != 0) {\n                        size = (int) Math.ceil(size / 9.0) * 9;\n                        Debug.echoError(this, \"Inventory size must be a multiple of 9! Rounding up to \" + size + \"...\");\n                    }\n                    if (size < 0) {\n                        size = size * -1;\n                        Debug.echoError(this, \"Inventory size must be a positive number! Inverting to \" + size + \"...\");\n                    }\n                }\n            }\n            if (size == 0) {\n                if (contains(\"slots\", List.class) && type == InventoryType.CHEST) {\n                    size = getStringList(\"slots\").size() * 9;\n                }\n                else {\n                    size = type.getDefaultSize();\n                }\n            }\n            String title;\n            Debug.pushErrorContext(\"While reading 'title' input\");\n            try {\n                title = contains(\"title\", String.class) ? TagManager.tag(getString(\"title\"), context) : null;\n            }\n            finally {\n                Debug.popErrorContext();\n            }\n            if (type == InventoryType.CHEST) {\n                inventory = new InventoryTag(size, title != null ? title : \"Chest\");\n            }\n            else if (InventoryScriptHelper.isPersonalSpecialInv(type)) {\n                inventory = new InventoryTag(type);\n                inventory.customTitle = title;\n            }\n            else {\n                if (title == null) {\n                    inventory = new InventoryTag(type);\n                }\n                else {\n                    inventory = new InventoryTag(type, title);\n                }\n            }\n            inventory.idType = \"script\";\n            inventory.idHolder = new ScriptTag(this);\n            boolean[] filledSlots = new boolean[size];\n            Debug.pushErrorContext(\"While reading 'slots' input\");\n            try {\n                if (contains(\"slots\", List.class)) {\n                    ItemStack[] finalItems = new ItemStack[size];\n                    int itemsAdded = 0;\n                    for (String items : getStringList(\"slots\")) {\n                        items = TagManager.tag(items, context).trim();\n                        if (items.isEmpty()) {\n                            continue;\n                        }\n                        if (!items.startsWith(\"[\") || !items.endsWith(\"]\")) {\n                            Debug.echoError(this, \"Invalid slots line: [\" + items + \"]... Ignoring it\");\n                            continue;\n                        }\n                        String[] itemsInLine = items.substring(1, items.length() - 1).split(\"\\\\[?\\\\]?\\\\s+\\\\[\", -1);\n                        for (String item : itemsInLine) {\n                            if (item.isEmpty()) {\n                                finalItems[itemsAdded++] = new ItemStack(Material.AIR);\n                                continue;\n                            }\n                            filledSlots[itemsAdded] = true;\n                            if (contains(\"definitions.\" + item, String.class)) {\n                                ItemTag def = ItemTag.valueOf(TagManager.tag(getString(\"definitions.\" + item), context), context);\n                                if (def == null) {\n                                    Debug.echoError(this, \"Invalid definition '\" + item + \"'... Ignoring it and assuming 'AIR'\");\n                                    finalItems[itemsAdded] = new ItemStack(Material.AIR);\n                                }\n                                else {\n                                    finalItems[itemsAdded] = def.getItemStack();\n                                }\n                            }\n                            else {\n                                try {\n                                    ItemTag itemTag = ItemTag.valueOf(item, context);\n                                    if (itemTag == null) {\n                                        finalItems[itemsAdded] = new ItemStack(Material.AIR);\n                                        Debug.echoError(this, \"Invalid slot item: [\" + item + \"]... ignoring it and assuming 'AIR'\");\n                                    }\n                                    else {\n                                        finalItems[itemsAdded] = itemTag.getItemStack();\n                                    }\n                                }\n                                catch (Exception ex) {\n                                    Debug.echoError(this, \"Invalid slot item: [\" + item + \"]...\");\n                                    Debug.echoError(ex);\n                                }\n                            }\n                            itemsAdded++;\n                        }\n                    }\n                    inventory.setContents(finalItems);\n                }\n            }\n            finally {\n                Debug.popErrorContext();\n            }\n            if (containsScriptSection(\"procedural items\")) {\n                List<ScriptEntry> entries = getEntries(context.getScriptEntryData(), \"procedural items\");\n                if (!entries.isEmpty()) {\n                    InstantQueue queue = new InstantQueue(getName());\n                    queue.addEntries(entries);\n                    if (contains(\"definitions\", Map.class)) {\n                        for (Map.Entry<StringHolder, Object> entry : getConfigurationSection(\"definitions\").getMap().entrySet()) {\n                            ItemTag definitionValue = ItemTag.valueOf(TagManager.tag(entry.getValue().toString(), context), context);\n                            if (definitionValue == null) {\n                                Debug.echoError(this, \"Invalid item '\" + entry.getValue() + \"' for definition '\" + entry.getKey().str + \"'\");\n                                continue;\n                            }\n                            queue.addDefinition(entry.getKey().low, definitionValue);\n                        }\n                    }\n                    queue.procedural = true;\n                    queue.start();\n                    if (queue.determinations != null) {\n                        ListTag list = ListTag.getListFor(queue.determinations.getObject(0), context);\n                        if (list != null) {\n                            int x = 0;\n                            for (ItemTag item : list.filter(ItemTag.class, context, true)) {\n                                while (x < filledSlots.length && filledSlots[x]) {\n                                    x++;\n                                }\n                                if (x >= filledSlots.length || filledSlots[x]) {\n                                    break;\n                                }\n                                inventory.setSlots(x, item.getItemStack());\n                                filledSlots[x] = true;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        catch (Exception e) {\n            Debug.echoError(this, \"Woah! An exception has been called while building this inventory script!\");\n            Debug.echoError(e);\n            inventory = null;\n        }\n        finally {\n            Debug.popErrorContext();\n        }\n        if (inventory != null) {\n            InventoryTag.trackTemporaryInventory(inventory);\n        }\n        return inventory;\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/InventoryScriptHelper.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.*;\r\nimport org.bukkit.event.player.PlayerLoginEvent;\r\nimport org.bukkit.inventory.Inventory;\r\n\r\nimport java.util.*;\r\n\r\npublic class InventoryScriptHelper implements Listener {\r\n\r\n    public static boolean isPersonalSpecialInv(InventoryType type) {\r\n        return type == InventoryType.ANVIL || type == InventoryType.WORKBENCH;\r\n    }\r\n\r\n    public static boolean isPersonalSpecialInv(Inventory inv) {\r\n        return isPersonalSpecialInv(inv.getType());\r\n    }\r\n\r\n    public static Map<Inventory, InventoryTag> notedInventories = new HashMap<>();\r\n\r\n    public static HashMap<String, InventoryScriptContainer> inventoryScripts = new HashMap<>();\r\n\r\n    public InventoryScriptHelper() {\r\n        Denizen.getInstance().getServer().getPluginManager().registerEvents(this, Denizen.getInstance());\r\n    }\r\n\r\n    private final static List<UUID> toClearOfflinePlayers = new ArrayList<>();\r\n\r\n    public static void savePlayerInventories() {\r\n        for (ImprovedOfflinePlayer player : ImprovedOfflinePlayer.offlinePlayers.values()) {\r\n            if (player.inventory != null) { // TODO: optimize - remove inventories when no longer in use?\r\n                player.setInventory(player.inventory);\r\n            }\r\n            if (player.enderchest != null) {\r\n                player.setEnderChest(player.enderchest);\r\n            }\r\n            if (player.modified) {\r\n                player.saveToFile();\r\n            }\r\n            if (player.timeLastLoaded + Settings.worldPlayerDataMaxCacheTicks < DenizenCore.currentTimeMonotonicMillis) {\r\n                toClearOfflinePlayers.add(player.player);\r\n            }\r\n        }\r\n        for (UUID id : toClearOfflinePlayers) {\r\n            ImprovedOfflinePlayer.offlinePlayers.remove(id);\r\n        }\r\n        toClearOfflinePlayers.clear();\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerLogin(PlayerLoginEvent event) {\r\n        ImprovedOfflinePlayer.invalidateNow(event.getPlayer().getUniqueId());\r\n    }\r\n\r\n    public static HashSet<ClickType> allowedClicks = new HashSet<>(Arrays.asList(ClickType.CONTROL_DROP, ClickType.CREATIVE, ClickType.DROP, ClickType.LEFT,\r\n            ClickType.MIDDLE, ClickType.NUMBER_KEY, ClickType.RIGHT, ClickType.WINDOW_BORDER_LEFT, ClickType.WINDOW_BORDER_RIGHT));\r\n\r\n    public static boolean isGUI(Inventory inv) {\r\n        InventoryTag inventory = InventoryTag.mirrorBukkitInventory(inv);\r\n        if (inventory.getIdHolder() instanceof ScriptTag) {\r\n            if (((InventoryScriptContainer) ((ScriptTag) inventory.getIdHolder()).getContainer()).gui) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)\r\n    public void onPlayerClicks(InventoryClickEvent event) {\r\n        if (event.getRawSlot() >= event.getInventory().getSize() || event.getRawSlot() < 0) {\r\n            if (allowedClicks.contains(event.getClick())) {\r\n                return;\r\n            }\r\n        }\r\n        if (isGUI(event.getInventory())) {\r\n            event.setCancelled(true);\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> {\r\n                ((Player) event.getWhoClicked()).updateInventory();\r\n            }, 1);\r\n        }\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)\r\n    public void onPlayerDrags(InventoryDragEvent event) {\r\n        if (isGUI(event.getInventory())) {\r\n            boolean anyInTop = false;\r\n            for (int slot : event.getRawSlots()) {\r\n                if (slot < event.getInventory().getSize()) {\r\n                    anyInTop = true;\r\n                    break;\r\n                }\r\n            }\r\n            if (anyInTop) {\r\n                event.setCancelled(true);\r\n            }\r\n        }\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)\r\n    public void onPlayerCloses(InventoryCloseEvent event) {\r\n        if (isPersonalSpecialInv(event.getInventory()) && isGUI(event.getInventory())) {\r\n            event.getInventory().clear();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/ItemScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EnchantmentTag;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.nbt.LeatherColorer;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.meta.EnchantmentStorageMeta;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class ItemScriptContainer extends ScriptContainer {\r\n\r\n    // <--[language]\r\n    // @name Item Script Containers\r\n    // @group Script Container System\r\n    // @description\r\n    // Item script containers are an easy way to pre-define custom items for use within scripts. Item\r\n    // scripts work with the ItemTag object, and can be fetched with the Object Fetcher by using the\r\n    // ItemTag constructor ItemTag_script_name. Example: - drop <player.location> super_dooper_diamond\r\n    //\r\n    // The following is the format for the container. Except for the 'material' key (and the dScript\r\n    // required 'type' key), all other keys are optional.\r\n    //\r\n    // <code>\r\n    // # The name of the item script is the same name that you can use to construct a new\r\n    // # ItemTag based on this item script. For example, an item script named 'sword_of_swiftness'\r\n    // # can be referred to as simply 'sword_of_swiftness'.\r\n    // Item_Script_Name:\r\n    //\r\n    //     type: item\r\n    //\r\n    //     # Must be a valid ItemTag. See 'ItemTag' for more information.\r\n    //     # | All item scripts MUST have this key!\r\n    //     material: base_material\r\n    //\r\n    //     # List any mechanisms you want to apply to the item within\r\n    //     # | Some item scripts should have this key!\r\n    //     mechanisms:\r\n    //       # An example of a mechanism to apply\r\n    //       unbreakable: true\r\n    //       # Other common example\r\n    //       custom_model_data: 5\r\n    //       # This demonstrates how to add a custom attribute modifier.\r\n    //       attribute_modifiers:\r\n    //           # One subkey for each attribute you want to modify.\r\n    //           # Valid attribute names are listed at https://hub.spigotmc.org/javadocs/spigot/org/bukkit/attribute/Attribute.html\r\n    //           generic_armor:\r\n    //               # Each attribute can have a list of modifiers, using numbered keys. They will be applied in numeric order, low to high.\r\n    //               1:\r\n    //                   # Each modifier requires keys 'operation' and 'amount', and can optionally have keys 'name', 'slot', and 'id'.\r\n    //                   # Operations can be: ADD_NUMBER, ADD_SCALAR, and MULTIPLY_SCALAR_1\r\n    //                   operation: add_number\r\n    //                   amount: 5\r\n    //                   # Slots can be: HAND, OFF_HAND, FEET, LEGS, CHEST, HEAD, ANY\r\n    //                   slot: head\r\n    //                   # ID is a UUID used to uniquely identify modifiers. If unspecified the ID will be randomly generated.\r\n    //                   # Items with modifiers that lack IDs cannot be stacked due to the random generation.\r\n    //                   id: 10000000-1000-1000-1000-100000000000\r\n    //\r\n    //     # The 'custom name' can be anything you wish. Use color tags to make colored custom names.\r\n    //     # | Some item scripts should have this key!\r\n    //     display name: custom name\r\n    //\r\n    //     # Lore lines can make items extra unique. This is a list, so multiple entries will result in multiple lores.\r\n    //     # If using a replaceable tag, they are filled in when the item script is given/created/dropped/etc.\r\n    //     # | Some item scripts should have this key!\r\n    //     lore:\r\n    //     - item\r\n    //     - ...\r\n    //\r\n    //     # If you want an item to be damaged on creation, you can change its durability.\r\n    //     # | Most item scripts should exclude this key!\r\n    //     durability: 12\r\n    //\r\n    //     # Each line must specify a valid enchantment name.\r\n    //     # | Some item scripts should have this key!\r\n    //     enchantments:\r\n    //     - enchantment_name:level\r\n    //     - ...\r\n    //\r\n    //     # Set this to 'true' to allow the item script item to be used in material-based recipe (eg most vanilla recipes).\r\n    //     # Defaults to false if unspecified.\r\n    //     # | Most item scripts should exclude this key!\r\n    //     allow in material recipes: false\r\n    //\r\n    //     # You can specify flags to be added to the item.\r\n    //     flags:\r\n    //       # Each line within the flags section should be a flag name as a key, and the flag value as the value.\r\n    //       # You can use lists or maps here the way you would expect them to work.\r\n    //       my_flag: my value\r\n    //\r\n    //     # You can optionally add crafting recipes for your item script.\r\n    //     # Note that recipes won't show in the recipe book when you add a new item script, until you either reconnect or use the \"resend_recipes\" mechanism.\r\n    //     # | Most item scripts should exclude this key, unless you're specifically building craftable items.\r\n    //     recipes:\r\n    //         1:\r\n    //             # The type can be: shaped, shapeless, stonecutting, furnace, blast, smoker, campfire, or smithing.\r\n    //             # | All recipes must include this key!\r\n    //             type: shaped\r\n    //             # The recipe can optionally have a custom internal recipe ID (for recipe books).\r\n    //             # If not specified, will be of the form \"<type>_<script.name>_<id>\" where ID is the recipe list index (starting at 1, counting up).\r\n    //             # IDs will always have the namespace \"denizen\". So, for the below, the full ID is \"denizen:my_custom_item_id\"\r\n    //             # Note that most users should not set a custom ID. If you choose to set a custom one, be careful to avoid duplicates or invalid text.\r\n    //             # Note that the internal rules for Recipe IDs are very strict (limited to \"a-z\", \"0-9\", \"/\", \".\", \"_\", or \"-\").\r\n    //             # | Most recipes should exclude this key.\r\n    //             recipe_id: my_custom_item_id\r\n    //             # You can optionally add a group as well. If unspecified, the item will have no group.\r\n    //             # Groups are used to merge together similar recipes in the recipe book (in particular, multiple recipes for one item).\r\n    //             # | Most recipes should exclude this key.\r\n    //             group: my_custom_group\r\n    //             # You can optionally specify the quantity to output. The default is 1 (or whatever the item script's quantity is).\r\n    //             # | Only some recipes should have this key.\r\n    //             output_quantity: 4\r\n    //             # You must specify the input for the recipe. The below is a sample of a 3x3 shaped recipe. Other recipe types have a different format.\r\n    //             # You are allowed to have non-3x3 shapes (can be any value 1-3 x 1-3, so for example 1x3, 2x1, and 2x2 are fine).\r\n    //             # For an empty slot, use \"air\".\r\n    //             # By default, items require an exact match. For a material-based match, use the format \"material:MaterialNameHere\" like \"material:stick\".\r\n    //             # To make multiple different items match for any slot, just separate them with slashes, like \"stick/stone\".\r\n    //             # To match multiple materials, use \"material:a/b/c\".\r\n    //             # You can also make a dynamic matcher using '*', like \"material:*_log\" to match any log block,\r\n    //             # or 'test_*' to match any item script that has name starting with 'test_'.\r\n    //             # Note that to require multiple of an item as an input, the only option is to use multiple slots.\r\n    //             # A single slot cannot require a quantity of items, as that is not part of the minecraft recipe system.\r\n    //             # | All recipes must include this key!\r\n    //             input:\r\n    //             - ItemTag|ItemTag|ItemTag\r\n    //             - ItemTag|ItemTag|ItemTag\r\n    //             - ItemTag|ItemTag|ItemTag\r\n    //        # You can add as many as you want.\r\n    //        2:\r\n    //             # Sample of the format for a 2x2 recipe\r\n    //             type: shaped\r\n    //             input:\r\n    //             - ItemTag|ItemTag\r\n    //             - ItemTag|ItemTag\r\n    //        3:\r\n    //            # Shapeless recipes take a list of input items.\r\n    //            type: shapeless\r\n    //            # Optionally specify the shapeless category for shapeless recipes, as \"building\", \"redstone\", \"equipment\", or \"misc\". Defaults to \"misc\" if unspecified.\r\n    //            # | Only some recipes should have this key.\r\n    //            category: misc\r\n    //            input: ItemTag|...\r\n    //        4:\r\n    //            # Stonecutting recipes take exactly one input item.\r\n    //            type: stonecutting\r\n    //            input: ItemTag\r\n    //        5:\r\n    //            # Furnace, blast, smoker, and campfire recipes take one input and have additional options.\r\n    //            type: furnace\r\n    //            # Optionally specify the cook time as a duration (default 2s).\r\n    //            # | Only some recipes should have this key.\r\n    //            cook_time: 1s\r\n    //            # Optionally specify the cooking category for cooking recipes, as \"food\", \"blocks\", or \"misc\". Defaults to \"misc\" if unspecified.\r\n    //            # | Only some recipes should have this key.\r\n    //            category: misc\r\n    //            # Optionally specify experience reward amount (default 0).\r\n    //            # | Only some recipes should have this key.\r\n    //            experience: 5\r\n    //            input: ItemTag\r\n    //        6:\r\n    //            # Smithing recipes take one base item and one upgrade item.\r\n    //            # In versions 1.20 and up, smithing recipes take one template item, one base item, and one upgrade item.\r\n    //            type: smithing\r\n    //            template: ItemTag\r\n    //            base: ItemTag\r\n    //            # Optionally, choose what values to retain, as a simple pipe-separated list of parts to retain.\r\n    //            # If unspecified, no values will be retained.\r\n    //            # Parts can be: 'display', 'enchantments'\r\n    //            retain: display|enchantments\r\n    //            upgrade: ItemTag\r\n    //\r\n    //        7:\r\n    //            # Brewing recipes take one base item and one ingredient item.\r\n    //            # | Brewing recipes are only available on Paper versions 1.18 and up.\r\n    //            # | Brewing recipes also have a special input option on 1.20 and above: \"matcher:<item matcher>\", to allow advanced matchers on the input/ingredient items.\r\n    //            type: brewing\r\n    //            input: ItemTag\r\n    //            ingredient: ItemTag\r\n    //\r\n    //     # Set to true to not store the scriptID on the item, treating it as an item dropped by any other plugin.\r\n    //     # NOTE: THIS IS NOT RECOMMENDED UNLESS YOU HAVE A SPECIFIC REASON TO USE IT.\r\n    //     # | Most item scripts should exclude this key!\r\n    //     no_id: true/false\r\n    //\r\n    //     # If your material is a 'written_book', you can specify a book script to automatically scribe your item\r\n    //     # upon creation. See 'book script containers' for more information.\r\n    //     # | Most item scripts should exclude this key, though there are certain rare cases it may be useful to.\r\n    //     book: book_script_name\r\n    // </code>\r\n    //\r\n    // -->\r\n\r\n    String hash = \"\";\r\n\r\n    public boolean allowInMaterialRecipes;\r\n\r\n    public ItemScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\r\n        super(configurationSection, scriptContainerName);\r\n        canRunScripts = false;\r\n        allowInMaterialRecipes = getString(\"allow in material recipes\", \"false\").equalsIgnoreCase(\"true\");\r\n        ItemScriptHelper.item_scripts.put(CoreUtilities.toLowerCase(getName()), this);\r\n        ItemScriptHelper.item_scripts_by_hash_id.put(ItemScriptHelper.createItemScriptID(this), this);\r\n    }\r\n\r\n    private ItemTag cleanReference;\r\n\r\n    public ItemTag getCleanReference() {\r\n        if (cleanReference == null) {\r\n            cleanReference = getItemFrom();\r\n            if (cleanReference == null) {\r\n                Debug.echoError(\"Item script '\" + getName() + \"' is invalid and unable to construct an ItemTag.\");\r\n                return null;\r\n            }\r\n        }\r\n        return new ItemTag(cleanReference.getItemStack().clone());\r\n    }\r\n\r\n    public void setHashID(String HashID) {\r\n        hash = HashID;\r\n    }\r\n\r\n    public ItemTag getItemFrom() {\r\n        return getItemFrom(null);\r\n    }\r\n\r\n    boolean isProcessing = false;\r\n\r\n    public ItemTag getItemFrom(TagContext context) {\r\n        if (isProcessing) {\r\n            Debug.echoError(\"Item script contains (or chains to) a reference to itself. Cannot process.\");\r\n            return null;\r\n        }\r\n        if (context == null) {\r\n            context = new BukkitTagContext(null, null, new ScriptTag(this));\r\n        }\r\n        else {\r\n            context = new BukkitTagContext((BukkitTagContext) context);\r\n            context.script = new ScriptTag(this);\r\n        }\r\n        // Try to use this script to make an item.\r\n        ItemTag stack;\r\n        isProcessing = true;\r\n        try {\r\n            if (!contains(\"material\", String.class)) {\r\n                Debug.echoError(\"Item script '\" + getName() + \"' does not contain a material. Script cannot function.\");\r\n                return null;\r\n            }\r\n            // Check validity of material\r\n            String material = TagManager.tag(getString(\"material\"), context);\r\n            if (material.startsWith(\"m@\")) {\r\n                material = material.substring(2);\r\n            }\r\n            stack = ItemTag.valueOf(material, this);\r\n            // Make sure we're working with a valid base ItemStack\r\n            if (stack == null) {\r\n                Debug.echoError(\"Item script '\" + getName() + \"' contains an invalid or incorrect material '\" + material + \"' (did you spell the material name wrong?). Script cannot function.\");\r\n                return null;\r\n            }\r\n            // Set Enchantments\r\n            if (contains(\"enchantments\", List.class)) {\r\n                for (String enchantment : getStringList(\"enchantments\")) {\r\n                    enchantment = TagManager.tag(enchantment, context);\r\n                    try {\r\n                        // Build enchantment context\r\n                        int level = 1;\r\n                        int colon = enchantment.lastIndexOf(':');\r\n                        if (colon == -1) {\r\n                            Debug.echoError(\"Item script '\" + getName() + \"' has enchantment '\" + enchantment + \"' without a level.\");\r\n                        }\r\n                        else {\r\n                            level = Integer.parseInt(enchantment.substring(colon + 1).replace(\" \", \"\"));\r\n                            enchantment = enchantment.substring(0, colon).replace(\" \", \"\");\r\n                        }\r\n                        // Add enchantment\r\n                        EnchantmentTag ench = EnchantmentTag.valueOf(enchantment, context);\r\n                        if (ench == null) {\r\n                            Debug.echoError(\"Item script '\" + getName() + \"' specifies enchantment '\" + enchantment + \"' which is invalid.\");\r\n                            continue;\r\n                        }\r\n                        if (stack.getBukkitMaterial() == Material.ENCHANTED_BOOK) {\r\n                            EnchantmentStorageMeta meta = (EnchantmentStorageMeta) stack.getItemMeta();\r\n                            meta.addStoredEnchant(ench.enchantment, level, true);\r\n                            stack.setItemMeta(meta);\r\n                        }\r\n                        else {\r\n                            stack.getItemStack().addUnsafeEnchantment(ench.enchantment, level);\r\n                            stack.resetCache();\r\n                        }\r\n                    }\r\n                    catch (Exception ex) {\r\n                        Debug.echoError(\"While constructing item script '\" + getName() + \"', encountered error while applying enchantment '\" + enchantment + \"':\");\r\n                        Debug.echoError(ex);\r\n                    }\r\n                }\r\n            }\r\n            // Handle listed mechanisms\r\n            if (contains(\"mechanisms\", Map.class)) {\r\n                YamlConfiguration mechs = getConfigurationSection(\"mechanisms\");\r\n                for (StringHolder key : mechs.getKeys(false)) {\r\n                    ObjectTag obj = CoreUtilities.objectToTagForm(mechs.get(key.low), context, true, true);\r\n                    stack.safeAdjust(new Mechanism(key.low, obj, context));\r\n                }\r\n            }\r\n            // Set Display Name\r\n            if (contains(\"display name\", String.class)) {\r\n                String displayName = TagManager.tag(getString(\"display name\"), context);\r\n                NMSHandler.itemHelper.setDisplayName(stack, displayName);\r\n            }\r\n            // Set if the object is bound to the player\r\n            if (contains(\"bound\", String.class)) {\r\n                BukkitImplDeprecations.boundWarning.warn(context);\r\n            }\r\n            // Set Lore\r\n            if (contains(\"lore\", List.class)) {\r\n                List<String> lore = NMSHandler.itemHelper.getLore(stack);\r\n                if (lore == null) {\r\n                    lore = new ArrayList<>();\r\n                }\r\n                for (String line : getStringList(\"lore\")) {\r\n                    line = TagManager.tag(line, context);\r\n                    lore.add(line);\r\n                }\r\n                CoreUtilities.fixNewLinesToListSeparation(lore);\r\n                NMSHandler.itemHelper.setLore(stack, lore);\r\n            }\r\n            // Set Durability\r\n            if (contains(\"durability\", String.class)) {\r\n                short durability = Short.parseShort(getString(\"durability\"));\r\n                stack.setDurability(durability);\r\n            }\r\n            // Set Color\r\n            if (contains(\"color\", String.class)) {\r\n                BukkitImplDeprecations.itemScriptColor.warn(context);\r\n                String color = TagManager.tag(getString(\"color\"), context);\r\n                LeatherColorer.colorArmor(stack, color);\r\n            }\r\n            // Set Book\r\n            if (contains(\"book\", String.class)) {\r\n                BookScriptContainer book = ScriptRegistry.getScriptContainer(TagManager.tag(getString(\"book\"), context).replace(\"s@\", \"\"));\r\n                stack = book.writeBookTo(stack, context);\r\n            }\r\n            if (contains(\"flags\", Map.class)) {\r\n                YamlConfiguration flagSection = getConfigurationSection(\"flags\");\r\n                AbstractFlagTracker tracker = stack.getFlagTracker();\r\n                for (StringHolder key : flagSection.getKeys(false)) {\r\n                    tracker.setFlag(key.str, CoreUtilities.objectToTagForm(flagSection.get(key.str), context, true, true), null);\r\n                }\r\n                stack.reapplyTracker(tracker);\r\n            }\r\n            stack.setItemScript(this);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(\"Woah! An exception has been called with this item script!\");\r\n            Debug.echoError(e);\r\n            stack = null;\r\n        }\r\n        finally {\r\n            isProcessing = false;\r\n        }\r\n\r\n        return stack;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/ItemScriptHelper.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.bukkit.ScriptReloadEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemHelper;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptBuilder;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport org.bukkit.*;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockCookEvent;\r\nimport org.bukkit.event.inventory.*;\r\nimport org.bukkit.inventory.*;\r\nimport org.bukkit.inventory.meta.ItemMeta;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.*;\r\nimport java.util.function.Function;\r\n\r\npublic class ItemScriptHelper implements Listener {\r\n\r\n    public static final Map<String, ItemScriptContainer> item_scripts = new HashMap<>();\r\n    public static final Map<String, ItemScriptContainer> item_scripts_by_hash_id = new HashMap<>();\r\n    public static final Map<String, ItemScriptContainer> recipeIdToItemScript = new HashMap<>();\r\n    public static HashMap<String, String[]> smithingRetain = new HashMap<>();\r\n\r\n    public ItemScriptHelper() {\r\n        Denizen.getInstance().getServer().getPluginManager().registerEvents(this, Denizen.getInstance());\r\n    }\r\n\r\n    public static void removeDenizenRecipes() {\r\n        smithingRetain.clear();\r\n        recipeCache.clear();\r\n        recipeIdToItemScript.clear();\r\n        Iterator<Recipe> recipeIterator = Bukkit.recipeIterator();\r\n        ArrayList<NamespacedKey> keys = new ArrayList<>();\r\n        while (recipeIterator.hasNext()) {\r\n            try {\r\n                if (recipeIterator.next() instanceof Keyed keyed && keyed.getKey().getNamespace().equals(\"denizen\")) {\r\n                    if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n                        keys.add(keyed.getKey());\r\n                    }\r\n                    else {\r\n                        recipeIterator.remove();\r\n                    }\r\n                }\r\n            }\r\n            catch (AbstractMethodError ignored) {} // TODO: 26.1: work around Spigot bug\r\n        }\r\n        if (!keys.isEmpty()) {\r\n            NMSHandler.itemHelper.removeRecipes(keys);\r\n        }\r\n        PaperAPITools.instance.clearBrewingRecipes();\r\n    }\r\n\r\n    public static String getIdFor(ItemScriptContainer container, String type, int id) {\r\n        String basicId = type + \"_\" + Utilities.cleanseNamespaceID(container.getName()) + \"_\" + id;\r\n        if (!recipeIdToItemScript.containsKey(basicId)) {\r\n            recipeIdToItemScript.put(\"denizen:\" + basicId, container);\r\n            return basicId;\r\n        }\r\n        int newNumber = 1;\r\n        String newId = basicId + \"_1\";\r\n        while (recipeIdToItemScript.containsKey(newId)) {\r\n            newId = basicId + \"_\" + newNumber++;\r\n        }\r\n        recipeIdToItemScript.put(\"denizen:\" + newId, container);\r\n        return newId;\r\n    }\r\n\r\n    public static List<String> splitByNonBracketedSlashes(String str) {\r\n        boolean brackets = false;\r\n        int start = 0;\r\n        List<String> output = new ArrayList<>(4);\r\n        for (int i = 0; i < str.length(); i++) {\r\n            char c = str.charAt(i);\r\n            if (c == '[') {\r\n                brackets = true;\r\n            }\r\n            else if (c == ']') {\r\n                brackets = false;\r\n            }\r\n            else if (c == '/' && !brackets) {\r\n                output.add(str.substring(start, i));\r\n                start = i + 1;\r\n            }\r\n        }\r\n        output.add(str.substring(start));\r\n        return output;\r\n    }\r\n\r\n    public static ItemStack[] textToItemArray(ItemScriptContainer container, String text, boolean exact) {\r\n        if (CoreUtilities.toLowerCase(text).equals(\"air\")) {\r\n            return new ItemStack[0];\r\n        }\r\n        List<String> ingredientText = splitByNonBracketedSlashes(text);\r\n        List<ItemStack> outputItems = new ArrayList<>(ingredientText.size());\r\n        for (int i = 0; i < ingredientText.size(); i++) {\r\n            String entry = ingredientText.get(i);\r\n            if (ScriptEvent.isAdvancedMatchable(entry)) {\r\n                boolean any = false;\r\n                for (Material material : Material.values()) {\r\n                    if (material.isItem() && MaterialTag.advancedMatchesInternal(material, entry, true)) {\r\n                        outputItems.add(new ItemStack(material, 1));\r\n                        any = true;\r\n                    }\r\n                }\r\n                if (exact) {\r\n                    TagContext context = DenizenCore.implementation.getTagContext(container);\r\n                    for (ItemScriptContainer possibleContainer : ItemScriptHelper.item_scripts.values()) {\r\n                        if (possibleContainer.getCleanReference() != null && possibleContainer.getCleanReference().tryAdvancedMatcher(entry, context)) {\r\n                            outputItems.add(possibleContainer.getCleanReference().getItemStack());\r\n                            any = true;\r\n                        }\r\n                    }\r\n                }\r\n                if (!any) {\r\n                    Debug.echoError(\"Invalid ItemTag ingredient (empty advanced matcher), recipe will not be registered for item script '\" + container.getName() + \"': \" + entry);\r\n                    return null;\r\n                }\r\n            }\r\n            else {\r\n                ItemTag ingredient = ItemTag.valueOf(entry, container);\r\n                if (ingredient == null) {\r\n                    Debug.echoError(\"Invalid ItemTag ingredient, recipe will not be registered for item script '\" + container.getName() + \"': \" + entry);\r\n                    return null;\r\n                }\r\n                outputItems.add(ingredient.getItemStack().clone());\r\n            }\r\n        }\r\n        return outputItems.toArray(new ItemStack[0]);\r\n    }\r\n\r\n    public static void registerShapedRecipe(ItemScriptContainer container, ItemStack item, List<String> recipeList, String internalId, String group) {\r\n        for (int n = 0; n < recipeList.size(); n++) {\r\n            recipeList.set(n, TagManager.tag(ScriptBuilder.stripLinePrefix(recipeList.get(n)), new BukkitTagContext(container)));\r\n        }\r\n        List<ItemStack[]> ingredients = new ArrayList<>();\r\n        List<Boolean> exacts = new ArrayList<>();\r\n        int width = 1;\r\n        for (String recipeRow : recipeList) {\r\n            String[] elements = recipeRow.split(\"\\\\|\", 3);\r\n            if (width < 3 && elements.length == 3) {\r\n                width = 3;\r\n            }\r\n            if (width < 2 && elements.length >= 2) {\r\n                width = 2;\r\n            }\r\n            for (String element : elements) {\r\n                String itemText = element;\r\n                boolean isExact = !itemText.startsWith(\"material:\");\r\n                if (!isExact) {\r\n                    itemText = itemText.substring(\"material:\".length());\r\n                }\r\n                exacts.add(isExact);\r\n                ItemStack[] items = textToItemArray(container, itemText, isExact);\r\n                if (items == null) {\r\n                    return;\r\n                }\r\n                ingredients.add(items);\r\n            }\r\n        }\r\n        NamespacedKey key = new NamespacedKey(\"denizen\", internalId);\r\n        ShapedRecipe recipe = new ShapedRecipe(key, item);\r\n        recipe.setGroup(group);\r\n        String shape1 = \"ABC\".substring(0, width);\r\n        String shape2 = \"DEF\".substring(0, width);\r\n        String shape3 = \"GHI\".substring(0, width);\r\n        String itemChars = shape1 + shape2 + shape3;\r\n        if (recipeList.size() == 3) {\r\n            recipe = recipe.shape(shape1, shape2, shape3);\r\n        }\r\n        else if (recipeList.size() == 2) {\r\n            recipe = recipe.shape(shape1, shape2);\r\n        }\r\n        else {\r\n            recipe = recipe.shape(shape1);\r\n        }\r\n        for (int i = 0; i < ingredients.size(); i++) {\r\n            if (ingredients.get(i).length != 0) {\r\n                NMSHandler.itemHelper.setShapedRecipeIngredient(recipe, itemChars.charAt(i), ingredients.get(i), exacts.get(i));\r\n            }\r\n        }\r\n        NMSHandler.itemHelper.registerOtherRecipe(recipe);\r\n    }\r\n\r\n    public static void registerShapelessRecipe(ItemScriptContainer container, ItemStack item, String shapelessString, String internalId, String group, String category) {\r\n        TagContext context = new BukkitTagContext(container);\r\n        List<ItemStack[]> ingredients = new ArrayList<>();\r\n        List<Boolean> exacts = new ArrayList<>();\r\n        for (String element : ListTag.valueOf(shapelessString, context)) {\r\n            String itemText = element;\r\n            boolean isExact = !itemText.startsWith(\"material:\");\r\n            if (!isExact) {\r\n                itemText = itemText.substring(\"material:\".length());\r\n            }\r\n            exacts.add(isExact);\r\n            ItemStack[] items = textToItemArray(container, itemText, isExact);\r\n            if (items == null) {\r\n                return;\r\n            }\r\n            ingredients.add(items);\r\n        }\r\n        boolean[] bools = new boolean[exacts.size()];\r\n        for (int i = 0; i < exacts.size(); i++) {\r\n            bools[i] = exacts.get(i);\r\n        }\r\n        NMSHandler.itemHelper.registerShapelessRecipe(internalId, group, item, ingredients, bools, category);\r\n    }\r\n\r\n    public static void registerFurnaceRecipe(ItemScriptContainer container, ItemStack item, String furnaceItemString, float exp, int time, String type, String internalId, String group, String category) {\r\n        boolean exact = true;\r\n        if (furnaceItemString.startsWith(\"material:\")) {\r\n            exact = false;\r\n            furnaceItemString = furnaceItemString.substring(\"material:\".length());\r\n        }\r\n        ItemStack[] items = textToItemArray(container, furnaceItemString, exact);\r\n        if (items == null) {\r\n            return;\r\n        }\r\n        NMSHandler.itemHelper.registerFurnaceRecipe(internalId, group, item, items, exp, time, type, exact, category);\r\n    }\r\n\r\n    public static void registerStonecuttingRecipe(ItemScriptContainer container, ItemStack item, String inputItemString, String internalId, String group) {\r\n        boolean exact = true;\r\n        if (inputItemString.startsWith(\"material:\")) {\r\n            exact = false;\r\n            inputItemString = inputItemString.substring(\"material:\".length());\r\n        }\r\n        ItemStack[] items = textToItemArray(container, inputItemString, exact);\r\n        if (items == null) {\r\n            return;\r\n        }\r\n        NMSHandler.itemHelper.registerStonecuttingRecipe(internalId, group, item, items, exact);\r\n    }\r\n\r\n    public static void registerSmithingRecipe(ItemScriptContainer container, ItemStack item, String templateString, String baseString, String additionString, String internalId, String retain) {\r\n        ItemStack[] template = null;\r\n        boolean templateExact = true;\r\n        if (templateString != null) {\r\n            if (templateString.startsWith(\"material:\")) {\r\n                templateExact = false;\r\n                templateString = templateString.substring(\"material:\".length());\r\n            }\r\n            template = textToItemArray(container, templateString, templateExact);\r\n            if (template == null) {\r\n                return;\r\n            }\r\n        }\r\n        boolean baseExact = true;\r\n        if (baseString.startsWith(\"material:\")) {\r\n            baseExact = false;\r\n            baseString = baseString.substring(\"material:\".length());\r\n        }\r\n        ItemStack[] baseItems = textToItemArray(container, baseString, baseExact);\r\n        if (baseItems == null) {\r\n            return;\r\n        }\r\n        boolean additionExact = true;\r\n        if (additionString.startsWith(\"material:\")) {\r\n            additionExact = false;\r\n            additionString = additionString.substring(\"material:\".length());\r\n        }\r\n        ItemStack[] additionItems = textToItemArray(container, additionString, additionExact);\r\n        if (additionItems == null) {\r\n            return;\r\n        }\r\n        smithingRetain.put(internalId, retain == null ? new String[0] : CoreUtilities.split(CoreUtilities.toLowerCase(retain), '|').toArray(new String[0]));\r\n        NMSHandler.itemHelper.registerSmithingRecipe(internalId, item, baseItems, baseExact, additionItems, additionExact, template, templateExact);\r\n    }\r\n\r\n    public static void rebuildRecipes() {\r\n        NMSHandler.itemHelper.blockRecipeFinalization();\r\n        for (ItemScriptContainer container : item_scripts.values()) {\r\n            try {\r\n                if (container.contains(\"recipes\", Map.class)) {\r\n                    TagContext context = new BukkitTagContext(container);\r\n                    YamlConfiguration section = container.getConfigurationSection(\"recipes\");\r\n                    int id = 0;\r\n                    for (StringHolder key : section.getKeys(false)) {\r\n                        id++;\r\n                        YamlConfiguration subSection = section.getConfigurationSection(key.str);\r\n                        String type = CoreUtilities.toLowerCase(subSection.getString(\"type\"));\r\n                        String internalId;\r\n                        Function<String, String> getString = (s) -> TagManager.tag(subSection.getString(s), context);\r\n                        if (subSection.contains(\"recipe_id\")) {\r\n                            internalId = getString.apply(\"recipe_id\");\r\n                            recipeIdToItemScript.put(\"denizen:\" + internalId, container);\r\n                        }\r\n                        else {\r\n                            internalId = getIdFor(container, type + \"_recipe\", id);\r\n                        }\r\n                        String group = subSection.contains(\"group\") ? getString.apply(\"group\") : \"\";\r\n                        ItemStack item = container.getCleanReference().getItemStack().clone();\r\n                        if (subSection.contains(\"output_quantity\")) {\r\n                            item.setAmount(Integer.parseInt(getString.apply(\"output_quantity\")));\r\n                        }\r\n                        switch (type) {\r\n                            case \"shaped\" -> registerShapedRecipe(container, item, subSection.getStringList(\"input\"), internalId, group); // tagged in register code\r\n                            case \"shapeless\" -> registerShapelessRecipe(container, item, getString.apply(\"input\"), internalId, group, subSection.getString(\"category\"));\r\n                            case \"stonecutting\" -> registerStonecuttingRecipe(container, item, getString.apply(\"input\"), internalId, group);\r\n                            case \"furnace\", \"blast\", \"smoker\", \"campfire\" -> {\r\n                                float exp = 0;\r\n                                int cookTime = 40;\r\n                                if (subSection.contains(\"experience\")) {\r\n                                    exp = Float.parseFloat(getString.apply(\"experience\"));\r\n                                }\r\n                                if (subSection.contains(\"cook_time\")) {\r\n                                    cookTime = DurationTag.valueOf(getString.apply(\"cook_time\"), context).getTicksAsInt();\r\n                                }\r\n                                registerFurnaceRecipe(container, item, getString.apply(\"input\"), exp, cookTime, type, internalId, group, subSection.getString(\"category\"));\r\n                            }\r\n                            case \"smithing\" -> {\r\n                                String retain = null;\r\n                                if (subSection.contains(\"retain\")) {\r\n                                    retain = getString.apply(\"retain\");\r\n                                }\r\n                                String template = null;\r\n                                if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                                    template = getString.apply(\"template\");\r\n                                }\r\n                                registerSmithingRecipe(container, item, template, getString.apply(\"base\"), getString.apply(\"upgrade\"), internalId, retain);\r\n                            }\r\n                            case \"brewing\" -> PaperAPITools.instance.registerBrewingRecipe(internalId, item, getString.apply(\"input\"), getString.apply(\"ingredient\"), container);\r\n                        }\r\n                    }\r\n                }\r\n                // Old script style\r\n                if (container.contains(\"RECIPE\", List.class)) {\r\n                    BukkitImplDeprecations.oldRecipeScript.warn(container);\r\n                    registerShapedRecipe(container, container.getCleanReference().getItemStack().clone(), container.getStringList(\"RECIPE\"), getIdFor(container, \"old_recipe\", 0), \"custom\");\r\n                }\r\n                if (container.contains(\"SHAPELESS_RECIPE\", String.class)) {\r\n                    BukkitImplDeprecations.oldRecipeScript.warn(container);\r\n                    registerShapelessRecipe(container, container.getCleanReference().getItemStack().clone(), container.getString(\"SHAPELESS_RECIPE\"), getIdFor(container, \"old_shapeless\", 0), \"custom\", null);\r\n                }\r\n                if (container.contains(\"FURNACE_RECIPE\", String.class)) {\r\n                    BukkitImplDeprecations.oldRecipeScript.warn(container);\r\n                    registerFurnaceRecipe(container, container.getCleanReference().getItemStack().clone(), container.getString(\"FURNACE_RECIPE\"), 0, 40, \"furnace\", getIdFor(container, \"old_furnace\", 0), \"custom\", null);\r\n                }\r\n            }\r\n            catch (Exception ex) {\r\n                Debug.echoError(\"Error while rebuilding item script recipes for '\" + container.getName() + \"'...\");\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        NMSHandler.itemHelper.restoreRecipeFinalization();\r\n    }\r\n\r\n    @EventHandler\r\n    public void scriptReload(ScriptReloadEvent event) {\r\n        rebuildRecipes();\r\n    }\r\n\r\n    public static boolean isItemscript(ItemStack item) {\r\n        return getItemScriptNameText(item) != null;\r\n    }\r\n\r\n    public static String getItemScriptNameText(ItemStack item) {\r\n        if (item == null) {\r\n            return null;\r\n        }\r\n        CompoundBinaryTag tag = NMSHandler.itemHelper.getCustomData(item);\r\n        if (tag == null) {\r\n            return null;\r\n        }\r\n        String scriptName = tag.getString(\"DenizenItemScript\", null);\r\n        if (scriptName != null) {\r\n            return scriptName;\r\n        }\r\n        // NOTE: Legacy hashed format\r\n        String nbt = tag.getString(\"Denizen Item Script\", null);\r\n        if (nbt != null) {\r\n            ItemScriptContainer container = item_scripts_by_hash_id.get(nbt);\r\n            if (container != null) {\r\n                return container.getName();\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static ItemScriptContainer getItemScriptContainer(ItemStack item) {\r\n        if (item == null) {\r\n            return null;\r\n        }\r\n        CompoundBinaryTag tag = NMSHandler.itemHelper.getCustomData(item);\r\n        if (tag == null) {\r\n            return null;\r\n        }\r\n        String scriptName = tag.getString(\"DenizenItemScript\", null);\r\n        if (scriptName != null) {\r\n            return item_scripts.get(scriptName);\r\n        }\r\n        // NOTE: Legacy hashed format\r\n        String nbt = tag.getString(\"Denizen Item Script\", null);\r\n        if (nbt != null) {\r\n            return item_scripts_by_hash_id.get(nbt);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static String ItemScriptHashID = ChatColor.RED.toString() + ChatColor.BLUE + ChatColor.BLACK;\r\n\r\n    public static String createItemScriptID(ItemScriptContainer container) {\r\n        String colors = createItemScriptID(container.getName());\r\n        container.setHashID(colors);\r\n        return colors;\r\n    }\r\n\r\n    public static String createItemScriptID(String name) {\r\n        String script = name.toUpperCase();\r\n        StringBuilder colors = new StringBuilder();\r\n        colors.append(ItemScriptHashID);\r\n        try {\r\n            String hash = CoreUtilities.hash_md5(script.getBytes(StandardCharsets.UTF_8));\r\n            for (int i = 0; i < 16; i++) {\r\n                colors.append(ChatColor.COLOR_CHAR).append(hash.charAt(i));\r\n            }\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n            colors.append(ChatColor.BLUE);\r\n        }\r\n        return colors.toString();\r\n    }\r\n\r\n    public static boolean isAllowedChoice(ItemScriptContainer script, RecipeChoice choice) {\r\n        if (choice instanceof RecipeChoice.ExactChoice) {\r\n            for (ItemStack choiceOpt : ((RecipeChoice.ExactChoice) choice).getChoices()) {\r\n                ItemScriptContainer choiceOptContainer = getItemScriptContainer(choiceOpt);\r\n                if (script == choiceOptContainer) {\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    private static ItemStack AIR = new ItemStack(Material.AIR);\r\n\r\n    public enum DenyCraftReason {\r\n        ALLOWED,\r\n        IMPOSSIBLE,\r\n        NOT_ALLOWED\r\n    }\r\n\r\n    public static DenyCraftReason shouldDenyCraft(ItemStack[] items, Recipe recipe) {\r\n        int width = items.length == 9 ? 3 : 2;\r\n        int shapeStartX = 0, shapeStartY = 0;\r\n        if (recipe instanceof ShapedRecipe) {\r\n            String[] shape = ((ShapedRecipe) recipe).getShape();\r\n            if (shape.length != width || shape[0].length() != width) {\r\n                if (shape.length > width || shape[0].length() > width) {\r\n                    return DenyCraftReason.ALLOWED; // Already impossible regardless\r\n                }\r\n                loopStart:\r\n                for (shapeStartX = 0; shapeStartX <= width - shape[0].length(); shapeStartX++) {\r\n                    for (shapeStartY = 0; shapeStartY <= width - shape.length; shapeStartY++) {\r\n                        boolean hasAnyInvalid = false;\r\n                        for (int x = 0; x < shape[0].length(); x++) {\r\n                            for (int y = 0; y < shape.length; y++) {\r\n                                ItemStack item = items[(y + shapeStartY) * width + (x + shapeStartX)];\r\n                                RecipeChoice choice = ((ShapedRecipe) recipe).getChoiceMap().get(shape[y].charAt(x));\r\n                                if (choice != null && !choice.test(item == null ? AIR : item)) {\r\n                                    hasAnyInvalid = true;\r\n                                    break;\r\n                                }\r\n                            }\r\n                        }\r\n                        if (!hasAnyInvalid) {\r\n                            break loopStart;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        for (int i = 0; i < items.length; i++) {\r\n            ItemStack item = items[i];\r\n            if (item == null || item.getType() == Material.AIR) {\r\n                continue;\r\n            }\r\n            ItemScriptContainer container = getItemScriptContainer(item);\r\n            if (container == null || container.allowInMaterialRecipes) {\r\n                continue;\r\n            }\r\n            boolean allowed = false;\r\n            if (recipe instanceof ShapelessRecipe) {\r\n                for (RecipeChoice choice : ((ShapelessRecipe) recipe).getChoiceList()) {\r\n                    if (isAllowedChoice(container, choice)) {\r\n                        allowed = true;\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            else if (recipe instanceof ShapedRecipe) {\r\n                int x = i % width - shapeStartX;\r\n                int y = i / width - shapeStartY;\r\n                if (x < 0 || y < 0) {\r\n                    return DenyCraftReason.IMPOSSIBLE;\r\n                }\r\n                String[] shape = ((ShapedRecipe) recipe).getShape();\r\n                if (y < shape.length && x < shape[y].length()) {\r\n                    char c = shape[y].charAt(x);\r\n                    RecipeChoice choice = ((ShapedRecipe) recipe).getChoiceMap().get(c);\r\n                    if (isAllowedChoice(container, choice)) {\r\n                        allowed = true;\r\n                    }\r\n                }\r\n            }\r\n            else if (recipe instanceof CookingRecipe) {\r\n                allowed = isAllowedChoice(container, ((CookingRecipe) recipe).getInputChoice());\r\n            }\r\n            else {\r\n                allowed = true; // Shouldn't be possible?\r\n            }\r\n            if (!allowed) {\r\n                return DenyCraftReason.NOT_ALLOWED;\r\n            }\r\n        }\r\n        return DenyCraftReason.ALLOWED;\r\n    }\r\n\r\n    public static HashMap<Material, Collection<Recipe>> recipeCache = new HashMap<>();\r\n\r\n    public static Collection<Recipe> getRecipesFor(Material item) {\r\n        return recipeCache.computeIfAbsent(item, (i) -> {\r\n            return Bukkit.getRecipesFor(new ItemStack(i));\r\n        });\r\n    }\r\n\r\n    public static boolean hasAlternateValidRecipe(Recipe recipe, ItemStack[] items) {\r\n        // Workaround for Spigot bug with the wrong recipe ID getting grabbed\r\n        if (recipe instanceof ShapedRecipe) {\r\n            ItemStack result = recipe.getResult();\r\n            if (isItemscript(result)) {\r\n                for (Recipe altRecipe : getRecipesFor(result.getType())) {\r\n                    if (altRecipe instanceof ShapedRecipe) {\r\n                        if (shouldDenyCraft(items, altRecipe) == DenyCraftReason.ALLOWED) {\r\n                            return true;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.LOW)\r\n    public void onCraftPrepared(PrepareItemCraftEvent event) {\r\n        Recipe recipe = event.getRecipe();\r\n        if (recipe == null) {\r\n            return;\r\n        }\r\n        ItemStack[] items = event.getInventory().getMatrix();\r\n        if (shouldDenyCraft(items, recipe) != DenyCraftReason.ALLOWED && !hasAlternateValidRecipe(recipe, items)) {\r\n            event.getInventory().setResult(null);\r\n        }\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)\r\n    public void onItemCrafted(CraftItemEvent event) {\r\n        Recipe recipe = event.getRecipe();\r\n        ItemStack[] items = event.getInventory().getMatrix();\r\n        if (shouldDenyCraft(items, recipe) != DenyCraftReason.ALLOWED && !hasAlternateValidRecipe(recipe, items)) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)\r\n    public void onItemCooked(BlockCookEvent event) {\r\n        ItemScriptContainer container = getItemScriptContainer(event.getSource());\r\n        if (container == null || container.allowInMaterialRecipes) {\r\n            return;\r\n        }\r\n        ItemStack[] stacks = new ItemStack[] { event.getSource() };\r\n        for (Recipe recipe : getRecipesFor(event.getResult().getType())) {\r\n            if (recipe instanceof CookingRecipe && shouldDenyCraft(stacks, recipe) == DenyCraftReason.ALLOWED) {\r\n                return;\r\n            }\r\n        }\r\n        event.setCancelled(true);\r\n    }\r\n\r\n    public static boolean isAllowedToCraftWith(ItemStack item) {\r\n        if (item == null || item.getType() == Material.AIR) {\r\n            return true;\r\n        }\r\n        ItemScriptContainer container = getItemScriptContainer(item);\r\n        if (container == null) {\r\n            return true;\r\n        }\r\n        return container.allowInMaterialRecipes;\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.LOW)\r\n    public void onBrewingStandBrews(BrewEvent event) {\r\n        ItemStack ingredient = event.getContents().getIngredient();\r\n        boolean ingredientBlockCraft = !isAllowedToCraftWith(ingredient);\r\n        List<ItemHelper.BrewingRecipe> potentialRecipes = null;\r\n        for (int i = 0; i < 3; i++) {\r\n            ItemStack currInput = event.getContents().getItem(i);\r\n            boolean inputBlockCraft = !isAllowedToCraftWith(currInput);\r\n            if (!inputBlockCraft && !ingredientBlockCraft) {\r\n                continue;\r\n            }\r\n            ItemHelper.BrewingRecipe customRecipe = null;\r\n            if (Denizen.supportsPaper && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n                if (potentialRecipes == null) {\r\n                    potentialRecipes = new ArrayList<>();\r\n                    for (ItemHelper.BrewingRecipe recipe : NMSHandler.itemHelper.getCustomBrewingRecipes().values()) {\r\n                        if (recipe.ingredient().test(ingredient)) {\r\n                            potentialRecipes.add(recipe);\r\n                        }\r\n                    }\r\n                }\r\n                for (ItemHelper.BrewingRecipe recipe : potentialRecipes) {\r\n                    if (currInput != null && recipe.input().test(currInput)) {\r\n                        customRecipe = recipe;\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            // If it's a vanilla mix and either the ingredient or input should be blocked (checked above)\r\n            if (customRecipe == null && NMSHandler.itemHelper.isValidMix(currInput, ingredient)) {\r\n                event.getResults().set(i, currInput);\r\n                continue;\r\n            }\r\n            // If it's a custom recipe and either the input or ingredient are material choices and should be blocked\r\n            if (customRecipe != null && (shouldBlockChoice(customRecipe.ingredient(), ingredientBlockCraft) || shouldBlockChoice(customRecipe.input(), inputBlockCraft))) {\r\n                event.getResults().set(i, currInput);\r\n            }\r\n        }\r\n    }\r\n\r\n    private boolean shouldBlockChoice(RecipeChoice choice, boolean blockCraft) {\r\n        return blockCraft && choice instanceof RecipeChoice.MaterialChoice;\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.LOW)\r\n    public void onBrewingStandFuel(BrewingStandFuelEvent event) {\r\n        if (!isAllowedToCraftWith(event.getFuel())) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.LOW)\r\n    public void onItemSmithing(PrepareSmithingEvent event) {\r\n        ItemStack inputItem = event.getInventory().getItem(NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) ? 1 : 0);\r\n        if (inputItem == null) {\r\n            return;\r\n        }\r\n        Recipe recipe = event.getInventory().getRecipe();\r\n        SmithingRecipe smithRecipe = (SmithingRecipe) recipe;\r\n        if (smithRecipe == null || !(smithRecipe.getKey().getNamespace().equals(\"denizen\"))) {\r\n            if (!isAllowedToCraftWith(inputItem)) {\r\n                event.setResult(new ItemStack(Material.AIR));\r\n            }\r\n            return;\r\n        }\r\n        ItemScriptContainer realResult = recipeIdToItemScript.get(smithRecipe.getKey().toString());\r\n        if (realResult == null) {\r\n            if (!isAllowedToCraftWith(inputItem)) {\r\n                event.setResult(new ItemStack(Material.AIR));\r\n            }\r\n            return;\r\n        }\r\n        String[] retain = smithingRetain.get(smithRecipe.getKey().getKey());\r\n        if (retain == null) {\r\n            Debug.echoError(\"Smithing recipe mis-registered for script item: \" + realResult.getName());\r\n            if (!isAllowedToCraftWith(inputItem)) {\r\n                event.setResult(new ItemStack(Material.AIR));\r\n            }\r\n            return;\r\n        }\r\n        PlayerTag player = null;\r\n        if (!event.getInventory().getViewers().isEmpty()) {\r\n            HumanEntity human = event.getInventory().getViewers().get(0);\r\n            if (!EntityTag.isNPC(human) && human instanceof Player) {\r\n                player = new PlayerTag((Player) human);\r\n            }\r\n        }\r\n        ItemTag got = realResult.getItemFrom(new BukkitTagContext(player, null, new ScriptTag(realResult)));\r\n        if (got == null) {\r\n            return;\r\n        }\r\n        if (retain.length > 0) {\r\n            ItemMeta originalMeta = inputItem.getItemMeta();\r\n            ItemMeta newMeta = got.getItemMeta();\r\n            for (String retainable : retain) {\r\n                switch (retainable) {\r\n                    case \"display\" -> {\r\n                        if (originalMeta.hasDisplayName()) {\r\n                            String originalName = NMSHandler.itemHelper.getDisplayName(new ItemTag(inputItem));\r\n                            ItemScriptContainer origScript = getItemScriptContainer(inputItem);\r\n                            if (origScript == null || !originalName.equals(NMSHandler.itemHelper.getDisplayName(origScript.getItemFrom()))) {\r\n                                NMSHandler.itemHelper.setDisplayName(got, originalName);\r\n                            }\r\n                        }\r\n                        newMeta = got.getItemMeta();\r\n                    }\r\n                    case \"enchantments\" -> {\r\n                        if (originalMeta.hasEnchants()) {\r\n                            for (Map.Entry<Enchantment, Integer> enchant : originalMeta.getEnchants().entrySet()) {\r\n                                newMeta.addEnchant(enchant.getKey(), enchant.getValue(), true);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            got.setItemMeta(newMeta);\r\n        }\r\n        event.setResult(got.getItemStack());\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/MapScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.maps.*;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.NaturalOrderComparator;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class MapScriptContainer extends ScriptContainer {\r\n\r\n    public MapScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\r\n        super(configurationSection, scriptContainerName);\r\n        canRunScripts = false;\r\n    }\r\n\r\n    // <--[language]\r\n    // @name Map Script Containers\r\n    // @group Script Container System\r\n    // @description\r\n    // Map scripts allow you define custom in-game map items, for usage with the map command.\r\n    //\r\n    // The following is the format for the container.\r\n    //\r\n    // <code>\r\n    // # The name of the map script is used by the map command.\r\n    // Map_Script_Name:\r\n    //\r\n    //     type: map\r\n    //\r\n    //     # Whether to display the original map below the custom values. Defaults to true.\r\n    //     # | Some map scripts should have this key!\r\n    //     original: true/false\r\n    //\r\n    //     # Whether to constantly update things. Defaults to true.\r\n    //     # | Some map scripts should have this key!\r\n    //     auto update: true\r\n    //\r\n    //     # Whether this map script renders uniquely per-player. Defaults to true.\r\n    //     # | Some map scripts should have this key!\r\n    //     contextual: true\r\n    //\r\n    //     # Lists all contained objects.\r\n    //     # | Most map scripts should have this key!\r\n    //     objects:\r\n    //\r\n    //         # The first object...\r\n    //         1:\r\n    //             # Specify the object type\r\n    //             # Type can be IMAGE, TEXT, CURSOR, or DOT.\r\n    //             type: image\r\n    //             # Specify an HTTP url or file path within Denizen/images/ for the image. Supports animated .gif!\r\n    //             image: my_image.png\r\n    //             # Optionally add width/height numbers.\r\n    //             width: 128\r\n    //             height: 128\r\n    //             # Specify a tag to show or hide custom content! Valid for all objects.\r\n    //             # Note that all inputs other than 'type' for all objects support tags that will be dynamically reparsed per-player each time the map updates.\r\n    //             visible: <player.name.contains_text[bob].not>\r\n    //\r\n    //         2:\r\n    //             type: text\r\n    //             # Specify any text to display. Color codes not permitted (unless you know how to format CraftMapCanvas byte-ID color codes).\r\n    //             text: Hello <player.name>\r\n    //             # Specify the color of the text as any valid ColorTag.\r\n    //             color: red\r\n    //             # | Optionally, specify the following additional options:\r\n    //             # Specify a font to use, which allows using special characters/other languages the default font may not support.\r\n    //             font: arial\r\n    //             # Specify a text size (only available with a custom font).\r\n    //             size: 18\r\n    //             # Specify a style, as a list that contains either \"bold\", \"italic\", or both (only available with a custom font).\r\n    //             style: bold|italic\r\n    //         3:\r\n    //             type: cursor\r\n    //             # Specify a cursor type\r\n    //             cursor: red_marker\r\n    //             # Optionally, specify a cursor direction. '180' seems to display as up-right usually.\r\n    //             direction: 180\r\n    //             # Supported on all objects: x/y positions, and whether to use worldly or map coordinates.\r\n    //             x: 5\r\n    //             # If 'world_coordinates' is set to 'true', the 'y' value corresponds to the 'z' value of a location.\r\n    //             y: 5\r\n    //             # If true: uses world coordinates. If false: uses map local coordinates. (Defaults to false).\r\n    //             world_coordinates: false\r\n    //             # If true: when the object goes past the edge, will stay in view at the corner. If false: disappears past the edge (defaults to false).\r\n    //             show_past_edge: false\r\n    //\r\n    //         4:\r\n    //             type: dot\r\n    //             # Specify the radius of the dot.\r\n    //             radius: 1\r\n    //             # Specify the color of the dot as any valid ColorTag.\r\n    //             color: red\r\n    //\r\n    // </code>\r\n    //\r\n    // A list of cursor types is available through <@link tag server.map_cursor_types>.\r\n    //\r\n    // -->\r\n\r\n    public void applyTo(MapView mapView) {\r\n        boolean contextual = getString(\"contextual\", \"true\").equalsIgnoreCase(\"true\");\r\n        DenizenMapRenderer renderer = new DenizenMapRenderer(mapView.getRenderers(), getString(\"auto update\", \"true\").equalsIgnoreCase(\"true\"), contextual);\r\n        if (contains(\"original\", String.class)) {\r\n            renderer.displayOriginal = getString(\"original\").equalsIgnoreCase(\"true\");\r\n        }\r\n        if (contains(\"objects\", Map.class)) {\r\n            YamlConfiguration objectsSection = getConfigurationSection(\"objects\");\r\n            List<StringHolder> objectKeys1 = new ArrayList<>(objectsSection.getKeys(false));\r\n            List<String> objectKeys = new ArrayList<>(objectKeys1.size());\r\n            for (StringHolder sh : objectKeys1) {\r\n                objectKeys.add(sh.str);\r\n            }\r\n            objectKeys.sort(new NaturalOrderComparator());\r\n            for (String objectKey : objectKeys) {\r\n                YamlConfiguration objectSection = objectsSection.getConfigurationSection(objectKey);\r\n                if (!objectSection.contains(\"type\")) {\r\n                    Debug.echoError(\"Map script '\" + getName() + \"' has an object without a specified type!\");\r\n                    return;\r\n                }\r\n                String type = CoreUtilities.toLowerCase(objectSection.getString(\"type\"));\r\n                String x = objectSection.getString(\"x\", \"0\");\r\n                String y = objectSection.getString(\"y\", \"0\");\r\n                String visible = objectSection.getString(\"visible\", \"true\");\r\n                MapObject added = null;\r\n                switch (type) {\r\n                    case \"image\":\r\n                        if (!objectSection.contains(\"image\")) {\r\n                            Debug.echoError(\"Map script '\" + getName() + \"'s image '\" + objectKey\r\n                                    + \"' has no specified image location!\");\r\n                            return;\r\n                        }\r\n                        String image = objectSection.getString(\"image\");\r\n                        int width = Integer.parseInt(objectSection.getString(\"width\", \"0\"));\r\n                        int height = Integer.parseInt(objectSection.getString(\"height\", \"0\"));\r\n                        added = new MapImage(renderer, x, y, visible, shouldDebug(), image, width, height);\r\n                        break;\r\n                    case \"text\":\r\n                        if (!objectSection.contains(\"text\")) {\r\n                            Debug.echoError(\"Map script '\" + getName() + \"'s text object '\" + objectKey\r\n                                    + \"' has no specified text!\");\r\n                            return;\r\n                        }\r\n                        added = new MapText(x, y, visible, shouldDebug(), objectSection.getString(\"text\"), objectSection.getString(\"color\", \"black\"),\r\n                                objectSection.getString(\"font\"), objectSection.getString(\"size\"), objectSection.getString(\"style\"));\r\n                        break;\r\n                    case \"cursor\":\r\n                        if (!objectSection.contains(\"cursor\")) {\r\n                            Debug.echoError(\"Map script '\" + getName() + \"'s cursor '\" + objectKey\r\n                                    + \"' has no specified cursor type!\");\r\n                            return;\r\n                        }\r\n                        String cursor = objectSection.getString(\"cursor\");\r\n                        if (cursor == null) {\r\n                            Debug.echoError(\"Map script '\" + getName() + \"'s cursor '\" + objectKey\r\n                                    + \"' is missing a cursor type!\");\r\n                            return;\r\n                        }\r\n                        added = new MapCursor(x, y, visible, shouldDebug(), objectSection.getString(\"direction\", \"0\"), cursor);\r\n                        break;\r\n                    case \"dot\":\r\n                        added = new MapDot(x, y, visible, shouldDebug(), objectSection.getString(\"radius\", \"1\"), objectSection.getString(\"color\", \"black\"));\r\n                        break;\r\n                    default:\r\n                        Debug.echoError(\"Weird map data!\");\r\n                        break;\r\n                }\r\n                if (added != null) {\r\n                    renderer.addObject(added);\r\n                    if (objectSection.contains(\"world_coordinates\") && objectSection.getString(\"world_coordinates\", \"false\").equalsIgnoreCase(\"true\")) {\r\n                        added.worldCoordinates = true;\r\n                    }\r\n                    added.showPastEdge = CoreUtilities.equalsIgnoreCase(objectSection.getString(\"show_past_edge\", \"false\"), \"true\");\r\n                }\r\n            }\r\n        }\r\n        DenizenMapManager.setMap(mapView, renderer);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/containers/core/VersionScriptContainer.java",
    "content": "package com.denizenscript.denizen.scripts.containers.core;\n\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\n\npublic class VersionScriptContainer extends ScriptContainer {\n\n    public VersionScriptContainer(YamlConfiguration configurationSection, String scriptContainerName) {\n        super(configurationSection, scriptContainerName);\n        canRunScripts = false;\n        BukkitImplDeprecations.versionScripts.warn(this);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/triggers/AbstractTrigger.java",
    "content": "package com.denizenscript.denizen.scripts.triggers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptContainer;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.queues.ContextSource;\r\nimport com.denizenscript.denizencore.scripts.queues.ScriptQueue;\r\nimport com.denizenscript.denizencore.scripts.queues.core.InstantQueue;\r\nimport com.denizenscript.denizencore.scripts.queues.core.TimedQueue;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug.DebugElement;\r\nimport com.denizenscript.denizencore.utilities.debugging.SlowWarning;\r\n\r\nimport java.util.*;\r\n\r\npublic abstract class AbstractTrigger {\r\n\r\n    /**\r\n     * Number of times this trigger has been seen as enabled in onSpawn of NPCs since start.\r\n     * If value is '0', trigger isn't in use.\r\n     */\r\n    public int timesUsed = 0;\r\n\r\n    protected String name;\r\n\r\n    public AbstractTrigger as(String triggerName) {\r\n        this.name = triggerName.toUpperCase();\r\n        // Register command with Registry\r\n        Denizen.getInstance().triggerRegistry.register(triggerName, this);\r\n        onEnable();\r\n        return this;\r\n    }\r\n\r\n    public String getName() {\r\n        // Return the name of the trigger specified upon registration.\r\n        return name;\r\n    }\r\n\r\n    /**\r\n     * Part of the Plugin disable sequence.\r\n     * <p/>\r\n     * Can be '@Override'n by a Trigger which requires a method when bukkit sends a\r\n     * onDisable() to Denizen. (ie. Server shuts down or restarts)\r\n     */\r\n    public void onDisable() {\r\n        // Nothing to do here on this level of abstraction.\r\n    }\r\n\r\n    /**\r\n     * Part of the Plugin enable sequence.\r\n     * <p/>\r\n     * Can be '@Override'n by a Trigger which requires a method when bukkit sends a\r\n     * onEnable() to Denizen. (ie. Server shuts down or restarts)\r\n     */\r\n    public void onEnable() {\r\n        // Nothing to do here on this level of abstraction.\r\n    }\r\n\r\n    /**\r\n     * Part of the Plugin enable sequence.\r\n     * <p/>\r\n     * Can be '@Override'n by a Trigger which requires a method when being enabled via\r\n     * the Trigger Registry, usually upon startup.\r\n     */\r\n    public AbstractTrigger activate() {\r\n        // Nothing to do here on this level of abstraction.\r\n        return this;\r\n    }\r\n\r\n    public boolean parse(NPCTag npc, PlayerTag player, InteractScriptContainer script, String id) {\r\n        return parse(npc, player, script, id, null);\r\n    }\r\n\r\n    public static SlowWarning missetWarning = new SlowWarning(\"npcTriggerMisset\", \"Trigger '{NAME}' on NPC '{NPC}' activated and used but not properly set via the 'trigger' command in 'on assignment'.\");\r\n\r\n    public boolean parse(NPCTag npc, PlayerTag player, InteractScriptContainer script, String id, Map<String, ObjectTag> context) {\r\n        if (npc == null || player == null || script == null) {\r\n            return false;\r\n        }\r\n        List<ScriptEntry> entries = script.getEntriesFor(this.getClass(), player, npc, id, true);\r\n        if (entries.isEmpty()) {\r\n            return false;\r\n        }\r\n        Debug.echoDebug(script, DebugElement.Header, \"Parsing \" + name + \" trigger: n@\" + npc.getName() + \"/p@\" + player.getName());\r\n        // Create Queue\r\n        long speedTicks;\r\n        if (script.contains(\"SPEED\", String.class)) {\r\n            speedTicks = DurationTag.valueOf(script.getString(\"SPEED\", \"0\"), new BukkitTagContext(script)).getTicks();\r\n        }\r\n        else {\r\n            speedTicks = DurationTag.valueOf(Settings.interactQueueSpeed(), new BukkitTagContext(script)).getTicks();\r\n        }\r\n        ScriptQueue queue;\r\n        if (speedTicks > 0) {\r\n            queue = new TimedQueue(script.getName()).setSpeed(speedTicks);\r\n        }\r\n        else {\r\n            queue = new InstantQueue(script.getName());\r\n        }\r\n        // Add all entries to set it up\r\n        queue.addEntries(entries);\r\n        // Add context\r\n        if (context != null) {\r\n            ContextSource.SimpleMap src = new ContextSource.SimpleMap();\r\n            src.contexts = context;\r\n            queue.setContextSource(src);\r\n        }\r\n        if (!npc.getTriggerTrait().properly_set.get(name)) {\r\n            if (missetWarning.testShouldWarn()) {\r\n                Debug.echoError(missetWarning.message.replace(\"{NAME}\", name).replace(\"{NPC}\", npc.getId() + \"/\" + npc.getName()));\r\n            }\r\n        }\r\n        // Start it\r\n        queue.start();\r\n        return true;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/triggers/TriggerRegistry.java",
    "content": "package com.denizenscript.denizen.scripts.triggers;\r\n\r\nimport com.denizenscript.denizen.scripts.triggers.core.ChatTrigger;\r\nimport com.denizenscript.denizen.scripts.triggers.core.DamageTrigger;\r\nimport com.denizenscript.denizen.scripts.triggers.core.ProximityTrigger;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.triggers.core.ClickTrigger;\r\nimport net.citizensnpcs.api.npc.NPC;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class TriggerRegistry {\r\n\r\n    ////////\r\n    // Registry\r\n    //////\r\n\r\n    private Map<String, AbstractTrigger> instances = new HashMap<>();\r\n    private Map<Class<? extends AbstractTrigger>, String> classes = new HashMap<>();\r\n\r\n    public void disableCoreMembers() {\r\n        for (AbstractTrigger member : instances.values()) {\r\n            try {\r\n                member.onDisable();\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(\"Unable to disable '\" + member.getClass().getName() + \"'!\");\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n    }\r\n\r\n    public <T extends AbstractTrigger> T get(Class<T> clazz) {\r\n        if (classes.containsKey(clazz)) {\r\n            return (T) instances.get(classes.get(clazz));\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public AbstractTrigger get(String triggerName) {\r\n        return instances.getOrDefault(triggerName.toUpperCase(), null);\r\n    }\r\n\r\n    public Map<String, AbstractTrigger> list() {\r\n        return instances;\r\n    }\r\n\r\n    public void register(String triggerName, AbstractTrigger instance) {\r\n        this.instances.put(triggerName.toUpperCase(), instance);\r\n        this.classes.put(((AbstractTrigger) instance).getClass(), triggerName.toUpperCase());\r\n    }\r\n\r\n    public void registerCoreMembers() {\r\n        new ClickTrigger().activate().as(\"Click\");\r\n        new ChatTrigger().activate().as(\"Chat\");\r\n        new DamageTrigger().activate().as(\"Damage\");\r\n        new ProximityTrigger().activate().as(\"Proximity\");\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.echoApproval(\"Loaded core triggers: \" + instances.keySet());\r\n        }\r\n        else {\r\n            Debug.log(\"Loaded <A>\" + instances.size() + \"<W> core triggers\");\r\n        }\r\n    }\r\n\r\n    /////////\r\n    // Trigger Cooldowns\r\n    ///////\r\n\r\n    Map<String, Map<String, Long>> playerCooldown = new HashMap<>();\r\n\r\n    public boolean checkCooldown(NPC npc, PlayerTag player, AbstractTrigger triggerClass) {\r\n        if (!playerCooldown.containsKey(player.getName() + \"/\" + npc.getId())) {\r\n            return true;\r\n        }\r\n        else if (!playerCooldown.get(player.getName() + \"/\" + npc.getId()).containsKey(triggerClass.name)) {\r\n            return true;\r\n        }\r\n        else if (CoreUtilities.monotonicMillis() > playerCooldown.get(player.getName() + \"/\" + npc.getId()).get(triggerClass.name)) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void setCooldown(NPC npc, PlayerTag player, AbstractTrigger triggerClass, double seconds) {\r\n        Map<String, Long> triggerMap = new HashMap<>();\r\n        boolean noCooldown = seconds <= 0;\r\n        if (playerCooldown.containsKey(player.getName() + \"/\" + npc.getId())) {\r\n            triggerMap = playerCooldown.get(player.getName() + \"/\" + npc.getId());\r\n        }\r\n        if (noCooldown && playerCooldown.containsKey(player.getName() + \"/\" + npc.getId())) {\r\n            triggerMap.remove(player.getName() + \"/\" + npc.getId());\r\n        }\r\n        else {\r\n            triggerMap.put(triggerClass.name, CoreUtilities.monotonicMillis() + (long) (seconds * 1000));\r\n        }\r\n        playerCooldown.put(player.getName() + \"/\" + npc.getId(), triggerMap);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/triggers/core/ChatTrigger.java",
    "content": "package com.denizenscript.denizen.scripts.triggers.core;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptContainer;\r\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.npc.traits.TriggerTrait;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.triggers.AbstractTrigger;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.AsyncPlayerChatEvent;\r\nimport org.bukkit.event.player.PlayerChatEvent;\r\n\r\nimport java.util.*;\r\nimport java.util.concurrent.FutureTask;\r\nimport java.util.regex.Matcher;\r\nimport java.util.regex.Pattern;\r\n\r\n@SuppressWarnings(\"deprecation\")\r\npublic class ChatTrigger extends AbstractTrigger implements Listener {\r\n\r\n    final static Pattern triggerPattern = Pattern.compile(\"/([^/]*)/\");\r\n\r\n    // <--[language]\r\n    // @name Chat Triggers\r\n    // @group NPC Interact Scripts\r\n    // @description\r\n    // Chat Triggers are triggered when a player chats to the NPC (usually while standing close to the NPC and facing the NPC).\r\n    //\r\n    // They can also be triggered by the command \"/denizenclickable chat hello\" (where 'hello' is replaced with the chat message). This is used for clickable triggers.\r\n    // This option enforces all the same limitations as chatting directly, but unlike real chat, won't display the message in global chat when there's no match.\r\n    // This requires players have the permission \"denizen.clickable\".\r\n    //\r\n    // Interact scripts are allowed to define a list of possible messages a player may type and the scripts triggered in response.\r\n    //\r\n    // Within any given step, the format is then as follows:\r\n    // <code>\r\n    // # Some identifier for the trigger, this only serves to make the sub-triggers unique, and sort them (alphabetically).\r\n    // 1:\r\n    //     # The trigger message written by a player. The text between // must be typed by a player, the other text is filled automatically.\r\n    //     trigger: /keyword/ othertext\r\n    //     script:\r\n    //     # Your code here\r\n    //     - wait 1\r\n    //     # use \"<context.message>\" for the exact text written by the player.\r\n    //     - chat \"<context.message> eh?\"\r\n    // # You can list as many as you want\r\n    // 2:\r\n    //     # You can have multi-option triggers, separated by pipes (the \"|\" symbol). This example matches if player types 'hi', 'hello', OR 'hey'.\r\n    //     trigger: /hi|hello|hey/\r\n    //     script:\r\n    //     - wait 1\r\n    //     # use \"<context.keyword>\" for the specific word that was said.\r\n    //     # this example will respond to players that said 'hi' with \"hi there buddy!\", 'hello' with \"hello there buddy!\", etc.\r\n    //     - chat \"<context.keyword> there buddy!\"\r\n    // 3:\r\n    //     # You can have regex triggers. This example matches when the player types any numbers.\r\n    //     trigger: /regex:\\d+/\r\n    //     script:\r\n    //     - wait 1\r\n    //     # use \"<context.keyword>\" for the text matched by the regex matcher.\r\n    //     - chat \"<context.keyword> eh?\"\r\n    // 4:\r\n    //     # Use '*' as the trigger to match anything at all.\r\n    //     trigger: /*/\r\n    //     # Add this line to hide the \"[Player -> NPC]: hi\" initial trigger message.\r\n    //     hide trigger message: true\r\n    //     # Add this line to show the player chat message in the normal chat.\r\n    //     show as normal chat: true\r\n    //     script:\r\n    //     # If you hide the trigger message but not show as normal chat, you might want to fill that spot with something else.\r\n    //     - narrate \"[Player -> NPC]: I don't know how to type the right thing\"\r\n    //     - wait 1\r\n    //     - chat \"Well type 'hello' or any number!\"\r\n    //     - narrate \"Click <element[here].on_hover[click me!].on_click[/denizenclickable chat hello]> to auto-activate the 'hello' trigger!\"\r\n    // </code>\r\n    //\r\n    // -->\r\n\r\n    public static ChatTrigger instance;\r\n\r\n    @Override\r\n    public AbstractTrigger activate() {\r\n        instance = this;\r\n        return super.activate();\r\n    }\r\n\r\n    @Override\r\n    public void onEnable() {\r\n        Bukkit.getServer().getPluginManager().registerEvents(this, Denizen.getInstance());\r\n    }\r\n\r\n    // Technically defined in TriggerTrait, but placing here instead.\r\n    // <--[action]\r\n    // @Actions\r\n    // chat\r\n    //\r\n    // @Triggers when a player chats to the NPC.\r\n    //\r\n    // @Context\r\n    // <context.message> returns the triggering message\r\n    // <context.keyword> returns the keyword matched by a RegEx trigger\r\n    //\r\n    // @Determine\r\n    // \"CANCELLED\" to stop the player from chatting.\r\n    // ElementTag to change the message.\r\n    //\r\n    // -->\r\n    public ChatContext process(Player player, String message) {\r\n        NPCTag npc = Utilities.getClosestNPC_ChatTrigger(player.getLocation(), 25);\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.log(\"Processing chat trigger: valid npc? \" + (npc != null));\r\n        }\r\n        if (npc == null) {\r\n            return new ChatContext(false);\r\n        }\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.log(\"Has trait?  \" + npc.getCitizen().hasTrait(TriggerTrait.class));\r\n        }\r\n        if (!npc.getCitizen().hasTrait(TriggerTrait.class)) {\r\n            return new ChatContext(false);\r\n        }\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.log(\"enabled? \" + npc.getCitizen().getOrAddTrait(TriggerTrait.class).isEnabled(name));\r\n        }\r\n        if (!npc.getCitizen().getOrAddTrait(TriggerTrait.class).isEnabled(name)) {\r\n            return new ChatContext(false);\r\n        }\r\n        if (npc.getTriggerTrait().getRadius(name) < npc.getLocation().distance(player.getLocation())) {\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.log(\"Not in range\");\r\n            }\r\n            return new ChatContext(false);\r\n        }\r\n        if (Settings.chatMustSeeNPC()) {\r\n            if (!player.hasLineOfSight(npc.getEntity())) {\r\n                if (CoreConfiguration.debugVerbose) {\r\n                    Debug.log(\"no LOS\");\r\n                }\r\n                return new ChatContext(false);\r\n            }\r\n        }\r\n        if (Settings.chatMustLookAtNPC()) {\r\n            if (!NMSHandler.entityHelper.isFacingEntity(player, npc.getEntity(), 45)) {\r\n                if (CoreConfiguration.debugVerbose) {\r\n                    Debug.log(\"Not facing\");\r\n                }\r\n                return new ChatContext(false);\r\n            }\r\n        }\r\n        boolean ret = false;\r\n        Map<String, ObjectTag> context = new HashMap<>();\r\n        context.put(\"message\", new ElementTag(message));\r\n        TriggerTrait.TriggerContext trigger = npc.getTriggerTrait().trigger(ChatTrigger.this, new PlayerTag(player), context);\r\n        if (trigger.hasDetermination()) {\r\n            if (trigger.getDeterminations().containsCaseInsensitive(\"cancelled\")) {\r\n                if (CoreConfiguration.debugVerbose) {\r\n                    Debug.log(\"Cancelled\");\r\n                }\r\n                return new ChatContext(true);\r\n            }\r\n        }\r\n        if (!trigger.wasTriggered()) {\r\n            if (Settings.chatGloballyIfUninteractable()) {\r\n                if (CoreConfiguration.debugVerbose) {\r\n                    Debug.log(ChatColor.YELLOW + \"Resuming. \" + ChatColor.WHITE + \"The NPC is currently cooling down or engaged.\");\r\n                }\r\n                return new ChatContext(false);\r\n            }\r\n            else {\r\n                ret = true;\r\n            }\r\n        }\r\n        if (trigger.hasDetermination()) {\r\n            message = trigger.getDeterminations().get(0);\r\n        }\r\n        List<InteractScriptContainer> scripts = npc.getInteractScripts(new PlayerTag(player), ChatTrigger.class);\r\n        if (scripts == null) {\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.log(\"null scripts\");\r\n            }\r\n            return new ChatContext(message, false);\r\n        }\r\n        ChatContext returnable = new ChatContext(ret);\r\n        for (InteractScriptContainer script : scripts) {\r\n            processSingle(message, player, npc, context, script, returnable);\r\n        }\r\n        return returnable;\r\n    }\r\n\r\n    public void processSingle(String message, Player player, NPCTag npc, Map<String, ObjectTag> context, InteractScriptContainer script, ChatContext returnable) {\r\n        context = new HashMap<>(context);\r\n        PlayerTag denizenPlayer = PlayerTag.mirrorBukkitPlayer(player);\r\n        if (script.shouldDebug()) {\r\n            Debug.report(script, name, ArgumentHelper.debugObj(\"Player\", player.getName())\r\n                    + ArgumentHelper.debugObj(\"NPC\", npc.toString())\r\n                    + ArgumentHelper.debugObj(\"Radius(Max)\", npc.getLocation().distance(player.getLocation())\r\n                    + \"(\" + npc.getTriggerTrait().getRadius(name) + \")\")\r\n                    + ArgumentHelper.debugObj(\"Trigger text\", message)\r\n                    + ArgumentHelper.debugObj(\"LOS\", String.valueOf(player.hasLineOfSight(npc.getEntity())))\r\n                    + ArgumentHelper.debugObj(\"Facing\", String.valueOf(NMSHandler.entityHelper.isFacingEntity(player, npc.getEntity(), 45))));\r\n        }\r\n        String step = InteractScriptHelper.getCurrentStep(denizenPlayer, script.getName());\r\n        if (!script.containsTriggerInStep(step, ChatTrigger.class)) {\r\n            if (!Settings.chatGloballyIfNoChatTriggers()) {\r\n                Debug.echoDebug(script, player.getName() + \" says to \" + npc.getNicknameTrait().getNickname() + \", \" + message);\r\n            }\r\n            else if (CoreConfiguration.debugVerbose) {\r\n                Debug.log(\"No trigger in step, chatting globally\");\r\n            }\r\n            return;\r\n        }\r\n        String id = null;\r\n        String replacementText = null;\r\n        String messageLow = CoreUtilities.toLowerCase(message);\r\n        Map<String, String> idMap = script.getIdMapFor(ChatTrigger.class, denizenPlayer);\r\n        if (!idMap.isEmpty()) {\r\n            mainLoop:\r\n            for (Map.Entry<String, String> entry : idMap.entrySet()) {\r\n                String triggerText = TagManager.tag(entry.getValue(), new BukkitTagContext(denizenPlayer, npc, null, false, null));\r\n                Matcher matcher = triggerPattern.matcher(triggerText);\r\n                while (matcher.find()) {\r\n                    String keyword = TagManager.tag(matcher.group().replace(\"/\", \"\"), new BukkitTagContext(denizenPlayer, npc, null, false, null));\r\n                    String[] split = keyword.split(\"\\\\\\\\\\\\+REPLACE:\", 2);\r\n                    String replace = null;\r\n                    if (split.length == 2) {\r\n                        keyword = split[0];\r\n                        replace = split[1];\r\n                    }\r\n                    String keywordLow = CoreUtilities.toLowerCase(keyword);\r\n                    if (keywordLow.startsWith(\"regex:\")) {\r\n                        Pattern pattern = Pattern.compile(keyword.substring(6));\r\n                        Matcher m = pattern.matcher(message);\r\n                        if (m.find()) {\r\n                            id = entry.getKey();\r\n                            replacementText = triggerText.replace(matcher.group(), m.group());\r\n                            context.put(\"keyword\", new ElementTag(m.group()));\r\n                            if (replace != null) {\r\n                                replacementText = replace;\r\n                            }\r\n                            break mainLoop;\r\n                        }\r\n                    }\r\n                    else if (keyword.contains(\"|\")) {\r\n                        for (String subkeyword : CoreUtilities.split(keyword, '|')) {\r\n                            if (messageLow.contains(CoreUtilities.toLowerCase(subkeyword))) {\r\n                                id = entry.getKey();\r\n                                replacementText = triggerText.replace(matcher.group(), subkeyword);\r\n                                context.put(\"keyword\", new ElementTag(subkeyword));\r\n                                if (replace != null) {\r\n                                    replacementText = replace;\r\n                                }\r\n                                break mainLoop;\r\n                            }\r\n                        }\r\n                    }\r\n                    else if (keyword.equals(\"*\")) {\r\n                        id = entry.getKey();\r\n                        replacementText = triggerText.replace(\"/*/\", message);\r\n                        if (replace != null) {\r\n                            replacementText = replace;\r\n                        }\r\n                        break mainLoop;\r\n                    }\r\n                    else if (keywordLow.startsWith(\"strict:\") && messageLow.equals(keywordLow.substring(\"strict:\".length()))) {\r\n                        id = entry.getKey();\r\n                        replacementText = triggerText.replace(matcher.group(), keyword.substring(\"strict:\".length()));\r\n                        if (replace != null) {\r\n                            replacementText = replace;\r\n                        }\r\n                        break mainLoop;\r\n                    }\r\n                    else if (messageLow.contains(keywordLow)) {\r\n                        id = entry.getKey();\r\n                        replacementText = triggerText.replace(matcher.group(), keyword);\r\n                        if (replace != null) {\r\n                            replacementText = replace;\r\n                        }\r\n                        break mainLoop;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        // If there was a match, the id of the match should have been returned.\r\n        String showNormalChat = script.getString(\"STEPS.\" + step + \".CHAT TRIGGER.\" + id + \".SHOW AS NORMAL CHAT\", \"false\");\r\n        if (id != null) {\r\n            String hideTriggerMessage = script.getString(\"STEPS.\" + step + \".CHAT TRIGGER.\" + id + \".HIDE TRIGGER MESSAGE\", \"false\");\r\n            if (!hideTriggerMessage.equalsIgnoreCase(\"true\")) {\r\n                Utilities.talkToNPC(replacementText, denizenPlayer, npc, Settings.chatToNpcOverhearingRange(), new ScriptTag(script));\r\n            }\r\n            parse(npc, denizenPlayer, script, id, context);\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.log(\"chat to NPC\");\r\n            }\r\n            if (!showNormalChat.equalsIgnoreCase(\"true\")) {\r\n                returnable.triggered = true;\r\n            }\r\n            return;\r\n        }\r\n        else {\r\n            if (!Settings.chatGloballyIfFailedChatTriggers()) {\r\n                Utilities.talkToNPC(message, denizenPlayer, npc, Settings.chatToNpcOverhearingRange(), new ScriptTag(script));\r\n                if (CoreConfiguration.debugVerbose) {\r\n                    Debug.log(\"Chat globally\");\r\n                }\r\n                if (!showNormalChat.equalsIgnoreCase(\"true\")) {\r\n                    returnable.triggered = true;\r\n                }\r\n                return;\r\n            }\r\n            // No matching chat triggers, and the config.yml says we\r\n            // should just ignore the interaction...\r\n        }\r\n        if (CoreConfiguration.debugVerbose) {\r\n            Debug.log(\"Finished calculating\");\r\n        }\r\n        returnable.changed_text = message;\r\n    }\r\n\r\n    @EventHandler\r\n    public void asyncChatTrigger(final AsyncPlayerChatEvent event) {\r\n        if (event.isCancelled()) {\r\n            return;\r\n        }\r\n        if (!Settings.chatAsynchronous()) {\r\n            return;\r\n        }\r\n        if (!event.isAsynchronous()) {\r\n            syncChatTrigger(new PlayerChatEvent(event.getPlayer(), event.getMessage(), event.getFormat(), event.getRecipients()));\r\n            return;\r\n        }\r\n        FutureTask<ChatContext> futureTask = new FutureTask<>(() -> process(event.getPlayer(), event.getMessage()));\r\n        Bukkit.getScheduler().runTask(Denizen.getInstance(), futureTask);\r\n        try {\r\n            ChatContext context = futureTask.get();\r\n            if (context.wasTriggered()) {\r\n                event.setCancelled(true);\r\n            }\r\n            if (context.hasChanges()) {\r\n                event.setMessage(context.getChanges());\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void syncChatTrigger(PlayerChatEvent event) {\r\n        if (event.isCancelled()) {\r\n            return;\r\n        }\r\n        if (Settings.chatAsynchronous()) {\r\n            return;\r\n        }\r\n        chatTriggerInternal(event);\r\n    }\r\n\r\n    public void chatTriggerInternal(PlayerChatEvent event) {\r\n        ChatContext chat = process(event.getPlayer(), event.getMessage());\r\n        if (chat.wasTriggered()) {\r\n            event.setCancelled(true);\r\n        }\r\n        if (chat.hasChanges()) {\r\n            event.setMessage(chat.getChanges());\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Contains whether the chat trigger successfully 'triggered' and any context that was\r\n     * available while triggering or attempting to trigger.\r\n     */\r\n    public static class ChatContext {\r\n\r\n        public ChatContext(boolean triggered) {\r\n            this.triggered = triggered;\r\n        }\r\n\r\n        public ChatContext(String changed_text, boolean triggered) {\r\n            this.changed_text = changed_text;\r\n            this.triggered = triggered;\r\n        }\r\n\r\n        String changed_text;\r\n        boolean triggered;\r\n\r\n        public boolean hasChanges() {\r\n            return changed_text != null;\r\n        }\r\n\r\n        public String getChanges() {\r\n            return changed_text != null ? changed_text : \"none\";\r\n        }\r\n\r\n        public boolean wasTriggered() {\r\n            return triggered;\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/triggers/core/ClickTrigger.java",
    "content": "package com.denizenscript.denizen.scripts.triggers.core;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptContainer;\nimport com.denizenscript.denizen.npc.traits.TriggerTrait;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.scripts.triggers.AbstractTrigger;\nimport com.denizenscript.denizen.tags.BukkitTagContext;\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\nimport com.denizenscript.denizencore.tags.TagContext;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.denizenscript.denizencore.tags.TagManager;\nimport net.citizensnpcs.api.event.NPCRightClickEvent;\nimport org.bukkit.Bukkit;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class ClickTrigger extends AbstractTrigger implements Listener {\n\n    // <--[language]\n    // @name Click Triggers\n    // @group NPC Interact Scripts\n    // @description\n    // Click Triggers are triggered when a player right clicks the NPC.\n    //\n    // These are very basic with no extraneous complexity.\n    //\n    // <code>\n    // click trigger:\n    //     script:\n    //     - narrate \"hi <player.name>\"\n    // </code>\n    //\n    // They can optionally have an item matcher with multiple triggers, for the item in the player's hand. For example:\n    // <code>\n    // click trigger:\n    //     1:\n    //         trigger: my_item_script\n    //         script:\n    //         - narrate \"Nice item script\"\n    //     2:\n    //         trigger: stone\n    //         script:\n    //         - narrate \"Nice vanilla item\"\n    //     3:\n    //         script:\n    //         - narrate \"I don't recognize your held item\"\n    // </code>\n    //\n    // -->\n\n    // <--[action]\n    // @Actions\n    // no click trigger\n    //\n    // @Triggers when the NPC is clicked but no click trigger fires.\n    //\n    // @Context\n    // None\n    //\n    // -->\n    // Technically defined in TriggerTrait, but placing here instead.\n    // <--[action]\n    // @Actions\n    // click\n    //\n    // @Triggers when the NPC is clicked by a player.\n    //\n    // @Context\n    // None\n    //\n    // @Determine\n    // \"cancelled\" to cancel the click event completely.\n    //\n    // -->\n    @EventHandler\n    public void clickTrigger(NPCRightClickEvent event) {\n        if (!event.getNPC().hasTrait(TriggerTrait.class)) {\n            return;\n        }\n        NPCTag npc = new NPCTag(event.getNPC());\n        if (!npc.getTriggerTrait().isEnabled(name)) {\n            return;\n        }\n        TriggerTrait triggerTrait = npc.getTriggerTrait();\n        double radius = triggerTrait.getRadius(name);\n        if (radius > 0 && event.getClicker().getLocation().distanceSquared(npc.getLocation()) > radius * radius) {\n            return;\n        }\n        PlayerTag player = PlayerTag.mirrorBukkitPlayer(event.getClicker());\n        TriggerTrait.TriggerContext trigger = npc.getTriggerTrait().trigger(this, player);\n        if (!trigger.wasTriggered()) {\n            return;\n        }\n        if (trigger.hasDetermination() && trigger.getDeterminations().containsCaseInsensitive(\"cancelled\")) {\n            event.setCancelled(true);\n            return;\n        }\n        List<InteractScriptContainer> scripts = npc.getInteractScripts(player, ClickTrigger.class);\n        boolean any = false;\n        if (scripts != null) {\n            for (InteractScriptContainer script : scripts) {\n                String id = null;\n                Map<String, String> idMap = script.getIdMapFor(ClickTrigger.class, player);\n                if (!idMap.isEmpty()) {\n                    ItemTag heldItem = new ItemTag(player.getPlayerEntity().getEquipment().getItemInMainHand());\n                    TagContext context = new BukkitTagContext(player, npc, new ScriptTag(script));\n                    for (Map.Entry<String, String> entry : idMap.entrySet()) {\n                        String entry_value = TagManager.tag(entry.getValue(), context);\n                        boolean isMatch = entry_value.isEmpty() || heldItem.tryAdvancedMatcher(entry_value, context);\n                        if (script.shouldDebug()) {\n                            Debug.echoDebug(script, \"Comparing click trigger '<A>\" + entry_value + \"<W>' with item '<A>\" + heldItem.debuggable() + \"<W>': \" + (isMatch ? \"<GR>Match!\" : \"<Y>Not a match\"));\n                        }\n                        if (isMatch) {\n                            id = entry.getKey();\n                            break;\n                        }\n                    }\n                }\n                if (parse(npc, player, script, id)) {\n                    any = true;\n                }\n            }\n        }\n        if (!any) {\n            npc.action(\"no click trigger\", player);\n        }\n    }\n\n    @Override\n    public void onEnable() {\n        Bukkit.getServer().getPluginManager().registerEvents(this, Denizen.getInstance());\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/triggers/core/DamageTrigger.java",
    "content": "package com.denizenscript.denizen.scripts.triggers.core;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptContainer;\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptHelper;\nimport com.denizenscript.denizen.npc.traits.TriggerTrait;\nimport com.denizenscript.denizen.objects.EntityTag;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.scripts.triggers.AbstractTrigger;\nimport com.denizenscript.denizen.tags.BukkitTagContext;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport com.denizenscript.denizencore.objects.ObjectTag;\nimport com.denizenscript.denizencore.objects.core.ListTag;\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\nimport com.denizenscript.denizencore.tags.TagManager;\nimport net.citizensnpcs.api.CitizensAPI;\nimport org.bukkit.Bukkit;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.entity.EntityDamageByEntityEvent;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class DamageTrigger extends AbstractTrigger implements Listener {\n\n    // <--[language]\n    // @name Damage Triggers\n    // @group NPC Interact Scripts\n    // @description\n    // Damage Triggers are triggered when when a player left clicks the NPC.\n    // Despite the name, these do not actually require the NPC take any damage, only that the player left clicks the NPC.\n    //\n    // In scripts, use <context.damage> to measure how much damage was done to the NPC\n    // (though note that invincible NPCs don't necessarily take any damage even when this is non-zero).\n    //\n    // These are very basic with no extraneous complexity.\n    //\n    // -->\n\n    // <--[action]\n    // @Actions\n    // no damage trigger\n    //\n    // @Triggers when the NPC is damaged by a player but no damage trigger fires.\n    //\n    // @Context\n    // None\n    //\n    // -->\n\n    // Technically defined in TriggerTrait, but placing here instead.\n    // <--[action]\n    // @Actions\n    // damage\n    //\n    // @Triggers when the NPC is damaged by a player and the Damage trigger fires. Action does not run if you do not enable the Damage trigger.\n    //\n    // @Context\n    // <context.damage> returns how much damage was done.\n    //\n    // @Determine\n    // \"cancelled\" to cancel the damage event.\n    //\n    // -->\n\n    // <--[action]\n    // @Actions\n    // damaged\n    //\n    // @Triggers when the NPC is damaged by an entity.\n    //\n    // @Context\n    // <context.damage> returns how much damage was done.\n    // <context.damager> returns the entity that did the damage.\n    //\n    // @Determine\n    // \"cancelled\" to cancel the damage event.\n    //\n    // -->\n    @EventHandler(ignoreCancelled = true)\n    public void damageTrigger(EntityDamageByEntityEvent event) {\n        Map<String, ObjectTag> context = new HashMap<>();\n        context.put(\"damage\", new ElementTag(event.getDamage()));\n        if (CitizensAPI.getNPCRegistry().isNPC(event.getEntity())) {\n            NPCTag npc = new NPCTag(CitizensAPI.getNPCRegistry().getNPC(event.getEntity()));\n            if (npc.getCitizen() == null) {\n                return;\n            }\n            EntityTag damager = new EntityTag(event.getDamager());\n            if (damager.isProjectile() && damager.hasShooter()) {\n                damager = damager.getShooter();\n            }\n            context.put(\"damager\", damager.getDenizenObject());\n            ListTag determ = npc.action(\"damaged\", null, context);\n            if (determ != null && determ.containsCaseInsensitive(\"cancelled\")) {\n                event.setCancelled(true);\n                return;\n            }\n            if (!damager.isPlayer()) {\n                return;\n            }\n            PlayerTag dplayer = damager.getDenizenPlayer();\n            if (!npc.getCitizen().hasTrait(TriggerTrait.class)) {\n                return;\n            }\n            if (!npc.getTriggerTrait().isEnabled(name)) {\n                return;\n            }\n            TriggerTrait.TriggerContext trigger = npc.getTriggerTrait().trigger(this, dplayer);\n            if (!trigger.wasTriggered()) {\n                return;\n            }\n            if (trigger.hasDetermination() && trigger.getDeterminations().containsCaseInsensitive(\"cancelled\")) {\n                event.setCancelled(true);\n                return;\n            }\n            List<InteractScriptContainer> scripts = InteractScriptHelper.getInteractScripts(npc, dplayer, true, ClickTrigger.class);\n            boolean any = false;\n            if (scripts != null) {\n                for (InteractScriptContainer script : scripts) {\n                    String id = null;\n                    Map<String, String> idMap = script.getIdMapFor(ClickTrigger.class, dplayer);\n                    if (!idMap.isEmpty()) {\n                        for (Map.Entry<String, String> entry : idMap.entrySet()) {\n                            String entry_value = TagManager.tag(entry.getValue(), new BukkitTagContext(dplayer, npc, null, false, new ScriptTag(script)));\n                            if (ItemTag.valueOf(entry_value, script).comparesTo(dplayer.getPlayerEntity().getEquipment().getItemInMainHand()) >= 0) {\n                                id = entry.getKey();\n                            }\n                        }\n                    }\n                    if (parse(npc, dplayer, script, id, context)) {\n                        any = true;\n                    }\n                }\n            }\n            if (!any) {\n                npc.action(\"no damage trigger\", dplayer);\n            }\n        }\n    }\n\n    @Override\n    public void onEnable() {\n        Bukkit.getServer().getPluginManager().registerEvents(this, Denizen.getInstance());\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/scripts/triggers/core/ProximityTrigger.java",
    "content": "package com.denizenscript.denizen.scripts.triggers.core;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.scripts.containers.core.InteractScriptContainer;\nimport com.denizenscript.denizen.npc.traits.TriggerTrait;\nimport com.denizenscript.denizen.objects.NPCTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.scripts.triggers.AbstractTrigger;\nimport net.citizensnpcs.api.CitizensAPI;\nimport net.citizensnpcs.api.npc.NPC;\nimport net.citizensnpcs.api.npc.NPCRegistry;\nimport org.bukkit.Bukkit;\nimport org.bukkit.Location;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.player.PlayerQuitEvent;\n\nimport java.util.*;\n\npublic class ProximityTrigger extends AbstractTrigger implements Listener {\n\n    // <--[language]\n    // @name Proximity Triggers\n    // @group NPC Interact Scripts\n    // @description\n    // Proximity Triggers are triggered when when a player moves in the area around the NPC.\n    //\n    // Proximity triggers must have a sub-key identifying what type of proximity trigger to use.\n    // The three types are \"entry\", \"exit\", and \"move\".\n    //\n    // Entry and exit do exactly as the names imply: Entry fires when the player walks into range of the NPC, and exit fires when the player walks out of range.\n    //\n    // Move is a bit more subtle: it fires very rapidly so long as a player remains within range of the NPC.\n    // This is useful for eg script logic that needs to be constantly updating whenever a player is nearby (eg a combat NPC script needs to constantly update its aim).\n    //\n    // The radius that the proximity trigger detects at is set by <@link command trigger>.\n    //\n    // -->\n\n    private static int maxProximityDistance = 75; // TODO: is this reasonable to have?\n\n    // <--[action]\n    // @Actions\n    // enter proximity\n    //\n    // @Triggers when a player enters the NPC's proximity trigger's radius.\n    //\n    // @Context\n    // None\n    //\n    // -->\n    // <--[action]\n    // @Actions\n    // exit proximity\n    //\n    // @Triggers when a player exits the NPC's proximity trigger's radius.\n    //\n    // @Context\n    // None\n    //\n    // -->\n    // <--[action]\n    // @Actions\n    // move proximity\n    //\n    // @Triggers when a player moves inside the NPC's proximity trigger's radius.\n    //\n    // @Context\n    // None\n    //\n    // -->\n    // Technically defined in TriggerTrait, but placing here instead.\n    // <--[action]\n    // @Actions\n    // proximity\n    //\n    // @Triggers when a player moves inside the NPC's proximity trigger's radius.\n    //\n    // @Context\n    // None\n    //\n    // -->\n    int taskID = -1;\n\n    @Override\n    public void onEnable() {\n        Bukkit.getServer().getPluginManager().registerEvents(this, Denizen.getInstance());\n        taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(Denizen.getInstance(), () -> {\n            if (timesUsed == 0) { // skip if not in use\n                return;\n            }\n            Collection<? extends Player> allPlayers = Bukkit.getOnlinePlayers();\n            for (NPCRegistry registry : CitizensAPI.getNPCRegistries()) {\n                for (NPC citizensNPC : registry) {\n                    if (citizensNPC == null || !citizensNPC.isSpawned()) {\n                        continue;\n                    }\n                    if (!citizensNPC.hasTrait(TriggerTrait.class) || !citizensNPC.getOrAddTrait(TriggerTrait.class).isEnabled(name)) {\n                        continue;\n                    }\n                    NPCTag npc = new NPCTag(citizensNPC);\n                    TriggerTrait triggerTrait = npc.getTriggerTrait();\n                    for (Player bukkitPlayer : allPlayers) {\n                        tryProcessSinglePair(npc, triggerTrait, bukkitPlayer);\n                    }\n                }\n            }\n        }, 5, 5);\n    }\n\n    public final void tryProcessSinglePair(NPCTag npc, TriggerTrait triggerTrait, Player bukkitPlayer) {\n        boolean exitedProximity = hasExitedProximityOf(bukkitPlayer, npc);\n        if (!npc.getWorld().equals(bukkitPlayer.getWorld()) && exitedProximity) {\n            return;\n        }\n        if (!isCloseEnough(bukkitPlayer, npc) && exitedProximity) {\n            return;\n        }\n        PlayerTag player = PlayerTag.mirrorBukkitPlayer(bukkitPlayer);\n        double radius = triggerTrait.getRadius(name);\n        Location npcLocation = npc.getLocation();\n        double distance;\n        if (npcLocation.getWorld() != player.getWorld()) {\n            distance = radius + 1;\n        }\n        else {\n            distance = npcLocation.distance(player.getLocation());\n        }\n        if (!exitedProximity) {\n            if (distance >= radius) {\n                if (!triggerTrait.triggerCooldownOnly(this, player)) {\n                    return;\n                }\n                exitProximityOf(bukkitPlayer, npc);\n                npc.action(\"exit proximity\", player);\n                parseAll(npc, player, \"EXIT\");\n            }\n            else {\n                npc.action(\"move proximity\", player);\n                parseAll(npc, player, \"MOVE\");\n            }\n        }\n        else if (distance <= radius) {\n            if (!triggerTrait.triggerCooldownOnly(this, player)) {\n                return;\n            }\n            enterProximityOf(bukkitPlayer, npc);\n            npc.action(\"enter proximity\", player);\n            parseAll(npc, player, \"ENTRY\");\n        }\n    }\n\n    public void parseAll(NPCTag npc, PlayerTag player, String id) {\n        List<InteractScriptContainer> scripts = npc.getInteractScriptsQuietly(player, ProximityTrigger.class);\n        if (scripts != null) {\n            for (InteractScriptContainer container : scripts) {\n                parse(npc, player, container, id);\n            }\n        }\n    }\n\n    @EventHandler\n    public void onPlayerQuit(PlayerQuitEvent event) {\n        Set<UUID> npcs = proximityTracker.remove(event.getPlayer().getUniqueId());\n        if (npcs == null) {\n            return;\n        }\n        PlayerTag player = new PlayerTag(event.getPlayer());\n        for (UUID id : npcs) {\n            NPC citizen = CitizensAPI.getNPCRegistry().getByUniqueId(id);\n            if (citizen == null) {\n                continue;\n            }\n            NPCTag npc = new NPCTag(citizen);\n            TriggerTrait triggerTrait = citizen.getTraitNullable(TriggerTrait.class);\n            if (triggerTrait == null) {\n                continue;\n            }\n            if (!triggerTrait.triggerCooldownOnly(this, player)) {\n                return;\n            }\n            npc.action(\"exit proximity\", player);\n            parseAll(npc, player, \"EXIT\");\n        }\n    }\n\n    @Override\n    public void onDisable() {\n        Bukkit.getScheduler().cancelTask(taskID);\n    }\n\n    /**\n     * Checks if the Player in Proximity is close enough to be calculated.\n     *\n     * @param player the Player\n     * @param npc    the NPC\n     * @return true if within maxProximityDistance in all directions\n     */\n    private boolean isCloseEnough(Player player, NPCTag npc) {\n        Location pLoc = player.getLocation();\n        Location nLoc = npc.getLocation();\n        if (pLoc.getWorld() != nLoc.getWorld() || pLoc.distanceSquared(nLoc) > maxProximityDistance * maxProximityDistance) {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * Player UUID to set of NPC UUIDs.\n     */\n    private static Map<UUID, Set<UUID>> proximityTracker = new HashMap<>();\n\n    //\n    // Ensures that a Player who has entered proximity of an NPC also fires Exit Proximity.\n    //\n    private boolean hasExitedProximityOf(Player player, NPCTag npc) {\n        // If Player hasn't entered proximity, it's not in the Map. Return true, must be exited.\n        Set<UUID> existing = proximityTracker.get(player.getUniqueId());\n        if (existing == null) {\n            return true;\n        }\n        // If Player has no entry for this NPC, return true.\n        if (!existing.contains(npc.getCitizen().getUniqueId())) {\n            return true;\n        }\n        // Entry is present, NPC has not yet triggered exit proximity.\n        return false;\n    }\n\n    /**\n     * Called when a 'Enter Proximity' has been called to make sure an exit\n     * proximity will be called.\n     *\n     * @param player the Player\n     * @param npc    the NPC\n     */\n    private void enterProximityOf(Player player, NPCTag npc) {\n        Set<UUID> npcs = proximityTracker.computeIfAbsent(player.getUniqueId(), k -> new HashSet<>());\n        npcs.add(npc.getCitizen().getUniqueId());\n    }\n\n    /**\n     * Called when an 'Exit Proximity' has been called. Once successfully exited,\n     * a Player can enter proximity again.\n     *\n     * @param player the Player\n     * @param npc    the NPC\n     */\n    private void exitProximityOf(Player player, NPCTag npc) {\n        Set<UUID> npcs = proximityTracker.get(player.getUniqueId());\n        if (npcs == null) {\n            return;\n        }\n        npcs.remove(npc.getCitizen().getUniqueId());\n        if (npcs.isEmpty()) {\n            proximityTracker.remove(player.getUniqueId());\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/tags/BukkitTagContext.java",
    "content": "package com.denizenscript.denizen.tags;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\n\r\npublic class BukkitTagContext extends TagContext {\r\n    public PlayerTag player;\r\n    public NPCTag npc;\r\n\r\n    public BukkitTagContext(BukkitTagContext copyFrom) {\r\n        this(copyFrom.player, copyFrom.npc, copyFrom.entry, copyFrom.debug, copyFrom.script);\r\n    }\r\n\r\n    public BukkitTagContext(EntityTag entity, ScriptTag script) {\r\n        super(script == null || script.getContainer().shouldDebug(), null, script);\r\n        this.player = entity != null && entity.isPlayer() ? entity.getDenizenPlayer() : null;\r\n        this.npc = entity != null && entity.isNPC() ? entity.getDenizenNPC() : null;\r\n    }\r\n\r\n    public BukkitTagContext(PlayerTag player, NPCTag npc, ScriptTag script) {\r\n        super(script == null || script.getContainer().shouldDebug(), null, script);\r\n        this.player = player;\r\n        this.npc = npc;\r\n    }\r\n\r\n    public BukkitTagContext(PlayerTag player, NPCTag npc, ScriptEntry entry, boolean debug, ScriptTag script) {\r\n        super(debug, entry, script);\r\n        this.player = player;\r\n        this.npc = npc;\r\n    }\r\n\r\n    public BukkitTagContext(ScriptEntry entry) {\r\n        super(entry);\r\n        if (entry != null) {\r\n            BukkitScriptEntryData data = (BukkitScriptEntryData) entry.entryData;\r\n            player = data.getPlayer();\r\n            npc = data.getNPC();\r\n        }\r\n    }\r\n\r\n    public BukkitTagContext(ScriptContainer container) {\r\n        super(container == null || container.shouldDebug(), null, container == null ? null : new ScriptTag(container));\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getScriptEntryData() {\r\n        BukkitScriptEntryData bsed = new BukkitScriptEntryData(player, npc);\r\n        bsed.scriptEntry = entry;\r\n        return bsed;\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return \"Context{player=\" + player + \",npc=\" + npc + \",entry=\" + entry + \",debug=\" + debug + \",script=\" + script + \"}\";\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/tags/core/CustomColorTagBase.java",
    "content": "package com.denizenscript.denizen.tags.core;\r\n\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport net.md_5.bungee.api.ChatColor;\r\n\r\nimport java.util.HashMap;\r\n\r\npublic class CustomColorTagBase {\r\n\r\n    public static HashMap<String, String> customColorsRaw = new HashMap<>();\r\n\r\n    public  static String defaultColorRaw = ChatColor.WHITE.toString();\r\n\r\n    public static HashMap<String, String> customColors = new HashMap<>();\r\n\r\n    public  static String defaultColor = null;\r\n\r\n    public static String getColor(String nameLow, TagContext context) {\r\n        String result = customColors.get(nameLow);\r\n        if (result != null) {\r\n            return result;\r\n        }\r\n        String unparsed = customColorsRaw.get(nameLow);\r\n        if (unparsed != null) {\r\n            result = TagManager.tag(unparsed, context);\r\n            customColors.put(nameLow, result);\r\n            return result;\r\n        }\r\n        if (TagManager.isStaticParsing) {\r\n            return null;\r\n        }\r\n        if (defaultColor == null) {\r\n            defaultColor = TagManager.tag(defaultColorRaw, context);\r\n        }\r\n        return defaultColor;\r\n    }\r\n\r\n    public CustomColorTagBase() {\r\n\r\n        // <--[tag]\r\n        // @attribute <&[<custom_color_name>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a custom color value based on the common base color names defined in the Denizen config file.\r\n        // If the color name is unrecognized, returns the value of color named 'default'.\r\n        // Default color names are 'base', 'emphasis', 'warning', 'error'.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, ElementTag.class, \"&\", (attribute, name) -> {\r\n            String color = getColor(name.asLowerString(), attribute.context);\r\n            if (color == null) {\r\n                return null;\r\n            }\r\n            return new ElementTag(color, true);\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/tags/core/NPCTagBase.java",
    "content": "package com.denizenscript.denizen.tags.core;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.npc.traits.AssignmentTrait;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.ai.TargetType;\r\nimport net.citizensnpcs.api.ai.TeleportStuckAction;\r\nimport net.citizensnpcs.api.ai.event.NavigationBeginEvent;\r\nimport net.citizensnpcs.api.ai.event.NavigationCancelEvent;\r\nimport net.citizensnpcs.api.ai.event.NavigationCompleteEvent;\r\nimport net.citizensnpcs.api.ai.event.NavigationStuckEvent;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.entity.Projectile;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityDamageByBlockEvent;\r\nimport org.bukkit.event.entity.EntityDamageByEntityEvent;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\nimport org.bukkit.event.entity.EntityDeathEvent;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class NPCTagBase implements Listener {\r\n\r\n    public NPCTagBase() {\r\n\r\n        // <--[tag]\r\n        // @attribute <npc[(<npc>)]>\r\n        // @returns NPCTag\r\n        // @description\r\n        // Returns an npc object constructed from the input value.\r\n        // Refer to <@link objecttype NPCTag>.\r\n        // If no input value is specified, returns the linked NPC.\r\n        // -->\r\n        if (Depends.citizens != null) {\r\n            Bukkit.getServer().getPluginManager().registerEvents(this, Denizen.getInstance());\r\n            TagManager.registerTagHandler(NPCTag.class, \"npc\", (attribute) -> {\r\n                if (!attribute.hasParam()) {\r\n                    NPCTag npc = ((BukkitTagContext) attribute.context).npc;\r\n                    if (npc != null) {\r\n                        return npc;\r\n                    }\r\n                    else {\r\n                        attribute.echoError(\"Missing NPC for npc tag.\");\r\n                        return null;\r\n                    }\r\n                }\r\n                return NPCTag.valueOf(attribute.getParam(), attribute.context);\r\n            });\r\n        }\r\n    }\r\n\r\n    ///////\r\n    // Keep track of previous locations and fire navigation actions\r\n    ////\r\n\r\n    public static Map<Integer, LocationTag> previousLocations = new HashMap<>();\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // complete navigation\r\n    //\r\n    // @Triggers when the NPC has finished a 'walk' command,\r\n    // or has reached a path point.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n    @EventHandler\r\n    public void navComplete(NavigationCompleteEvent event) {\r\n        // Do the assignment script action\r\n        if (!event.getNPC().hasTrait(AssignmentTrait.class)) {\r\n            return;\r\n        }\r\n        NPCTag npc = new NPCTag(event.getNPC());\r\n\r\n        npc.action(\"complete navigation\", null);\r\n\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // begin navigation\r\n    //\r\n    // @Triggers when the NPC has received a 'walk' command,\r\n    // or is about to follow a path.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n    @EventHandler\r\n    public void navBegin(NavigationBeginEvent event) {\r\n        if (!event.getNPC().hasTrait(AssignmentTrait.class)) {\r\n            return;\r\n        }\r\n        NPCTag npc = new NPCTag(event.getNPC());\r\n        npc.action(\"begin navigation\", null);\r\n        if (event.getNPC().getNavigator().getTargetType() == TargetType.ENTITY) {\r\n            LivingEntity entity = (LivingEntity) event.getNPC().getNavigator().getEntityTarget().getTarget();\r\n\r\n            // If the NPC has an entity target, is aggressive towards it\r\n            // and that entity is not dead, trigger \"on attack\" command\r\n            if (event.getNPC().getNavigator().getEntityTarget().isAggressive()\r\n                    && !entity.isDead()) {\r\n\r\n                PlayerTag player = null;\r\n\r\n                // Check if the entity attacked by this NPC is a player\r\n                if (entity instanceof Player) {\r\n                    player = PlayerTag.mirrorBukkitPlayer((Player) entity);\r\n                }\r\n\r\n                // <--[action]\r\n                // @Actions\r\n                // attack\r\n                // attack on <entity>\r\n                //\r\n                // @Triggers when the NPC is about to attack an enemy.\r\n                //\r\n                // @Context\r\n                // None\r\n                //\r\n                // -->\r\n                npc.action(\"attack\", player);\r\n\r\n                npc.action(\"attack on \"\r\n                        + entity.getType(), player);\r\n            }\r\n            previousLocations.put(event.getNPC().getId(), npc.getLocation());\r\n        }\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // cancel navigation\r\n    // cancel navigation due to <reason>\r\n    //\r\n    // @Triggers when a plugin or script cancels an NPC's navigation.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n    @EventHandler\r\n    public void navCancel(NavigationCancelEvent event) {\r\n        if (!event.getNPC().hasTrait(AssignmentTrait.class)) {\r\n            return;\r\n        }\r\n        NPCTag npc = new NPCTag(event.getNPC());\r\n        npc.action(\"cancel navigation\", null);\r\n        npc.action(\"cancel navigation due to \" + event.getCancelReason().toString(), null);\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // stuck\r\n    //\r\n    // @Triggers when the NPC's navigator is stuck.\r\n    //\r\n    // @Context\r\n    // <context.action> returns 'teleport' or 'none'\r\n    //\r\n    // @Determine\r\n    // \"NONE\" to do nothing.\r\n    // \"TELEPORT\" to teleport.\r\n    //\r\n    // -->\r\n    @EventHandler\r\n    public void navStuck(NavigationStuckEvent event) {\r\n        // Do the assignment script action\r\n        if (!event.getNPC().hasTrait(AssignmentTrait.class)) {\r\n            return;\r\n        }\r\n        NPCTag npc = new NPCTag(event.getNPC());\r\n        Map<String, ObjectTag> context = new HashMap<>();\r\n        context.put(\"action\", new ElementTag(event.getAction() == TeleportStuckAction.INSTANCE ? \"teleport\" : \"none\"));\r\n        ListTag determination = npc.action(\"stuck\", null, context);\r\n        if (determination.containsCaseInsensitive(\"none\")) {\r\n            event.setAction(null);\r\n        }\r\n        if (determination.containsCaseInsensitive(\"teleport\")) {\r\n            event.setAction(TeleportStuckAction.INSTANCE);\r\n        }\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // death\r\n    // death by entity\r\n    // death by <entity>\r\n    // death by block\r\n    // death by <cause>\r\n    //\r\n    // @Triggers when the NPC dies.\r\n    //\r\n    // @Context\r\n    // <context.killer> returns the entity that killed the NPC (if any)\r\n    // <context.shooter> returns the shooter of the killing projectile (if any)\r\n    // <context.damage> returns the last amount of damage applied (if any)\r\n    // <context.death_cause> returns the last damage cause (if any)\r\n    //\r\n    // -->\r\n    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\r\n    public void onDeath(EntityDeathEvent deathEvent) {\r\n        NPC citizen = CitizensAPI.getNPCRegistry().getNPC(deathEvent.getEntity());\r\n        if (citizen == null || !citizen.hasTrait(AssignmentTrait.class)) {\r\n            return;\r\n        }\r\n        NPCTag npc = new NPCTag(citizen);\r\n        EntityDamageEvent event = deathEvent.getEntity().getLastDamageCause();\r\n        String deathCause = event == null ? \"unknown\" : CoreUtilities.toLowerCase(event.getCause().toString()).replace('_', ' ');\r\n        Map<String, ObjectTag> context = new HashMap<>();\r\n        context.put(\"damage\", new ElementTag(event == null ? 0 : event.getDamage()));\r\n        context.put(\"death_cause\", new ElementTag(deathCause));\r\n        PlayerTag player = null;\r\n        if (event instanceof EntityDamageByEntityEvent) {\r\n            Entity killerEntity = ((EntityDamageByEntityEvent) event).getDamager();\r\n            context.put(\"killer\", new EntityTag(killerEntity).getDenizenObject());\r\n            if (killerEntity instanceof Player) {\r\n                player = PlayerTag.mirrorBukkitPlayer((Player) killerEntity);\r\n            }\r\n            else if (killerEntity instanceof Projectile) {\r\n                ProjectileSource shooter = ((Projectile) killerEntity).getShooter();\r\n                if (shooter instanceof LivingEntity) {\r\n                    context.put(\"shooter\", new EntityTag((LivingEntity) shooter).getDenizenObject());\r\n                    if (shooter instanceof Player) {\r\n                        player = PlayerTag.mirrorBukkitPlayer((Player) shooter);\r\n                    }\r\n                    npc.action(\"death by \" + ((LivingEntity) shooter).getType(), player, context);\r\n                }\r\n            }\r\n            npc.action(\"death by entity\", player, context);\r\n            npc.action(\"death by \" + killerEntity.getType(), player, context);\r\n        }\r\n        else if (event instanceof EntityDamageByBlockEvent) {\r\n            npc.action(\"death by block\", null, context);\r\n        }\r\n        npc.action(\"death\", player, context);\r\n        npc.action(\"death by \" + deathCause, player, context);\r\n    }\r\n\r\n    // <--[action]\r\n    // @Actions\r\n    // hit\r\n    // hit on <entity>\r\n    //\r\n    // @Triggers when the NPC hits an enemy.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n    // <--[action]\r\n    // @Actions\r\n    // kill\r\n    // kill of <entity>\r\n    //\r\n    // @Triggers when the NPC kills an enemy.\r\n    //\r\n    // @Context\r\n    // None\r\n    //\r\n    // -->\r\n    @EventHandler(priority = EventPriority.MONITOR)\r\n    public void onHit(EntityDamageByEntityEvent event) {\r\n        NPC citizen = CitizensAPI.getNPCRegistry().getNPC(event.getDamager());\r\n        if (citizen == null) {\r\n            if (event.getDamager() instanceof Projectile) {\r\n                if (((Projectile) event.getDamager()).getShooter() instanceof Entity) {\r\n                    citizen = CitizensAPI.getNPCRegistry().getNPC((Entity) ((Projectile) event.getDamager()).getShooter());\r\n                }\r\n            }\r\n        }\r\n        if (citizen == null || !citizen.hasTrait(AssignmentTrait.class)) {\r\n            return;\r\n        }\r\n        NPCTag npc = new NPCTag(citizen);\r\n        PlayerTag player = null;\r\n        if (event.getEntity() instanceof Player) {\r\n            player = PlayerTag.mirrorBukkitPlayer((Player) event.getEntity());\r\n        }\r\n        // TODO: Context containing the entity hit\r\n        npc.action(\"hit\", player);\r\n        npc.action(\"hit on \" + event.getEntityType().name(), player);\r\n        if (event.getEntity() instanceof LivingEntity) {\r\n            if (((LivingEntity) event.getEntity()).getHealth() - event.getFinalDamage() <= 0) {\r\n                npc.action(\"kill\", player);\r\n                npc.action(\"kill of \" + event.getEntityType().name(), player);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/tags/core/PlayerTagBase.java",
    "content": "package com.denizenscript.denizen.tags.core;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.AsyncPlayerChatEvent;\r\n\r\nimport java.util.*;\r\n\r\npublic class PlayerTagBase implements Listener {\r\n\r\n    public PlayerTagBase() {\r\n\r\n        // <--[tag]\r\n        // @attribute <player[(<player>)]>\r\n        // @returns PlayerTag\r\n        // @description\r\n        // Returns a player object constructed from the input value.\r\n        // Refer to <@link objecttype PlayerTag>.\r\n        // If no input value is specified, returns the linked player.\r\n        // -->\r\n        Bukkit.getServer().getPluginManager().registerEvents(this, Denizen.getInstance());\r\n        TagManager.registerTagHandler(PlayerTag.class, \"player\", (attribute) -> {\r\n            if (!attribute.hasParam()) {\r\n                PlayerTag player = ((BukkitTagContext) attribute.context).player;\r\n                if (player != null) {\r\n                    return player;\r\n                }\r\n                else {\r\n                    attribute.echoError(\"Missing player for player tag.\");\r\n                    return null;\r\n                }\r\n            }\r\n            return PlayerTag.valueOf(attribute.getParam(), attribute.context);\r\n        });\r\n    }\r\n\r\n    ///////////\r\n    // Player Chat History\r\n    /////////\r\n\r\n    public static Map<UUID, List<String>> playerChatHistory = new HashMap<>();\r\n\r\n    @EventHandler(priority = EventPriority.MONITOR)\r\n    public void addMessage(final AsyncPlayerChatEvent event) {\r\n        final int maxSize = Settings.chatHistoryMaxMessages();\r\n        if (maxSize > 0) {\r\n            Bukkit.getScheduler().runTaskLater(Denizen.getInstance(), () -> {\r\n                List<String> history = playerChatHistory.get(event.getPlayer().getUniqueId());\r\n                // If history hasn't been started for this player, initialize a new ArrayList\r\n                if (history == null) {\r\n                    history = new ArrayList<>();\r\n                }\r\n                // Maximum history size is specified by config.yml\r\n                if (history.size() > maxSize) {\r\n                    history.remove(maxSize - 1);\r\n                }\r\n                // Add message to history\r\n                history.add(0, event.getMessage());\r\n                // Store the new history\r\n                playerChatHistory.put(event.getPlayer().getUniqueId(), history);\r\n            }, 1);\r\n        }\r\n    }\r\n}\r\n\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/tags/core/ServerTagBase.java",
    "content": "package com.denizenscript.denizen.tags.core;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.BukkitScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemHelper;\r\nimport com.denizenscript.denizen.npc.traits.AssignmentTrait;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.scripts.commands.server.BossBarCommand;\r\nimport com.denizenscript.denizen.scripts.containers.core.AssignmentScriptContainer;\r\nimport com.denizenscript.denizen.scripts.containers.core.CommandScriptHelper;\r\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper;\r\nimport com.denizenscript.denizen.utilities.*;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.utilities.inventory.SlotHelper;\r\nimport com.denizenscript.denizen.utilities.world.GameRuleReflect;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.ObjectFetcher;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.*;\r\nimport com.denizenscript.denizencore.objects.notable.Notable;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.scripts.commands.core.AdjustCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.core.SQLCommand;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.tags.PseudoObjectTagBase;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.tags.TagRunnable;\r\nimport com.denizenscript.denizencore.tags.core.UtilTagBase;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.Deprecations;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\r\nimport it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport net.citizensnpcs.api.npc.NPCRegistry;\r\nimport net.citizensnpcs.api.trait.TraitInfo;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.Biome;\r\nimport org.bukkit.block.banner.PatternType;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.command.PluginCommand;\r\nimport org.bukkit.configuration.file.YamlConfiguration;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.Event;\r\nimport org.bukkit.event.HandlerList;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\nimport org.bukkit.inventory.*;\r\nimport org.bukkit.loot.LootContext;\r\nimport org.bukkit.loot.LootTable;\r\nimport org.bukkit.map.MapCursor;\r\nimport org.bukkit.permissions.Permission;\r\nimport org.bukkit.permissions.PermissionDefault;\r\nimport org.bukkit.plugin.Plugin;\r\nimport org.bukkit.plugin.RegisteredListener;\r\nimport org.bukkit.potion.PotionEffectType;\r\nimport org.bukkit.potion.PotionType;\r\nimport org.bukkit.scoreboard.*;\r\nimport org.bukkit.util.permissions.DefaultPermissions;\r\n\r\nimport java.sql.Connection;\r\nimport java.sql.SQLException;\r\nimport java.util.*;\r\nimport java.util.function.Consumer;\r\n\r\npublic class ServerTagBase extends PseudoObjectTagBase<ServerTagBase> {\r\n\r\n    public static ServerTagBase instance;\r\n\r\n    // <--[ObjectType]\r\n    // @name server\r\n    // @prefix None\r\n    // @base None\r\n    // @ExampleAdjustObject server\r\n    // @format\r\n    // N/A\r\n    //\r\n    // @description\r\n    // \"server\" is an internal pseudo-ObjectType that is used as a mechanism adjust target for some global mechanisms.\r\n    //\r\n    // -->\r\n\r\n    public ServerTagBase() {\r\n        instance = this;\r\n        TagManager.registerStaticTagBaseHandler(ServerTagBase.class, \"server\", (t) -> instance);\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"global\", (attribute) -> {\r\n            BukkitImplDeprecations.globalTagName.warn(attribute.context);\r\n            return null;\r\n        });\r\n        AdjustCommand.specialAdjustables.put(\"server\", mechanism -> tagProcessor.processMechanism(instance, mechanism));\r\n    }\r\n\r\n    @Override\r\n    public void register() {\r\n        tagProcessor.registerTag(ElementTag.class, \"economy\", (attribute, object) -> {\r\n            if (Depends.economy == null) {\r\n                attribute.echoError(\"No economy loaded! Have you installed Vault and a compatible economy plugin?\");\r\n                return null;\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.economy.format[<#.#>]>\r\n            // @returns ElementTag\r\n            // @plugin Vault\r\n            // @description\r\n            // Returns the amount of money, formatted according to the server's economy.\r\n            // -->\r\n            if (attribute.startsWith(\"format\", 2) && attribute.hasContext(2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(Depends.economy.format(attribute.getDoubleParam()));\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.economy.currency_name[<#.#>]>\r\n            // @returns ElementTag\r\n            // @plugin Vault\r\n            // @description\r\n            // Returns the server's economy currency name (automatically singular or plural based on input value).\r\n            // -->\r\n            if (attribute.startsWith(\"currency_name\", 2) && attribute.hasContext(2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(attribute.getDoubleParam() == 1 ? Depends.economy.currencyNameSingular() : Depends.economy.currencyNamePlural());\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.economy.currency_plural>\r\n            // @returns ElementTag\r\n            // @plugin Vault\r\n            // @description\r\n            // Returns the server's economy currency name (in the plural form, like \"Dollars\").\r\n            // -->\r\n            if (attribute.startsWith(\"currency_plural\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(Depends.economy.currencyNamePlural());\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.economy.currency_singular>\r\n            // @returns ElementTag\r\n            // @plugin Vault\r\n            // @description\r\n            // Returns the server's economy currency name (in the singular form, like \"Dollar\").\r\n            // -->\r\n            if (attribute.startsWith(\"currency_singular\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(Depends.economy.currencyNameSingular());\r\n            }\r\n\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.slot_id[<slot>]>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the slot ID number for an input slot (see <@link language Slot Inputs>).\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"slot_id\", (attribute, object, input) -> {\r\n            int slotId = SlotHelper.nameToIndex(input.asString(), null);\r\n            return slotId != -1 ? new ElementTag(slotId) : null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.parse_bukkit_item[<serial>]>\r\n        // @returns ItemTag\r\n        // @description\r\n        // Returns the ItemTag resultant from parsing Bukkit item serialization data (under subkey \"item\").\r\n        // -->\r\n        tagProcessor.registerTag(ItemTag.class, ElementTag.class, \"parse_bukkit_item\", (attribute, object, input) -> {\r\n            YamlConfiguration config = new YamlConfiguration();\r\n            try {\r\n                config.loadFromString(input.asString());\r\n                ItemStack item = config.getItemStack(\"item\");\r\n                if (item != null) {\r\n                    return new ItemTag(item);\r\n                }\r\n            }\r\n            catch (Exception ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.recipe_ids[(<type>)]>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all recipe IDs on the server.\r\n        // Returns a list in the Namespace:Key format, for example \"minecraft:gold_nugget\".\r\n        // Optionally, specify a recipe type (CRAFTING, FURNACE, COOKING, BLASTING, SHAPED, SHAPELESS, SMOKING, CAMPFIRE, STONECUTTING, SMITHING, BREWING)\r\n        // to limit to just recipes of that type.\r\n        // Brewing recipes are only supported on Paper, and only custom ones are available.\r\n        // Note: this will produce an error if all recipes of any one type have been removed from the server, due to an error in Spigot.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"recipe_ids\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            String type = attribute.hasParam() ? CoreUtilities.toLowerCase(attribute.getParam()) : null;\r\n            ListTag recipeIds = new ListTag();\r\n            if (type == null || !type.equals(\"brewing\")) {\r\n                Bukkit.recipeIterator().forEachRemaining(recipe -> {\r\n                    if (recipe instanceof Keyed keyedRecipe && Utilities.isRecipeOfType(recipe, type)) {\r\n                        recipeIds.add(keyedRecipe.getKey().toString());\r\n                    }\r\n                });\r\n            }\r\n            if (Denizen.supportsPaper && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18) && (type == null || type.equals(\"brewing\"))) {\r\n                for (NamespacedKey brewingRecipe : NMSHandler.itemHelper.getCustomBrewingRecipes().keySet()) {\r\n                    recipeIds.add(brewingRecipe.toString());\r\n                }\r\n            }\r\n            return recipeIds;\r\n        }, \"list_recipe_ids\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.recipe_items[<id>]>\r\n        // @returns ListTag(ItemTag)\r\n        // @description\r\n        // Returns a list of the items used as input to the recipe within the input ID.\r\n        // This is formatted equivalently to the item script recipe input, with \"material:\" for non-exact matches, and a full ItemTag for exact matches.\r\n        // Note that this won't represent all recipes perfectly (primarily those with multiple input choices per slot).\r\n        // Brewing recipes are only supported on Paper, and only custom ones are available.\r\n        // For brewing recipes, currently \"matcher:<item matcher>\" input options are only supported in recipes added by Denizen.\r\n        // For furnace-style and stonecutting recipes, this will return a list with only 1 item.\r\n        // For shaped recipes, this will include 'air' for slots that are part of the shape but don't require an item.\r\n        // For smithing recipes, this will return a list with the 'base' item and the 'addition'.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"recipe_items\", (attribute, object, input) -> {\r\n            NamespacedKey recipeKey = Utilities.parseNamespacedKey(input.asString());\r\n            Recipe recipe = Bukkit.getRecipe(recipeKey);\r\n            ItemHelper.BrewingRecipe brewingRecipe = Denizen.supportsPaper && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18) ? NMSHandler.itemHelper.getCustomBrewingRecipes().get(recipeKey) : null;\r\n            if (recipe == null && brewingRecipe == null) {\r\n                return null;\r\n            }\r\n            ListTag recipeItems = new ListTag();\r\n            Consumer<RecipeChoice> addChoice = (choice) -> {\r\n                if (choice == null) {\r\n                    recipeItems.addObject(new ItemTag(Material.AIR));\r\n                }\r\n                else {\r\n                    if (choice instanceof RecipeChoice.ExactChoice) {\r\n                        recipeItems.addObject(new ItemTag(choice.getItemStack()));\r\n                    }\r\n                    else {\r\n                        recipeItems.add(\"material:\" + choice.getItemStack().getType().name());\r\n                    }\r\n                }\r\n            };\r\n            if (recipe instanceof ShapedRecipe shapedRecipe) {\r\n                Map<Character, RecipeChoice> choiceMap = shapedRecipe.getChoiceMap();\r\n                for (String row : shapedRecipe.getShape()) {\r\n                    for (char column : row.toCharArray()) {\r\n                        addChoice.accept(choiceMap.get(column));\r\n                    }\r\n                }\r\n            }\r\n            else if (recipe instanceof ShapelessRecipe shapelessRecipe) {\r\n                for (RecipeChoice choice : shapelessRecipe.getChoiceList()) {\r\n                    addChoice.accept(choice);\r\n                }\r\n            }\r\n            else if (recipe instanceof CookingRecipe<?> cookingRecipe) {\r\n                addChoice.accept(cookingRecipe.getInputChoice());\r\n            }\r\n            else if (recipe instanceof StonecuttingRecipe stonecuttingRecipe) {\r\n                addChoice.accept(stonecuttingRecipe.getInputChoice());\r\n            }\r\n            else if (recipe instanceof SmithingRecipe smithingRecipe) {\r\n                addChoice.accept(smithingRecipe.getBase());\r\n                addChoice.accept(smithingRecipe.getAddition());\r\n            }\r\n            else if (brewingRecipe != null) {\r\n                if (brewingRecipe.ingredient() instanceof RecipeChoice.ExactChoice || brewingRecipe.ingredient() instanceof RecipeChoice.MaterialChoice) {\r\n                    addChoice.accept(brewingRecipe.ingredient());\r\n                }\r\n                else {\r\n                    recipeItems.addObject(new ElementTag(PaperAPITools.instance.getBrewingRecipeIngredientMatcher(recipeKey), true));\r\n                }\r\n                if (brewingRecipe.input() instanceof RecipeChoice.ExactChoice || brewingRecipe.input() instanceof RecipeChoice.MaterialChoice) {\r\n                    addChoice.accept(brewingRecipe.input());\r\n                }\r\n                else {\r\n                    recipeItems.addObject(new ElementTag(PaperAPITools.instance.getBrewingRecipeInputMatcher(recipeKey), true));\r\n                }\r\n            }\r\n            return recipeItems;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.recipe_shape[<id>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the shape of a shaped recipe, like '2x2' or '3x3'.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"recipe_shape\", (attribute, object, input) -> {\r\n            if (Bukkit.getRecipe(Utilities.parseNamespacedKey(input.asString())) instanceof ShapedRecipe shapedRecipe) {\r\n                String[] shape = shapedRecipe.getShape();\r\n                return new ElementTag(shape[0].length() + \"x\" + shape.length);\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.recipe_type[<id>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the type of recipe that the given recipe ID is.\r\n        // Will be one of FURNACE, BLASTING, SHAPED, SHAPELESS, SMOKING, CAMPFIRE, STONECUTTING, SMITHING, BREWING.\r\n        // Brewing recipes are only supported on Paper, and only custom ones are available.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"recipe_type\", (attribute, object, input) -> {\r\n            NamespacedKey recipeKey = Utilities.parseNamespacedKey(input.asString());\r\n            Recipe recipe = Bukkit.getRecipe(recipeKey);\r\n            if (recipe != null) {\r\n                return new ElementTag(Utilities.getRecipeType(recipe));\r\n            }\r\n            if (Denizen.supportsPaper && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18) && NMSHandler.itemHelper.getCustomBrewingRecipes().containsKey(recipeKey)) {\r\n                return new ElementTag(\"brewing\");\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.recipe_result[<id>]>\r\n        // @returns ItemTag\r\n        // @description\r\n        // Returns the item that a recipe will create when crafted.\r\n        // Brewing recipes are only supported on Paper, and only custom ones are available.\r\n        // -->\r\n        tagProcessor.registerTag(ItemTag.class, ElementTag.class, \"recipe_result\", (attribute, object, input) -> {\r\n            NamespacedKey recipeKey = Utilities.parseNamespacedKey(input.asString());\r\n            Recipe recipe = Bukkit.getRecipe(recipeKey);\r\n            if (recipe != null) {\r\n                return new ItemTag(recipe.getResult());\r\n            }\r\n            if (Denizen.supportsPaper && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\r\n                ItemHelper.BrewingRecipe brewingRecipe = NMSHandler.itemHelper.getCustomBrewingRecipes().get(recipeKey);\r\n                if (brewingRecipe != null) {\r\n                    return new ItemTag(brewingRecipe.result());\r\n                }\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.scoreboards>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of scoreboard IDs currently registered on the server.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"scoreboards\", (attribute, object) -> {\r\n            ListTag scoreboards = new ListTag(ScoreboardHelper.scoreboardMap.size());\r\n            for (String board : ScoreboardHelper.scoreboardMap.keySet()) {\r\n                scoreboards.addObject(new ElementTag(board, true));\r\n            }\r\n            return scoreboards;\r\n        });\r\n\r\n        tagProcessor.registerTag(ObjectTag.class, \"scoreboard\", (attribute, object) -> {\r\n            String name = \"main\";\r\n            Scoreboard board;\r\n            if (attribute.hasParam()) {\r\n                name = attribute.getParam();\r\n                board = ScoreboardHelper.getScoreboard(name);\r\n            }\r\n            else {\r\n                board = ScoreboardHelper.getMain();\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.scoreboard[<board>].exists>\r\n            // @returns ElementTag(Boolean)\r\n            // @description\r\n            // Returns whether a given scoreboard exists on the server.\r\n            // -->\r\n            if (attribute.startsWith(\"exists\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(board != null);\r\n            }\r\n            if (board == null) {\r\n                attribute.echoError(\"Scoreboard '\" + name + \"' does not exist.\");\r\n                return null;\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.scoreboard[(<board>)].objectives>\r\n            // @returns ListTag\r\n            // @description\r\n            // Returns a list of all objective names in the scoreboard.\r\n            // Optionally, specify which scoreboard to use.\r\n            // -->\r\n            if (attribute.startsWith(\"objectives\", 2)) {\r\n                attribute.fulfill(1);\r\n                ListTag objectives = new ListTag();\r\n                for (Objective objective : board.getObjectives()) {\r\n                    objectives.add(objective.getName());\r\n                }\r\n                return objectives;\r\n            }\r\n\r\n            if (attribute.startsWith(\"objective\", 2) && attribute.hasContext(2)) {\r\n                attribute.fulfill(1);\r\n                Objective objective = board.getObjective(attribute.getParam());\r\n                if (objective == null) {\r\n                    attribute.echoError(\"Scoreboard objective '\" + attribute.getParam() + \"' does not exist.\");\r\n                    return null;\r\n                }\r\n\r\n                // <--[tag]\r\n                // @attribute <server.scoreboard[(<board>)].objective[<name>].criteria>\r\n                // @returns ElementTag\r\n                // @description\r\n                // Returns the criteria specified for the given objective.\r\n                // Optionally, specify which scoreboard to use.\r\n                // -->\r\n                if (attribute.startsWith(\"criteria\", 2)) {\r\n                    attribute.fulfill(1);\r\n                    return new ElementTag(objective.getCriteria()); // TODO: once minimum supported version is 1.19 or above, use getTrackedCriteria().getName()\r\n                }\r\n\r\n                // <--[tag]\r\n                // @attribute <server.scoreboard[(<board>)].objective[<name>].display_name>\r\n                // @returns ElementTag\r\n                // @description\r\n                // Returns the display name specified for the given objective.\r\n                // Optionally, specify which scoreboard to use.\r\n                // -->\r\n                if (attribute.startsWith(\"display_name\", 2)) {\r\n                    attribute.fulfill(1);\r\n                    return new ElementTag(objective.getDisplayName());\r\n                }\r\n\r\n                // <--[tag]\r\n                // @attribute <server.scoreboard[(<board>)].objective[<name>].display_slot>\r\n                // @returns ElementTag\r\n                // @description\r\n                // Returns the display slot specified for the given objective. Can be any of <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/scoreboard/DisplaySlot.html>.\r\n                // Note that not all objectives have a display slot.\r\n                // Optionally, specify which scoreboard to use.\r\n                // -->\r\n                if (attribute.startsWith(\"display_slot\", 2)) {\r\n                    attribute.fulfill(1);\r\n                    DisplaySlot displaySlot = objective.getDisplaySlot();\r\n                    return displaySlot != null ? new ElementTag(displaySlot) : null;\r\n                }\r\n\r\n                // <--[tag]\r\n                // @attribute <server.scoreboard[(<board>)].objective[<name>].score[<input>]>\r\n                // @returns ElementTag(Number)\r\n                // @description\r\n                // Returns the current score in the objective for the given input.\r\n                // Input can be a PlayerTag (translates to name internally), EntityTag (translates to UUID internally) or any plaintext score holder label.\r\n                // Optionally, specify which scoreboard to use.\r\n                // -->\r\n                if (attribute.startsWith(\"score\", 2) && attribute.hasContext(2)) {\r\n                    attribute.fulfill(1);\r\n                    String value;\r\n                    ObjectTag param = attribute.getParamObject();\r\n                    if (param.shouldBeType(PlayerTag.class)) {\r\n                        value = param.asType(PlayerTag.class, attribute.context).getName();\r\n                    }\r\n                    else if (param.shouldBeType(EntityTag.class)) {\r\n                        value = param.asType(EntityTag.class, attribute.context).getUUID().toString();\r\n                    }\r\n                    else {\r\n                        value = param.toString();\r\n                    }\r\n                    Score score = objective.getScore(value);\r\n                    return score.isScoreSet() ? new ElementTag(score.getScore()) : null;\r\n                }\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.scoreboard[(<board>)].team_names>\r\n            // @returns ListTag\r\n            // @description\r\n            // Returns a list of the names of all teams within the scoreboard.\r\n            // Optionally, specify which scoreboard to use.\r\n            // -->\r\n            if (attribute.startsWith(\"team_names\", 2)) {\r\n                attribute.fulfill(1);\r\n                ListTag teams = new ListTag();\r\n                for (Team team : board.getTeams()) {\r\n                    teams.add(team.getName());\r\n                }\r\n                return teams;\r\n            }\r\n\r\n            if (attribute.startsWith(\"team\", 2) && attribute.hasContext(2)) {\r\n                attribute.fulfill(1);\r\n                Team team = board.getTeam(attribute.getParam());\r\n                if (team == null) {\r\n                    attribute.echoError(\"Scoreboard team '\" + attribute.getParam() + \"' does not exist.\");\r\n                    return null;\r\n                }\r\n\r\n                // <--[tag]\r\n                // @attribute <server.scoreboard[(<board>)].team[<team>].members>\r\n                // @returns ListTag\r\n                // @description\r\n                // Returns a list of all members of a scoreboard team. Generally returns as a list of names or text entries.\r\n                // Members are not necessarily written in any given format and are not guaranteed to validly fit any requirements.\r\n                // Optionally, specify which scoreboard to use.\r\n                // -->\r\n                if (attribute.startsWith(\"members\", 2)) {\r\n                    attribute.fulfill(1);\r\n                    return new ListTag(team.getEntries());\r\n                }\r\n\r\n                // <--[tag]\r\n                // @attribute <server.scoreboard[(<board>)].team[<team>].prefix>\r\n                // @returns ElementTag\r\n                // @description\r\n                // Returns the team's prefix.\r\n                // Optionally, specify which scoreboard to use.\r\n                // -->\r\n                if (attribute.startsWith(\"prefix\", 2)) {\r\n                    attribute.fulfill(1);\r\n                    return new ElementTag(PaperAPITools.instance.getTeamPrefix(team));\r\n                }\r\n\r\n                // <--[tag]\r\n                // @attribute <server.scoreboard[(<board>)].team[<team>].suffix>\r\n                // @returns ElementTag\r\n                // @description\r\n                // Returns the team's suffix.\r\n                // Optionally, specify which scoreboard to use.\r\n                // -->\r\n                if (attribute.startsWith(\"suffix\", 2)) {\r\n                    attribute.fulfill(1);\r\n                    return new ElementTag(PaperAPITools.instance.getTeamSuffix(team));\r\n                }\r\n            }\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.object_is_valid[<object>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @deprecated Use 'exists' or 'is_truthy'\r\n        // @description\r\n        // Deprecated in favor of <@link tag ObjectTag.exists> or <@link tag ObjectTag.is_truthy>\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, ObjectTag.class, \"object_is_valid\", (attribute, object, input) -> {\r\n            BukkitImplDeprecations.serverObjectExistsTags.warn(attribute.context);\r\n            ObjectTag o = ObjectFetcher.pickObjectFor(input.toString(), CoreUtilities.noDebugContext);\r\n            return new ElementTag(!(o == null || o instanceof ElementTag));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.has_whitelist>\r\n        // @returns ElementTag(Boolean)\r\n        // @mechanism server.has_whitelist\r\n        // @description\r\n        // Returns whether the server's whitelist is active.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"has_whitelist\", (attribute, object) -> {\r\n            return new ElementTag(Bukkit.hasWhitelist());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.whitelisted_players>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of all players whitelisted on this server.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"whitelisted_players\", (attribute, object) -> {\r\n            ListTag whitelisted = new ListTag();\r\n            for (OfflinePlayer player : Bukkit.getWhitelistedPlayers()) {\r\n                whitelisted.addObject(new PlayerTag(player));\r\n            }\r\n            return whitelisted;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.has_flag[<flag_name>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // See <@link tag FlaggableObject.has_flag>\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"has_flag\", (attribute, object, input) -> {\r\n            return DenizenCore.serverFlagMap.doHasFlagTag(attribute);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.flag_expiration[<flag_name>]>\r\n        // @returns TimeTag\r\n        // @description\r\n        // See <@link tag FlaggableObject.flag_expiration>\r\n        // -->\r\n        tagProcessor.registerTag(TimeTag.class, ElementTag.class, \"flag_expiration\", (attribute, object, input) -> {\r\n            return DenizenCore.serverFlagMap.doFlagExpirationTag(attribute);\r\n        });\r\n        // <--[tag]\r\n        // @attribute <server.flag[<flag_name>]>\r\n        // @returns ObjectTag\r\n        // @description\r\n        // See <@link tag FlaggableObject.flag>\r\n        // -->\r\n        tagProcessor.registerTag(ObjectTag.class, ElementTag.class, \"flag\", (attribute, object, input) -> {\r\n            return DenizenCore.serverFlagMap.doFlagTag(attribute);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.list_flags>\r\n        // @returns ListTag\r\n        // @description\r\n        // See <@link tag FlaggableObject.list_flags>\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"list_flags\", (attribute, object) -> {\r\n            return DenizenCore.serverFlagMap.doListFlagsTag(attribute);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.flag_map[<name>|...]>\r\n        // @returns MapTag\r\n        // @description\r\n        // See <@link tag FlaggableObject.flag_map>\r\n        // -->\r\n        tagProcessor.registerTag(MapTag.class, \"flag_map\", (attribute, object) -> {\r\n            return DenizenCore.serverFlagMap.doFlagMapTag(attribute);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.gamerules>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all available gamerules on the server.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ListTag.class, \"gamerules\", (attribute, object) -> {\r\n            return new ListTag(Arrays.asList(GameRuleReflect.values()), gameRule -> new ElementTag(GameRuleReflect.getName(gameRule), true));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.commands>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all registered command names in Bukkit.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"commands\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            CommandScriptHelper.init();\r\n            return new ListTag(CommandScriptHelper.knownCommands.keySet());\r\n        }, \"list_commands\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.command_plugin[<name>]>\r\n        // @returns PluginTag\r\n        // @description\r\n        // Returns the plugin that created a command (if known).\r\n        // @example\r\n        // # Should show \"Denizen\".\r\n        // - narrate <server.command_plugin[ex].name>\r\n        // -->\r\n        tagProcessor.registerTag(PluginTag.class, ElementTag.class, \"command_plugin\", (attribute, object, input) -> {\r\n            PluginCommand command = Bukkit.getPluginCommand(input.asString());\r\n            return command != null ? new PluginTag(command.getPlugin()) : null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.art_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all known art types.\r\n        // Generally used with <@link tag EntityTag.painting> and <@link mechanism EntityTag.painting>.\r\n        // For the default (\"vanilla\") art types, see the \"Resource location\" column in <@link url https://minecraft.wiki/w/Painting#Canvases>.\r\n        // -->\r\n        registerEnumListTag(\"art_types\", Art.class);\r\n\r\n        // <--[tag]\r\n        // @attribute <server.advancement_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all registered advancement names.\r\n        // Generally used with <@link tag PlayerTag.has_advancement>.\r\n        // See also <@link url https://minecraft.wiki/w/Advancement>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"advancement_types\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag advancements = new ListTag();\r\n            Bukkit.advancementIterator().forEachRemaining(adv -> advancements.add(adv.getKey().toString()));\r\n            return advancements;\r\n        }, \"list_advancements\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.nbt_attribute_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all registered attribute names.\r\n        // Generally used with <@link tag EntityTag.has_attribute>.\r\n        // For the default (\"vanilla\") attribute types, see <@link url https://minecraft.wiki/w/Attribute#Attributes>.\r\n        // -->\r\n        registerEnumListTag(\"nbt_attribute_types\", org.bukkit.attribute.Attribute.class, \"list_nbt_attribute_types\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.damage_causes>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all registered damage causes.\r\n        // Generally used with <@link event entity damaged>.\r\n        // This is only their Bukkit enum names, as seen at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html>.\r\n        // -->\r\n        registerEnumListTag(\"damage_causes\", EntityDamageEvent.DamageCause.class, \"list_damage_causes\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.teleport_causes>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all registered player teleport causes.\r\n        // Generally used with <@link event entity teleports>.\r\n        // See <@link language teleport cause> for the current list of causes.\r\n        // -->\r\n        registerEnumListTag(\"teleport_causes\", PlayerTeleportEvent.TeleportCause.class);\r\n\r\n        // <--[tag]\r\n        // @attribute <server.biome_types>\r\n        // @returns ListTag(BiomeTag)\r\n        // @description\r\n        // Returns a list of all biomes known to the server, including custom added ones.\r\n        // Generally used with <@link objecttype BiomeTag>.\r\n        // See <@link url https://minecraft.wiki/w/Biome#List_of_biomes> for a list of all default (vanilla) biomes.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ListTag.class, \"biome_types\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag biomes = new ListTag();\r\n            for (Biome biome : Registry.BIOME) {\r\n                BiomeTag biomeTag = new BiomeTag(biome);\r\n                if (biomeTag.getBiome() != null) {\r\n                    biomes.addObject(biomeTag);\r\n                }\r\n            }\r\n            return biomes;\r\n        }, \"list_biome_types\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.enchantments>\r\n        // @returns ListTag(EnchantmentTag)\r\n        // @description\r\n        // Returns a list of all enchantments known to the server.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"enchantments\", (attribute, object) -> {\r\n            ListTag enchantments = new ListTag();\r\n            for (Enchantment enchantment : Enchantment.values()) {\r\n                enchantments.addObject(new EnchantmentTag(enchantment));\r\n            }\r\n            return enchantments;\r\n        });\r\n\r\n        tagProcessor.registerTag(ListTag.class, \"enchantment_types\", (attribute, object) -> {\r\n            BukkitImplDeprecations.echantmentTagUpdate.warn(attribute.context);\r\n            listDeprecateWarn(attribute);\r\n            ListTag enchants = new ListTag();\r\n            for (Enchantment e : Enchantment.values()) {\r\n                enchants.add(e.getName());\r\n            }\r\n            return enchants;\r\n        }, \"list_enchantments\");\r\n\r\n        tagProcessor.registerTag(ListTag.class, \"enchantment_keys\", (attribute, object) -> {\r\n            BukkitImplDeprecations.echantmentTagUpdate.warn(attribute.context);\r\n            listDeprecateWarn(attribute);\r\n            ListTag enchants = new ListTag();\r\n            for (Enchantment e : Enchantment.values()) {\r\n                enchants.add(e.getKey().getKey());\r\n            }\r\n            return enchants;\r\n        }, \"list_enchantment_keys\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.entity_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all entity types known to the server.\r\n        // Generally used with <@link objecttype EntityTag>.\r\n        // For the default (\"vanilla\") entity types, see <@link url https://minecraft.wiki/w/Java_Edition_data_values#Entities>.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ListTag.class, \"entity_types\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                return Utilities.registryKeys(Registry.ENTITY_TYPE);\r\n            }\r\n            ListTag entityTypes = new ListTag();\r\n            for (EntityType entityType : EntityType.values()) {\r\n                if (entityType != EntityType.UNKNOWN) {\r\n                    entityTypes.add(entityType.name());\r\n                }\r\n            }\r\n            return entityTypes;\r\n        }, \"list_entity_types\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.material_types>\r\n        // @returns ListTag(MaterialTag)\r\n        // @description\r\n        // Returns a list of all materials known to the server.\r\n        // Generally used with <@link objecttype MaterialTag>.\r\n        // This is only types listed in the Bukkit Material enum, as seen at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html>.\r\n        // Note: Some materials might be disabled in specific worlds, check using <@link tag MaterialTag.is_enabled>.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ListTag.class, \"material_types\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag materials = new ListTag();\r\n            for (Material material : Material.values()) {\r\n                materials.addObject(new MaterialTag(material));\r\n            }\r\n            return materials;\r\n        }, \"list_material_types\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.sound_types>\r\n        // @returns ListTag\r\n        // @deprecated Use 'server.sound_keys' on MC 1.21+.\r\n        // @description\r\n        // Deprecated in favor of <@link tag server.sound_keys> on MC 1.21+.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ListTag.class, \"sound_types\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n                BukkitImplDeprecations.oldSpigotNames.warn(attribute.context);\r\n            }\r\n            return Utilities.listLegacyTypes(Sound.class);\r\n        }, \"list_sounds\");\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <server.sound_keys>\r\n            // @returns ListTag\r\n            // @description\r\n            // Returns a list of the keys/names of all sounds known to the server.\r\n            // -->\r\n            tagProcessor.registerStaticTag(ListTag.class, \"sound_keys\", (attribute, object) -> {\r\n                return Utilities.registryKeys(Registry.SOUNDS);\r\n            });\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <server.particle_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all particle effect types known to the server.\r\n        // Generally used with <@link command playeffect>.\r\n        // For the default (\"vanilla\") particle types, see <@link url https://minecraft.wiki/w/Particles_(Java_Edition)#Types_of_particles>.\r\n        // Refer also to <@link tag server.effect_types>.\r\n        // -->\r\n        registerEnumListTag(\"particle_types\", Particle.class, \"list_particles\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.effect_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all 'effect' types known to the server.\r\n        // Generally used with <@link command playeffect>.\r\n        // This is only their Bukkit enum names, as seen at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Effect.html>.\r\n        // Refer also to <@link tag server.particle_types>.\r\n        // -->\r\n        registerEnumListTag(\"effect_types\", Effect.class, \"list_effects\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.pattern_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all banner patterns known to the server.\r\n        // Generally used with <@link tag ItemTag.patterns>.\r\n        // For the default (\"vanilla\") pattern types, see the \"Resource name\" column in <@link url https://minecraft.wiki/w/Banner/Patterns>.\r\n        // -->\r\n        registerEnumListTag(\"pattern_types\", PatternType.class, \"list_patterns\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.potion_effect_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all potion effects known to the server.\r\n        // Can be used with <@link command cast>.\r\n        // This is only their Bukkit enum names, as seen at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/potion/PotionEffectType.html>.\r\n        // Refer also to <@link tag server.potion_types>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"potion_effect_types\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag potionEffects = new ListTag();\r\n            for (PotionEffectType potionEffect : PotionEffectType.values()) {\r\n                if (potionEffect != null) {\r\n                    String name = potionEffect.getName();\r\n                    if (name.startsWith(\"minecraft:\")) {\r\n                        name = CoreUtilities.toUpperCase(name.substring(\"minecraft:\".length()));\r\n                    }\r\n                    potionEffects.add(name);\r\n                }\r\n            }\r\n            return potionEffects;\r\n        }, \"list_potion_effects\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.potion_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all potion types known to the server, including their \"strong\" and extended variants.\r\n        // For the default (\"vanilla\") potion types, see the table in <@link url https://minecraft.wiki/w/Potion#Item_data>.\r\n        // Refer also to <@link tag server.potion_effect_types>.\r\n        // -->\r\n        registerEnumListTag(\"potion_types\", PotionType.class, \"list_potion_types\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.tree_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all tree types known to the server.\r\n        // Generally used with <@link mechanism LocationTag.generate_tree>.\r\n        // This is only their Bukkit enum names, as seen at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/TreeType.html>.\r\n        // -->\r\n        registerEnumListTag(\"tree_types\", TreeType.class, \"list_tree_types\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.map_cursor_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all map cursor types known to the server.\r\n        // Generally used with <@link command map> and <@link language Map Script Containers>.\r\n        // For the default (\"vanilla\") map cursor types, see the \"Text ID\" column in <@link url https://minecraft.wiki/w/Map#Map_icons>.\r\n        // -->\r\n        // TODO once 1.20 is the minimum supported version, replace with direct registry-based handling\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            tagProcessor.registerStaticTag(ListTag.class, \"map_cursor_types\", (attribute, object) -> {\r\n                listDeprecateWarn(attribute);\r\n                return Utilities.registryKeys(Registry.MAP_DECORATION_TYPE);\r\n            }, \"list_map_cursor_types\");\r\n        }\r\n        else {\r\n            registerEnumListTag(\"map_cursor_types\", MapCursor.Type.class, \"list_map_cursor_types\");\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <server.world_types>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all world types known to the server.\r\n        // Generally used with <@link command createworld>.\r\n        // This is only their Bukkit enum names, as seen at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/WorldType.html>.\r\n        // -->\r\n        registerEnumListTag(\"world_types\", WorldType.class, \"list_world_types\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.statistic_types[(<type>)]>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all statistic types known to the server.\r\n        // Generally used with <@link tag PlayerTag.statistic>.\r\n        // This is only their Bukkit enum names, as seen at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Statistic.html>.\r\n        // Optionally, specify a type to limit to statistics of a given type. Can be any of <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Statistic.Type.html>.\r\n        // Refer also to <@link tag server.statistic_type>.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ListTag.class, \"statistic_types\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            Statistic.Type type = attribute.hasParam() ? attribute.getParamElement().asEnum(Statistic.Type.class) : null;\r\n            ListTag statistics = new ListTag();\r\n            for (Statistic statistic : Statistic.values()) {\r\n                if (type == null || type == statistic.getType()) {\r\n                    statistics.add(statistic.name());\r\n                }\r\n            }\r\n            return statistics;\r\n        }, \"list_statistics\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.structure_types>\r\n        // @returns ListTag\r\n        // @deprecated use 'server.structures' on 1.19+.\r\n        // @description\r\n        // Deprecated in favor of <@link tag server.structures> on 1.19+.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"structure_types\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n                BukkitImplDeprecations.oldStructureTypes.warn(attribute.context);\r\n            }\r\n            return new ListTag(StructureType.getStructureTypes().keySet());\r\n        }, \"list_structure_types\");\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) {\r\n\r\n            // <--[tag]\r\n            // @attribute <server.structures>\r\n            // @returns ListTag\r\n            // @description\r\n            // Returns a list of all structures known to the server, including custom ones added by datapacks.\r\n            // For more information and a list of default structures, see <@link url https://minecraft.wiki/w/Structure>.\r\n            // For locating specific structures, see <@link language Structure lookups>.\r\n            // -->\r\n            tagProcessor.registerTag(ListTag.class, \"structures\", (attribute, object) -> {\r\n                return Utilities.registryKeys(Registry.STRUCTURE);\r\n            });\r\n        }\r\n\r\n        // <--[tag]\r\n        // @attribute <server.statistic_type[<statistic>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the qualifier type of the given statistic, will be one of <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Statistic.Type.html>.\r\n        // Generally relevant to usage with <@link tag PlayerTag.statistic.qualifier>.\r\n        // Refer also to <@link tag server.statistic_types>.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ElementTag.class, ElementTag.class, \"statistic_type\", (attribute, object, input) -> {\r\n            Statistic statistic = input.asEnum(Statistic.class);\r\n            if (statistic == null) {\r\n                attribute.echoError(\"Statistic '\" + input + \"' does not exist.\");\r\n                return null;\r\n            }\r\n            return new ElementTag(statistic.getType());\r\n        });\r\n\r\n        tagProcessor.registerTag(ElementTag.class, EnchantmentTag.class, \"enchantment_max_level\", (attribute, object, input) -> {\r\n            BukkitImplDeprecations.echantmentTagUpdate.warn(attribute.context);\r\n            return new ElementTag(input.enchantment.getMaxLevel());\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, EnchantmentTag.class, \"enchantment_start_level\", (attribute, object, input) -> {\r\n            BukkitImplDeprecations.echantmentTagUpdate.warn(attribute.context);\r\n            return new ElementTag(input.enchantment.getStartLevel());\r\n        });\r\n\r\n        // Historical variants of \"notes\" tag\r\n        tagProcessor.registerTag(ListTag.class, \"notables\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            BukkitImplDeprecations.serverUtilTags.warn(attribute.context);\r\n            ListTag allNotables = new ListTag();\r\n            if (attribute.hasParam()) {\r\n                String type = CoreUtilities.toLowerCase(attribute.getParam());\r\n                for (Map.Entry<String, Class> typeClass : NoteManager.namesToTypes.entrySet()) {\r\n                    if (type.equals(CoreUtilities.toLowerCase(typeClass.getKey()))) {\r\n                        for (Object notable : NoteManager.getAllType(typeClass.getValue())) {\r\n                            allNotables.addObject((ObjectTag) notable);\r\n                        }\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            else {\r\n                for (Notable notable : NoteManager.nameToObject.values()) {\r\n                    allNotables.addObject((ObjectTag) notable);\r\n                }\r\n            }\r\n            return allNotables;\r\n        }, \"list_notables\");\r\n        // Historical variant of \"sql_connections\" tag\r\n        tagProcessor.registerTag(ListTag.class, \"list_sql_connections\", (attribute, object) -> {\r\n            BukkitImplDeprecations.listStyleTags.warn(attribute.context);\r\n            BukkitImplDeprecations.serverUtilTags.warn(attribute.context);\r\n            ListTag list = new ListTag();\r\n            for (Map.Entry<String, Connection> entry : SQLCommand.connections.entrySet()) {\r\n                try {\r\n                    if (!entry.getValue().isClosed()) {\r\n                        list.add(entry.getKey());\r\n                    }\r\n                    else {\r\n                        SQLCommand.connections.remove(entry.getKey());\r\n                    }\r\n                }\r\n                catch (SQLException e) {\r\n                    Debug.echoError(attribute.getScriptEntry(), e);\r\n                }\r\n            }\r\n            return list;\r\n        });\r\n        // Historical variant of \"scripts\" tag\r\n        tagProcessor.registerTag(ListTag.class, \"list_scripts\", (attribute, object) -> {\r\n            BukkitImplDeprecations.listStyleTags.warn(attribute.context);\r\n            BukkitImplDeprecations.serverUtilTags.warn(attribute.context);\r\n            ListTag scripts = new ListTag();\r\n            for (ScriptContainer script : ScriptRegistry.scriptContainers.values()) {\r\n                scripts.addObject(new ScriptTag(script));\r\n            }\r\n            return scripts;\r\n        });\r\n        // Pre-timetag-rewrite historical tag\r\n        tagProcessor.registerTag(DurationTag.class, \"start_time\", (attribute, object) -> {\r\n            Deprecations.timeTagRewrite.warn(attribute.context);\r\n            return new DurationTag(CoreUtilities.monotonicMillisToReal(DenizenCore.startTime) / 50);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.has_permissions>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the server has a known permission plugin loaded.\r\n        // Note: should not be considered incredibly reliable.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ElementTag.class, \"has_permissions\", (attribute, object) -> {\r\n            return new ElementTag(Depends.permissions != null && Depends.permissions.isEnabled());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.has_economy>\r\n        // @returns ElementTag(Boolean)\r\n        // @plugin Vault\r\n        // @description\r\n        // Returns whether the server has a known economy plugin loaded.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"has_economy\", (attribute, object) -> {\r\n            return new ElementTag(Depends.economy != null && Depends.economy.isEnabled());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.denizen_version>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the version of Denizen currently being used.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ElementTag.class, \"denizen_version\", (attribute, object) -> {\r\n            return new ElementTag(Denizen.versionTag);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.bukkit_name>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the name of the Bukkit platform, such as \"Paper\".\r\n        // -->\r\n        tagProcessor.registerStaticTag(ElementTag.class, \"bukkit_name\", (attribute, object) -> {\r\n            return new ElementTag(Bukkit.getName());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.bukkit_version>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the version of Bukkit currently being used.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ElementTag.class, \"bukkit_version\", (attribute, object) -> {\r\n            return new ElementTag(Bukkit.getBukkitVersion());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.version>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the version of the server.\r\n        // -->\r\n        tagProcessor.registerStaticTag(ElementTag.class, \"version\", (attribute, object) -> {\r\n            return new ElementTag(Bukkit.getVersion());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.max_players>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the maximum number of players allowed on the server.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"max_players\", (attribute, object) -> {\r\n            return new ElementTag(Bukkit.getMaxPlayers());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.group_prefix[<group>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns an ElementTag of a group's chat prefix.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"group_prefix\", (attribute, object, input) -> {\r\n            if (Depends.permissions == null) {\r\n                attribute.echoError(\"No permission system loaded! Have you installed Vault and a compatible permissions plugin?\");\r\n                return null;\r\n            }\r\n\r\n            String group = input.asString();\r\n            if (!Arrays.asList(Depends.permissions.getGroups()).contains(group)) {\r\n                attribute.echoError(\"Invalid group! '\" + group + \"' could not be found.\");\r\n                return null;\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.group_prefix[<group>].world[<world>]>\r\n            // @returns ElementTag\r\n            // @description\r\n            // Returns an ElementTag of a group's chat prefix for the specified WorldTag.\r\n            // -->\r\n            if (attribute.startsWith(\"world\", 2)) {\r\n                attribute.fulfill(1);\r\n                WorldTag world = attribute.paramAsType(WorldTag.class);\r\n                return world != null ? new ElementTag(Depends.chat.getGroupPrefix(world.getWorld(), group)) : null;\r\n            }\r\n\r\n            // Prefix in default world\r\n            return new ElementTag(Depends.chat.getGroupPrefix(Bukkit.getWorlds().get(0), group));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.group_suffix[<group>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns an ElementTag of a group's chat suffix.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"group_suffix\", (attribute, object, input) -> {\r\n            if (Depends.permissions == null) {\r\n                attribute.echoError(\"No permission system loaded! Have you installed Vault and a compatible permissions plugin?\");\r\n                return null;\r\n            }\r\n\r\n            String group = input.asString();\r\n            if (!Arrays.asList(Depends.permissions.getGroups()).contains(group)) {\r\n                attribute.echoError(\"Invalid group! '\" + group + \"' could not be found.\");\r\n                return null;\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.group_suffix[<group>].world[<world>]>\r\n            // @returns ElementTag\r\n            // @description\r\n            // Returns an ElementTag of a group's chat suffix for the specified WorldTag.\r\n            // -->\r\n            if (attribute.startsWith(\"world\", 2)) {\r\n                attribute.fulfill(1);\r\n                WorldTag world = attribute.paramAsType(WorldTag.class);\r\n                return world != null ? new ElementTag(Depends.chat.getGroupSuffix(world.getWorld(), group)) : null;\r\n            }\r\n\r\n            // Suffix in default world\r\n            return new ElementTag(Depends.chat.getGroupSuffix(Bukkit.getWorlds().get(0), group));\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.permission_groups>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all permission groups on the server.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"permission_groups\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            if (Depends.permissions == null) {\r\n                attribute.echoError(\"No permission system loaded! Have you installed Vault and a compatible permissions plugin?\");\r\n                return null;\r\n            }\r\n            return new ListTag(Arrays.asList(Depends.permissions.getGroups()));\r\n        }, \"list_permission_groups\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.match_player[<name>]>\r\n        // @returns PlayerTag\r\n        // @description\r\n        // Returns the online player that best matches the input name.\r\n        // EG, in a group of 'bo', 'bob', and 'bobby'... input 'bob' returns player object for 'bob',\r\n        // input 'bobb' returns player object for 'bobby', and input 'b' returns player object for 'bo'.\r\n        // -->\r\n        tagProcessor.registerTag(PlayerTag.class, ElementTag.class, \"match_player\", (attribute, object, input) -> {\r\n            Player matchPlayer = null;\r\n            String matchInput = input.asLowerString();\r\n            if (matchInput.isEmpty()) {\r\n                return null;\r\n            }\r\n            for (Player player : Bukkit.getOnlinePlayers()) {\r\n                String nameLow = CoreUtilities.toLowerCase(player.getName());\r\n                if (nameLow.equals(matchInput)) {\r\n                    matchPlayer = player;\r\n                    break;\r\n                }\r\n                else if (nameLow.contains(matchInput)) {\r\n                    if (matchPlayer == null || nameLow.startsWith(matchInput)) {\r\n                        matchPlayer = player;\r\n                    }\r\n                }\r\n            }\r\n            return matchPlayer != null ? new PlayerTag(matchPlayer) : null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.match_offline_player[<name>]>\r\n        // @returns PlayerTag\r\n        // @description\r\n        // Returns any player (online or offline) that best matches the input name.\r\n        // EG, in a group of 'bo', 'bob', and 'bobby'... input 'bob' returns player object for 'bob',\r\n        // input 'bobb' returns player object for 'bobby', and input 'b' returns player object for 'bo'.\r\n        // When both an online player and an offline player match the name search, the online player will be returned.\r\n        // -->\r\n        tagProcessor.registerTag(PlayerTag.class, ElementTag.class, \"match_offline_player\", (attribute, object, input) -> {\r\n            PlayerTag matchPlayer = null;\r\n            String matchInput = input.asLowerString();\r\n            if (matchInput.isEmpty()) {\r\n                return null;\r\n            }\r\n            for (Map.Entry<String, UUID> entry : PlayerTag.getAllPlayers().entrySet()) {\r\n                String nameLow = CoreUtilities.toLowerCase(entry.getKey());\r\n                if (nameLow.equals(matchInput)) {\r\n                    matchPlayer = new PlayerTag(entry.getValue());\r\n                    break;\r\n                }\r\n                else if (nameLow.contains(matchInput)) {\r\n                    PlayerTag newMatch = new PlayerTag(entry.getValue());\r\n                    if (matchPlayer == null) {\r\n                        matchPlayer = newMatch;\r\n                    }\r\n                    else if (newMatch.isOnline() && !matchPlayer.isOnline()) {\r\n                        matchPlayer = newMatch;\r\n                    }\r\n                    else if (nameLow.startsWith(matchInput) && (newMatch.isOnline() == matchPlayer.isOnline())) {\r\n                        matchPlayer = newMatch;\r\n                    }\r\n                }\r\n            }\r\n            return matchPlayer;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.online_players_flagged[<flag_name>]>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of all online players with a specified flag set.\r\n        // Can use \"!<flag_name>\" style to only return players *without* the flag.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"online_players_flagged\", (attribute, object, input) -> {\r\n            listDeprecateWarn(attribute);\r\n            String flag = input.asString();\r\n            ListTag flaggedPlayers = new ListTag();\r\n            boolean want = true;\r\n            if (flag.startsWith(\"!\")) {\r\n                want = false;\r\n                flag = flag.substring(1);\r\n            }\r\n            for (Player player : Bukkit.getOnlinePlayers()) {\r\n                PlayerTag playerTag = new PlayerTag(player);\r\n                if (playerTag.getFlagTracker().hasFlag(flag) == want) {\r\n                    flaggedPlayers.addObject(playerTag);\r\n                }\r\n            }\r\n            return flaggedPlayers;\r\n        }, \"list_online_players_flagged\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.players_flagged[<flag_name>]>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of all players (online or offline) with a specified flag set.\r\n        // Warning: this will cause the player flag cache to temporarily fill with ALL historical playerdata.\r\n        // Can use \"!<flag_name>\" style to only return players *without* the flag.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"players_flagged\", (attribute, object, input) -> {\r\n            listDeprecateWarn(attribute);\r\n            String flag = input.asString();\r\n            ListTag flaggedPlayers = new ListTag();\r\n            boolean want = true;\r\n            if (flag.startsWith(\"!\")) {\r\n                want = false;\r\n                flag = flag.substring(1);\r\n            }\r\n            for (UUID playerId : PlayerTag.getAllPlayers().values()) {\r\n                PlayerTag player = new PlayerTag(playerId);\r\n                if (player.getFlagTracker().hasFlag(flag) == want) {\r\n                    flaggedPlayers.addObject(player);\r\n                }\r\n            }\r\n            return flaggedPlayers;\r\n        }, \"list_players_flagged\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.worlds>\r\n        // @returns ListTag(WorldTag)\r\n        // @description\r\n        // Returns a list of all worlds.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"worlds\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag worlds = new ListTag();\r\n            for (World world : Bukkit.getWorlds()) {\r\n                worlds.addObject(new WorldTag(world));\r\n            }\r\n            return worlds;\r\n        }, \"list_worlds\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.plugins>\r\n        // @returns ListTag(PluginTag)\r\n        // @description\r\n        // Gets a list of currently enabled PluginTags from the server.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"plugins\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag plugins = new ListTag();\r\n            for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {\r\n                plugins.addObject(new PluginTag(plugin));\r\n            }\r\n            return plugins;\r\n        }, \"list_plugins\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.players>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of all players that have ever played on the server, online or not.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"players\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            OfflinePlayer[] allPlayers = Bukkit.getOfflinePlayers();\r\n            ListTag players = new ListTag(allPlayers.length);\r\n            for (OfflinePlayer player : allPlayers) {\r\n                players.addObject(PlayerTag.mirrorBukkitPlayer(player));\r\n            }\r\n            return players;\r\n        }, \"list_players\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.online_players>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of all online players.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"online_players\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag players = new ListTag();\r\n            for (Player player : Bukkit.getOnlinePlayers()) {\r\n                players.addObject(PlayerTag.mirrorBukkitPlayer(player));\r\n            }\r\n            return players;\r\n        }, \"list_online_players\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.offline_players>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of all offline players.\r\n        // This specifically excludes currently online players.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"offline_players\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag players = new ListTag();\r\n            for (OfflinePlayer player : Bukkit.getOfflinePlayers()) {\r\n                if (!player.isOnline()) {\r\n                    players.addObject(PlayerTag.mirrorBukkitPlayer(player));\r\n                }\r\n            }\r\n            return players;\r\n        }, \"list_offline_players\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.banned_players>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of all banned players.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"banned_players\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag banned = new ListTag();\r\n            for (OfflinePlayer player : Bukkit.getBannedPlayers()) {\r\n                banned.addObject(PlayerTag.mirrorBukkitPlayer(player));\r\n            }\r\n            return banned;\r\n        }, \"list_banned_players\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.banned_addresses>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all banned ip addresses.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"banned_addresses\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag bannedIPs = new ListTag();\r\n            bannedIPs.addAll(Bukkit.getIPBans());\r\n            return bannedIPs;\r\n        }, \"list_banned_addresses\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.is_banned[<address>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @description\r\n        // Returns whether the given ip address is banned.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"is_banned\", (attribute, object, input) -> {\r\n            // BanList contains an isBanned method that doesn't check expiration time\r\n            BanEntry ban = Bukkit.getBanList(BanList.Type.IP).getBanEntry(input.asString());\r\n            if (ban == null) {\r\n                return new ElementTag(false);\r\n            }\r\n            return new ElementTag(ban.getExpiration() == null || ban.getExpiration().after(new Date()));\r\n        });\r\n\r\n        tagProcessor.registerTag(ObjectTag.class, ElementTag.class, \"ban_info\", (attribute, object, input) -> {\r\n            BanEntry ban = Bukkit.getBanList(BanList.Type.IP).getBanEntry(input.asString());\r\n            Date expiration;\r\n            if (ban == null || ((expiration = ban.getExpiration()) != null && expiration.before(new Date()))) {\r\n                return null;\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.ban_info[<address>].expiration_time>\r\n            // @returns TimeTag\r\n            // @description\r\n            // Returns the expiration of the ip address's ban, if it is banned.\r\n            // Potentially can be null.\r\n            // -->\r\n            if (attribute.startsWith(\"expiration_time\", 2)) {\r\n                attribute.fulfill(1);\r\n                return expiration != null ? new TimeTag(expiration.getTime()) : null;\r\n            }\r\n            if (attribute.startsWith(\"expiration\", 2)) {\r\n                attribute.fulfill(1);\r\n                Deprecations.timeTagRewrite.warn(attribute.context);\r\n                return expiration != null ? new DurationTag(expiration.getTime() / 50) : null;\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.ban_info[<address>].reason>\r\n            // @returns ElementTag\r\n            // @description\r\n            // Returns the reason for the ip address's ban, if it is banned.\r\n            // -->\r\n            if (attribute.startsWith(\"reason\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(ban.getReason());\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.ban_info[<address>].created_time>\r\n            // @returns TimeTag\r\n            // @description\r\n            // Returns when the ip address's ban was created, if it is banned.\r\n            // -->\r\n            if (attribute.startsWith(\"created_time\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new TimeTag(ban.getCreated().getTime());\r\n            }\r\n            if (attribute.startsWith(\"created\", 2)) {\r\n                attribute.fulfill(1);\r\n                Deprecations.timeTagRewrite.warn(attribute.context);\r\n                return new DurationTag(ban.getCreated().getTime() / 50);\r\n            }\r\n\r\n            // <--[tag]\r\n            // @attribute <server.ban_info[<address>].source>\r\n            // @returns ElementTag\r\n            // @description\r\n            // Returns the source of the ip address's ban, if it is banned.\r\n            // -->\r\n            if (attribute.startsWith(\"source\", 2)) {\r\n                attribute.fulfill(1);\r\n                return new ElementTag(ban.getSource());\r\n            }\r\n\r\n            return null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.ops>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of all ops, online or not.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"ops\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag ops = new ListTag();\r\n            for (OfflinePlayer player : Bukkit.getOperators()) {\r\n                ops.addObject(PlayerTag.mirrorBukkitPlayer(player));\r\n            }\r\n            return ops;\r\n        }, \"list_ops\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.online_ops>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of all online ops.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"online_ops\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag onlineOps = new ListTag();\r\n            for (OfflinePlayer player : Bukkit.getOperators()) {\r\n                if (player.isOnline()) {\r\n                    onlineOps.addObject(PlayerTag.mirrorBukkitPlayer(player));\r\n                }\r\n            }\r\n            return onlineOps;\r\n        }, \"list_online_ops\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.offline_ops>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of all offline ops.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"offline_ops\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag offlineOps = new ListTag();\r\n            for (OfflinePlayer player : Bukkit.getOperators()) {\r\n                if (!player.isOnline()) {\r\n                    offlineOps.addObject(PlayerTag.mirrorBukkitPlayer(player));\r\n                }\r\n            }\r\n            return offlineOps;\r\n        }, \"list_offline_ops\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.motd>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the server's current MOTD.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"motd\", (attribute, object) -> {\r\n            return new ElementTag(Bukkit.getMotd());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.view_distance>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the server's current view distance.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"view_distance\", (attribute, object) -> {\r\n            return new ElementTag(Bukkit.getViewDistance());\r\n        });\r\n\r\n        tagProcessor.registerTag(ElementTag.class, ObjectTag.class, \"entity_is_spawned\", (attribute, object, input) -> {\r\n            BukkitImplDeprecations.isValidTag.warn(attribute.context);\r\n            EntityTag entity = input.canBeType(EntityTag.class) ? input.asType(EntityTag.class, attribute.context) : null;\r\n            return new ElementTag(entity != null && entity.isUnique() && entity.isSpawnedOrValidForTag());\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, ElementTag.class, \"player_is_valid\", (attribute, object, input) -> {\r\n            BukkitImplDeprecations.isValidTag.warn(attribute.context);\r\n            return new ElementTag(PlayerTag.playerNameIsValid(input.asString()));\r\n        });\r\n        tagProcessor.registerTag(ElementTag.class, ObjectTag.class, \"npc_is_valid\", (attribute, object, input) -> {\r\n            BukkitImplDeprecations.isValidTag.warn(attribute.context);\r\n            NPCTag npc = input.canBeType(NPCTag.class) ? input.asType(NPCTag.class, attribute.context) : null;\r\n            return new ElementTag(npc != null && npc.isValid());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.current_bossbars>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all currently active boss bar IDs from <@link command bossbar>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"current_bossbars\", (attribute, context) -> {\r\n            return new ListTag(BossBarCommand.bossBarMap.keySet());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.bossbar_viewers[<bossbar_id>]>\r\n        // @returns ListTag(PlayerTag)\r\n        // @description\r\n        // Returns a list of players that should be able to see the given bossbar ID from <@link command bossbar>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"bossbar_viewers\", (attribute, object, input) -> {\r\n            BossBar bar = BossBarCommand.bossBarMap.get(input.asLowerString());\r\n            if (bar == null) {\r\n                return null;\r\n            }\r\n            ListTag viewers = new ListTag();\r\n            for (Player player : bar.getPlayers()) {\r\n                viewers.addObject(new PlayerTag(player));\r\n            }\r\n            return viewers;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.recent_tps>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns the 3 most recent ticks per second measurements.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"recent_tps\", (attribute, object) -> {\r\n            ListTag recentTPS = new ListTag(3);\r\n            for (double tps : PaperAPITools.instance.getRecentTps()) {\r\n                recentTPS.addObject(new ElementTag(tps));\r\n            }\r\n            return recentTPS;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.port>\r\n        // @returns ElementTag(Number)\r\n        // @description\r\n        // Returns the port that the server is running on.\r\n        // -->\r\n        tagProcessor.registerTag(ElementTag.class, \"port\", (attribute, object) -> {\r\n            return new ElementTag(Bukkit.getPort());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.idle_timeout>\r\n        // @returns DurationTag\r\n        // @mechanism server.idle_timeout\r\n        // @description\r\n        // Returns the server's current idle timeout limit (how long a player can sit still before getting kicked).\r\n        // Internally used with <@link tag PlayerTag.last_action_time>.\r\n        // -->\r\n        tagProcessor.registerTag(DurationTag.class, \"idle_timeout\", (attribute, object) -> {\r\n            return new DurationTag(Bukkit.getIdleTimeout() * 60);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.vanilla_entity_tags>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of vanilla tags applicable to entity types. See also <@link url https://minecraft.wiki/w/Tag>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"vanilla_entity_tags\", (attribute, object) -> {\r\n            return new ListTag(VanillaTagHelper.entityTagsByKey.keySet());\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.vanilla_tagged_entities[<tag>]>\r\n        // @returns ListTag(EntityTag)\r\n        // @description\r\n        // Returns a list of entity types referred to by the specified vanilla tag. See also <@link url https://minecraft.wiki/w/Tag>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"vanilla_tagged_entities\", (attribute, object, tag) -> {\r\n            Set<EntityType> entityTypes = VanillaTagHelper.entityTagsByKey.get(tag.asLowerString());\r\n            if (entityTypes == null) {\r\n                return null;\r\n            }\r\n            ListTag taggedEntities = new ListTag(entityTypes.size());\r\n            for (EntityType entityType : entityTypes) {\r\n                taggedEntities.addObject(new EntityTag(entityType));\r\n            }\r\n            return taggedEntities;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.vanilla_material_tags>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of vanilla tags applicable to blocks, fluids, or items. See also <@link url https://minecraft.wiki/w/Tag>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"vanilla_material_tags\", (attribute, object) -> {\r\n            return new ListTag(VanillaTagHelper.materialTagsByKey.keySet());\r\n        }, \"vanilla_tags\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.vanilla_tagged_materials[<tag>]>\r\n        // @returns ListTag(MaterialTag)\r\n        // @description\r\n        // Returns a list of materials referred to by the specified vanilla tag. See also <@link url https://minecraft.wiki/w/Tag>.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"vanilla_tagged_materials\", (attribute, object, tag) -> {\r\n            Set<Material> materials = VanillaTagHelper.materialTagsByKey.get(tag.asLowerString());\r\n            if (materials == null) {\r\n                return null;\r\n            }\r\n            ListTag taggedMaterials = new ListTag(materials.size());\r\n            for (Material material : materials) {\r\n                taggedMaterials.addObject(new MaterialTag(material));\r\n            }\r\n            return taggedMaterials;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.plugins_handling_event[<bukkit_event>]>\r\n        // @returns ListTag(PluginTag)\r\n        // @description\r\n        // Returns a list of all plugins that handle a given Bukkit event.\r\n        // Can specify by ScriptEvent name (\"PlayerBreaksBlock\"), or by full Bukkit class name (\"org.bukkit.event.block.BlockBreakEvent\").\r\n        // This is a primarily a dev tool and is not necessarily useful to most players or scripts.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"plugins_handling_event\", (attribute, object, input) -> {\r\n            listDeprecateWarn(attribute);\r\n            String eventName = input.asString();\r\n            if (CoreUtilities.contains(eventName, '.')) {\r\n                try {\r\n                    Class<?> clazz = Class.forName(eventName, false, ServerTagBase.class.getClassLoader());\r\n                    ListTag result = getHandlerPluginList(clazz);\r\n                    if (result != null) {\r\n                        return result;\r\n                    }\r\n                }\r\n                catch (ClassNotFoundException ex) {\r\n                    if (!attribute.hasAlternative()) {\r\n                        Debug.echoError(ex);\r\n                    }\r\n                }\r\n            }\r\n            else {\r\n                ScriptEvent scriptEvent = ScriptEvent.eventLookup.get(input.asLowerString());\r\n                if (scriptEvent instanceof Listener listener) {\r\n                    Plugin plugin = Denizen.getInstance();\r\n                    for (Class<? extends Event> eventClass : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).keySet()) {\r\n                        ListTag result = getHandlerPluginList(eventClass);\r\n                        // Return results for the first valid match.\r\n                        if (result != null && result.size() > 0) {\r\n                            return result;\r\n                        }\r\n                    }\r\n                    return new ListTag();\r\n                }\r\n            }\r\n            return null;\r\n        }, \"list_plugins_handling_event\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.generate_loot_table[id=<id>;location=<location>;(killer=<entity>);(entity=<entity>);(loot_bonus=<#>/{-1});(luck=<#.#>/{0})]>\r\n        // @returns ListTag(ItemTag)\r\n        // @description\r\n        // Returns a list of items from a loot table, given a map of input data.\r\n        // Required input: id: the loot table ID, location: the location where it's being generated (LocationTag).\r\n        // Optional inputs: killer: an online player (or player-type NPC) that is looting, entity: a dead entity being looted from (a valid EntityTag instance that is or was spawned in the world),\r\n        // loot_bonus: the loot bonus level (defaults to -1) as an integer number, luck: the luck potion level (defaults to 0) as a decimal number.\r\n        //\r\n        // Some inputs will be strictly required for some loot tables, and ignored for others.\r\n        //\r\n        // A list of valid loot tables can be found here: <@link url https://minecraft.wiki/w/Loot_table#List_of_loot_tables>\r\n        // Note that the tree view represented on the wiki should be split by slashes for the input - for example, \"cow\" is under \"entities\" in the tree so \"entities/cow\" is how you input that.\r\n        // CAUTION: Invalid loot table IDs will generate an empty list rather than an error.\r\n        //\r\n        // @example\r\n        // - give <server.generate_loot_table[id=chests/spawn_bonus_chest;killer=<player>;location=<player.location>]>\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, MapTag.class, \"generate_loot_table\", (attribute, object, map) -> {\r\n            ElementTag idObj = map.getRequiredObjectAs(\"id\", ElementTag.class, attribute);\r\n            LocationTag locationObj = map.getRequiredObjectAs(\"location\", LocationTag.class, attribute);\r\n            if (idObj == null || locationObj == null) {\r\n                return null;\r\n            }\r\n            LootTable table = Bukkit.getLootTable(Utilities.parseNamespacedKey(idObj.asLowerString()));\r\n            if (table == null) {\r\n                attribute.echoError(\"Invalid loot table ID '\" + idObj + \"' specified.\");\r\n                return null;\r\n            }\r\n            LootContext.Builder context = new LootContext.Builder(locationObj);\r\n            EntityTag killer = map.getObjectAs(\"killer\", EntityTag.class, attribute.context);\r\n            ElementTag luck = map.getElement(\"luck\");\r\n            ElementTag bonus = map.getElement(\"loot_bonus\");\r\n            EntityTag entity = map.getObjectAs(\"entity\", EntityTag.class, attribute.context);\r\n            if (entity != null) {\r\n                context = context.lootedEntity(entity.getBukkitEntity());\r\n            }\r\n            if (killer != null) {\r\n                if (killer.getLivingEntity() instanceof HumanEntity humanEntity) {\r\n                     context = context.killer(humanEntity);\r\n                }\r\n                else {\r\n                    attribute.echoError(\"Invalid killer '\" + killer + \"' specified: must be an online player or a player-type NPC.\");\r\n                }\r\n            }\r\n            if (luck != null) {\r\n                context = context.luck(luck.asFloat());\r\n            }\r\n            if (bonus != null) {\r\n                context = context.lootingModifier(bonus.asInt());\r\n            }\r\n            Collection<ItemStack> items;\r\n            try {\r\n                items = table.populateLoot(CoreUtilities.getRandom(), context.build());\r\n            }\r\n            catch (Throwable ex) {\r\n                attribute.echoError(\"Loot table failed to generate: \" + ex.getMessage());\r\n                if (CoreConfiguration.debugVerbose) {\r\n                    attribute.echoError(ex);\r\n                }\r\n                return null;\r\n            }\r\n            ListTag lootItems = new ListTag(items.size());\r\n            for (ItemStack item : items) {\r\n                lootItems.addObject(new ItemTag(item));\r\n            }\r\n            return lootItems;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.area_notes_debug>\r\n        // @returns MapTag\r\n        // @description\r\n        // Generates a report about noted area tracking.\r\n        // This tag is strictly for internal debugging reasons.\r\n        // -->\r\n        tagProcessor.registerTag(MapTag.class, \"area_notes_debug\", (attribute, object) -> {\r\n            MapTag worlds = new MapTag();\r\n            for (Map.Entry<String, NotedAreaTracker.PerWorldSet> set : NotedAreaTracker.worlds.entrySet()) {\r\n                MapTag worldData = new MapTag();\r\n                worldData.putObject(\"global\", new ListTag(set.getValue().globalSet.list, trackedArea -> trackedArea.area));\r\n                worldData.putObject(\"x50\", areaNotesDebug(set.getValue().sets50));\r\n                worldData.putObject(\"x50_offset\", areaNotesDebug(set.getValue().sets50_offset));\r\n                worldData.putObject(\"x200\", areaNotesDebug(set.getValue().sets200));\r\n                worldData.putObject(\"x200_offset\", areaNotesDebug(set.getValue().sets200_offset));\r\n                worlds.putObject(set.getKey(), worldData);\r\n            }\r\n            return worlds;\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name clean_flags\r\n        // @input None\r\n        // @description\r\n        // Cleans any expired flags from the object.\r\n        // Generally doesn't need to be called, using the 'skip flag cleanings' setting was enabled.\r\n        // This is an internal/special case mechanism, and should be avoided where possible.\r\n        // Does not function on all flaggable objects, particularly those that just store their flags into other objects.\r\n        // -->\r\n        tagProcessor.registerMechanism(\"clean_flags\", false, (object, mechanism) -> {\r\n            DenizenCore.serverFlagMap.doTotalClean();\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name reset_recipes\r\n        // @input None\r\n        // @description\r\n        // Resets the server's recipe list to the default vanilla recipe list + item script recipes.\r\n        // @tags\r\n        // <server.recipe_ids[(<type>)]>\r\n        // -->\r\n        tagProcessor.registerMechanism(\"reset_recipes\", false, (object, mechanism) -> {\r\n            Bukkit.resetRecipes();\r\n            ItemScriptHelper.rebuildRecipes();\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name remove_recipes\r\n        // @input ListTag\r\n        // @description\r\n        // Removes a recipe or list of recipes from the server, in Namespace:Key format.\r\n        // @example\r\n        // - adjust server remove_recipes:<item[torch].recipe_ids>\r\n        // @tags\r\n        // <server.recipe_ids[(<type>)]>\r\n        // -->\r\n        tagProcessor.registerMechanism(\"remove_recipes\", false, ListTag.class, (object, mechanism, recipes) -> {\r\n            for (String recipe : recipes) {\r\n                Bukkit.removeRecipe(Utilities.parseNamespacedKey(recipe));\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name idle_timeout\r\n        // @input DurationTag\r\n        // @description\r\n        // Sets the server's current idle timeout limit (how long a player can sit still before getting kicked).\r\n        // Will be rounded to the nearest number of minutes.\r\n        // Set to 0 to disable automatic timeout kick.\r\n        // @tags\r\n        // <server.idle_timeout>\r\n        // -->\r\n        tagProcessor.registerMechanism(\"idle_timeout\", false, DurationTag.class, (object, mechanism, timeout) -> {\r\n            Bukkit.setIdleTimeout((int) Math.round(timeout.getSeconds() / 60));\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name restart\r\n        // @input None\r\n        // @description\r\n        // Immediately stops the server entirely (Plugins will still finalize, and the shutdown event will fire), then starts it again.\r\n        // Requires config file setting \"Commands.Restart.Allow server restart\"!\r\n        // Note that if your server is not configured to restart, this mechanism will simply stop the server without starting it again!\r\n        // -->\r\n        tagProcessor.registerMechanism(\"restart\", false, (object, mechanism) -> {\r\n            if (!Settings.allowServerRestart()) {\r\n                Debug.echoError(\"Server restart disabled by administrator (refer to mechanism documentation). Consider using 'shutdown'.\");\r\n                return;\r\n            }\r\n            Bukkit.getConsoleSender().sendMessage(ChatColor.RED + \"+> Server restarted by a Denizen script, see config to prevent this!\");\r\n            Bukkit.spigot().restart();\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name save\r\n        // @input None\r\n        // @description\r\n        // Immediately saves the Denizen saves files.\r\n        // -->\r\n        tagProcessor.registerMechanism(\"save\", false, (object, mechanism) -> {\r\n            DenizenCore.saveAll(false);\r\n            Denizen.getInstance().saveSaves(false);\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name shutdown\r\n        // @input None\r\n        // @description\r\n        // Immediately stops the server entirely (Plugins will still finalize, and the shutdown event will fire).\r\n        // The server will remain shutdown until externally started again.\r\n        // Requires config file setting \"Commands.Restart.Allow server stop\"!\r\n        // -->\r\n        tagProcessor.registerMechanism(\"shutdown\", false, (object, mechanism) -> {\r\n            if (!Settings.allowServerStop()) {\r\n                Debug.echoError(\"Server stop disabled by administrator (refer to mechanism documentation). Consider using 'restart'.\");\r\n                return;\r\n            }\r\n            Bukkit.getConsoleSender().sendMessage(ChatColor.RED + \"+> Server shutdown by a Denizen script, see config to prevent this!\");\r\n            Bukkit.shutdown();\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name has_whitelist\r\n        // @input ElementTag(Boolean)\r\n        // @description\r\n        // Toggles whether the server's whitelist is enabled.\r\n        // @tags\r\n        // <server.has_whitelist>\r\n        // -->\r\n        tagProcessor.registerMechanism(\"has_whitelist\", false, ElementTag.class, (object, mechanism, input) -> {\r\n            if (mechanism.requireBoolean()) {\r\n                Bukkit.setWhitelist(input.asBoolean());\r\n            }\r\n        });\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name register_permission\r\n        // @input MapTag\r\n        // @description\r\n        // Input must be a map with the key 'name' set to the permission name.\r\n        // Can also set 'description' to a description of the permission.\r\n        // Can also set 'parent' to the name of the parent permission (must already be registered).\r\n        // Can also set 'default' to any of <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/permissions/PermissionDefault.html> to define default accessibility.\r\n        // This mechanism should probably be executed during <@link event server prestart>.\r\n        // @tags\r\n        // <server.has_whitelist>\r\n        // -->\r\n        tagProcessor.registerMechanism(\"register_permission\", false, MapTag.class, (object, mechanism, input) -> {\r\n            ElementTag name = input.getElement(\"name\"), parentInput = input.getElement(\"parent\"),\r\n                    defaultInput = input.getElement(\"default\"), description = input.getElement(\"description\");\r\n            Permission parent = parentInput == null ? null : Bukkit.getPluginManager().getPermission(parentInput.asString());\r\n            PermissionDefault permissionDefault = defaultInput == null ? null : defaultInput.asEnum(PermissionDefault.class);\r\n            if (parent == null) {\r\n                DefaultPermissions.registerPermission(name.asString(), description == null ? null : description.asString(), permissionDefault);\r\n            }\r\n            else {\r\n                DefaultPermissions.registerPermission(name.asString(), description == null ? null : description.asString(), permissionDefault, parent);\r\n            }\r\n        });\r\n\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n\r\n            // <--[mechanism]\r\n            // @object server\r\n            // @name links\r\n            // @input ListTag(MapTag)\r\n            // @description\r\n            // Sets the default server links. Each item in the list must be a MapTag in <@link language Server Links Format>.\r\n            // Generally prefer <@link mechanism server.add_links>.\r\n            // -->\r\n            tagProcessor.registerMechanism(\"links\", false, ListTag.class, (object, mechanism, input) -> {\r\n                Utilities.replaceServerLinks(Bukkit.getServerLinks(), input, mechanism.context);\r\n            });\r\n\r\n            // <--[mechanism]\r\n            // @object server\r\n            // @name add_links\r\n            // @input ListTag(MapTag)\r\n            // @description\r\n            // Adds links to the default server links. Each item in the list must be a MapTag in <@link language Server Links Format>.\r\n            // -->\r\n            tagProcessor.registerMechanism(\"add_links\", false, ListTag.class, (object, mechanism, input) -> {\r\n                Utilities.fillServerLinks(Bukkit.getServerLinks(), input, mechanism.context);\r\n            });\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name default_colors\r\n        // @input MapTag\r\n        // @description\r\n        // Sets a default value of a custom color, to be used if the config.yml does not specify a value for that color name.\r\n        // Input must be a map with the keys as custom color names, and the values as the default color.\r\n        // This mechanism should probably be executed during <@link event scripts loaded>.\r\n        // @tags\r\n        // <&>\r\n        // <ElementTag.custom_color>\r\n        // @Example\r\n        // on scripts loaded:\r\n        // - adjust server default_colors:[mymagenta=<&color[#ff00ff]>;myred=<&c>]\r\n        // - debug log \"The custom red is <&[myred]>\"\r\n        // -->\r\n        tagProcessor.registerMechanism(\"default_colors\", false, MapTag.class, (object, mechanism, input) -> {\r\n            for (Map.Entry<StringHolder, ObjectTag> pair : input.entrySet()) {\r\n                String name = pair.getKey().low;\r\n                if (!CustomColorTagBase.customColors.containsKey(name)) {\r\n                    CustomColorTagBase.customColors.put(name, pair.getValue().toString().replace(\"<\", \"<&lt>\"));\r\n                }\r\n            }\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.notes[<type>]>\r\n        // @returns ListTag\r\n        // @deprecated use util.notes\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.notes>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.started_time>\r\n        // @returns TimeTag\r\n        // @deprecated use util.started_time\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.started_time>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.disk_free>\r\n        // @returns ElementTag(Number)\r\n        // @deprecated use util.disk_free\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.disk_free>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.disk_total>\r\n        // @returns ElementTag(Number)\r\n        // @deprecated use util.disk_total\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.disk_total>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.disk_usage>\r\n        // @returns ElementTag(Number)\r\n        // @deprecated use util.disk_usage\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.disk_usage>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.ram_allocated>\r\n        // @returns ElementTag(Number)\r\n        // @deprecated use util.ram_allocated\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.ram_allocated>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.ram_max>\r\n        // @returns ElementTag(Number)\r\n        // @deprecated use util.ram_max\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.ram_max>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.ram_free>\r\n        // @returns ElementTag(Number)\r\n        // @deprecated use util.ram_free\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.ram_free>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.ram_usage>\r\n        // @returns ElementTag(Number)\r\n        // @deprecated use util.ram_usage\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.ram_usage>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.available_processors>\r\n        // @returns ElementTag(Number)\r\n        // @deprecated use util.available_processors\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.available_processors>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.current_tick>\r\n        // @returns ElementTag(Number)\r\n        // @deprecated use util.current_tick\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.current_tick>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.delta_time_since_start>\r\n        // @returns DurationTag\r\n        // @deprecated use util.delta_time_since_start\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.delta_time_since_start>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.real_time_since_start>\r\n        // @returns DurationTag\r\n        // @deprecated use util.real_time_since_start\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.real_time_since_start>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.current_time_millis>\r\n        // @returns ElementTag(Number)\r\n        // @deprecated use util.current_time_millis\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.current_time_millis>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.has_file[<name>]>\r\n        // @returns ElementTag(Boolean)\r\n        // @deprecated use util.has_file\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.has_file>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.list_files[<path>]>\r\n        // @returns ListTag\r\n        // @deprecated use util.list_files\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.list_files>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.java_version>\r\n        // @returns ElementTag\r\n        // @deprecated use util.java_version\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.java_version>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.sql_connections>\r\n        // @returns ListTag\r\n        // @deprecated use util.sql_connections\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.sql_connections>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.scripts>\r\n        // @returns ListTag(ScriptTag)\r\n        // @deprecated use util.scripts\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.scripts>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.last_reload>\r\n        // @returns TimeTag\r\n        // @deprecated use util.last_reload\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.last_reload>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.stack_trace>\r\n        // @returns ElementTag\r\n        // @deprecated use util.stack_trace\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.stack_trace>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.debug_enabled>\r\n        // @returns ElementTag(Boolean)\r\n        // @deprecated use util.debug_enabled\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.debug_enabled>\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <server.color_names>\r\n        // @returns ListTag\r\n        // @deprecated use util.color_names\r\n        // @description\r\n        // Deprecated in favor of <@link tag util.color_names>\r\n        // -->\r\n        for (String tagName : new String[] { \"current_time_millis\", \"real_time_since_start\", \"color_names\",\r\n                \"delta_time_since_start\", \"current_tick\", \"available_processors\", \"ram_usage\", \"ram_free\", \"ram_max\", \"ram_allocated\", \"disk_usage\", \"debug_enabled\",\r\n                \"disk_total\", \"disk_free\", \"started_time\", \"has_file\", \"list_files\", \"notes\", \"last_reload\", \"scripts\", \"sql_connections\", \"java_version\", \"stack_trace\" }) {\r\n            TagRunnable.ObjectInterface<UtilTagBase, ?> runner = UtilTagBase.instance.tagProcessor.registeredObjectTags.get(tagName).runner;\r\n            tagProcessor.registerTag(ObjectTag.class, tagName, (attribute, object) -> {\r\n                BukkitImplDeprecations.serverUtilTags.warn(attribute.context);\r\n                return runner.run(attribute, UtilTagBase.instance);\r\n            });\r\n        }\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name delete_file\r\n        // @input ElementTag\r\n        // @deprecated use system.delete_file\r\n        // @description\r\n        // Deprecated in favor of <@link mechanism system.delete_file>\r\n        // -->\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name reset_event_stats\r\n        // @input None\r\n        // @deprecated use system.reset_event_stats\r\n        // @description\r\n        // Deprecated in favor of <@link mechanism system.reset_event_stats>\r\n        // -->\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name cleanmem\r\n        // @input None\r\n        // @deprecated use system.cleanmem\r\n        // @description\r\n        // Deprecated in favor of <@link mechanism system.cleanmem>\r\n        // -->\r\n\r\n        for (String mechName : new String[] { \"delete_file\", \"reset_event_stats\", \"cleanmem\" }) {\r\n            Mechanism.GenericMechRunnerInterface<UtilTagBase> runner = UtilTagBase.instance.tagProcessor.registeredMechanisms.get(mechName).runner;\r\n            tagProcessor.registerMechanism(mechName, false, (object, mechanism) -> {\r\n                BukkitImplDeprecations.serverSystemMechanisms.warn(mechanism.context);\r\n                runner.run(UtilTagBase.instance, mechanism);\r\n            });\r\n        }\r\n\r\n        if (Depends.citizens != null) {\r\n            registerCitizensFeatures();\r\n        }\r\n    }\r\n\r\n    public void registerCitizensFeatures() {\r\n\r\n        // <--[tag]\r\n        // @attribute <server.selected_npc>\r\n        // @returns NPCTag\r\n        // @description\r\n        // Returns the server's currently selected NPC.\r\n        // -->\r\n        tagProcessor.registerTag(NPCTag.class, \"selected_npc\", (attribute, object) -> {\r\n            NPC npc = Depends.citizens.getNPCSelector().getSelected(Bukkit.getConsoleSender());\r\n            return npc != null ? new NPCTag(npc) : null;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.npcs_named[<name>]>\r\n        // @returns ListTag(NPCTag)\r\n        // @description\r\n        // Returns a list of NPCs with a certain name.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"npcs_named\", (attribute, object, input) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag npcs = new ListTag();\r\n            String name = input.asLowerString();\r\n            for (NPC npc : CitizensAPI.getNPCRegistry()) {\r\n                if (name.equals(CoreUtilities.toLowerCase(npc.getName()))) {\r\n                    npcs.addObject(new NPCTag(npc));\r\n                }\r\n            }\r\n            return npcs;\r\n        }, \"list_npcs_named\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.npcs_assigned[<assignment_script>]>\r\n        // @returns ListTag(NPCTag)\r\n        // @description\r\n        // Returns a list of all NPCs assigned to a specified script.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ScriptTag.class, \"npcs_assigned\", (attribute, object, script) -> {\r\n            listDeprecateWarn(attribute);\r\n            if (!(script.getContainer() instanceof AssignmentScriptContainer assignmentScriptContainer)) {\r\n                attribute.echoError(\"Invalid script '\" + script + \"' specified: must be an assignment script.\");\r\n                return null;\r\n            }\r\n            ListTag npcs = new ListTag();\r\n            for (NPC npc : CitizensAPI.getNPCRegistry()) {\r\n                if (npc.hasTrait(AssignmentTrait.class) && npc.getTraitNullable(AssignmentTrait.class).isAssigned(assignmentScriptContainer)) {\r\n                    npcs.addObject(new NPCTag(npc));\r\n                }\r\n            }\r\n            return npcs;\r\n        }, \"list_npcs_assigned\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.spawned_npcs_flagged[<flag_name>]>\r\n        // @returns ListTag(NPCTag)\r\n        // @description\r\n        // Returns a list of all spawned NPCs with a specified flag set.\r\n        // Can use \"!<flag_name>\" style to only return NPCs *without* the flag.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"spawned_npcs_flagged\", (attribute, object, input) -> {\r\n            listDeprecateWarn(attribute);\r\n            String flag = input.asString();\r\n            ListTag npcs = new ListTag();\r\n            boolean want = true;\r\n            if (flag.startsWith(\"!\")) {\r\n                want = false;\r\n                flag = flag.substring(1);\r\n            }\r\n            for (NPC npc : CitizensAPI.getNPCRegistry()) {\r\n                NPCTag npcTag = new NPCTag(npc);\r\n                if (npcTag.isSpawned() && npcTag.hasFlag(flag) == want) {\r\n                    npcs.addObject(npcTag);\r\n                }\r\n            }\r\n            return npcs;\r\n        }, \"list_spawned_npcs_flagged\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.npcs_flagged[<flag_name>]>\r\n        // @returns ListTag(NPCTag)\r\n        // @description\r\n        // Returns a list of all NPCs with a specified flag set.\r\n        // Can use \"!<flag_name>\" style to only return NPCs *without* the flag.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, ElementTag.class, \"npcs_flagged\", (attribute, object, input) -> {\r\n            listDeprecateWarn(attribute);\r\n            String flag = input.asString();\r\n            ListTag npcs = new ListTag();\r\n            boolean want = true;\r\n            if (flag.startsWith(\"!\")) {\r\n                want = false;\r\n                flag = flag.substring(1);\r\n            }\r\n            for (NPC npc : CitizensAPI.getNPCRegistry()) {\r\n                NPCTag npcTag = new NPCTag(npc);\r\n                if (npcTag.hasFlag(flag) == want) {\r\n                    npcs.addObject(npcTag);\r\n                }\r\n            }\r\n            return npcs;\r\n        }, \"list_npcs_flagged\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.npc_registries>\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all NPC registries.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"npc_registries\", (attribute, object) -> {\r\n            ListTag registries = new ListTag();\r\n            for (NPCRegistry registry : CitizensAPI.getNPCRegistries()) {\r\n                registries.add(registry.getName());\r\n            }\r\n            return registries;\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <server.npcs[(<registry>)]>\r\n        // @returns ListTag(NPCTag)\r\n        // @description\r\n        // Returns a list of all NPCs.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"npcs\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            NPCRegistry registry = CitizensAPI.getNPCRegistry();\r\n            if (attribute.hasParam()) {\r\n                registry = NPCTag.getRegistryByName(attribute.getParam());\r\n                if (registry == null) {\r\n                    attribute.echoError(\"NPC Registry '\" + attribute.getParam() + \"' does not exist.\");\r\n                    return null;\r\n                }\r\n            }\r\n            ListTag npcs = new ListTag();\r\n            for (NPC npc : registry) {\r\n                npcs.addObject(new NPCTag(npc));\r\n            }\r\n            return npcs;\r\n        }, \"list_npcs\");\r\n\r\n        // <--[tag]\r\n        // @attribute <server.traits>\r\n        // @Plugin Citizens\r\n        // @returns ListTag\r\n        // @description\r\n        // Returns a list of all available NPC traits on the server.\r\n        // -->\r\n        tagProcessor.registerTag(ListTag.class, \"traits\", (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            ListTag traits = new ListTag();\r\n            for (TraitInfo trait : CitizensAPI.getTraitFactory().getRegisteredTraits()) {\r\n                traits.add(trait.getTraitName());\r\n            }\r\n            return traits;\r\n        }, \"list_traits\");\r\n\r\n        // <--[mechanism]\r\n        // @object server\r\n        // @name save_citizens\r\n        // @input None\r\n        // @description\r\n        // Immediately saves the Citizens saves files.\r\n        // -->\r\n        tagProcessor.registerMechanism(\"save_citizens\", false, (object, mechanism) -> {\r\n            Depends.citizens.storeNPCs();\r\n        });\r\n    }\r\n\r\n    public void registerEnumListTag(String name, Class<?> enumType, String... deprecatedVariants) {\r\n        tagProcessor.registerStaticTag(ListTag.class, name, (attribute, object) -> {\r\n            listDeprecateWarn(attribute);\r\n            return Utilities.listTypes(enumType);\r\n        }, deprecatedVariants);\r\n    }\r\n\r\n    private static MapTag areaNotesDebug(Int2ObjectOpenHashMap<NotedAreaTracker.AreaSet> set) {\r\n        MapTag out = new MapTag();\r\n        for (Int2ObjectMap.Entry<NotedAreaTracker.AreaSet> pair : set.int2ObjectEntrySet()) {\r\n            out.putObject(String.valueOf(pair.getIntKey()), new ListTag(pair.getValue().list, trackedArea -> trackedArea.area));\r\n        }\r\n        return out;\r\n    }\r\n\r\n    public static void listDeprecateWarn(Attribute attribute) {\r\n        if (attribute.getAttributeWithoutParam(1).startsWith(\"list_\")) {\r\n            BukkitImplDeprecations.listStyleTags.warn(attribute.context);\r\n        }\r\n    }\r\n\r\n    public static ListTag getHandlerPluginList(Class eventClass) {\r\n        if (Event.class.isAssignableFrom(eventClass)) {\r\n            HandlerList handlers = BukkitScriptEvent.getEventListeners(eventClass);\r\n            if (handlers != null) {\r\n                ListTag result = new ListTag();\r\n                HashSet<String> deduplicationSet = new HashSet<>();\r\n                for (RegisteredListener listener : handlers.getRegisteredListeners()) {\r\n                    if (deduplicationSet.add(listener.getPlugin().getName())) {\r\n                        result.addObject(new PluginTag(listener.getPlugin()));\r\n                    }\r\n                }\r\n                return result;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/tags/core/TextTagBase.java",
    "content": "package com.denizenscript.denizen.tags.core;\r\n\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitElementExtensions;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.HoverFormatHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.tags.core.EscapeTagUtil;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.md_5.bungee.api.chat.HoverEvent;\r\nimport org.bukkit.ChatColor;\r\n\r\npublic class TextTagBase {\r\n\r\n    public TextTagBase() {\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&amp\", (attribute) -> { BukkitImplDeprecations.pointlessTextTags.warn(attribute.context); return new ElementTag(\"&\"); });\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&cm\", (attribute) -> { BukkitImplDeprecations.pointlessTextTags.warn(attribute.context); return new ElementTag(\",\"); });\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&sc\", (attribute) -> { BukkitImplDeprecations.pointlessTextTags.warn(attribute.context); return new ElementTag(\";\"); });\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&pipe\", (attribute) -> { BukkitImplDeprecations.pointlessTextTags.warn(attribute.context); return new ElementTag(\"|\"); });\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&dot\", (attribute) -> { BukkitImplDeprecations.pointlessTextTags.warn(attribute.context); return new ElementTag(\".\"); });\r\n\r\n        // <--[tag]\r\n        // @attribute <p>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a paragraph, for use in books.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"p\", (attribute) -> new ElementTag(\"\\n \" + ChatColor.RESET + \" \\n\"));\r\n\r\n        // <--[tag]\r\n        // @attribute <&hover[<hover_text>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a special chat code that makes the following text display the input hover text when the mouse is left over it.\r\n        // This tag must be followed by an <&end_hover> tag.\r\n        // For example: - narrate \"There is a <&hover[you found it!]>secret<&end_hover> in this message!\"\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        TagManager.registerTagHandler(ElementTag.class, ObjectTag.class, \"&hover\", (attribute, hover) -> { // Cannot be static due to hacked sub-tag\r\n\r\n              // <--[tag]\r\n              // @attribute <&hover[<hover_text>].type[<type>]>\r\n              // @returns ElementTag\r\n              // @description\r\n              // Returns a special chat code that makes the following text display the input hover text when the mouse is left over it.\r\n              // This tag must be followed by an <&end_hover> tag.\r\n              // Available hover types: SHOW_TEXT, SHOW_ITEM, or SHOW_ENTITY.\r\n              // For example: - narrate \"There is a <&hover[you found it!].type[SHOW_TEXT]>secret<&end_hover> in this message!\"\r\n              // Note: for \"SHOW_ITEM\", replace the text with a valid ItemTag. For \"SHOW_ENTITY\", replace the text with a valid spawned EntityTag (requires F3+H to see entities).\r\n              // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n              // -->\r\n              HoverEvent.Action type = HoverEvent.Action.SHOW_TEXT;\r\n              if (attribute.startsWith(\"type\", 2)) {\r\n                  attribute.fulfill(1);\r\n                  type = ElementTag.asEnum(HoverEvent.Action.class, attribute.getParam());\r\n                  if (type == null) {\r\n                      attribute.echoError(\"Invalid hover type specified.\");\r\n                      return null;\r\n                  }\r\n              }\r\n              String hoverData = HoverFormatHelper.parseObjectToHover(hover, type, attribute);\r\n              if (hoverData == null) {\r\n                  return null;\r\n              }\r\n              return new ElementTag(ChatColor.COLOR_CHAR + \"[hover=\" + type + ';' + FormattedTextHelper.escape(hoverData) + ']', true);\r\n          });\r\n\r\n        // <--[tag]\r\n        // @attribute <&click[<click_command>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a special chat code that makes the following text execute the input command line value when clicked.\r\n        // To execute a command \"/\" should be used at the start. Otherwise, it will display as chat.\r\n        // This tag must be followed by an <&end_click> tag.\r\n        // For example: - narrate \"You can <&click[wow]>click here<&end_click> to say wow!\"\r\n        // For example: - narrate \"You can <&click[/help]>click here<&end_click> for help!\"\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        TagManager.registerTagHandler(ElementTag.class, \"&click\", (attribute) -> { // Cannot be static due to hacked sub-tag\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            String clickText = attribute.getParam();\r\n\r\n            // <--[tag]\r\n            // @attribute <&click[<click_command>].type[<type>]>\r\n            // @returns ElementTag\r\n            // @description\r\n            // Returns a special chat code that makes the following text execute the input command when clicked.\r\n            // This tag must be followed by an <&end_click> tag.\r\n            // Available command types: OPEN_URL, OPEN_FILE, RUN_COMMAND, SUGGEST_COMMAND, COPY_TO_CLIPBOARD, or CHANGE_PAGE.\r\n            // For example: - narrate \"You can <&click[https://denizenscript.com].type[OPEN_URL]>click here<&end_click> to learn about Denizen!\"\r\n            // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n            // -->\r\n            String type = \"RUN_COMMAND\";\r\n            if (attribute.startsWith(\"type\", 2)) {\r\n                type = attribute.getContext(2);\r\n                attribute.fulfill(1);\r\n            }\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[click=\" + type + \";\" + FormattedTextHelper.escape(clickText) + \"]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&insertion[<message>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a special chat code that makes the following text insert the input message to chat when shift-clicked.\r\n        // This tag must be followed by an <&end_insertion> tag.\r\n        // For example: - narrate \"You can <&insertion[wow]>click here<&end_insertion> to add 'wow' to your chat!\"\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&insertion\", (attribute) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            String insertText = attribute.getParam();\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[insertion=\" + FormattedTextHelper.escape(insertText) + \"]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&end_click>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a special chat code that ends a '&click' tag.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&end_click\", (attribute) -> {\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[/click]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&end_hover>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a special chat code that ends a '&hover' tag.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&end_hover\", (attribute) -> {\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[/hover]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&end_insertion>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a special chat code that ends an '&insertion' tag.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&end_insertion\", (attribute) -> {\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[/insertion]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&keybind[<key>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a special chat code that displays a keybind.\r\n        // For example: - narrate \"Press your <&keybind[key.jump]> key!\"\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&keybind\", (attribute) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            String keybindText = attribute.getParam();\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[keybind=\" + FormattedTextHelper.escape(keybindText) + \"]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&selector[<key>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a special chat code that displays a vanilla selector.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&selector\", (attribute) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            String selectorText = attribute.getParam();\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[selector=\" + FormattedTextHelper.escape(selectorText) + \"]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&translate[key=<key>;(fallback=<fallback>);(with=<text>|...)]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a special chat code that is read by the client to display an auto-translated message.\r\n        // \"key\" is the translation key.\r\n        // Optionally specify \"fallback\" as text to display when the client can't find a translation for the key.\r\n        // Optionally specify \"with\" as a list of input data for the translatable message (parts of the message that are dynamic).\r\n        // Be warned that language keys can change between Minecraft versions.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // You can use <@link tag ElementTag.strip_color> to convert the translated output to plain text (pre-translated).\r\n        // @example\r\n        // # Narrates a translatable of a diamond sword's name.\r\n        // - narrate \"Reward: <&translate[key=item.minecraft.diamond_sword]>\"\r\n        // @example\r\n        // # Narrates a translatable with some input data.\r\n        // - narrate <&translate[key=commands.give.success.single;with=32|<&translate[key=item.minecraft.diamond_sword]>|<player.name>]>\r\n        // @example\r\n        // # Narrates a custom translatable (from something like a resource pack), with a fallback in case it can't be translated.\r\n        // - narrate <&translate[key=my.custom.translation;fallback=Please use the resource pack!]>\r\n        // -->\r\n        TagManager.registerTagHandler(ElementTag.class, ObjectTag.class, \"&translate\", (attribute, param) -> { // Cannot be static due to hacked sub-tag\r\n            MapTag translateMap = param.asType(MapTag.class, CoreUtilities.noDebugContext);\r\n            if (translateMap == null) {\r\n                BukkitImplDeprecations.translateLegacySyntax.warn(attribute.context);\r\n                translateMap = new MapTag();\r\n                translateMap.putObject(\"key\", param);\r\n\r\n                // <--[tag]\r\n                // @attribute <&translate[<key>].with[<text>|...]>\r\n                // @returns ElementTag\r\n                // @deprecated Use '<&translate[key=<key>;with=<text>|...]>'.\r\n                // @description\r\n                // Deprecated in favor of <@link tag &translate>.\r\n                // -->\r\n                if (attribute.startsWith(\"with\", 2)) {\r\n                    translateMap.putObject(\"with\", new ListTag(attribute.contextAsType(2, ListTag.class), with -> new ElementTag(EscapeTagUtil.unEscape(with), true)));\r\n                    attribute.fulfill(1);\r\n                }\r\n            }\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[translate=\" + FormattedTextHelper.escape(translateMap.savable()) + ']', true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&score[<name>|<objective>(|<value>)]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a special chat code that displays a scoreboard entry. Input is an escaped list of:\r\n        // Name of the relevant entity, name of the objective, then optionally a value (if unspecified, will use current scoreboard value).\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        //\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&score\", (attribute) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            ListTag scoreList = attribute.paramAsType(ListTag.class);\r\n            if (scoreList.size() < 2) {\r\n                return null;\r\n            }\r\n            String name = FormattedTextHelper.escape(EscapeTagUtil.unEscape(scoreList.get(0)));\r\n            String objective = FormattedTextHelper.escape(EscapeTagUtil.unEscape(scoreList.get(1)));\r\n            String value = scoreList.size() >= 3 ? FormattedTextHelper.escape(EscapeTagUtil.unEscape(scoreList.get(2))) : \"\";\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[score=\" + name + \";\" + objective + \";\" + value + \"]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&color[<color>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a chat code that makes the following text be the specified color.\r\n        // Color can be a color name, color code, hex, or ColorTag... that is: \"&color[gold]\", \"&color[6]\", and \"&color[#AABB00]\" are all valid.\r\n        // The ColorTag input option can be used for dynamic color effects, such as automatic rainbows.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&color\", (attribute) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            String colorName = attribute.getParam();\r\n            String colorOut = null;\r\n            if (colorName.length() == 1) {\r\n                ChatColor color = ChatColor.getByChar(colorName.charAt(0));\r\n                if (color != null) {\r\n                    colorOut = color.toString();\r\n                }\r\n            }\r\n            else if (colorName.length() == 7 && colorName.startsWith(\"#\")) {\r\n                colorOut = FormattedTextHelper.stringifyRGBSpigot(colorName.substring(1));\r\n            }\r\n            else if (colorName.startsWith(\"co@\") || colorName.lastIndexOf(',') > colorName.indexOf(',')) {\r\n                ColorTag color = ColorTag.valueOf(colorName, attribute.context);\r\n                if (color == null && TagManager.isStaticParsing) {\r\n                    return null;\r\n                }\r\n                String hex = Integer.toHexString(color.asRGB());\r\n                colorOut = FormattedTextHelper.stringifyRGBSpigot(hex);\r\n            }\r\n            if (colorOut == null) {\r\n                try {\r\n                    ChatColor color = ChatColor.valueOf(CoreUtilities.toUpperCase(colorName));\r\n                    colorOut = color.toString();\r\n                }\r\n                catch (IllegalArgumentException ex) {\r\n                    attribute.echoError(\"Color '\" + colorName + \"' doesn't exist (for tag &color[...]).\");\r\n                    return null;\r\n                }\r\n            }\r\n            return new ElementTag(colorOut);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&gradient[from=<color>;to=<color>;(style={RGB}/HSB)]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a chat code that makes the following text be the specified color.\r\n        // Input works equivalently to <@link tag ElementTag.color_gradient>, return to that tag for more documentation detail and input examples.\r\n        // The gradient runs from whatever text is after this gradient, until the next color tag (0-9, a-f, 'r' reset, or an RGB code. Does not get stop at formatting codes, they will be included in the gradient).\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // @example\r\n        // - narrate \"<&gradient[from=black;to=white]>these are the shades of gray <white>that solidifies to pure white\"\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, MapTag.class, \"&gradient\", (attribute, inputMap) -> {\r\n            ColorTag fromColor = inputMap.getRequiredObjectAs(\"from\", ColorTag.class, attribute);\r\n            ColorTag toColor = inputMap.getRequiredObjectAs(\"to\", ColorTag.class, attribute);\r\n            ElementTag style = inputMap.getElement(\"style\", \"RGB\");\r\n            if (fromColor == null || toColor == null) {\r\n                return null;\r\n            }\r\n            if (!style.matchesEnum(BukkitElementExtensions.GradientStyle.class)) {\r\n                attribute.echoError(\"Invalid gradient style '\" + style + \"'\");\r\n                return null;\r\n            }\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[gradient=\" + fromColor + \";\" + toColor + \";\" + style + \"]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&font[<font>]>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a chat code that makes the following text display with the specified font.\r\n        // The default font is \"minecraft:default\".\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&font\", (attribute) -> {\r\n            if (!attribute.hasParam()) {\r\n                return null;\r\n            }\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[font=\" + attribute.getParam() + \"]\");\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&optimize>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns a chat code that tells the formatted text parser to try to produce mininalist JSON text.\r\n        // This is useful in particular for very long text or where text is being sent rapidly/repeatedly.\r\n        // It is not needed in most normal messages.\r\n        // It will produce incompatibility issues if used in items or other locations where raw JSON matching is required.\r\n        // Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.\r\n        // -->\r\n        TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&optimize\", (attribute) -> {\r\n            return new ElementTag(ChatColor.COLOR_CHAR + \"[optimize=true]\", true);\r\n        });\r\n\r\n        // <--[tag]\r\n        // @attribute <&0>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Black.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&1>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Blue.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&2>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Green.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&3>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Cyan.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&4>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Red.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&5>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Magenta.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&6>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Gold.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&7>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Light Gray.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&8>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Gray.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&9>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Light Blue.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&a>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Light Green.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&b>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Cyan.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&c>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Light Red.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&d>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Magenta.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&e>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Yellow.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&f>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters White.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&k>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters obfuscated.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&l>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters bold.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&m>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters have a strike-through.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&n>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters have an underline.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&o>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters italicized.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <&r>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that resets the following characters to normal.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <black>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Black.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <dark_blue>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Blue.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <dark_green>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Green.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <dark_aqua>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Cyan.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <dark_red>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Red.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <dark_purple>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Magenta.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <gold>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Gold.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <gray>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Light Gray.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <dark_gray>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Dark Gray.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <blue>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Light Blue.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <green>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Light Green.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <aqua>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Cyan.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <red>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Light Red.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <light_purple>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Magenta.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <yellow>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters Yellow.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <white>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters White.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <magic>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters obfuscated.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <bold>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters bold.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <strikethrough>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters have a strike-through.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <underline>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters have an underline.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <italic>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that makes the following characters italicized.\r\n        // -->\r\n\r\n        // <--[tag]\r\n        // @attribute <reset>\r\n        // @returns ElementTag\r\n        // @description\r\n        // Returns the ChatColor that resets the following characters to normal.\r\n        // -->\r\n\r\n        for (ChatColor color : ChatColor.values()) {\r\n            final String nameVal = CoreUtilities.toLowerCase(color.name());\r\n            final String retVal = color.toString();\r\n            TagManager.registerStaticTagBaseHandler(ElementTag.class, nameVal, (attribute) -> new ElementTag(retVal));\r\n            TagManager.registerStaticTagBaseHandler(ElementTag.class, \"&\" + color.getChar(), (attribute) -> new ElementTag(retVal));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/BukkitImplDeprecations.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.*;\r\n\r\nimport static com.denizenscript.denizencore.utilities.Deprecations.pointlessSubtagPrefix;\r\n\r\npublic class BukkitImplDeprecations {\r\n\r\n    // ==================== REMOVE THESE ====================\r\n    // Every warning inside this section should be removed from the codebase.\r\n\r\n    // Added on 2019/08/11\r\n    // Safe to remove now.\r\n    public static Warning oldEconomyTags = new StrongWarning(\"oldEconomyTags\", \"player.money.currency* tags are deprecated in favor of server.economy.currency* tags.\");\r\n\r\n    // Added on 2019/09/18, but was deprecated earlier.\r\n    // 2022-year-end commonality: #27\r\n    // Safe to remove now.\r\n    public static Warning playerRightClicksEntityContext = new StrongWarning(\"playerRightClicksEntityContext\", \"'context.location' in event 'on player right clicks entity' is deprecated: use 'context.entity.location'.\");\r\n\r\n    // Added on 2019/09/25, but was deprecated earlier.\r\n    // Bad candidate for functionality removal - used to be commonly used\r\n    // 2022-year-end commonality: #13\r\n    // 2023-year-end commonality: #19\r\n    // Safe to remove now.\r\n    public static Warning qtyTags = new StrongWarning(\"qtyTags\", \"'qty' in a tag or command is deprecated: use 'quantity'.\");\r\n\r\n    // In Bukkit impl, Relevant as of 2019/09/25, made current on 2020/02/12, made strong 2022/12/31.\r\n    // Safe to remove now.\r\n    public static Warning npcNicknameTag = new StrongWarning(\"npcNicknameTag\", pointlessSubtagPrefix + \"npc.name.nickname is now just npc.nickname. Note that this historically appeared in the config.yml file, so check there if you're unsure what's using this tag.\");\r\n    public static Warning npcPreviousLocationTag = new StrongWarning(\"npcPreviousLocationTag\", pointlessSubtagPrefix + \"npc.location.previous_location is now just npc.previous_location.\");\r\n    public static Warning npcAnchorListTag = new StrongWarning(\"npcAnchorListTag\", pointlessSubtagPrefix + \"npc.anchor.list is now just npc.list_anchors.\");\r\n    public static Warning playerMoneyFormatTag = new StrongWarning(\"playerMoneyFormatTag\", pointlessSubtagPrefix + \"player.money.format is now just player.formatted_money.\");\r\n    public static Warning playerFoodLevelFormatTag = new StrongWarning(\"playerFoodLevelFormatTag\", pointlessSubtagPrefix + \"player.food_level.format is now just player.formatted_food_level.\");\r\n    public static Warning playerBanInfoTags = new StrongWarning(\"playerBanInfoTags\", pointlessSubtagPrefix + \"player.ban_info.* tags are now just player.ban_*.\");\r\n    public static Warning playerNameTags = new StrongWarning(\"playerNameTags\", pointlessSubtagPrefix + \"player.name.* tags are now just player.*_name.\");\r\n    public static Warning playerSidebarTags = new StrongWarning(\"playerSidebarTags\", pointlessSubtagPrefix + \"player.sidebar.* tags are now just player.sidebar_*.\");\r\n    public static Warning playerAttackCooldownTags = new StrongWarning(\"playerAttackCooldownTags\", pointlessSubtagPrefix + \"player.attack_cooldown.* tags are now just player.attack_cooldown_*.\");\r\n    public static Warning playerXpTags = new StrongWarning(\"playerXpTags\", pointlessSubtagPrefix + \"player.xp.* tags are now just player.xp_*.\");\r\n    public static Warning entityMaxOxygenTag = new StrongWarning(\"entityMaxOxygenTag\", pointlessSubtagPrefix + \"entity.oxygen.max is now just entity.max_oxygen.\");\r\n    public static Warning itemBookTags = new StrongWarning(\"itemBookTags\", pointlessSubtagPrefix + \"item.book.* tags are now just item.book_*.\");\r\n    public static Warning playerItemInHandSlotTag = new StrongWarning(\"playerItemInHandSlotTag\", pointlessSubtagPrefix + \"player.item_in_hand_slot is now just player.held_item_slot.\");\r\n\r\n    // Added on 2019/09/24, made normal 2021/11/2021, made strong 2022/12/31.\r\n    // Safe to remove now.\r\n    public static Warning oldRecipeScript = new StrongWarning(\"oldRecipeScript\", \"Item script single-recipe format is outdated. Use the modern 'recipes' list key (see meta docs).\");\r\n\r\n    // Added 2020/04/24, made strong 2022/12/31.\r\n    // Safe to remove now.\r\n    public static Warning itemInventoryTag = new StrongWarning(\"itemInventoryTag\", \"The tag 'item.inventory' is deprecated: use inventory_contents instead.\");\r\n\r\n    // Added 2020/05/21, made strong 2022/12/31.\r\n    // Safe to remove now.\r\n    public static Warning itemSkinFullTag = new StrongWarning(\"itemSkinFullTag\", pointlessSubtagPrefix + \"item.skin.full is now item.skull_skin.\");\r\n\r\n    // Added 2020/06/03 but deprecated long ago, made strong 2022/12/31.\r\n    // Safe to remove now.\r\n    public static Warning oldBossBarMech = new StrongWarning(\"oldBossBarMech\", \"The show_boss_bar mechanism is deprecated: use the bossbar command instead.\");\r\n    public static Warning oldTimeMech = new StrongWarning(\"oldTimeMech\", \"The player.*time mechanisms are deprecated: use the time command instead.\");\r\n    public static Warning oldWeatherMech = new StrongWarning(\"oldWeatherMech\", \"The player.*weather mechanisms are deprecated: use the weather command instead.\");\r\n    public static Warning oldKickMech = new StrongWarning(\"oldKickMech\", \"The player.kick mechanism is deprecated: use the kick command instead.\");\r\n    public static Warning oldMoneyMech = new StrongWarning(\"oldMoneyMech\", \"The player.money mechanism is deprecated: use the money command instead.\");\r\n\r\n    // added 2020/07/04, made normal 2021/11/2021, made strong 2022/12/31.\r\n    // Safe to remove now.\r\n    public static Warning cuboidFullTag = new StrongWarning(\"cuboidFullTag\", \"The tag cuboid.full is deprecated: this should just never be used.\");\r\n    public static Warning furnaceTimeTags = new StrongWarning(\"furnaceTimeTags\", \"The furnace_burn_time, cook time, and cook total time tag/mechs have been replaced by _duration instead of _time equivalents (using DurationTag now).\");\r\n    public static Warning playerTimePlayedTags = new StrongWarning(\"playerTimePlayedTags\", \"The tags player.first_played, last_played, ban_expiration, and ban_created have been replaced by tags of the same name with '_time' added to the end (using TimeTag now).\");\r\n\r\n    // added 2020/07/19, made normal 2021/11/2021, made strong 2022/12/31.\r\n    // Safe to remove now.\r\n    public static Warning airLevelEventDuration = new StrongWarning(\"airLevelEventDuration\", \"The 'entity changes air level' event uses 'air_duration' context now instead of the old tick count number.\");\r\n    public static Warning damageEventTypeMap = new StrongWarning(\"damageEventTypeMap\", \"The 'entity damaged' context 'damage_[TYPE]' is deprecated in favor of 'damage_type_map', which is operated as a MapTag.\");\r\n\r\n    // added 2020/07/28, made normal 2021/11/2021, made strong 2022/12/31.\r\n    public static Warning headCommand = new StrongWarning(\"headCommand\", \"The 'head' command is deprecated: use the 'equip' command with a 'player_head' item using the 'skull_skin' mechanism.\");\r\n\r\n    // added 2020/08/01, made normal 2021/11/2021, made strong 2022/12/31.\r\n    // Safe to remove now.\r\n    public static Warning entityRemoveWhenFar = new StrongWarning(\"entityRemoveWhenFar\", \"The EntityTag remove_when_far_away property is deprecated in favor of the persistent property (which is the exact inverse).\");\r\n    public static Warning entityPlayDeath = new StrongWarning(\"entityPlayDeath\", \"The EntityTag 'play_death' mechanism is deprecated: use the animate command.\");\r\n\r\n    // added 2020/08/19, made normal 2021/11/2021, made strong 2022/12/31.\r\n    // Safe to remove now.\r\n    public static Warning npcSpawnMechanism = new StrongWarning(\"npcSpawnMechanism\", \"The NPCTag 'spawn' mechanism is deprecated: use the spawn command.\");\r\n\r\n    // Added 2020/05/17, made current on 2020/10/24.\r\n    // 2022-year-end commonality: #28\r\n    // Safe to remove now.\r\n    public static Warning itemFlagsProperty = new StrongWarning(\"itemFlagsProperty\", \"The item.flags property has been renamed to item.hides, to avoid confusion with the new flaggable itemtags system.\");\r\n\r\n    // Added 2020/11/22, made current 2021/11/2021, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning biomeSpawnableTag = new StrongWarning(\"biomeSpawnableTag\", pointlessSubtagPrefix + \"The tag BiomeTag.spawnable_entities.(type) is deprecated: the type is now an input context instead.\");\r\n\r\n    // Added 2020/11/30, made current 2021/11/2021, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning npcDespawnMech = new StrongWarning(\"npcDespawnMech\", \"The NPCTag despawn mechanism is deprecated: use the despawn command.\");\r\n\r\n    // Added 2021/02/25, made current 2022/12/31, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning zapPrefix = new StrongWarning(\"zapPrefix\", \"The 'zap' command should be used with the scriptname and step as two separate arguments, not just one.\");\r\n\r\n    // Added 2020/03/05, made current on 2021/04/16, made current 2022/12/31, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning oldPlayEffectSpecials = new StrongWarning(\"oldPlayEffectSpecials\", \"The playeffect input of forms like 'iconcrack_' have been deprecated in favor of using the special_data input (refer to meta docs).\");\r\n\r\n    // Added 2020/04/16, made current 2022/12/31, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning entityStandingOn = new StrongWarning(\"entityStandingOn\", pointlessSubtagPrefix + \"entity.location.standing_on is now just entity.standing_on.\");\r\n\r\n    // Added 2021/05/05, made current 2022/12/31, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning materialLit = new StrongWarning(\"materialLit\", \"The MaterialTag property 'lit' is deprecated in favor of 'switched'.\");\r\n    public static Warning materialCampfire = new StrongWarning(\"materialCampfire\", \"The MaterialTag property 'campfire' are deprecated in favor of 'type'.\");\r\n    public static Warning materialDrags = new StrongWarning(\"materialDrags\", \"The MaterialTag property 'drags' are deprecated in favor of 'mode'.\");\r\n\r\n    // Added 2021/06/15, but was irrelevant years earlier, made current 2022/12/31, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning itemMessage = new StrongWarning(\"itemMessage\", \"The PlayerTag mechanism 'item_message' is deprecated in favor of using the actionbar.\");\r\n\r\n    // Added 2021/11/14, made current 2022/12/31, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning blockSpreads = new StrongWarning(\"blockSpreads\", \"There are two '<block> spreads' events - use 'block spreads type:<block>' or 'liquid spreads type:<block>'\");\r\n\r\n    // Added 2021/11/15, made current 2022/12/31, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning horseJumpsFormat = new StrongWarning(\"horseJumpsFormat\", \"The '<color> horse jumps' event is deprecated: don't put the color in the event line. (Deprecated for technical design reasons).\");\r\n\r\n    // Added 2019/11/11, made slow 2021/11/2021, made current 2022/12/31, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning entityLocationCursorOnTag = new StrongWarning(\"entityLocationCursorOnTag\", \"entity.location.cursor_on tags should be replaced by entity.cursor_on (be careful with the slight differences though).\");\r\n\r\n    // Added 2021/05/05, made current 2022/12/31, made strong 2024/01/02.\r\n    // Safe to remove now.\r\n    public static Warning locationDistanceTag = new StrongWarning(\"locationDistanceTag\", \"locationtag.tree_distance is deprecated in favor of location.material.distance\");\r\n\r\n    // Added 2024/02/10, made strong 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning fakePlayer = new StrongWarning(\"fakePlayer\", \"The 'fake_player' entity was an experiment that was always redundant due to the availability of Citizens NPCs. If you use 'fake_player' please let us know on Discord ASAP.\");\r\n\r\n    // ==================== STRONG deprecations ====================\r\n    // These show up every time, and warn any online ops. These are made clear they need to be fixed ASAP.\r\n\r\n    // In Bukkit impl, Relevant as of 2019/09/25, made current on 2020/02/12, made strong 2022/12/31.\r\n    // 2023-year-end commonality: #36\r\n    // 2024-year-end commonality: #21\r\n    public static Warning entityHealthTags = new StrongWarning(\"entityHealthTags\", pointlessSubtagPrefix + \"entity.health.* tags are now just entity.health_*.\");\r\n\r\n    // In Bukkit impl, Added on 2019/08/19\r\n    // Bad candidate for functionality removal - sometimes used by accident (when misreading the escape-tag docs)\r\n    public static Warning pointlessTextTags = new StrongWarning(\"pointlessTextTags\", \"Several text tags like '&dot' or '&cm' are pointless (there's no reason you can't just directly write them in). Please replace them with the actual intended text.\");\r\n\r\n    // Added 2021/09/08, but was irrelevant years earlier, made normal 2024/01/02, made strong 2025/01/15.\r\n    // 2022-year-end commonality: #31\r\n    // Safe to remove now.\r\n    public static Warning isValidTag = new StrongWarning(\"isValidTag\", \"The 'server.x_is_valid' style tags are deprecated: use '.exists', '.is_spawned.if_null[false]', etc.\");\r\n\r\n    // Added 2022/05/07, made normal 2024/01/02, made strong 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning armorStandRawSlot = new StrongWarning(\"armorStandRawSlot\", \"The EntityTag.disabled_slots.raw tag and EntityTag.disabled_slots_raw mechanism are deprecated, use the EntityTag.disabled_slots_data tag and EntityTag.disabled_slots mechanism instead.\");\r\n\r\n    // Added 2022/07/28, made normal 2024/01/02, made strong 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning internalEventReflectionContext = new StrongWarning(\"internalEventReflectionContext\", \"The context.field_<name> and fields special tags for 'internal bukkit event' are deprecated in favor of the 'reflect_event' global context.\");\r\n\r\n    // Added 2022/10/14, made normal 2024/01/02, made strong 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning skeletonSwingArm = new StrongWarning(\"skeletonSwingArm\", \"The 'SKELETON_START/STOP_SWING_ARM' animations are deprecated in favor of the 'EntityTag.aggressive' property.\");\r\n    public static Warning entityArmsRaised = new StrongWarning(\"entityArmsRaised\", \"The 'EntityTag.arms_raised' property is deprecated in favor of 'EntityTag.aggressive'.\");\r\n\r\n    // Added 2022/12/16, made normal 2024/01/02, made strong 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning entitySkeletonArmsRaised = new StrongWarning(\"entitySkeletonArmsRaised\", \"The 'EntityTag.skeleton_arms_raised' mechanism is deprecated in favor of 'EntityTag.aggressive'.\");\r\n\r\n    // Added 2021/10/24, made slow 2022/12/31, made normal 2024/01/02, made strong 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning entityArmorPose = new StrongWarning(\"entityArmorPose\", \"The old EntityTag.armor_pose and armor_pose_list tags are deprecated in favor of armor_pose_map.\");\r\n\r\n    // Added 2020/07/03, made slow 2022/12/31, made normal 2024/01/02, made strong 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning attachToMech = new StrongWarning(\"attachToMech\", \"The entity 'attach_to' mechanism is deprecated: use the new 'attach' command instead!\");\r\n\r\n    // Added 2021/04/13, made slow 2022/12/31, made normal 2024/01/02, made strong 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning materialHasDataPackTag = new StrongWarning(\"materialHasDataPackTag\", \"The tag 'MaterialTag.has_vanilla_data_tag[...]' is deprecated in favor of MaterialTag.vanilla_tags.contains[<name>]\");\r\n    public static Warning materialPropertyTags = new StrongWarning(\"materialPropertyTags\", \"Old MaterialTag.is_x property tags are deprecated in favor of PropertyHolderObject.supports[property-name]\");\r\n\r\n    // Added 2024/04/02, is for a feature that was broken from the start in 2022, made strong 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning forcedNoPersist = new StrongWarning(\"forcedNoPersist\", \"The 'forced_no_persist' was misspelled and inverted, use 'force_no_persist' instead.\");\r\n\r\n    // ==================== Normal deprecations ====================\r\n    // These show up every time, and should get the server owner's attention quickly if they check their logs.\r\n\r\n    // Added on 2018/12/23\r\n    // Bad candidate for functionality removal - a bit handy to use in \"/ex\", despite being clearly bad in standard scripts.\r\n    // Recommend never removing.\r\n    // 2022-year-end commonality: #17\r\n    // 2023-year-end commonality: #8\r\n    // 2024-year-end commonality: #10\r\n    public static Warning playerByNameWarning = new Warning(\"playerByNameWarning\", \"Warning: loading player by name - use the UUID instead (or use tag server.match_player)!\");\r\n\r\n    // Added 2020/06/13, made slow 2022/12/31, made normal 2024/01/02.\r\n    // 2023-year-end commonality: #16\r\n    // 2024-year-end commonality: #18\r\n    public static Warning listStyleTags = new Warning(\"listStyleTags\", \"'list_' tags are deprecated: just remove the 'list_' prefix.\");\r\n\r\n    // Added 2020/04/19, Relevant for many years now, made slow 2022/12/31, made normal 2025/01/15.\r\n    // 2022-year-end commonality: #35\r\n    // 2023-year-end commonality: #32\r\n    // 2024-year-end commonality: #13\r\n    public static Warning interactScriptPriority = new Warning(\"interactScriptPriority\", \"Assignment script 'interact scripts' section should not have numbered priority values, these were removed years ago. Check https://guide.denizenscript.com/guides/troubleshooting/updates-since-videos.html#assignment-script-updates for more info.\");\r\n\r\n    // Added 2020/12/25, made slow 2022/12/31, made normal 2025/01/15.\r\n    // 2022-year-end commonality: #36\r\n    // Safe to remove now.\r\n    public static Warning itemEnchantmentTags = new Warning(\"itemEnchantmentTags\", pointlessSubtagPrefix + \"The ItemTag.enchantments.* tags are deprecated: use enchantment_map and relevant MapTag subtags.\");\r\n\r\n    // Added 2021/02/05, made very-slow 2022/12/31, made slow 2024/01/02, made normal 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning itemProjectile = new Warning(\"itemProjectile\", \"The item_projectile custom entity type is deprecated: modern minecraft lets you set the item of any projectile, like 'snowball[item=stick]'\");\r\n\r\n    // Added 2021/03/02, made very-slow 2022/12/31, made slow 2024/01/02, made normal 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning itemScriptColor = new Warning(\"itemScriptColor\", \"The item script 'color' key is deprecated: use the 'color' mechanism under the 'mechanisms' key instead.\");\r\n\r\n    // Added 2021/07/26, made very-slow 2022/12/31, made slow 2024/01/02, made normal 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning itemEnchantmentsLegacy = new Warning(\"itemEnchantmentsLegacy\", \"The tag 'ItemTag.enchantments' is deprecated: use enchantment_map, or enchantment_types.\");\r\n    public static Warning echantmentTagUpdate = new Warning(\"echantmentTagUpdate\", \"Several legacy enchantment-related tags are deprecated in favor of using EnchantmentTag.\");\r\n\r\n    // Added 2021/06/19, made very-slow 2022/12/31, made slow 2024/01/02, made normal 2025/01/15.\r\n    public static Warning entityMapTraceTag = new Warning(\"entityMapTraceTag\", \"The tag 'EntityTag.map_trace' is deprecated in favor of EntityTag.trace_framed_map\");\r\n\r\n    // Added 2021/06/27, made very-slow 2022/12/31, made slow 2024/01/02, made normal 2025/01/15.\r\n    // 2023-year-end commonality: #11\r\n    // 2024-year-end commonality: #12\r\n    public static Warning serverUtilTags = new Warning(\"serverUtilTags\", \"Some 'server.' tags for core features are deprecated in favor of 'util.' equivalents, including 'java_version', '*_file', 'ram_*', 'disk_*', 'notes', 'last_reload', 'scripts', 'sql_connections', '*_time_*', ...\");\r\n\r\n    // Added 2021/06/27, made very-slow 2022/12/31, made slow 2024/01/02, made normal 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning serverObjectExistsTags = new Warning(\"serverObjectExistsTags\", \"The 'object_is_valid' tag is a historical version of modern '.exists' or '.is_truthy' fallback tags.\");\r\n\r\n    // Added 2021/06/27, made very-slow 2022/12/31, made slow 2024/01/02, made normal 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning hsbColorGradientTag = new Warning(\"hsbColorGradientTag\", \"The tag 'ElementTag.hsb_color_gradient' is deprecated: use 'color_gradient' with 'style=hsb'\");\r\n\r\n    // Added 2021/11/07, made very-slow 2022/12/31, made slow 2024/01/02, made normal 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning assignmentRemove = new Warning(\"assignmentRemove\", \"'assignment remove' without a script is deprecated: use 'clear' to clear all scripts, or 'remove' to remove one at a time.\");\r\n    public static Warning npcScriptSingle = new Warning(\"npcScriptSingle\", \"'npc.script' is deprecated in favor of 'npc.scripts' (plural).\");\r\n\r\n    // Added 2024/02/04, made normal 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning oldStructureTypes = new Warning(\"oldStructureTypes\", \"'server.structure_types' is based on outdated API and doesn't support modern datapack features. Use 'server.structures' instead.\");\r\n    public static Warning findStructureTags = new Warning(\"findStructureTags\", \"'LocationTag.find.structure' and related tags are deprecated in favor of 'LocationTag.find_structure'.\");\r\n\r\n    // Added 2025/03/14\r\n    public static Warning settingBoatType = new Warning(\"settingBoatType\", \"As of MC 1.21, separate boat wood types are separate entity types, meaning the wood type of an existing boat entity cannot be changed without spawning a new one.\");\r\n\r\n    // Added 2025/07/10\r\n    public static Warning entityKnockback = new Warning(\"entityKnockback\", \"The 'EntityTag.knockback' property is deprecated. You should adjust the knockback enchantment on the weapon itself.\");\r\n\r\n    // Added 2025/10/24\r\n    public static Warning explosionPrimeDetermination = new Warning(\"explosionPrimeDetermination\", \"The determination to control fire in the '<entity> explosion primes' event is now formatted as 'FIRE:<ElementTag(Boolean)>'.\");\r\n\r\n    // ==================== SLOW deprecations ====================\r\n    // These aren't spammed, but will show up repeatedly until fixed. Server owners will probably notice them.\r\n\r\n    // In Paper module, Added 2022/03/20\r\n    // bump to normal warning and/or past warning after 1.18 is the minimum supported version (change happened in MC 1.18)\r\n    public static Warning paperNoTickViewDistance = new SlowWarning(\"paperNoTickViewDistance\", \"Paper's 'no_tick_view_distance' is deprecated in favor of modern minecraft's 'simulation_distance' and 'view_distance' separation\");\r\n\r\n    // Added 2023/06/30\r\n    // Bump to normal/past warning after 1.19 is the minimum supported version (change happened in 1.19)\r\n    public static Warning biomeGlobalDownfallType = new SlowWarning(\"biomeGlobalDownfallType\", \"The 'BiomeTag.downfall_type' tag is deprecated in favor of 'BiomeTag.downfall_at', as biome downfall is now location-based\");\r\n    public static Warning biomeSettingDownfallType = new SlowWarning(\"biomeSettingDownfallType\", \"The 'BiomeTag.downfall_type' mechanism is removed, as Minecraft no longer allows for this value to be set.\");\r\n\r\n    // Added 2023/09/16\r\n    // Bump to normal warning after 1.19 is the minimum supported version (change happened in 1.19)\r\n    public static Warning boatType = new SlowWarning(\"boatType\", \"The 'EntityTag.boat_type' property is deprecated in favor of 'EntityTag.color' in 1.19+.\");\r\n\r\n    // Added 2021/03/29, made very-slow 2022/12/31, made slow 2024/05/09.\r\n    // 2022-year-end commonality: #7\r\n    // 2023-year-end commonality: #31\r\n    // Safe to remove now.\r\n    public static Warning legacyAttributeProperties = new SlowWarning(\"legacyAttributeProperties\", \"The 'attribute' properties are deprecated in favor of the 'attribute_modifiers' properties which more fully implement the attribute system.\");\r\n\r\n    // Added 2024/05/31\r\n    // 2024-year-end commonality: #15\r\n    public static Warning oldNbtProperty = new SlowWarning(\"oldNbtProperty\", \"'ItemTag.raw_nbt' is deprecated in favor of 'ItemTag.custom_data', as item NBT was removed by Mojang in favor of item components.\");\r\n\r\n    // Added 2021/10/18, made very-slow 2022/12/31, made slow 2025/01/15.\r\n    // 2022-year-end commonality: #10\r\n    // Safe to remove now.\r\n    public static Warning entityMechanismsFormat = new SlowWarning(\"entityMechanismsFormat\", \"Entity script containers previously allowed mechanisms in the script's root, however they should now be under a 'mechanisms' key.\");\r\n\r\n    // Added 2021/08/30, made very-slow 2022/12/31, made slow 2025/01/15.\r\n    // 2022-year-end commonality: #23\r\n    // Safe to remove now.\r\n    public static Warning takeMoney = new SlowWarning(\"takeMoney\", \"Using the 'take' command to take money is deprecated in favor of the 'money' command.\");\r\n\r\n    // Added 2021/03/27, made very-slow 2024/01/02, made slow 2025/01/15.\r\n    // 2022-year-end commonality: #6\r\n    // 2023-year-end commonality: #13\r\n    // Safe to remove now.\r\n    public static Warning locationFindEntities = new SlowWarning(\"locationFindEntities\", \"The tag 'LocationTag.find.entities.within' and 'blocks' tags are replaced by the 'find_entities' and 'find_blocks' versions. They are mostly compatible, but now have advanced matcher options.\");\r\n\r\n    // Added 2021/03/27, made very-slow 2024/01/02, made slow 2025/01/15.\r\n    // 2022-year-end commonality: #16\r\n    // 2023-year-end commonality: #26\r\n    // Safe to remove now.\r\n    public static Warning inventoryNonMatcherTags = new SlowWarning(\"inventoryNonMatcherTags\", \"The 'InventoryTag' tags 'contains', 'quantity', 'find', 'exclude' with raw items are deprecated and replaced by 'contains_item', 'quantity_item', 'find_item', 'exclude_item' that use advanced matcher logic.\");\r\n\r\n    // Added 2021/03/27, made very-slow 2024/01/02, made slow 2025/01/15.\r\n    // 2022-year-end commonality: #14\r\n    // 2023-year-end commonality: #10\r\n    // 2024-year-end commonality: #17\r\n    public static Warning takeRawItems = new SlowWarning(\"takeRawItems\", \"The 'take' command's ability to remove raw items without any command prefix, and the 'material' and 'scriptname' options are deprecated: use the 'item:<matcher>' option.\");\r\n\r\n    // Added 2021/08/30, made very-slow 2024/01/02, made slow 2025/01/15.\r\n    // 2022-year-end commonality: #26\r\n    // 2023-year-end commonality: #22\r\n    // Safe to remove now.\r\n    public static Warning playerResourcePackMech = new SlowWarning(\"playerResourcePackMech\", \"The 'resource_pack' mechanism is deprecated in favor of using the 'resourcepack' command.\");\r\n\r\n    // Added 2022/02/21, made very-slow 2024/01/02, made slow 2025/01/15.\r\n    // 2022-year-end commonality: #8\r\n    // 2023-year-end commonality: #17\r\n    // Safe to remove now.\r\n    public static Warning oldPotionEffects = new SlowWarning(\"oldPotionEffects\", \"The comma-separated-list potion effect tags like 'list_effects' are deprecated in favor of MapTag based tags - 'effects_data'. Refer to meta documentation for details.\");\r\n\r\n    // Added 2022/05/07, made very-slow 2024/01/02, made slow 2025/01/15.\r\n    // 2022-year-end commonality: #37\r\n    // Safe to remove now.\r\n    public static Warning armorStandDisabledSlotsOldFormat = new SlowWarning(\"armorStandDisabledSlotsOldFormat\", \"The EntityTag.disabled_slots tag and the SLOT/ACTION format in the EntityTag.disabled_slots mechanism are deprecated in favour of the EntityTag.disabled_slots_data tag and the MapTag format.\");\r\n\r\n    // Added 2021/06/17, made very-slow 2024/01/02.\r\n    // 2022-year-end commonality: #18\r\n    // 2023-year-end commonality: #30\r\n    // Safe to remove now.\r\n    public static Warning debugBlockAlpha = new SlowWarning(\"debugBlockAlpha\", \"The 'alpha' argument for the 'debugblock' command is deprecated: put the alpha in the color input instead.\");\r\n\r\n    // Added 2025/03/16\r\n    // Bump once 1.21 is the minimum supported version (as that is where boat types were split)\r\n    public static Warning gettingBoatType = new SlowWarning(\"gettingBoatType\", \"Getting boat wood types is deprecated, as separate boat types are separate entity types now: should check the entity type.\");\r\n\r\n    // Added 2025/09/14\r\n    public static Warning lookCommandNoEntities = new SlowWarning(\"lookCommandNoEntities\", \"The 'look' command now requires both the entity and location inputs.\");\r\n\r\n    // ==================== VERY SLOW deprecations ====================\r\n    // These are only shown minimally, so server owners are aware of them but not bugged by them. Only servers with active scripters (using 'ex reload') will see them often.\r\n\r\n    // Added 2021/06/15, made very-slow 2024/01/02\r\n    // Bad candidate for functionality removal - tags have been around a long time and some were used often.\r\n    // 2024-year-end commonality: #19\r\n    public static Warning locationOldCursorOn = new VerySlowWarning(\"locationOldCursorOn\", \"Several of the old 'LocationTag.cursor_on', 'precise_target_position', 'precise_impact_normal' variants are deprecated in favor of the 'ray_trace' tags.\");\r\n\r\n    // Added 2020/10/18, made very-slow 2022/12/31.\r\n    // Bad candidate for functionality removal due to frequency of use and likelihood of pre-existing data in save files.\r\n    // 2022-year-end commonality: #2\r\n    // 2023-year-end commonality: #3\r\n    // 2024-year-end commonality: #7\r\n    public static Warning itemDisplayNameMechanism = new VerySlowWarning(\"itemDisplayNameMechanism\", \"The item 'display_name' mechanism is now just the 'display' mechanism.\");\r\n\r\n    // Added 2020/12/05, made very-slow 2022/12/31.\r\n    // Bad candidate for functionality removal due to frequency of use and likelihood of pre-existing data remaining in world data.\r\n    // 2022-year-end commonality: #4\r\n    // 2023-year-end commonality: #14\r\n    // 2024-year-end commonality: #9\r\n    public static Warning itemNbt = new VerySlowWarning(\"itemNbt\", \"The item 'nbt' property is deprecated: use ItemTag flags instead!\");\r\n\r\n    // Added 2021/02/03, made very-slow 2022/12/31.\r\n    // Bad candidate for functional removal due to the \"scriptname\" variant being useful for debugging sometimes.\r\n    // 2022-year-end commonality: #3\r\n    // 2023-year-end commonality: #24\r\n    // Safe to remove now.\r\n    public static Warning hasScriptTags = new VerySlowWarning(\"hasScriptTags\", \"The ItemTag.scriptname and EntityTag.scriptname and ItemTag.has_script and NPCTag.has_script tags are deprecated: use '.script.name' or a null check on .script.\");\r\n\r\n    // Added 2023/07/21, bump when 1.17 is gone.\r\n    public static Warning chunkRefreshSections = new VerySlowWarning(\"chunkRefreshSections\", \"ChunkTag.refresh_chunk_sections, as of MC 1.18, is just a replica of ChunkTag.refresh_chunk, and so that mech should be used instead.\");\r\n\r\n    // Added 2024/07/13\r\n    // 2024-year-end commonality: #22\r\n    public static Warning pre1_21AttributeFormat = new VerySlowWarning(\"pre1_21AttributeFormat\", \"Attribute modifiers were changed in 1.21, now using slot groups instead of slots and namespaced keys instead of UUIDS; check relevant meta docs for more information.\");\r\n\r\n    // Added 2023/01/15, made very-slow 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning entityShootsMaterialEvent = new VerySlowWarning(\"entityShootsMaterialEvent\", \"The '<entity> shoots <material>' event is deprecated in favor of '<projectile> hits' with the 'block' and 'shooter' switches.\");\r\n\r\n    // Added 2023/01/15, made very-slow 2025/01/15.\r\n    // 2023-year-end commonality: #28\r\n    // 2024-year-end commonality: #16\r\n    public static Warning projectileHitsBlockLocationContext = new VerySlowWarning(\"projectileHitsBlockLocationContext\", \"'context.location' in the '<projectile> hits' event is deprecated in favor of 'context.hit_block'.\");\r\n\r\n    // Added 2023/01/15, made very-slow 2025/01/15.\r\n    // 2023-year-end commonality: #5\r\n    // 2024-year-end commonality: #8\r\n    public static Warning projectileHitsEventMatchers = new VerySlowWarning(\"projectileHitsEventMatchers\", \"The block/entity matchers in '<projectile> hits <block>/<entity>' are deprecated in favor of the 'block' and 'entity' switches.\");\r\n\r\n    // Added 2023/03/05, made very-slow 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning serverSystemMechanisms = new VerySlowWarning(\"serverSystemMechanisms\", \"Some 'server' mechanisms for core features are deprecated in favor of 'system' equivalents.\");\r\n\r\n    // Added 2023/03/27, made very-slow 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning oldAgeLockedControls = new VerySlowWarning(\"oldAgeLockedControls\", \"Several old ways of controlling whether an entity's age is locked are deprecated in favor of the 'EntityTag.age_locked' tag/mech pair.\");\r\n\r\n    // Added 2023/10/04, made very-slow 2025/01/15.\r\n    // 2023-year-end commonality: #4\r\n    // 2024-year-end commonality: #5\r\n    public static Warning translateLegacySyntax = new VerySlowWarning(\"translateLegacySyntax\", \"<&translate[...].with[...]> is deprecated in favor of the modern <&translate[key=...;with=...]> syntax.\");\r\n\r\n    // Added 2024/02/19, made very-slow 2025/01/15.\r\n    // Safe to remove now.\r\n    public static Warning lecternPage = new VerySlowWarning(\"lecternPage\", \"'LocationTag.lectern_page' is deprecated in favor of 'LocationTag.page'.\");\r\n\r\n    // ==================== FUTURE deprecations ====================\r\n\r\n    // Added 2023/01/15\r\n    // Bump once 1.19 is the minimum supported version, as the change happened on that version.\r\n    public static Warning projectileCollideEvent = new FutureWarning(\"projectileCollideEvent\", \"The '<projectile> collides with <entity>' event is deprecated in favor of '<projectile> hits' with the 'entity' switch.\");\r\n\r\n    // Added 2023/11/16\r\n    // Safe to remove now.\r\n    public static Warning takeExperience = new FutureWarning(\"takeExperience\", \"Using the 'take' command to take experience is deprecated in favor of the 'experience' command.\");\r\n\r\n    // Added 2024/10/12\r\n    // Good candidate for bumping, as this is a niche feature only on 1.19+ that already had some issues\r\n    public static Warning entityStepHeight = new FutureWarning(\"entityStepHeight\", \"'EntityTag.step_height' is deprecated in favor of the step height attribute.\");\r\n\r\n    // Added 2024/06/17, do not deprecate officially before end-of-year 2025 hitrate commonality review.\r\n    // Bad candidate for bumping, targets extremely commonly used naming, some of which may be hard to remove (eg stored in flag data).\r\n    // 2024-year-end commonality: #3\r\n    public static Warning oldSpigotNames = new FutureWarning(\"oldSpigotNames\", \"Several features (particles, entities, etc.) had alternative naming added by Spigot, which are now deprecated in favor of the official Minecraft naming; see relevant feature's meta docs for more information.\");\r\n\r\n    // Added 2024/11/19\r\n    public static Warning oldPotionEffectType = new FutureWarning(\"oldPotionEffectType\", \"Potion effects now use an 'effect' key for the potion effect's type, see meta docs for more information.\");\r\n\r\n    // Added 2024/12/27\r\n    public static Warning entityIsSheared = new FutureWarning(\"entityIsSheared\", \"'EntityTag.is_sheared' and 'EntityTag.has_pumpkin_head' properties are deprecated in favor of 'EntityTag.sheared'.\");\r\n\r\n    // Added 2025/01/12\r\n    public static Warning splashPotionItem = new FutureWarning(\"splashPotionItem\", \"Using 'EntityTag.potion' to get a splash potion's item is deprecated in favor of 'EntityTag.item'.\");\r\n\r\n    // Added 2025/01/12\r\n    public static Warning arrowBasePotionType = new FutureWarning(\"arrowBasePotionType\", \"Using 'EntityTag.potion' to get an arrow's base potion type is deprecated in favor of 'EntityTag.potion_type'.\");\r\n\r\n    // Added 2025/01/04\r\n    public static Warning playEffectSpecialDataListInput = new FutureWarning(\"playEffectSpecialDataListInput\", \"List input for the special_data argument in playeffect command is now deprecated. Please use a MapTag instead.\");\r\n\r\n    // Added 2025/01/23\r\n    public static Warning projectileLaunchedEntityContext = new FutureWarning(\"projectileLaunchedEntityContext\", \"'context.entity' in the 'projectile launched' event is deprecated in favor of 'context.projectile'.\");\r\n\r\n    // Added 2025/03/29\r\n    public static Warning areaEffectCloudControls = new FutureWarning(\"areaEffectCloudControls\", \"Several tags/mechanisms for controlling area effect clouds have been merged into existing properties, check relevant meta docs for more information.\");\r\n\r\n    // Added 2025/04/27\r\n    public static Warning playerChangesWorldSwitches = new FutureWarning(\"playerChangesWorldSwitches\", \"The 'from' and 'to' arguments in the 'player changes world' script event have been deprecated in favor of the 'from' and 'to' switches.\");\r\n\r\n    // Added 2025/05/02\r\n    public static Warning timeSubTags = new FutureWarning(\"timeSubTags\", pointlessSubtagPrefix + \"'time.*' tags are now just 'time_*'.\");\r\n\r\n    // Added 2025/08/06\r\n    public static Warning horseArmorEquipCommand = new FutureWarning(\"horseArmorEquipCommand\", \"The 'horse_armor' argument in the 'equip' command has been deprecated in favor of 'body'.\");\r\n\r\n    // Added 2025/08/06\r\n    public static Warning horseArmorTag = new FutureWarning(\"horseArmorTag\", \"The 'EntityTag.horse_armor' tag has been deprecated in favor of 'EntityTag.equipment_map.get[body]'.\");\r\n\r\n    // Added 2025/08/06\r\n    public static Warning entityEquipmentListTag = new FutureWarning(\"entityEquipmentListTag\", \"The ListTag 'EntityTag.equipment' has been deprecated in favor of the MapTag 'EntityTag.equipment_map'.\");\r\n\r\n    // Added 2025/07/11\r\n    public static Warning assignmentOptionalPrefixArgs = new FutureWarning(\"assignmentOptionalPrefixArgs\", \"For the 'assignment' command, the args 'script' and 'to' now require a prefix to use.\");\r\n\r\n    // Added 2025/06/29\r\n    public static Warning blockExplodesStrengthDetermination = new FutureWarning(\"blockExplodesStrengthDetermination\", \"The determination to control strength in the 'block explodes' script event has been changed into the 'STRENGTH:<ElementTag(Decimal)>' format.\");\r\n\r\n    // Added 2025/06/29\r\n    public static Warning blockDispensesItemDetermination = new FutureWarning(\"blockDispensesItemDetermination\", \"The determination to control the item in the 'block dispenses' script event has been changed into the 'ITEM:<ItemTag>' format.\");\r\n\r\n    // Added 2025/08/23\r\n    public static Warning advancementBackgroundFormat = new FutureWarning(\"advancementBackgroundFormat\", \"The 'background:' input in the advancement command no longer uses the 'textures/' path or '.png' suffix, so for example 'minecraft:textures/gui/advancements/backgrounds/stone.png' would be 'minecraft:gui/advancements/backgrounds/stone'.\");\r\n\r\n    // Added 2025/09/07\r\n    public static Warning brewingStandConsumeDetermination = new FutureWarning(\"brewingStandConsumeDetermination\", \"The 'consuming' and 'not_consuming' determinations in the 'brewing stand fueled' event have been deprecated in favor of 'CONSUMING:<ElementTag(Boolean)>'.\");\r\n\r\n    // Added 2025/09/22\r\n    public static Warning playerSteerEntityEvent = new FutureWarning(\"playerSteerEntityEvent\", \"The 'player steers <entity>' event is deprecated in favor of the 'player input' event in MC 1.21+.\");\r\n\r\n    // ==================== PAST deprecations of things that are already gone but still have a warning left behind ====================\r\n\r\n    // Removed upstream 2023/10/29 without warning.\r\n    public static Warning npcHologramDirection = new StrongWarning(\"npcHologramDirection\", \"NPCTag's 'hologram_direction' is deprecated: it was removed from Citizens. Ask in the Citizens channel on the Discord if you need it.\");\r\n\r\n    // Added on 2019/10/13\r\n    public static Warning versionScripts = new StrongWarning(\"versionScripts\", \"Version script containers are deprecated due to the old script repo no longer being active.\");\r\n\r\n    // Added on 2019/03/08, removed 2020/10/24.\r\n    public static Warning boundWarning = new StrongWarning(\"boundWarning\", \"Item script 'bound' functionality has never been reliable and should not be used. Consider replicating the concept with world events.\");\r\n\r\n    // Deprecated 2019/02/06, removed 2022/03/19.\r\n    public static Warning globalTagName = new StrongWarning(\"globalTagName\", \"Using 'global' as a base tag is a deprecated alternate name. Please use 'server' instead.\");\r\n\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/CommonRegistries.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\r\nimport com.denizenscript.denizen.tags.core.*;\r\nimport com.denizenscript.denizencore.objects.ObjectType;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizencore.objects.ObjectFetcher;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.Biome;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.MerchantRecipe;\r\nimport org.bukkit.plugin.Plugin;\r\n\r\npublic class CommonRegistries {\r\n\r\n    // <--[language]\r\n    // @name ObjectTags\r\n    // @group Object System\r\n    // @description\r\n    // ObjectTags are a system put into place by Denizen that make working with things, or 'objects',\r\n    // in Minecraft and Denizen easier. Many parts of scripts will require some kind of object as an\r\n    // argument, identifier/type, or such as in world events, part of an event name. The ObjectTags notation\r\n    // system helps both you and Denizen know what type of objects are being referenced and worked with.\r\n    //\r\n    // So when should you use ObjectTags? In arguments, event names, replaceable tags, configs, flags, and\r\n    // more! If you're just a beginner, you've probably been using them without even realizing it!\r\n    //\r\n    // ObjectTag is a broader term for a 'type' of object that more specifically represents something,\r\n    // such as a LocationTag or ScriptTag, often times just referred to as a 'location' or 'script'. Denizen\r\n    // employs many object types that you should be familiar with. You'll notice that many times objects\r\n    // are referenced with their 'ObjectTag notation' which is in the format of 'x@', the x being the specific\r\n    // notation of an object type. Example: player objects use the p@ notation, and locations use l@.\r\n    // This notation is automatically generated when directly displaying objects, or saving them into data files.\r\n    // It should never be manually typed into a script.\r\n    //\r\n    // Let's take the tag system, for example. It uses the ObjectTags system pretty heavily. For instance,\r\n    // every time you use <player.name> or <npc.id>, you're using a ObjectTag, which brings us to a simple\r\n    // clarification: Why <player.name> and not <PlayerTag.name>? That's because Denizen allows Players,\r\n    // NPCs and other 'in-context objects' to be linked to certain scripts. In short, <player> already\r\n    // contains a reference to a specific player, such as the player that died in a world event 'on player dies'.\r\n    // <PlayerTag.name> is instead the format for documentation, with \"PlayerTag\" simply indicating 'any player object here'.\r\n    //\r\n    // ObjectTags can be used to CREATE new instances of objects, too! Though not all types allow 'new'\r\n    // objects to be created, many do, such as ItemTags. With the use of tags, it's easy to reference a specific\r\n    // item, say -- an item in the Player's hand -- items are also able to use a constructor to make a new item,\r\n    // and say, drop it in the world. Take the case of the command/usage '- drop diamond_ore'. The item object\r\n    // used is a brand new diamond_ore, which is then dropped by the command to a location of your choice -- just\r\n    // specify an additional location argument.\r\n    //\r\n    // There's a great deal more to learn about ObjectTags, so be sure to check out each object type for more\r\n    // specific information. While all ObjectTags share some features, many contain goodies on top of that!\r\n    // -->\r\n\r\n    // <--[language]\r\n    // @name Tick\r\n    // @group Common Terminology\r\n    // @description\r\n    // A 'tick' is usually referred to as 1/20th of a second, the speed at which Minecraft servers update\r\n    // and process everything on them.\r\n    // -->\r\n\r\n    public static void registerMainTagHandlers() {\r\n        // Objects\r\n        if (Depends.citizens != null) {\r\n            new NPCTagBase();\r\n        }\r\n        new PlayerTagBase();\r\n        // Other bases\r\n        new CustomColorTagBase();\r\n        new ServerTagBase();\r\n        new TextTagBase();\r\n    }\r\n\r\n    public static void registerMainObjects() {\r\n        registerObjectTypes();\r\n        registerNotables();\r\n        registerConversions();\r\n        registerSubtypeSets();\r\n        // Final debug\r\n        if (CoreConfiguration.debugVerbose) {\r\n            StringBuilder debug = new StringBuilder(256);\r\n            for (ObjectType<?> objectType : ObjectFetcher.objectsByPrefix.values()) {\r\n                debug.append(DebugInternals.getClassNameOpti(objectType.clazz)).append(\" as \").append(objectType.prefix).append(\", \");\r\n            }\r\n            Debug.echoApproval(\"Loaded core object types: [\" + debug.substring(0, debug.length() - 2) + \"]\");\r\n        }\r\n    }\r\n\r\n    public static ObjectType<BiomeTag> TYPE_BIOME;\r\n    public static ObjectType<ChunkTag> TYPE_CHUNK;\r\n    public static ObjectType<CuboidTag> TYPE_CUBOID;\r\n    public static ObjectType<EllipsoidTag> TYPE_ELLIPSOID;\r\n    public static ObjectType<EnchantmentTag> TYPE_ENCHANTMENT;\r\n    public static ObjectType<EntityTag> TYPE_ENTITY;\r\n    public static ObjectType<InventoryTag> TYPE_INVENTORY;\r\n    public static ObjectType<ItemTag> TYPE_ITEM;\r\n    public static ObjectType<LocationTag> TYPE_LOCATION;\r\n    public static ObjectType<MaterialTag> TYPE_MATERIAL;\r\n    public static ObjectType<NPCTag> TYPE_NPC;\r\n    public static ObjectType<PlayerTag> TYPE_PLAYER;\r\n    public static ObjectType<PluginTag> TYPE_PLUGIN;\r\n    public static ObjectType<PolygonTag> TYPE_POLYGON;\r\n    public static ObjectType<TradeTag> TYPE_TRADE;\r\n    public static ObjectType<WorldTag> TYPE_WORLD;\r\n\r\n    private static void registerObjectTypes() {\r\n\r\n        // <--[tag]\r\n        // @attribute <biome[<biome>]>\r\n        // @returns BiomeTag\r\n        // @description\r\n        // Returns a biome object constructed from the input value.\r\n        // Refer to <@link objecttype BiomeTag>.\r\n        // -->\r\n        TYPE_BIOME = ObjectFetcher.registerWithObjectFetcher(BiomeTag.class, BiomeTag.tagProcessor).setAsNOtherCode().setCanConvertStatic().generateBaseTag(); // b@\r\n\r\n        // <--[tag]\r\n        // @attribute <chunk[<chunk>]>\r\n        // @returns ChunkTag\r\n        // @description\r\n        // Returns a chunk object constructed from the input value.\r\n        // Refer to <@link objecttype ChunkTag>.\r\n        // -->\r\n        TYPE_CHUNK = ObjectFetcher.registerWithObjectFetcher(ChunkTag.class, ChunkTag.tagProcessor).setAsNOtherCode().setCanConvertStatic().generateBaseTag(); // ch@\r\n\r\n        // <--[tag]\r\n        // @attribute <cuboid[<cuboid>]>\r\n        // @returns CuboidTag\r\n        // @description\r\n        // Returns a cuboid object constructed from the input value.\r\n        // Refer to <@link objecttype CuboidTag>.\r\n        // -->\r\n        TYPE_CUBOID = ObjectFetcher.registerWithObjectFetcher(CuboidTag.class, CuboidTag.tagProcessor).setAsNOtherCode().setCanConvertStatic().generateBaseTag(); // cu@\r\n\r\n        // <--[tag]\r\n        // @attribute <ellipsoid[<ellipsoid>]>\r\n        // @returns EllipsoidTag\r\n        // @description\r\n        // Returns an ellipsoid object constructed from the input value.\r\n        // Refer to <@link objecttype EllipsoidTag>.\r\n        // -->\r\n        TYPE_ELLIPSOID = ObjectFetcher.registerWithObjectFetcher(EllipsoidTag.class, EllipsoidTag.tagProcessor).setAsNOtherCode().setCanConvertStatic().generateBaseTag(); // ellipsoid@\r\n\r\n        // <--[tag]\r\n        // @attribute <enchantment[<enchantment>]>\r\n        // @returns EnchantmentTag\r\n        // @description\r\n        // Returns an enchantment object constructed from the input value.\r\n        // Refer to <@link objecttype EnchantmentTag>.\r\n        // -->\r\n        TYPE_ENCHANTMENT = ObjectFetcher.registerWithObjectFetcher(EnchantmentTag.class, EnchantmentTag.tagProcessor).setAsNOtherCode().setCanConvertStatic().generateBaseTag(); // enchantment@\r\n\r\n        // <--[tag]\r\n        // @attribute <entity[<entity>]>\r\n        // @returns EntityTag\r\n        // @description\r\n        // Returns an entity object constructed from the input value.\r\n        // Refer to <@link objecttype EntityTag>.\r\n        // -->\r\n        TYPE_ENTITY = ObjectFetcher.registerWithObjectFetcher(EntityTag.class, EntityTag.tagProcessor).generateBaseTag(); // e@\r\n        TYPE_ENTITY.typeChecker = (inp) -> { // This is adapted 'no other type code' but for e@, p@, and n@\r\n            if (inp == null) {\r\n                return false;\r\n            }\r\n            if (inp instanceof PlayerTag || inp instanceof EntityTag || inp instanceof NPCTag) {\r\n                return true;\r\n            }\r\n            if (inp instanceof ElementTag) {\r\n                String simple = inp.identifySimple();\r\n                int atIndex = simple.indexOf('@');\r\n                if (atIndex != -1) {\r\n                    String code = simple.substring(0, atIndex);\r\n                    if (!code.equals(\"e\") && !code.equals(\"p\") && !code.equals(\"n\") && !code.equals(\"el\")) {\r\n                        if (ObjectFetcher.objectsByPrefix.containsKey(code)) {\r\n                            return false;\r\n                        }\r\n                    }\r\n                }\r\n                return true;\r\n            }\r\n            return false;\r\n        };\r\n        TYPE_ENTITY.typeConverter = (obj, context) -> {\r\n            if (obj instanceof PlayerTag) {\r\n                if (!((PlayerTag) obj).isOnline()) {\r\n                    if (context.showErrors()) {\r\n                        Debug.echoError(\"Player '\" + obj.debuggable() + \"' is offline, cannot convert to EntityTag.\");\r\n                    }\r\n                    return null;\r\n                }\r\n                return new EntityTag(((PlayerTag) obj).getPlayerEntity());\r\n            }\r\n            else if (obj instanceof NPCTag) {\r\n                if (!((NPCTag) obj).isSpawned() && !EntityTag.allowDespawnedNpcs) {\r\n                    if (context.showErrors()) {\r\n                        Debug.echoError(\"NPC '\" + obj.debuggable() + \"' is unspawned, cannot convert to EntityTag.\");\r\n                    }\r\n                    return null;\r\n                }\r\n                return new EntityTag((NPCTag) obj);\r\n            }\r\n            return EntityTag.valueOf(obj.toString(), context);\r\n        };\r\n        TYPE_ENTITY.typeShouldBeChecker = (obj) -> {\r\n            if (obj instanceof EntityFormObject) {\r\n                return true;\r\n            }\r\n            String raw = obj.toString();\r\n            if (raw.startsWith(\"p@\") || raw.startsWith(\"e@\") || raw.startsWith(\"n@\")) {\r\n                return true;\r\n            }\r\n            return false;\r\n        };\r\n\r\n        // <--[tag]\r\n        // @attribute <inventory[<inventory>]>\r\n        // @returns InventoryTag\r\n        // @description\r\n        // Returns an inventory object constructed from the input value.\r\n        // Refer to <@link objecttype InventoryTag>.\r\n        // -->\r\n        // non-static due to notes and inventory scripts\r\n        TYPE_INVENTORY = ObjectFetcher.registerWithObjectFetcher(InventoryTag.class, InventoryTag.tagProcessor).setAsNOtherCode().generateBaseTag(); // in@\r\n\r\n        // <--[tag]\r\n        // @attribute <item[<item>]>\r\n        // @returns ItemTag\r\n        // @description\r\n        // Returns an item object constructed from the input value.\r\n        // Refer to <@link objecttype ItemTag>.\r\n        // -->\r\n        // non-static as item scripts can contain tags\r\n        TYPE_ITEM = ObjectFetcher.registerWithObjectFetcher(ItemTag.class, ItemTag.tagProcessor).setAsNOtherCode().generateBaseTag(); // i@\r\n\r\n        // <--[tag]\r\n        // @attribute <location[<location>]>\r\n        // @returns LocationTag\r\n        // @description\r\n        // Returns a location object constructed from the input value.\r\n        // Refer to <@link objecttype LocationTag>.\r\n        // -->\r\n        TYPE_LOCATION = ObjectFetcher.registerWithObjectFetcher(LocationTag.class, LocationTag.tagProcessor).setAsNOtherCode().setCanConvertStatic().generateBaseTag(); // l@\r\n\r\n        // <--[tag]\r\n        // @attribute <material[<material>]>\r\n        // @returns MaterialTag\r\n        // @description\r\n        // Returns a material object constructed from the input value.\r\n        // Refer to <@link objecttype MaterialTag>.\r\n        // -->\r\n        TYPE_MATERIAL = ObjectFetcher.registerWithObjectFetcher(MaterialTag.class, MaterialTag.tagProcessor).setAsNOtherCode().setCanConvertStatic().generateBaseTag(); // m@\r\n\r\n        if (Depends.citizens != null) {\r\n            // Tag generated externally as input is optional\r\n            TYPE_NPC = ObjectFetcher.registerWithObjectFetcher(NPCTag.class, NPCTag.tagProcessor); // n@\r\n            TYPE_NPC.typeChecker = (inp) -> { // This is adapted 'no other type code' but allows instanceof EntityTag\r\n                if (inp == null) {\r\n                    return false;\r\n                }\r\n                if (inp instanceof NPCTag || inp instanceof EntityTag) {\r\n                    return true;\r\n                }\r\n                if (inp instanceof ElementTag) {\r\n                    String simple = inp.identifySimple();\r\n                    int atIndex = simple.indexOf('@');\r\n                    if (atIndex != -1) {\r\n                        String code = simple.substring(0, atIndex);\r\n                        if (!code.equals(\"n\") && !code.equals(\"el\")) {\r\n                            if (ObjectFetcher.objectsByPrefix.containsKey(code)) {\r\n                                return false;\r\n                            }\r\n                        }\r\n                    }\r\n                    return true;\r\n                }\r\n                return false;\r\n            };\r\n            TYPE_NPC.typeConverter = (obj, context) -> {\r\n                if (obj instanceof EntityTag && ((EntityTag) obj).isCitizensNPC()) {\r\n                    return ((EntityTag) obj).getDenizenNPC();\r\n                }\r\n                return NPCTag.valueOf(obj.toString(), context);\r\n            };\r\n        }\r\n\r\n        // Tag generated externally as input is optional\r\n        TYPE_PLAYER = ObjectFetcher.registerWithObjectFetcher(PlayerTag.class, PlayerTag.tagProcessor); // p@\r\n        TYPE_PLAYER.typeChecker = (inp) -> { // This is adapted 'no other type code' but allows instanceof EntityTag\r\n            if (inp == null) {\r\n                return false;\r\n            }\r\n            if (inp instanceof PlayerTag || inp instanceof EntityTag) {\r\n                return true;\r\n            }\r\n            if (inp instanceof ElementTag) {\r\n                String simple = inp.identifySimple();\r\n                int atIndex = simple.indexOf('@');\r\n                if (atIndex != -1) {\r\n                    String code = simple.substring(0, atIndex);\r\n                    if (!code.equals(\"p\") && !code.equals(\"el\")) {\r\n                        if (ObjectFetcher.objectsByPrefix.containsKey(code)) {\r\n                            return false;\r\n                        }\r\n                    }\r\n                }\r\n                return true;\r\n            }\r\n            return false;\r\n        };\r\n        TYPE_PLAYER.typeConverter = (obj, context) -> {\r\n            if (obj instanceof EntityTag && ((EntityTag) obj).isPlayer()) {\r\n                return ((EntityTag) obj).getDenizenPlayer();\r\n            }\r\n            return PlayerTag.valueOf(obj.toString(), context);\r\n        };\r\n\r\n        // <--[tag]\r\n        // @attribute <plugin[<plugin>]>\r\n        // @returns PluginTag\r\n        // @description\r\n        // Returns a plugin object constructed from the input value.\r\n        // Refer to <@link objecttype PluginTag>.\r\n        // -->\r\n        TYPE_PLUGIN = ObjectFetcher.registerWithObjectFetcher(PluginTag.class, PluginTag.tagProcessor).setAsNOtherCode().setCanConvertStatic().generateBaseTag(); // pl@\r\n\r\n        // <--[tag]\r\n        // @attribute <polygon[<polygon>]>\r\n        // @returns PolygonTag\r\n        // @description\r\n        // Returns a polygon object constructed from the input value.\r\n        // Refer to <@link objecttype PolygonTag>.\r\n        // -->\r\n        TYPE_POLYGON = ObjectFetcher.registerWithObjectFetcher(PolygonTag.class, PolygonTag.tagProcessor).setAsNOtherCode().setCanConvertStatic().generateBaseTag(); // polygon@\r\n\r\n        // <--[tag]\r\n        // @attribute <trade[<trade>]>\r\n        // @returns TradeTag\r\n        // @description\r\n        // Returns a trade object constructed from the input value.\r\n        // Refer to <@link objecttype TradeTag>.\r\n        // -->\r\n        // Non-static due to potential for dynamic items.\r\n        TYPE_TRADE = ObjectFetcher.registerWithObjectFetcher(TradeTag.class, TradeTag.tagProcessor).setAsNOtherCode().generateBaseTag(); // trade@\r\n\r\n        // <--[tag]\r\n        // @attribute <world[<world>]>\r\n        // @returns WorldTag\r\n        // @description\r\n        // Returns a world object constructed from the input value.\r\n        // Refer to <@link objecttype WorldTag>.\r\n        // -->\r\n        // non-static as worlds can be dynamically loaded\r\n        TYPE_WORLD = ObjectFetcher.registerWithObjectFetcher(WorldTag.class, WorldTag.tagProcessor).setAsNOtherCode().generateBaseTag(); // w@\r\n    }\r\n\r\n    private static void registerNotables() {\r\n        NoteManager.registerObjectTypeAsNotable(CuboidTag.class);\r\n        NoteManager.registerObjectTypeAsNotable(EllipsoidTag.class);\r\n        NoteManager.registerObjectTypeAsNotable(InventoryTag.class);\r\n        NoteManager.registerObjectTypeAsNotable(ItemTag.class);\r\n        NoteManager.registerObjectTypeAsNotable(LocationTag.class);\r\n        NoteManager.registerObjectTypeAsNotable(PolygonTag.class);\r\n    }\r\n\r\n    private static void registerConversions() {\r\n        CoreUtilities.objectConversions.add((obj) -> {\r\n            if (obj instanceof Biome) {\r\n                return new BiomeTag((Biome) obj);\r\n            }\r\n            if (obj instanceof Chunk) {\r\n                return new ChunkTag((Chunk) obj);\r\n            }\r\n            if (obj instanceof Color) {\r\n                return BukkitColorExtensions.fromColor((Color) obj);\r\n            }\r\n            if (obj instanceof Enchantment) {\r\n                return new EnchantmentTag((Enchantment) obj);\r\n            }\r\n            if (obj instanceof Entity) {\r\n                return new EntityTag((Entity) obj).getDenizenObject();\r\n            }\r\n            if (obj instanceof Inventory) {\r\n                return InventoryTag.mirrorBukkitInventory((Inventory) obj);\r\n            }\r\n            if (obj instanceof ItemStack) {\r\n                return new ItemTag((ItemStack) obj);\r\n            }\r\n            if (obj instanceof Location) {\r\n                return new LocationTag((Location) obj);\r\n            }\r\n            if (obj instanceof Material) {\r\n                return new MaterialTag((Material) obj);\r\n            }\r\n            if (obj instanceof BlockData) {\r\n                return new MaterialTag((BlockData) obj);\r\n            }\r\n            if (obj instanceof Block) {\r\n                return new LocationTag(((Block) obj).getLocation());\r\n            }\r\n            if (Depends.citizens != null && obj instanceof NPC) {\r\n                return new NPCTag((NPC) obj);\r\n            }\r\n            if (obj instanceof OfflinePlayer) {\r\n                return new PlayerTag((OfflinePlayer) obj);\r\n            }\r\n            if (obj instanceof Plugin) {\r\n                return new PluginTag((Plugin) obj);\r\n            }\r\n            if (obj instanceof MerchantRecipe) {\r\n                return new TradeTag((MerchantRecipe) obj);\r\n            }\r\n            if (obj instanceof World) {\r\n                return new WorldTag((World) obj);\r\n            }\r\n            return null;\r\n        });\r\n    }\r\n\r\n    private static void registerSubtypeSets() {\r\n        ObjectFetcher.registerCrossType(EntityTag.class, EntityFormObject.class);\r\n        ObjectFetcher.registerCrossType(PlayerTag.class, EntityTag.class);\r\n        ObjectFetcher.registerCrossType(PlayerTag.class, EntityFormObject.class);\r\n        if (Depends.citizens != null) {\r\n            ObjectFetcher.registerCrossType(NPCTag.class, EntityTag.class);\r\n            ObjectFetcher.registerCrossType(NPCTag.class, EntityFormObject.class);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/Conversion.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport org.bukkit.Color;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.AbstractMap;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class Conversion {\r\n\r\n    public static List<Color> convertColors(List<ColorTag> colors) {\r\n        List<Color> newList = new ArrayList<>();\r\n        for (ColorTag color : colors) {\r\n            newList.add(BukkitColorExtensions.getColor(color));\r\n        }\r\n        return newList;\r\n    }\r\n\r\n    public static List<ItemStack> convertItems(List<ItemTag> items) {\r\n        List<ItemStack> newList = new ArrayList<>();\r\n        for (ItemTag item : items) {\r\n            newList.add(item.getItemStack());\r\n        }\r\n        return newList;\r\n    }\r\n\r\n    public static List<Entity> convertEntities(List<EntityTag> entities) {\r\n        List<Entity> newList = new ArrayList<>();\r\n        for (EntityTag entity : entities) {\r\n            newList.add(entity.getBukkitEntity());\r\n        }\r\n        return newList;\r\n    }\r\n\r\n    public static AbstractMap.SimpleEntry<Integer, InventoryTag> getInventory(Argument arg, ScriptEntry scriptEntry) {\r\n        return getInventory(arg, scriptEntry == null ? null : scriptEntry.context);\r\n    }\r\n\r\n    public static AbstractMap.SimpleEntry<Integer, InventoryTag> getInventory(Argument arg, TagContext context) {\r\n        boolean isElement = arg.object instanceof ElementTag;\r\n        if (arg.object instanceof InventoryTag || (isElement && InventoryTag.matches(arg.getValue()))) {\r\n            InventoryTag inv = arg.object instanceof InventoryTag ? (InventoryTag) arg.object : InventoryTag.valueOf(arg.getValue(), context);\r\n            if (inv != null) {\r\n                return new AbstractMap.SimpleEntry<>(inv.getContents().length, inv);\r\n            }\r\n        }\r\n        else if (arg.object instanceof MapTag || (isElement && arg.getValue().startsWith(\"map@\"))) {\r\n            MapTag map = arg.object instanceof MapTag ? (MapTag) arg.object : MapTag.valueOf(arg.getValue(), context);\r\n            int maxSlot = 0;\r\n            for (Map.Entry<StringHolder, ObjectTag> entry : map.entrySet()) {\r\n                if (!ArgumentHelper.matchesInteger(entry.getKey().str)) {\r\n                    return null;\r\n                }\r\n                int slot = new ElementTag(entry.getKey().str).asInt();\r\n                if (slot > maxSlot) {\r\n                    maxSlot = slot;\r\n                }\r\n            }\r\n            InventoryTag inventory = new InventoryTag(Math.min(InventoryTag.maxSlots, (maxSlot / 9) * 9 + 9));\r\n            for (Map.Entry<StringHolder, ObjectTag> entry : map.entrySet()) {\r\n                int slot = new ElementTag(entry.getKey().str).asInt();\r\n                ItemTag item = ItemTag.getItemFor(entry.getValue(), context);\r\n                if (item == null) {\r\n                    if (context == null || context.debug || CoreConfiguration.debugOverride) {\r\n                        Debug.echoError(\"Not a valid item: '\" + entry.getValue() + \"'\");\r\n                    }\r\n                    continue;\r\n                }\r\n                inventory.getInventory().setItem(slot - 1, item.getItemStack());\r\n            }\r\n            return new AbstractMap.SimpleEntry<>(maxSlot, inventory);\r\n        }\r\n        else if (arg.object instanceof LocationTag || (isElement && LocationTag.matches(arg.getValue()))) {\r\n            InventoryTag inv = (arg.object instanceof LocationTag ? (LocationTag) arg.object : LocationTag.valueOf(arg.getValue(), context)).getInventory();\r\n            if (inv != null) {\r\n                return new AbstractMap.SimpleEntry<>(inv.getContents().length, inv);\r\n            }\r\n        }\r\n        else if (arg.object instanceof EntityTag || arg.object instanceof PlayerTag || arg.object instanceof NPCTag || (isElement && EntityTag.matches(arg.getValue()))) {\r\n            InventoryTag inv = EntityTag.valueOf(arg.getValue(), context).getInventory();\r\n            if (inv != null) {\r\n                return new AbstractMap.SimpleEntry<>(inv.getContents().length, inv);\r\n            }\r\n        }\r\n        ListTag asList = ListTag.getListFor(arg.object, context);\r\n        if (asList.containsObjectsFrom(ItemTag.class) || asList.isEmpty()) {\r\n            List<ItemTag> list = asList.filter(ItemTag.class, context);\r\n            ItemStack[] items = convertItems(list).toArray(new ItemStack[list.size()]);\r\n            InventoryTag inventory = new InventoryTag(Math.min(InventoryTag.maxSlots, (items.length / 9) * 9 + 9));\r\n            inventory.setContents(items);\r\n            return new AbstractMap.SimpleEntry<>(items.length, inventory);\r\n        }\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/DataPersistenceHelper.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.objects.ObjectFetcher;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.persistence.PersistentDataAdapterContext;\r\nimport org.bukkit.persistence.PersistentDataHolder;\r\nimport org.bukkit.persistence.PersistentDataType;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\n\r\n/**\r\n * Helper class for PersistentDataContainers.\r\n */\r\npublic class DataPersistenceHelper {\r\n\r\n    public static class DenizenObjectType implements PersistentDataType<byte[], ObjectTag> {\r\n        @Override\r\n        public Class<byte[]> getPrimitiveType() {\r\n            return byte[].class;\r\n        }\r\n\r\n        @Override\r\n        public Class<ObjectTag> getComplexType() {\r\n            return ObjectTag.class;\r\n        }\r\n\r\n        @Override\r\n        public byte[] toPrimitive(ObjectTag complex, PersistentDataAdapterContext context) {\r\n            return complex.toString().getBytes(StandardCharsets.UTF_8);\r\n        }\r\n\r\n        @Override\r\n        public ObjectTag fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) {\r\n            return ObjectFetcher.pickObjectFor(new String(primitive, StandardCharsets.UTF_8), CoreUtilities.noDebugContext);\r\n        }\r\n    }\r\n\r\n    public static final DenizenObjectType PERSISTER_TYPE = new DenizenObjectType();\r\n\r\n    public static void removeDenizenKey(PersistentDataHolder holder, String keyName) {\r\n        holder.getPersistentDataContainer().remove(new NamespacedKey(Denizen.getInstance(), keyName));\r\n    }\r\n\r\n    public static void setDenizenKey(PersistentDataHolder holder, String keyName, ObjectTag keyValue) {\r\n        holder.getPersistentDataContainer().set(new NamespacedKey(Denizen.getInstance(), keyName), PERSISTER_TYPE, keyValue);\r\n    }\r\n\r\n    public static boolean hasDenizenKey(PersistentDataHolder holder, String keyName) {\r\n        return NMSHandler.instance.containerHas(holder.getPersistentDataContainer(), \"denizen:\" + keyName);\r\n    }\r\n\r\n    public static ObjectTag getDenizenKey(PersistentDataHolder holder, String keyName) {\r\n        try {\r\n            String str = NMSHandler.instance.containerGetString(holder.getPersistentDataContainer(), \"denizen:\" + keyName);\r\n            if (str == null) {\r\n                return null;\r\n            }\r\n            return ObjectFetcher.pickObjectFor(str, CoreUtilities.noDebugContext);\r\n        }\r\n        catch (IllegalArgumentException ex) {\r\n            if (holder instanceof Entity) {\r\n                Debug.echoError(\"Failed to read ObjectTag from entity key '\" + keyName + \"' for entity \" + ((Entity) holder).getUniqueId() + \"...\");\r\n            }\r\n            else {\r\n                Debug.echoError(\"Failed to read ObjectTag from object key '\" + keyName + \"' for holder '\" + holder + \"'...\");\r\n            }\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/FormattedTextHelper.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitElementExtensions;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.utilities.AsciiMatcher;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.gson.Gson;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.*;\r\nimport net.md_5.bungee.chat.ChatVersion;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.md_5.bungee.chat.VersionedComponentSerializer;\r\n\r\nimport java.util.List;\r\n\r\npublic class FormattedTextHelper {\r\n\r\n    // <--[language]\r\n    // @name Denizen Text Formatting\r\n    // @group Denizen Magic\r\n    // @description\r\n    // Denizen provides a variety of special chat format options like \"on_hover\" and \"on_click\".\r\n    // These options exist within Denizen and do not appear in the historical Minecraft legacy chat format that most plugins and systems read.\r\n    // That legacy system has 16 colors (0-9, A-F) and a few toggleable formats (bold, italic, etc). It does not contain anything that needs more than just an on/off.\r\n    //\r\n    // Modern Minecraft, however, supports a JSON based \"raw\" message format that can do click events, hover events, full RGB colors, etc.\r\n    //\r\n    // Denizen therefore has its own internal system that works like the legacy format system, but also supports the new options normally only available as 'raw JSON'.\r\n    //\r\n    // Because it is entirely processed within Denizen, these options only work within Denizen, when performing actions that support raw JSON input.\r\n    // This magic tool exists to let you write messages without having to write the messy JSON.\r\n    //\r\n    // Be aware that many inputs do not support raw JSON, and as such are limited only the historical Minecraft legacy format.\r\n    // Also be aware that click events, hover events, etc. are exclusively limited to the chat bar and the pages of books, as you cannot mouse over anything else.\r\n    //\r\n    // Also note that RGB colors use a format that Spigot invented, meaning they will work in places that use Spigot's parser OR Denizen's version, but nowhere that uses the vanilla format still.\r\n    //\r\n    // Thanks to Paper's implementation of component APIs where Spigot was too lazy to, Paper servers have advanced text formatting available in more areas.\r\n    // -->\r\n\r\n    public static AsciiMatcher needsEscapeMatcher = new AsciiMatcher(\"&;[]\");\r\n\r\n    public static String escape(String input) {\r\n        if (needsEscapeMatcher.containsAnyMatch(input)) {\r\n            input = input.replace(\"&\", \"&amp\").replace(\";\", \"&sc\").replace(\"[\", \"&lb\").replace(\"]\", \"&rb\").replace(\"\\n\", \"&nl\");\r\n        }\r\n        return input.replace(String.valueOf(ChatColor.COLOR_CHAR), \"&ss\");\r\n    }\r\n\r\n    public static String unescape(String input) {\r\n        if (input.indexOf('&') != -1) {\r\n            return input.replace(\"&sc\", \";\").replace(\"&lb\", \"[\").replace(\"&rb\", \"]\").replace(\"&nl\", \"\\n\").replace(\"&ss\", String.valueOf(ChatColor.COLOR_CHAR)).replace(\"&amp\", \"&\");\r\n        }\r\n        return input;\r\n    }\r\n\r\n    public static boolean hasRootFormat(BaseComponent component) {\r\n        if (component == null) {\r\n            return false;\r\n        }\r\n        if (component.hasFormatting()) {\r\n            return true;\r\n        }\r\n        if (!(component instanceof TextComponent)) {\r\n            return false;\r\n        }\r\n        if (!((TextComponent) component).getText().isEmpty()) {\r\n            return false;\r\n        }\r\n        List<BaseComponent> extra = component.getExtra();\r\n        if (extra == null || extra.isEmpty()) {\r\n            return false;\r\n        }\r\n        return hasRootFormat(extra.get(0));\r\n    }\r\n\r\n    public static String stringify(BaseComponent[] components) {\r\n        if (components == null) {\r\n            return null;\r\n        }\r\n        if (components.length == 0) {\r\n            return \"\";\r\n        }\r\n        StringBuilder builder = new StringBuilder(128 * components.length);\r\n        if (hasRootFormat(components[0])) {\r\n            builder.append(RESET);\r\n        }\r\n        for (BaseComponent component : components) {\r\n            if (component != null) {\r\n                builder.append(stringify(component));\r\n            }\r\n        }\r\n        String output = builder.toString();\r\n        while (output.endsWith(RESET)) {\r\n            output = output.substring(0, output.length() - RESET.length());\r\n        }\r\n        while (output.startsWith(POSSIBLE_RESET_PREFIX) && output.length() > 4 && colorCodeInvalidator.isMatch(output.charAt(3))) {\r\n            output = output.substring(2);\r\n        }\r\n        return cleanRedundantCodes(output);\r\n    }\r\n\r\n    public static String stringifyRGBSpigot(String hex) {\r\n        StringBuilder hexBuilder = new StringBuilder(7);\r\n        hexBuilder.append('x');\r\n        for (int i = hex.length(); i < 6; i++) {\r\n            hexBuilder.append('0');\r\n        }\r\n        hexBuilder.append(hex);\r\n        hex = hexBuilder.toString();\r\n        StringBuilder outColor = new StringBuilder();\r\n        for (char c : hex.toCharArray()) {\r\n            outColor.append(org.bukkit.ChatColor.COLOR_CHAR).append(c);\r\n        }\r\n        return outColor.toString();\r\n    }\r\n\r\n    public static String stringify(BaseComponent component) {\r\n        return stringifySub(component, null);\r\n    }\r\n\r\n    public static String stringifySub(BaseComponent component, ChatColor parentColor) {\r\n        if (component == null) {\r\n            return null;\r\n        }\r\n        StringBuilder builder = new StringBuilder(128);\r\n        ChatColor color = component.getColorRaw();\r\n        if (color == null) {\r\n            color = parentColor;\r\n        }\r\n        if (color != null) {\r\n            builder.append(color);\r\n        }\r\n        if (component.isBold()) {\r\n            builder.append(ChatColor.BOLD);\r\n        }\r\n        if (component.isItalic()) {\r\n            builder.append(ChatColor.ITALIC);\r\n        }\r\n        if (component.isStrikethrough()) {\r\n            builder.append(ChatColor.STRIKETHROUGH);\r\n        }\r\n        if (component.isUnderlined()) {\r\n            builder.append(ChatColor.UNDERLINE);\r\n        }\r\n        if (component.isObfuscated()) {\r\n            builder.append(ChatColor.MAGIC);\r\n        }\r\n        boolean hasFont = component.getFontRaw() != null;\r\n        if (hasFont) {\r\n            builder.append(ChatColor.COLOR_CHAR).append(\"[font=\").append(component.getFont()).append(\"]\");\r\n        }\r\n        boolean hasInsertion = component.getInsertion() != null;\r\n        if (hasInsertion) {\r\n            builder.append(ChatColor.COLOR_CHAR).append(\"[insertion=\").append(escape(component.getInsertion())).append(\"]\");\r\n        }\r\n        boolean hasHover = component.getHoverEvent() != null;\r\n        if (hasHover) {\r\n            HoverEvent hover = component.getHoverEvent();\r\n            builder.append(ChatColor.COLOR_CHAR).append(\"[hover=\").append(hover.getAction().name()).append(\";\").append(escape(HoverFormatHelper.stringForHover(hover))).append(\"]\");\r\n        }\r\n        boolean hasClick = component.getClickEvent() != null;\r\n        if (hasClick) {\r\n            ClickEvent click = component.getClickEvent();\r\n            builder.append(ChatColor.COLOR_CHAR).append(\"[click=\").append(click.getAction().name()).append(\";\").append(escape(click.getValue())).append(\"]\");\r\n        }\r\n        if (component instanceof TextComponent) {\r\n            builder.append(((TextComponent) component).getText());\r\n        }\r\n        else if (component instanceof TranslatableComponent translatableComponent) {\r\n            MapTag map = new MapTag();\r\n            map.putObject(\"key\", new ElementTag(translatableComponent.getTranslate(), true));\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && translatableComponent.getFallback() != null) {\r\n                map.putObject(\"fallback\", new ElementTag(translatableComponent.getFallback(), true));\r\n            }\r\n            if (translatableComponent.getWith() != null) {\r\n                map.putObject(\"with\", new ListTag(translatableComponent.getWith(), baseComponent -> new ElementTag(stringify(baseComponent), true)));\r\n            }\r\n            builder.append(ChatColor.COLOR_CHAR).append(\"[translate=\").append(escape(map.savable())).append(']');\r\n        }\r\n        else if (component instanceof SelectorComponent) {\r\n            builder.append(ChatColor.COLOR_CHAR).append(\"[selector=\").append(escape(((SelectorComponent) component).getSelector())).append(\"]\");\r\n        }\r\n        else if (component instanceof KeybindComponent) {\r\n            builder.append(ChatColor.COLOR_CHAR).append(\"[keybind=\").append(escape(((KeybindComponent) component).getKeybind())).append(\"]\");\r\n        }\r\n        else if (component instanceof ScoreComponent) {\r\n            builder.append(ChatColor.COLOR_CHAR).append(\"[score=\").append(escape(((ScoreComponent) component).getName()))\r\n                    .append(\";\").append(escape(((ScoreComponent) component).getObjective()))\r\n                    .append(\";\").append(escape(((ScoreComponent) component).getValue())).append(\"]\");\r\n        }\r\n        List<BaseComponent> after = component.getExtra();\r\n        if (after != null) {\r\n            for (BaseComponent afterComponent : after) {\r\n                builder.append(stringifySub(afterComponent, color));\r\n            }\r\n        }\r\n        if (hasClick) {\r\n            builder.append(ChatColor.COLOR_CHAR + \"[/click]\");\r\n        }\r\n        if (hasHover) {\r\n            builder.append(ChatColor.COLOR_CHAR + \"[/hover]\");\r\n        }\r\n        if (hasInsertion) {\r\n            builder.append(ChatColor.COLOR_CHAR + \"[/insertion]\");\r\n        }\r\n        if (hasFont) {\r\n            builder.append(ChatColor.COLOR_CHAR + \"[reset=font]\");\r\n        }\r\n        builder.append(RESET);\r\n        String output = builder.toString();\r\n        return cleanRedundantCodes(output);\r\n    }\r\n\r\n    public static final String RESET = ChatColor.RESET.toString(), POSSIBLE_RESET_PREFIX = RESET + ChatColor.COLOR_CHAR;\r\n\r\n    private static Boolean procBool(Boolean input, boolean optimize) {\r\n        if (input == null) {\r\n            return null;\r\n        }\r\n        if (optimize) {\r\n            return input ? true : null;\r\n        }\r\n        return input;\r\n    }\r\n\r\n    public static TextComponent copyFormatToNewText(TextComponent last, boolean optimize) {\r\n        TextComponent toRet = new TextComponent();\r\n        toRet.setObfuscated(procBool(last.isObfuscatedRaw(), optimize));\r\n        toRet.setBold(procBool(last.isBoldRaw(), optimize));\r\n        toRet.setStrikethrough(procBool(last.isStrikethroughRaw(), optimize));\r\n        toRet.setUnderlined(procBool(last.isUnderlinedRaw(), optimize));\r\n        toRet.setItalic(procBool(last.isItalicRaw(), optimize));\r\n        toRet.setColor(last.getColorRaw());\r\n        return toRet;\r\n    }\r\n\r\n    public static BaseComponent[] parse(String str, ChatColor baseColor) {\r\n        if (str == null) {\r\n            return null;\r\n        }\r\n        return parse(str, baseColor, true);\r\n    }\r\n\r\n    public static int findNextNormalColorSymbol(String base, int startAt) {\r\n        while (true) {\r\n            int next = base.indexOf(ChatColor.COLOR_CHAR, startAt);\r\n            if (next == -1 || next + 1 >= base.length()) {\r\n                return -1;\r\n            }\r\n            char after = base.charAt(next + 1);\r\n            if (colorCodeInvalidator.isMatch(after)) {\r\n                return next;\r\n            }\r\n            startAt = next + 1;\r\n        }\r\n    }\r\n\r\n    public static int findEndIndexFor(String base, String startSymbol, String endSymbol, int startAt) {\r\n        int layers = 1;\r\n        while (true) {\r\n            int next = base.indexOf(ChatColor.COLOR_CHAR, startAt);\r\n            if (next == -1) {\r\n                return -1;\r\n            }\r\n            if (next + endSymbol.length() >= base.length()) {\r\n                return -1;\r\n            }\r\n            if (base.startsWith(startSymbol, next + 1)) {\r\n                layers++;\r\n            }\r\n            else if (base.startsWith(endSymbol, next + 1)) {\r\n                layers--;\r\n                if (layers == 0) {\r\n                    return next;\r\n                }\r\n            }\r\n            startAt = next + 1;\r\n        }\r\n    }\r\n\r\n    public static int findEndIndexFor(String base, String type, int startAt) {\r\n        return findEndIndexFor(base, \"[\" + type + \"=\", \"[/\" + type + \"]\", startAt);\r\n    }\r\n\r\n    public static String HEX = \"0123456789abcdefABCDEF\";\r\n\r\n    public static AsciiMatcher allowedCharCodes = new AsciiMatcher(HEX + \"klmnorxKLMNORX[\");\r\n\r\n    public static AsciiMatcher hexMatcher = new AsciiMatcher(HEX);\r\n\r\n    public static AsciiMatcher colorCodesOrReset = new AsciiMatcher(HEX + \"rR\"); // Any color code that can be invalidated\r\n\r\n    public static AsciiMatcher colorCodeInvalidator = new AsciiMatcher(HEX + \"rRxX\"); // Any code that can invalidate the colors above\r\n\r\n    public static String cleanRedundantCodes(String str) {\r\n        int index = str.indexOf(ChatColor.COLOR_CHAR);\r\n        if (index == -1) {\r\n            return str;\r\n        }\r\n        int start = 0;\r\n        StringBuilder output = new StringBuilder(str.length());\r\n        while (index != -1) {\r\n            output.append(str, start, index);\r\n            start = index;\r\n            if (index + 1 >= str.length()) {\r\n                break;\r\n            }\r\n            char symbol = str.charAt(index + 1);\r\n            if (allowedCharCodes.isMatch(symbol)) {\r\n                if (symbol == 'x' || symbol == 'X') { // Skip entire hex block\r\n                    index = str.indexOf(ChatColor.COLOR_CHAR, index + 14);\r\n                    continue;\r\n                }\r\n                int nextIndex = str.indexOf(ChatColor.COLOR_CHAR, index + 1);\r\n                if (colorCodesOrReset.isMatch(symbol) && nextIndex == index + 2 && nextIndex + 1 < str.length()) {\r\n                    char nextSymbol = str.charAt(nextIndex + 1);\r\n                    if (colorCodeInvalidator.isMatch(nextSymbol)) {\r\n                        start = index + 2; // Exclude from output the initial (redundant) color code\r\n                        index = nextIndex;\r\n                        continue;\r\n                    }\r\n                }\r\n            }\r\n            index = str.indexOf(ChatColor.COLOR_CHAR, index + 1);\r\n        }\r\n        output.append(str, start, str.length());\r\n        return output.toString();\r\n    }\r\n\r\n    public static TextComponent getCleanRef() {\r\n        TextComponent reference = new TextComponent();\r\n        reference.setBold(false);\r\n        reference.setItalic(false);\r\n        reference.setStrikethrough(false);\r\n        reference.setUnderlined(false);\r\n        reference.setObfuscated(false);\r\n        return reference;\r\n    }\r\n\r\n    public static BaseComponent[] parseSimpleColorsOnly(String str) {\r\n        TextComponent root = new TextComponent();\r\n        int firstChar = str.indexOf(ChatColor.COLOR_CHAR);\r\n        int lastStart = 0;\r\n        if (firstChar > 0) {\r\n            root.addExtra(new TextComponent(str.substring(0, firstChar)));\r\n            lastStart = firstChar;\r\n        }\r\n        TextComponent nextText = new TextComponent();\r\n        while (firstChar != -1 && firstChar + 1 < str.length()) {\r\n            char c = str.charAt(firstChar + 1);\r\n            if (allowedCharCodes.isMatch(c)) {\r\n                if (c == 'r' || c == 'R') {\r\n                    nextText.setText(str.substring(lastStart, firstChar));\r\n                    if (!nextText.getText().isEmpty()) {\r\n                        root.addExtra(nextText);\r\n                    }\r\n                    nextText = getCleanRef();\r\n                    lastStart = firstChar + 2;\r\n                }\r\n                else if (c == 'X' || c == 'x' && firstChar + 13 < str.length()) {\r\n                    StringBuilder color = new StringBuilder(12);\r\n                    color.append(\"#\");\r\n                    for (int i = 1; i <= 6; i++) {\r\n                        if (str.charAt(firstChar + i * 2) != ChatColor.COLOR_CHAR) {\r\n                            color = null;\r\n                            break;\r\n                        }\r\n                        char hexChar = str.charAt(firstChar + 1 + i * 2);\r\n                        if (!hexMatcher.isMatch(hexChar)) {\r\n                            color = null;\r\n                            break;\r\n                        }\r\n                        color.append(hexChar);\r\n                    }\r\n                    if (color != null) {\r\n                        nextText.setText(str.substring(lastStart, firstChar));\r\n                        if (!nextText.getText().isEmpty()) {\r\n                            root.addExtra(nextText);\r\n                        }\r\n                        nextText = getCleanRef();\r\n                        nextText.setColor(ChatColor.of(CoreUtilities.toUpperCase(color.toString())));\r\n                        firstChar += 12;\r\n                        lastStart = firstChar + 2;\r\n                    }\r\n                }\r\n                else if (colorCodesOrReset.isMatch(c)) {\r\n                    nextText.setText(str.substring(lastStart, firstChar));\r\n                    if (!nextText.getText().isEmpty()) {\r\n                        root.addExtra(nextText);\r\n                    }\r\n                    nextText = getCleanRef();\r\n                    nextText.setColor(ChatColor.getByChar(c));\r\n                    lastStart = firstChar + 2;\r\n                }\r\n                else { // format code\r\n                    nextText.setText(str.substring(lastStart, firstChar));\r\n                    if (!nextText.getText().isEmpty()) {\r\n                        root.addExtra(nextText);\r\n                    }\r\n                    nextText = copyFormatToNewText(nextText, false);\r\n                    if (c == 'k' || c == 'K') {\r\n                        nextText.setObfuscated(true);\r\n                    }\r\n                    else if (c == 'l' || c == 'L') {\r\n                        nextText.setBold(true);\r\n                    }\r\n                    else if (c == 'm' || c == 'M') {\r\n                        nextText.setStrikethrough(true);\r\n                    }\r\n                    else if (c == 'n' || c == 'N') {\r\n                        nextText.setUnderlined(true);\r\n                    }\r\n                    else if (c == 'o' || c == 'O') {\r\n                        nextText.setItalic(true);\r\n                    }\r\n                    lastStart = firstChar + 2;\r\n                }\r\n            }\r\n            firstChar = str.indexOf(ChatColor.COLOR_CHAR, firstChar + 1);\r\n        }\r\n        if (lastStart < str.length()) {\r\n            nextText.setText(str.substring(lastStart));\r\n            root.addExtra(nextText);\r\n        }\r\n        return new BaseComponent[] { root };\r\n    }\r\n\r\n    public static BaseComponent[] parse(String str, ChatColor baseColor, boolean cleanBase) {\r\n        if (str == null) {\r\n            return null;\r\n        }\r\n        try {\r\n            return parseInternal(str, baseColor, cleanBase, false);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return new BaseComponent[]{new TextComponent(str)};\r\n    }\r\n\r\n    private static BaseComponent parseTranslatable(String str, ChatColor baseColor, boolean optimize) {\r\n        if (!str.startsWith(\"map@\")) {\r\n            List<String> innardParts = CoreUtilities.split(str, ';');\r\n            TranslatableComponent component = new TranslatableComponent(unescape(innardParts.get(0)));\r\n            for (int i = 1; i < innardParts.size(); i++) {\r\n                for (BaseComponent subComponent : parseInternal(unescape(innardParts.get(i)), baseColor, false, optimize)) {\r\n                    component.addWith(subComponent);\r\n                }\r\n            }\r\n            return component;\r\n        }\r\n        MapTag map = MapTag.valueOf(unescape(str), CoreUtilities.noDebugContext);\r\n        if (map == null) {\r\n            return new TextComponent(str);\r\n        }\r\n        ElementTag translationKey = map.getElement(\"key\");\r\n        if (translationKey == null) {\r\n            return new TextComponent(str);\r\n        }\r\n        TranslatableComponent component = new TranslatableComponent(translationKey.asString());\r\n        ElementTag fallback = map.getElement(\"fallback\");\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && fallback != null) {\r\n            component.setFallback(fallback.asString());\r\n        }\r\n        ListTag withList = map.getObjectAs(\"with\", ListTag.class, CoreUtilities.noDebugContext);\r\n        if (withList != null) {\r\n            for (String with : withList) {\r\n                for (BaseComponent withComponent : parseInternal(with, baseColor, false, optimize)) {\r\n                    component.addWith(withComponent);\r\n                }\r\n            }\r\n        }\r\n        return component;\r\n    }\r\n\r\n    public static BaseComponent[] parseInternal(String str, ChatColor baseColor, boolean cleanBase, boolean optimize) {\r\n        str = CoreUtilities.clearNBSPs(str);\r\n        int firstChar = str.indexOf(ChatColor.COLOR_CHAR);\r\n        if (firstChar == -1) {\r\n            if (str.contains(\"://\")) {\r\n                firstChar = 0;\r\n            }\r\n            else {\r\n                TextComponent base = new TextComponent();\r\n                base.addExtra(new TextComponent(str)); // This is for compat with how Spigot does parsing of plaintext.\r\n                return new BaseComponent[]{base};\r\n            }\r\n        }\r\n        str = cleanRedundantCodes(str);\r\n        if (cleanBase && str.length() < 512) {\r\n            if (!str.contains(ChatColor.COLOR_CHAR + \"[\") && !str.contains(\"://\")) {\r\n                return parseSimpleColorsOnly(str);\r\n            }\r\n            // Ensure compat with certain weird vanilla translate strings.\r\n            if (str.startsWith(ChatColor.COLOR_CHAR + \"[translate=\") && str.indexOf(']') == str.length() - 1) {\r\n                return new BaseComponent[] {parseTranslatable(str.substring(\"&[translate=\".length(), str.length() - 1), baseColor, optimize)};\r\n            }\r\n            if (str.length() > 3 && str.startsWith((ChatColor.COLOR_CHAR + \"\")) && hexMatcher.isMatch(str.charAt(1))\r\n                    && str.startsWith(ChatColor.COLOR_CHAR + \"[translate=\", 2) && str.indexOf(']') == str.length() - 1) { // eg \"&6&[translate=block.minecraft.ominous_banner]\"\r\n                BaseComponent component = parseTranslatable(str.substring(\"&[translate=\".length() + 2, str.length() - 1), baseColor, optimize);\r\n                component.setColor(ChatColor.getByChar(str.charAt(1)));\r\n                return new BaseComponent[] {component};\r\n            }\r\n        }\r\n        if (!optimize) {\r\n            optimize = str.contains(ChatColor.COLOR_CHAR + \"[optimize=true]\");\r\n        }\r\n        TextComponent root = new TextComponent();\r\n        TextComponent base = new TextComponent();\r\n        if (cleanBase && !optimize) {\r\n            base.setBold(false);\r\n            base.setItalic(false);\r\n            base.setStrikethrough(false);\r\n            base.setUnderlined(false);\r\n            base.setObfuscated(false);\r\n            base.setColor(baseColor);\r\n            if (firstChar > 0) {\r\n                root.addExtra(new TextComponent(str.substring(0, firstChar)));\r\n            }\r\n        }\r\n        else {\r\n            base.setText(str.substring(0, firstChar));\r\n        }\r\n        root.addExtra(base);\r\n        str = str.substring(firstChar);\r\n        char[] chars = str.toCharArray();\r\n        int started = 0;\r\n        TextComponent nextText = new TextComponent();\r\n        TextComponent lastText;\r\n        for (int i = 0; i < chars.length; i++) {\r\n            if (chars[i] == ChatColor.COLOR_CHAR && i + 1 < chars.length) {\r\n                char code = chars[i + 1];\r\n                if (!allowedCharCodes.isMatch(code)) {\r\n                    continue;\r\n                }\r\n                if (code == '[') {\r\n                    int endBracket = str.indexOf(']', i + 2);\r\n                    if (endBracket == -1) {\r\n                        continue;\r\n                    }\r\n                    String innards = str.substring(i + 2, endBracket);\r\n                    List<String> innardParts = CoreUtilities.split(innards, ';');\r\n                    List<String> innardBase = CoreUtilities.split(innardParts.get(0), '=', 2);\r\n                    innardParts.remove(0);\r\n                    String innardType = CoreUtilities.toLowerCase(innardBase.get(0));\r\n                    if (innardBase.size() == 2) {\r\n                        nextText.setText(nextText.getText() + str.substring(started, i));\r\n                        base.addExtra(nextText);\r\n                        lastText = nextText;\r\n                        nextText = copyFormatToNewText(lastText, optimize);\r\n                        nextText.setText(\"\");\r\n                        if (innardType.equals(\"score\") && innardParts.size() == 2) {\r\n                            ScoreComponent component = new ScoreComponent(unescape(innardBase.get(1)), unescape(innardParts.get(0)), unescape(innardParts.get(1)));\r\n                            lastText.addExtra(component);\r\n                        }\r\n                        else if (innardType.equals(\"keybind\") && Utilities.matchesNamespacedKeyButCaseInsensitive(innardBase.get(1))) {\r\n                            KeybindComponent component = new KeybindComponent();\r\n                            component.setKeybind(unescape(innardBase.get(1)));\r\n                            lastText.addExtra(component);\r\n                        }\r\n                        else if (innardType.equals(\"selector\")) {\r\n                            SelectorComponent component = new SelectorComponent(unescape(innardBase.get(1)));\r\n                            lastText.addExtra(component);\r\n                        }\r\n                        else if (innardType.equals(\"translate\")) {\r\n                            lastText.addExtra(parseTranslatable(innards.substring(\"translate=\".length()), baseColor, optimize));\r\n                        }\r\n                        else if (innardType.equals(\"click\") && innardParts.size() == 1) {\r\n                            int endIndex = findEndIndexFor(str, \"click\", endBracket);\r\n                            if (endIndex == -1) {\r\n                                continue;\r\n                            }\r\n                            TextComponent clickableText = new TextComponent();\r\n                            ClickEvent.Action action = ElementTag.asEnum(ClickEvent.Action.class, innardBase.get(1));\r\n                            clickableText.setClickEvent(new ClickEvent(action == null ? ClickEvent.Action.SUGGEST_COMMAND : action, unescape(innardParts.get(0))));\r\n                            for (BaseComponent subComponent : parseInternal(str.substring(endBracket + 1, endIndex), baseColor, false, optimize)) {\r\n                                clickableText.addExtra(subComponent);\r\n                            }\r\n                            lastText.addExtra(clickableText);\r\n                            endBracket = endIndex + \"&[/click\".length();\r\n                        }\r\n                        else if (innardType.equals(\"hover\")) {\r\n                            int endIndex = findEndIndexFor(str, \"hover\", endBracket);\r\n                            if (endIndex == -1) {\r\n                                continue;\r\n                            }\r\n                            TextComponent hoverableText = new TextComponent();\r\n                            HoverEvent.Action action = ElementTag.asEnum(HoverEvent.Action.class, innardBase.get(1));\r\n                            if (HoverFormatHelper.processHoverInput(action == null ? HoverEvent.Action.SHOW_TEXT : action, hoverableText, innardParts.get(0))) {\r\n                                continue;\r\n                            }\r\n                            for (BaseComponent subComponent : parseInternal(str.substring(endBracket + 1, endIndex), baseColor, false, optimize)) {\r\n                                hoverableText.addExtra(subComponent);\r\n                            }\r\n                            lastText.addExtra(hoverableText);\r\n                            endBracket = endIndex + \"&[/hover\".length();\r\n                        }\r\n                        else if (innardType.equals(\"insertion\")) {\r\n                            int endIndex = str.indexOf(ChatColor.COLOR_CHAR + \"[/insertion]\", i);\r\n                            int backupEndIndex = str.indexOf(ChatColor.COLOR_CHAR + \"[insertion=\", i + 5);\r\n                            if (backupEndIndex > 0 && backupEndIndex < endIndex) {\r\n                                endIndex = backupEndIndex;\r\n                            }\r\n                            if (endIndex == -1) {\r\n                                continue;\r\n                            }\r\n                            TextComponent insertableText = new TextComponent();\r\n                            insertableText.setInsertion(unescape(innardBase.get(1)));\r\n                            for (BaseComponent subComponent : parseInternal(str.substring(endBracket + 1, endIndex), baseColor, false, optimize)) {\r\n                                insertableText.addExtra(subComponent);\r\n                            }\r\n                            lastText.addExtra(insertableText);\r\n                            endBracket = endIndex + \"&[/insertion\".length();\r\n                        }\r\n                        else if (innardType.equals(\"reset\")) {\r\n                            if (innardBase.get(1).length() == 1) {\r\n                                char subCode = innardBase.get(1).charAt(0);\r\n                                if (subCode == 'k' || subCode == 'K') {\r\n                                    nextText.setObfuscated(false);\r\n                                }\r\n                                else if (subCode == 'l' || subCode == 'L') {\r\n                                    nextText.setBold(false);\r\n                                }\r\n                                else if (subCode == 'm' || subCode == 'M') {\r\n                                    nextText.setStrikethrough(false);\r\n                                }\r\n                                else if (subCode == 'n' || subCode == 'N') {\r\n                                    nextText.setUnderlined(false);\r\n                                }\r\n                                else if (subCode == 'o' || subCode == 'O') {\r\n                                    nextText.setItalic(false);\r\n                                }\r\n                            }\r\n                            else if (innardBase.get(1).equals(\"font\")) {\r\n                                nextText.setFont(base.getFont());\r\n                            }\r\n                            else {\r\n                                nextText.setColor(base.getColor());\r\n                            }\r\n                        }\r\n                        else if (innardType.equals(\"color\")) {\r\n                            String colorChar = innardBase.get(1);\r\n                            ChatColor color = null;\r\n                            if (colorChar.length() == 1) {\r\n                                color = ChatColor.getByChar(colorChar.charAt(0));\r\n                            }\r\n                            else if (colorChar.length() == 7) {\r\n                                color = ChatColor.of(CoreUtilities.toUpperCase(colorChar));\r\n                            }\r\n                            else if (CoreConfiguration.debugVerbose) {\r\n                                Debug.echoError(\"Text parse issue: cannot interpret color '\" + innardBase.get(1) + \"'.\");\r\n                            }\r\n                            if (color != null) {\r\n                                int endIndex = findEndIndexFor(str, \"[color=\", \"[reset=color]\", endBracket);\r\n                                if (endIndex == -1) {\r\n                                    nextText.setColor(color);\r\n                                }\r\n                                else {\r\n                                    TextComponent colorText = new TextComponent();\r\n                                    colorText.setColor(color);\r\n                                    for (BaseComponent subComponent : parseInternal(str.substring(endBracket + 1, endIndex), color, false, optimize)) {\r\n                                        colorText.addExtra(subComponent);\r\n                                    }\r\n                                    lastText.addExtra(colorText);\r\n                                    endBracket = endIndex + \"&[reset=color\".length();\r\n                                }\r\n                            }\r\n                        }\r\n                        else if (innardType.equals(\"gradient\") && innardParts.size() == 2) {\r\n                            String from = innardBase.get(1), to = innardParts.get(0), style = innardParts.get(1);\r\n                            ColorTag fromColor = ColorTag.valueOf(from, CoreUtilities.noDebugContext);\r\n                            ColorTag toColor = ColorTag.valueOf(to, CoreUtilities.noDebugContext);\r\n                            BukkitElementExtensions.GradientStyle styleEnum = new ElementTag(style).asEnum(BukkitElementExtensions.GradientStyle.class);\r\n                            if (fromColor == null || toColor == null || styleEnum == null) {\r\n                                if (CoreConfiguration.debugVerbose) {\r\n                                    Debug.echoError(\"Text parse issue: cannot interpret gradient input '\" + innards + \"'.\");\r\n                                }\r\n                            }\r\n                            else {\r\n                                int endIndex = findNextNormalColorSymbol(str, i + 1);\r\n                                if (endIndex == -1) {\r\n                                    endIndex = str.length();\r\n                                }\r\n                                String gradientText = BukkitElementExtensions.doGradient(str.substring(endBracket + 1, endIndex), fromColor, toColor, styleEnum);\r\n                                for (BaseComponent subComponent : parseInternal(gradientText, baseColor, false, optimize)) {\r\n                                    lastText.addExtra(subComponent);\r\n                                }\r\n                                endBracket = endIndex - 1;\r\n                            }\r\n                        }\r\n                        else if (innardType.equals(\"font\") && Utilities.matchesNamespacedKey(innardBase.get(1))) {\r\n                            int endIndex = findEndIndexFor(str, \"[font=\", \"[reset=font]\", endBracket);\r\n                            if (endIndex == -1) {\r\n                                nextText.setFont(innardBase.get(1));\r\n                            }\r\n                            else {\r\n                                TextComponent fontText = new TextComponent();\r\n                                fontText.setFont(innardBase.get(1));\r\n                                for (BaseComponent subComponent : parseInternal(str.substring(endBracket + 1, endIndex), baseColor, false, optimize)) {\r\n                                    fontText.addExtra(subComponent);\r\n                                }\r\n                                lastText.addExtra(fontText);\r\n                                endBracket = endIndex + \"&[reset=font\".length();\r\n                            }\r\n                        }\r\n                        else if (innardType.equals(\"optimize\")) {\r\n                            // Ignore\r\n                        }\r\n                        else {\r\n                            if (CoreConfiguration.debugVerbose) {\r\n                                Debug.echoError(\"Text parse issue: cannot interpret type '\" + innardType + \"' with \" + innardParts.size() + \" parts.\");\r\n                            }\r\n                        }\r\n                    }\r\n                    i = endBracket;\r\n                    started = endBracket + 1;\r\n                    continue;\r\n                }\r\n                else if (code == 'r' || code == 'R') {\r\n                    nextText.setText(nextText.getText() + str.substring(started, i));\r\n                    if (!nextText.getText().isEmpty()) {\r\n                        base.addExtra(nextText);\r\n                    }\r\n                    nextText = new TextComponent();\r\n                    nextText.setColor(baseColor);\r\n                }\r\n                else if (colorCodesOrReset.isMatch(code)) {\r\n                    nextText.setText(nextText.getText() + str.substring(started, i));\r\n                    if (!nextText.getText().isEmpty()) {\r\n                        base.addExtra(nextText);\r\n                    }\r\n                    nextText = new TextComponent();\r\n                    nextText.setColor(ChatColor.getByChar(code));\r\n                }\r\n                else if (code == 'x') {\r\n                    if (i + 13 >= chars.length) {\r\n                        continue;\r\n                    }\r\n                    StringBuilder color = new StringBuilder(12);\r\n                    color.append(\"#\");\r\n                    for (int c = 1; c <= 6; c++) {\r\n                        if (chars[i + c * 2] != ChatColor.COLOR_CHAR) {\r\n                            color = null;\r\n                            break;\r\n                        }\r\n                        char hexPart = chars[i + 1 + c * 2];\r\n                        if (!hexMatcher.isMatch(hexPart)) {\r\n                            color = null;\r\n                            break;\r\n                        }\r\n                        color.append(hexPart);\r\n                    }\r\n                    if (color == null) {\r\n                        continue;\r\n                    }\r\n                    nextText.setText(nextText.getText() + str.substring(started, i));\r\n                    if (!nextText.getText().isEmpty()) {\r\n                        base.addExtra(nextText);\r\n                    }\r\n                    nextText = new TextComponent();\r\n                    nextText.setColor(ChatColor.of(CoreUtilities.toUpperCase(color.toString())));\r\n                    i += 13;\r\n                    started = i + 1;\r\n                    continue;\r\n                }\r\n                else {\r\n                    nextText.setText(nextText.getText() + str.substring(started, i));\r\n                    if (!nextText.getText().isEmpty()) {\r\n                        base.addExtra(nextText);\r\n                    }\r\n                    nextText = copyFormatToNewText(nextText, optimize);\r\n                    if (code == 'k' || code == 'K') {\r\n                        nextText.setObfuscated(true);\r\n                    }\r\n                    else if (code == 'l' || code == 'L') {\r\n                        nextText.setBold(true);\r\n                    }\r\n                    else if (code == 'm' || code == 'M') {\r\n                        nextText.setStrikethrough(true);\r\n                    }\r\n                    else if (code == 'n' || code == 'N') {\r\n                        nextText.setUnderlined(true);\r\n                    }\r\n                    else if (code == 'o' || code == 'O') {\r\n                        nextText.setItalic(true);\r\n                    }\r\n                }\r\n                i++;\r\n                started = i + 1;\r\n            }\r\n            else if (i + \"https://a.\".length() < chars.length && chars[i] == 'h' && chars[i + 1] == 't' && chars[i + 2] == 't' && chars[i  + 3] == 'p') {\r\n                String subStr = str.substring(i, i + \"https://a.\".length());\r\n                if (subStr.startsWith(\"https://\") || subStr.startsWith(\"http://\")) {\r\n                    int nextSpace = CoreUtilities.indexOfAny(str, i, ' ', '\\t', '\\n', ChatColor.COLOR_CHAR);\r\n                    if (nextSpace == -1) {\r\n                        nextSpace = str.length();\r\n                    }\r\n                    String url = str.substring(i, nextSpace);\r\n                    nextText.setText(nextText.getText() + str.substring(started, i));\r\n                    base.addExtra(nextText);\r\n                    lastText = nextText;\r\n                    nextText = new TextComponent(lastText);\r\n                    nextText.setText(\"\");\r\n                    TextComponent clickableText = new TextComponent(url);\r\n                    clickableText.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url));\r\n                    lastText.addExtra(clickableText);\r\n                    i = nextSpace - 1;\r\n                    started = nextSpace;\r\n                    continue;\r\n                }\r\n            }\r\n        }\r\n        nextText.setText(nextText.getText() + str.substring(started));\r\n        if (!nextText.getText().isEmpty()) {\r\n            base.addExtra(nextText);\r\n        }\r\n        return new BaseComponent[] { cleanBase && !optimize ? root : base };\r\n    }\r\n\r\n    public static int indexOfLastColorBlockStart(String text) {\r\n        int result = text.lastIndexOf(ChatColor.COLOR_CHAR + \"[\");\r\n        if (result == -1 || text.indexOf(']', result + 2) != -1) {\r\n            return -1;\r\n        }\r\n        return result;\r\n    }\r\n\r\n    /**\r\n     * Equivalent to DebugInternals.trimMessage, with a special check:\r\n     * If a message is cut in the middle of a format block like \"&[font=x:y]\", cut that block entirely out.\r\n     * (This is needed because a snip in the middle of this will explode with parsing errors).\r\n     */\r\n    public static String bukkitSafeDebugTrimming(String message) {\r\n        int trimSize = CoreConfiguration.debugTrimLength;\r\n        if (message.length() > trimSize) {\r\n            int firstCut = (trimSize / 2) - 10, secondCut = message.length() - ((trimSize / 2) - 10);\r\n            String prePart = message.substring(0, firstCut);\r\n            String cutPart = message.substring(firstCut, secondCut);\r\n            String postPart = message.substring(secondCut);\r\n            int preEarlyCut = indexOfLastColorBlockStart(prePart);\r\n            if (preEarlyCut != -1) {\r\n                prePart = message.substring(0, preEarlyCut);\r\n            }\r\n            if (indexOfLastColorBlockStart(cutPart) != -1 || (preEarlyCut != -1 && cutPart.indexOf(']') == -1)) {\r\n                int lateCut = postPart.indexOf(']');\r\n                if (lateCut != -1) {\r\n                    postPart = postPart.substring(lateCut + 1);\r\n                }\r\n            }\r\n            message = prePart + \"... *snip!*...\" + postPart;\r\n        }\r\n        return message;\r\n    }\r\n\r\n    public static Gson getBungeeGson() {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            return VersionedComponentSerializer.forVersion(ChatVersion.V1_21_5).getGson();\r\n        }\r\n        else {\r\n            return ReflectionHelper.getFieldValue(ComponentSerializer.class, \"gson\", null);\r\n        }\r\n    }\r\n\r\n    static {\r\n        // Explicitly before initializing vanillaStyleSpigotComponentGSON\r\n        HoverFormatHelper.tryInitializeItemHoverFix();\r\n    }\r\n\r\n    public static final Gson vanillaStyleSpigotComponentGSON = getBungeeGson().newBuilder().disableHtmlEscaping().create();\r\n\r\n    public static String componentToJson(BaseComponent[] components) {\r\n        if (components.length == 1) {\r\n            return vanillaStyleSpigotComponentGSON.toJson(components[0]);\r\n        }\r\n        return vanillaStyleSpigotComponentGSON.toJson(new TextComponent(components));\r\n    }\r\n\r\n    public static BaseComponent[] parseJson(String json) {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n            return VersionedComponentSerializer.forVersion(ChatVersion.V1_21_5).parse(json);\r\n        }\r\n        return ComponentSerializer.parse(json);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/HoverFormatHelper.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.tags.Attribute;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.gson.*;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport net.md_5.bungee.api.chat.HoverEvent;\r\nimport net.md_5.bungee.api.chat.TextComponent;\r\nimport net.md_5.bungee.api.chat.hover.content.*;\r\nimport net.md_5.bungee.chat.ChatVersion;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.md_5.bungee.chat.VersionedComponentSerializer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Registry;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.lang.reflect.Type;\r\nimport java.util.UUID;\r\n\r\npublic class HoverFormatHelper {\r\n\r\n    public static boolean processHoverInput(HoverEvent.Action action, TextComponent hoverableText, String input) {\r\n        Content content;\r\n        if (action == HoverEvent.Action.SHOW_ITEM) {\r\n            ItemTag item = ItemTag.valueOf(FormattedTextHelper.unescape(input), CoreUtilities.noDebugContext);\r\n            if (item == null) {\r\n                return true;\r\n            }\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n                content = new FixedItemHover(item.getBukkitMaterial().getKey().toString(), item.getAmount(), NMSHandler.itemHelper.getRawHoverComponentsJson(item.getItemStack()));\r\n            }\r\n            else {\r\n                content = new Item(item.getBukkitMaterial().getKey().toString(), item.getAmount(), net.md_5.bungee.api.chat.ItemTag.ofNbt(NMSHandler.itemHelper.getLegacyHoverNbt(item)));\r\n            }\r\n        }\r\n        else if (action == HoverEvent.Action.SHOW_ENTITY) {\r\n            String rawInput = FormattedTextHelper.unescape(input);\r\n            if (!rawInput.startsWith(\"map@\")) {\r\n                content = parseLegacyEntityHover(rawInput);\r\n                if (content == null) {\r\n                    return true;\r\n                }\r\n            }\r\n            else {\r\n                MapTag entityHoverData = MapTag.valueOf(rawInput, CoreUtilities.noDebugContext);\r\n                if (entityHoverData == null) {\r\n                    return true;\r\n                }\r\n                ElementTag uuid = entityHoverData.getElement(\"uuid\");\r\n                if (uuid == null) {\r\n                    return true;\r\n                }\r\n                ElementTag type = entityHoverData.getElement(\"type\");\r\n                ElementTag rawName = entityHoverData.getElement(\"name\");\r\n                BaseComponent name = rawName != null ? new TextComponent(FormattedTextHelper.parse(rawName.asString(), ChatColor.WHITE)) : null;\r\n                content = new Entity(type != null ? type.asString() : null, uuid.asString(), name);\r\n            }\r\n        }\r\n        else {\r\n            content = new Text(FormattedTextHelper.parse(FormattedTextHelper.unescape(input), ChatColor.WHITE));\r\n        }\r\n        hoverableText.setHoverEvent(new HoverEvent(action, content));\r\n        return false;\r\n    }\r\n\r\n    public static String stringForHover(HoverEvent hover) {\r\n        if (hover.getContents().isEmpty()) {\r\n            return \"\";\r\n        }\r\n        Content contentObject = hover.getContents().get(0);\r\n        if (contentObject instanceof Text textHover) {\r\n            Object value = textHover.getValue();\r\n            if (value instanceof BaseComponent[] componentsValue) {\r\n                return FormattedTextHelper.stringify(componentsValue);\r\n            }\r\n            else {\r\n                return value.toString();\r\n            }\r\n        }\r\n        else if (contentObject instanceof Item itemHover) {\r\n            ItemStack item = new ItemStack(Registry.MATERIAL.get(Utilities.parseNamespacedKey(itemHover.getId())), itemHover.getCount() == -1 ? 1 : itemHover.getCount());\r\n            if (itemHover instanceof FixedItemHover fixedItemHover && fixedItemHover.getComponents() != null) {\r\n                item = NMSHandler.itemHelper.applyRawHoverComponentsJson(item, fixedItemHover.getComponents());\r\n            }\r\n            else if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19) && itemHover.getTag() != null && itemHover.getTag().getNbt() != null) {\r\n                item = Bukkit.getUnsafe().modifyItemStack(item, itemHover.getTag().getNbt());\r\n            }\r\n            return new ItemTag(item).identify();\r\n        }\r\n        else if (contentObject instanceof Entity entityHover) {\r\n            return createEntityHoverData(entityHover.getId(), entityHover.getType(), entityHover.getName()).savable();\r\n        }\r\n        else {\r\n            throw new UnsupportedOperationException();\r\n        }\r\n    }\r\n\r\n    public static MapTag createEntityHoverData(String uuid, String type, BaseComponent name) {\r\n        MapTag entityHoverData = new MapTag();\r\n        entityHoverData.putObject(\"uuid\", new ElementTag(uuid, true));\r\n        if (type != null) {\r\n            entityHoverData.putObject(\"type\", new ElementTag(type, true));\r\n        }\r\n        else {\r\n            try {\r\n                // This isn't even optional, but is in Bungee for some reason - try our best to have a value\r\n                org.bukkit.entity.Entity found = EntityTag.getEntityForID(UUID.fromString(uuid));\r\n                if (found != null) {\r\n                    entityHoverData.putObject(\"type\", new ElementTag(found.getType().getKey().toString(), true));\r\n                }\r\n            }\r\n            catch (IllegalArgumentException ignore) {}\r\n        }\r\n        if (name != null) {\r\n            entityHoverData.putObject(\"name\", new ElementTag(FormattedTextHelper.stringify(name), true));\r\n        }\r\n        return entityHoverData;\r\n    }\r\n\r\n    public static String parseObjectToHover(ObjectTag object, HoverEvent.Action action, Attribute attribute) {\r\n        return switch (action) {\r\n            case SHOW_ENTITY -> {\r\n                EntityTag toShow = object.asType(EntityTag.class, attribute.context);\r\n                if (toShow == null) {\r\n                    attribute.echoError(\"Invalid hover object '\" + object + \"' specified for type 'SHOW_ENTITY': must be an EntityTag.\");\r\n                    yield null;\r\n                }\r\n                BaseComponent[] customName = PaperAPITools.instance.getCustomNameComponent(toShow.getBukkitEntity());\r\n                yield createEntityHoverData(toShow.getUUID().toString(), toShow.getBukkitEntityType().getKey().toString(), customName != null ? new TextComponent(customName) : null).savable();\r\n            }\r\n            case SHOW_ITEM -> {\r\n                ItemTag toShow = object.asType(ItemTag.class, attribute.context);\r\n                if (toShow == null) {\r\n                    attribute.echoError(\"Invalid hover object '\" + object + \"' specified for type 'SHOW_ITEM': must be an ItemTag.\");\r\n                    yield null;\r\n                }\r\n                yield toShow.identify();\r\n            }\r\n            case SHOW_TEXT -> object.toString();\r\n            default -> {\r\n                attribute.echoError(\"Using unsupported hover type: \" + action + '.');\r\n                yield null;\r\n            }\r\n        };\r\n    }\r\n\r\n    private static Entity parseLegacyEntityHover(String input) {\r\n        EntityTag entity = EntityTag.valueOf(input, CoreUtilities.basicContext);\r\n        if (entity == null) {\r\n            return null;\r\n        }\r\n        BaseComponent name = null;\r\n        if (entity.getBukkitEntity() != null && entity.getBukkitEntity().isCustomNameVisible()) {\r\n            name = new TextComponent();\r\n            for (BaseComponent component : FormattedTextHelper.parse(entity.getBukkitEntity().getCustomName(), ChatColor.WHITE)) {\r\n                name.addExtra(component);\r\n            }\r\n        }\r\n        return new Entity(entity.getBukkitEntityType().getKey().toString(), entity.getUUID().toString(), name);\r\n    }\r\n\r\n    public static void tryInitializeItemHoverFix() {\r\n        if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            return;\r\n        }\r\n        Gson bungeeGson = FormattedTextHelper.getBungeeGson();\r\n        if (bungeeGson == null) {\r\n            return;\r\n        }\r\n        Gson fixedGson = bungeeGson.newBuilder()\r\n                .registerTypeAdapter(FixedItemHover.class, new FixedItemHoverSerializer())\r\n                .registerTypeAdapter(Item.class, new FixedItemHoverSerializer())\r\n                .create();\r\n        try {\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n                ReflectionHelper.setFieldValue(VersionedComponentSerializer.class, \"gson\", VersionedComponentSerializer.forVersion(ChatVersion.V1_21_5), fixedGson);\r\n            }\r\n            else {\r\n                ReflectionHelper.getFinalSetter(ComponentSerializer.class, \"gson\").invoke(fixedGson);\r\n            }\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    public static class FixedItemHover extends Item {\r\n\r\n        private final JsonObject components;\r\n\r\n        public FixedItemHover(String id, int count, JsonObject components) {\r\n            super(id, count, null);\r\n            this.components = components;\r\n        }\r\n\r\n        public JsonObject getComponents() {\r\n            return components;\r\n        }\r\n    }\r\n\r\n    public static class FixedItemHoverSerializer extends ItemSerializer {\r\n\r\n        @Override\r\n        public Item deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {\r\n            Item deserialized = super.deserialize(element, type, context);\r\n            if (deserialized.getTag() != null) {\r\n                return deserialized;\r\n            }\r\n            JsonObject componentsObject = element.getAsJsonObject().getAsJsonObject(\"components\");\r\n            if (componentsObject == null) {\r\n                return deserialized;\r\n            }\r\n            return new FixedItemHover(deserialized.getId(), deserialized.getCount(), componentsObject);\r\n        }\r\n\r\n        @Override\r\n        public JsonElement serialize(Item content, Type type, JsonSerializationContext context) {\r\n            JsonElement serialized = super.serialize(content, type, context);\r\n            if (!(content instanceof FixedItemHover fixedItemHover) || fixedItemHover.getComponents() == null) {\r\n                return serialized;\r\n            }\r\n            JsonObject serializedObject = serialized.getAsJsonObject();\r\n            serializedObject.remove(\"tag\");\r\n            serializedObject.add(\"components\", fixedItemHover.getComponents());\r\n            return serializedObject;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/LegacyParticleNaming.java",
    "content": "package com.denizenscript.denizen.utilities;\n\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport org.bukkit.Particle;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class LegacyParticleNaming {\n\n    public static final Map<String, Particle> legacyParticleNames = new HashMap<>();\n\n    public static void registerLegacyName(String name, String particle) {\n        legacyParticleNames.put(name, Particle.valueOf(particle));\n    }\n    \n    static {\n        registerLegacyName(\"SMOKE\", \"SMOKE_NORMAL\");\n        registerLegacyName(\"HUGE_EXPLOSION\", \"EXPLOSION_HUGE\");\n        registerLegacyName(\"LARGE_EXPLODE\", \"EXPLOSION_LARGE\");\n        registerLegacyName(\"BUBBLE\", \"WATER_BUBBLE\");\n        registerLegacyName(\"SUSPEND\", \"SUSPENDED\");\n        registerLegacyName(\"DEPTH_SUSPEND\", \"SUSPENDED_DEPTH\");\n        registerLegacyName(\"CRIT\", \"CRIT\");\n        registerLegacyName(\"MAGIC_CRIT\", \"CRIT_MAGIC\");\n        registerLegacyName(\"MOB_SPELL\", \"SPELL_MOB\");\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\n            registerLegacyName(\"MOB_SPELL_AMBIENT\", \"SPELL_MOB_AMBIENT\");\n        }\n        registerLegacyName(\"INSTANT_SPELL\", \"SPELL_INSTANT\");\n        registerLegacyName(\"WITCH_MAGIC\", \"SPELL_WITCH\");\n        registerLegacyName(\"EXPLODE\", \"EXPLOSION_NORMAL\");\n        registerLegacyName(\"SPLASH\", \"WATER_SPLASH\");\n        registerLegacyName(\"LARGE_SMOKE\", \"SMOKE_LARGE\");\n        registerLegacyName(\"RED_DUST\", \"REDSTONE\");\n        registerLegacyName(\"SNOWBALL_POOF\", \"SNOWBALL\");\n        registerLegacyName(\"ANGRY_VILLAGER\", \"VILLAGER_ANGRY\");\n        registerLegacyName(\"HAPPY_VILLAGER\", \"VILLAGER_HAPPY\");\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/LegacySavesUpdater.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.TimeTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptHelper;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.util.UUID;\r\n\r\npublic class LegacySavesUpdater {\r\n\r\n    public static void updateLegacySaves() {\r\n        Debug.log(\"==== UPDATING LEGACY SAVES TO NEW FLAG ENGINE ====\");\r\n        File savesFile = new File(Denizen.getInstance().getDataFolder(), \"saves.yml\");\r\n        if (!savesFile.exists()) {\r\n            Debug.echoError(\"Legacy update went weird: file doesn't exist?\");\r\n            return;\r\n        }\r\n        YamlConfiguration saveSection;\r\n        try {\r\n            FileInputStream fis = new FileInputStream(savesFile);\r\n            String saveData = ScriptHelper.convertStreamToString(fis, false);\r\n            fis.close();\r\n            if (saveData.trim().length() == 0) {\r\n                Debug.log(\"Nothing to update.\");\r\n                savesFile.delete();\r\n                return;\r\n            }\r\n            saveSection = YamlConfiguration.load(saveData);\r\n            if (saveSection == null) {\r\n                Debug.echoError(\"Something went very wrong: legacy saves file failed to load!\");\r\n                return;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return;\r\n        }\r\n        if (!savesFile.renameTo(new File(Denizen.getInstance().getDataFolder(), \"saves.yml.bak\"))) {\r\n            Debug.echoError(\"Legacy saves file failed to rename!\");\r\n        }\r\n        if (saveSection.contains(\"Global\")) {\r\n            Debug.log(\"==== Update global data ====\");\r\n            YamlConfiguration globalSection = saveSection.getConfigurationSection(\"Global\");\r\n            if (globalSection.contains(\"Flags\")) {\r\n                applyFlags(\"Server\", DenizenCore.serverFlagMap, globalSection.getConfigurationSection(\"Flags\"));\r\n            }\r\n            if (globalSection.contains(\"Scripts\")) {\r\n                YamlConfiguration scriptsSection = globalSection.getConfigurationSection(\"Scripts\");\r\n                for (StringHolder script : scriptsSection.getKeys(false)) {\r\n                    YamlConfiguration scriptSection = scriptsSection.getConfigurationSection(script.str);\r\n                    if (scriptSection.contains(\"Cooldown Time\")) {\r\n                        long time = Long.parseLong(scriptSection.getString(\"Cooldown Time\"));\r\n                        TimeTag cooldown = new TimeTag(time);\r\n                        DenizenCore.serverFlagMap.setFlag(\"__interact_cooldown.\" + script.low, cooldown, cooldown);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (saveSection.contains(\"Players\")) {\r\n            Debug.log(\"==== Update player data ====\");\r\n            YamlConfiguration playerSection = saveSection.getConfigurationSection(\"Players\");\r\n            for (StringHolder plPrefix : playerSection.getKeys(false)) {\r\n                YamlConfiguration subSection = playerSection.getConfigurationSection(plPrefix.str);\r\n                for (StringHolder uuidString : subSection.getKeys(false)) {\r\n                    if (uuidString.str.length() != 32) {\r\n                        Debug.echoError(\"Cannot update data for player with non-ID entry listed: \" + uuidString);\r\n                        continue;\r\n                    }\r\n                    try {\r\n                        UUID id = UUID.fromString(uuidString.str.substring(0, 8) + \"-\" + uuidString.str.substring(8, 12) + \"-\" + uuidString.str.substring(12, 16) + \"-\" + uuidString.str.substring(16, 20) + \"-\" + uuidString.str.substring(20, 32));\r\n                        PlayerTag player = PlayerTag.valueOf(id.toString(), CoreUtilities.errorButNoDebugContext);\r\n                        if (player == null) {\r\n                            Debug.echoError(\"Cannot update data for player with id: \" + uuidString);\r\n                            continue;\r\n                        }\r\n                        YamlConfiguration actual = subSection.getConfigurationSection(uuidString.str);\r\n                        AbstractFlagTracker tracker = player.getFlagTracker();\r\n                        if (actual.contains(\"Flags\")) {\r\n                            applyFlags(player.identify(), tracker, actual.getConfigurationSection(\"Flags\"));\r\n                        }\r\n                        if (actual.contains(\"Scripts\")) {\r\n                            YamlConfiguration scriptsSection = actual.getConfigurationSection(\"Scripts\");\r\n                            for (StringHolder script : scriptsSection.getKeys(false)) {\r\n                                YamlConfiguration scriptSection = scriptsSection.getConfigurationSection(script.str);\r\n                                if (scriptSection.contains(\"Current Step\")) {\r\n                                    tracker.setFlag(\"__interact_step.\" + script, new ElementTag(scriptSection.getString(\"Current Step\")), null);\r\n                                }\r\n                                if (scriptSection.contains(\"Cooldown Time\")) {\r\n                                    long time = Long.parseLong(scriptSection.getString(\"Cooldown Time\"));\r\n                                    TimeTag cooldown = new TimeTag(time);\r\n                                    tracker.setFlag(\"__interact_cooldown.\" + script, cooldown, cooldown);\r\n                                }\r\n                            }\r\n                        }\r\n                        player.reapplyTracker(tracker);\r\n                    }\r\n                    catch (Throwable ex) {\r\n                        Debug.echoError(\"Error updating flags for player with ID \" + uuidString.str);\r\n                        Debug.echoError(ex);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (saveSection.contains(\"NPCs\")) {\r\n            final YamlConfiguration npcsSection = saveSection.getConfigurationSection(\"NPCs\");\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    Debug.log(\"==== Late update NPC data ====\");\r\n                    for (StringHolder npcId : npcsSection.getKeys(false)) {\r\n                        YamlConfiguration actual = npcsSection.getConfigurationSection(npcId.str);\r\n                        NPCTag npc = NPCTag.valueOf(npcId.str, CoreUtilities.errorButNoDebugContext);\r\n                        if (npc == null) {\r\n                            Debug.echoError(\"Cannot update data for NPC with id: \" + npcId.str);\r\n                            continue;\r\n                        }\r\n                        AbstractFlagTracker tracker = npc.getFlagTracker();\r\n                        if (actual.contains(\"Flags\")) {\r\n                            applyFlags(npc.identify(), tracker, actual.getConfigurationSection(\"Flags\"));\r\n                        }\r\n                        npc.reapplyTracker(tracker);\r\n                        Debug.log(\"==== Done late-updating NPC data ====\");\r\n                    }\r\n                }\r\n            }.runTaskLater(Denizen.getInstance(), 3);\r\n        }\r\n        Denizen.getInstance().saveSaves(false);\r\n        Debug.log(\"==== Done updating legacy saves (except NPCs) ====\");\r\n    }\r\n\r\n    public static void applyFlags(String object, AbstractFlagTracker tracker, YamlConfiguration section) {\r\n        try {\r\n            if (section == null || section.getKeys(false).isEmpty()) {\r\n                return;\r\n            }\r\n            for (StringHolder flagName : section.getKeys(false)) {\r\n                if (flagName.low.endsWith(\"-expiration\")) {\r\n                    continue;\r\n                }\r\n                TimeTag expireAt = null;\r\n                if (section.contains(flagName + \"-expiration\")) {\r\n                    long expireTime = Long.parseLong(section.getString(flagName + \"-expiration\"));\r\n                    expireAt = new TimeTag(expireTime);\r\n                }\r\n                Object value = section.get(flagName.str);\r\n                ObjectTag setAs = CoreUtilities.objectToTagForm(value, CoreUtilities.errorButNoDebugContext);\r\n                tracker.setFlag(flagName.low, setAs, expireAt);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(\"Error while updating legacy flags for \" + object);\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/MultiVersionHelper1_18.java",
    "content": "package com.denizenscript.denizen.utilities;\n\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport org.bukkit.entity.Player;\n\npublic class MultiVersionHelper1_18 {\n\n    public static ElementTag getSkinModel(Player player) {\n        return new ElementTag(player.getPlayerProfile().getTextures().getSkinModel());\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/MultiVersionHelper1_19.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport org.bukkit.World;\r\nimport org.bukkit.entity.*;\r\n\r\npublic class MultiVersionHelper1_19 {\r\n\r\n    public static boolean colorIsApplicable(EntityType type) {\r\n        return type == EntityType.FROG || Boat.class.isAssignableFrom(type.getEntityClass());\r\n    }\r\n\r\n    // TODO Frog variants technically have registries on all supported versions\r\n    public static String getColor(Entity entity, boolean includeDeprecated) {\r\n        if (entity instanceof Frog frog) {\r\n            return String.valueOf(frog.getVariant());\r\n        }\r\n        else if (entity instanceof Boat boat) {\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n                if (!includeDeprecated) {\r\n                    return null;\r\n                }\r\n                BukkitImplDeprecations.gettingBoatType.warn();\r\n            }\r\n            return boat.getBoatType().name();\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static ListTag getAllowedColors(EntityType type) {\r\n        if (type == EntityType.FROG) {\r\n            return Utilities.listTypes(Frog.Variant.class);\r\n        }\r\n        else if (Boat.class.isAssignableFrom(type.getEntityClass())) {\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n                BukkitImplDeprecations.gettingBoatType.warn();\r\n            }\r\n            return Utilities.listTypes(Boat.Type.class);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static void setColor(Entity entity, Mechanism mech) {\r\n        if (entity instanceof Frog frog && Utilities.requireEnumlike(mech, Frog.Variant.class)) {\r\n            frog.setVariant(Utilities.elementToEnumlike(mech.getValue(), Frog.Variant.class));\r\n        }\r\n        else if (entity instanceof Boat boat && mech.requireEnum(Boat.Type.class)) {\r\n            if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21)) {\r\n                BukkitImplDeprecations.settingBoatType.warn(mech.context);\r\n                return;\r\n            }\r\n            boat.setBoatType(mech.getValue().asEnum(Boat.Type.class));\r\n        }\r\n    }\r\n\r\n    public static MapTag interactionToMap(Interaction.PreviousInteraction interaction, World world) {\r\n        if (interaction == null) {\r\n            return null;\r\n        }\r\n        MapTag result = new MapTag();\r\n        result.putObject(\"player\", new PlayerTag(interaction.getPlayer()));\r\n        result.putObject(\"duration\", new DurationTag((world.getGameTime() - interaction.getTimestamp()) / 20d));\r\n        result.putObject(\"raw_game_time\", new ElementTag(interaction.getTimestamp()));\r\n        return result;\r\n    }\r\n\r\n    public static ElementTag getWardenAngerLevel(Warden warden) {\r\n        return new ElementTag(warden.getAngerLevel());\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/NotedAreaTracker.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.objects.AreaContainmentObject;\r\nimport com.denizenscript.denizen.objects.CuboidTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.function.Consumer;\r\n\r\n/**\r\n * Special helper class that tracks noted areas in a way that allows for very rapid \"get all areas containing\" checks, within the confines of the scales that Minecraft normally operates at.\r\n * This divides all notes first into one distinct set per world, and then within worlds it will sub-divide into 5 sets:\r\n * The \"global\" set, a 50x50 set, a 200x200 set, and both those sets again but with a half-width offset.\r\n * Any area that fits within a 50x50 grid gets added to the 50x50 grid... any area below 50x50 in scale that doesn't fit the grid will likely fit the offset grid instead.\r\n * If the 50x50 grids can't be fit, the 200x200 grids are tried. If those fail, the global set is used.\r\n * Note that vertical position (Y coordinate) is entirely ignored.\r\n * Because most noted areas are likely to fit into one of these grids, any lookups can confine themselves to only looking at the Areas defined within the same grid cell.\r\n * This uses multiple layers of imperfect checks before doing the final exact-containment check, as the imperfect checks are significantly faster to run, especially for complex area shapes like polygons.\r\n */\r\npublic class NotedAreaTracker {\r\n\r\n    public static final class TrackedArea {\r\n\r\n        public TrackedArea(AreaContainmentObject area, LocationTag low, LocationTag high) {\r\n            this.area = area;\r\n            lowX = low.getBlockX();\r\n            lowY = low.getBlockY();\r\n            lowZ = low.getBlockZ();\r\n            highX = high.getBlockX();\r\n            highY = high.getBlockY();\r\n            highZ = high.getBlockZ();\r\n        }\r\n\r\n        public TrackedArea(AreaContainmentObject area) {\r\n            CuboidTag boundary = area.getCuboidBoundary();\r\n            LocationTag low = boundary.getLow(0), high = boundary.getHigh(0);\r\n            this.area = area;\r\n            lowX = low.getBlockX();\r\n            lowY = low.getBlockY();\r\n            lowZ = low.getBlockZ();\r\n            highX = high.getBlockX();\r\n            highY = high.getBlockY();\r\n            highZ = high.getBlockZ();\r\n        }\r\n\r\n        public final AreaContainmentObject area;\r\n\r\n        public final int lowX, lowY, lowZ, highX, highY, highZ;\r\n\r\n        public boolean mightContain(int x, int y, int z) {\r\n            return x >= lowX && x <= highX && z >= lowZ && z <= highZ && y >= lowY && y <= highY;\r\n        }\r\n\r\n        public boolean mightIntersect(TrackedArea area2) {\r\n            return area2.lowX <= highX && area2.highX >= lowX && area2.lowZ <= highZ && area2.highZ >= lowZ && area2.lowY <= highY && area2.highY >= lowY;\r\n        }\r\n\r\n        @Override\r\n        public int hashCode() {\r\n            return area.hashCode();\r\n        }\r\n\r\n        @Override\r\n        public boolean equals(Object other) {\r\n            if (!(other instanceof TrackedArea compareTo)) {\r\n                return false;\r\n            }\r\n            return lowX == compareTo.lowX && lowZ == compareTo.lowZ && lowY == compareTo.lowY\r\n                    && highX == compareTo.highX && highZ == compareTo.highZ && highY == compareTo.highY\r\n                    && area.equals(compareTo.area);\r\n        }\r\n    }\r\n\r\n    public static final class AreaSet {\r\n\r\n        public AreaSet(int type, int index) {\r\n            this.type = type;\r\n            this.index = index;\r\n        }\r\n\r\n        public final ArrayList<TrackedArea> list = new ArrayList<>();\r\n\r\n        public final int index;\r\n\r\n        public final int type;\r\n\r\n        public boolean isEmpty() {\r\n            return list.isEmpty();\r\n        }\r\n    }\r\n\r\n    public static final class PerWorldSet {\r\n\r\n        public final AreaSet globalSet = new AreaSet(0, 0);\r\n\r\n        public final Int2ObjectOpenHashMap<AreaSet> sets50 = new Int2ObjectOpenHashMap<>(), sets50_offset = new Int2ObjectOpenHashMap<>(), sets200 = new Int2ObjectOpenHashMap<>(), sets200_offset = new Int2ObjectOpenHashMap<>();\r\n\r\n        public static boolean doesFit(TrackedArea area, int scale, int offset) {\r\n            int lowX = (area.lowX + offset) / scale, lowZ = (area.lowZ + offset) / scale, highX = (area.highX + offset) / scale, highZ = (area.highZ + offset) / scale;\r\n            return lowX == highX && lowZ == highZ;\r\n        }\r\n\r\n        public static int getIndex(int x, int z, int scale, int offset) {\r\n            // Index is unique \"enough\", these lists don't need to be perfect, and an int key is significantly faster than constructing an exact coordinate object.\r\n            int cleanX = (x + offset) / scale, cleanZ = (z + offset) / scale;\r\n            return cleanX + (cleanZ << 16);\r\n        }\r\n\r\n        public AreaSet getOrGenSetFor(Int2ObjectOpenHashMap<AreaSet> sets, int type, TrackedArea area, int scale, int offset, boolean generate) {\r\n            int index = getIndex(area.lowX, area.lowZ, scale, offset);\r\n            AreaSet set = sets.get(index);\r\n            if (set == null && generate) {\r\n                set = new AreaSet(type, index);\r\n                sets.put(index, set);\r\n            }\r\n            return set;\r\n        }\r\n\r\n        public AreaSet bestSetFor(TrackedArea area, boolean generate) {\r\n            if (doesFit(area, 50, 0)) {\r\n                return getOrGenSetFor(sets50, 1, area, 50, 0, generate);\r\n            }\r\n            else if (doesFit(area, 50, 25)) {\r\n                return getOrGenSetFor(sets50_offset, 2, area, 50, 25, generate);\r\n            }\r\n            else if (doesFit(area, 200, 0)) {\r\n                return getOrGenSetFor(sets200, 3, area, 200, 0, generate);\r\n            }\r\n            else if (doesFit(area, 200, 100)) {\r\n                return getOrGenSetFor(sets200_offset, 4, area, 200, 100, generate);\r\n            }\r\n            return globalSet;\r\n        }\r\n\r\n        public boolean isEmpty() {\r\n            return globalSet.isEmpty() && sets50.isEmpty() && sets50_offset.isEmpty() && sets200.isEmpty() && sets200_offset.isEmpty();\r\n        }\r\n\r\n        public void remove(AreaSet set) {\r\n            switch (set.type) {\r\n                case 1 -> sets50.remove(set.index);\r\n                case 2 -> sets50_offset.remove(set.index);\r\n                case 3 -> sets200.remove(set.index);\r\n                case 4 -> sets200_offset.remove(set.index);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static HashMap<String, PerWorldSet> worlds = new HashMap<>();\r\n\r\n    /**\r\n     * Call to add an area into the tracker.\r\n     */\r\n    public static void add(AreaContainmentObject area) {\r\n        String worldName = CoreUtilities.toLowerCase(area.getWorld().getName());\r\n        PerWorldSet set = worlds.get(worldName);\r\n        if (set == null) {\r\n            set = new PerWorldSet();\r\n            worlds.put(worldName, set);\r\n        }\r\n        TrackedArea tracker = new TrackedArea(area);\r\n        AreaSet areaSet = set.bestSetFor(tracker, true);\r\n        areaSet.list.add(tracker);\r\n    }\r\n\r\n    /**\r\n     * Call to remove an area from the tracker.\r\n     */\r\n    public static void remove(AreaContainmentObject area) {\r\n        String worldName = CoreUtilities.toLowerCase(area.getWorld().getName());\r\n        PerWorldSet set = worlds.get(worldName);\r\n        if (set == null) {\r\n            return;\r\n        }\r\n        TrackedArea tracker = new TrackedArea(area);\r\n        AreaSet areaSet = set.bestSetFor(tracker, false);\r\n        if (areaSet == null) {\r\n            return;\r\n        }\r\n        areaSet.list.remove(tracker);\r\n        if (areaSet.isEmpty()) {\r\n            set.remove(areaSet);\r\n            if (set.isEmpty()) {\r\n                worlds.remove(worldName);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void forEachAreaInSetThatContains(int x, int y, int z, LocationTag location, AreaSet set, Consumer<AreaContainmentObject> action) {\r\n        if (set == null) {\r\n            return;\r\n        }\r\n        for (TrackedArea area : set.list) {\r\n            if (area.mightContain(x, y, z) && area.area.doesContainLocation(location)) {\r\n                action.accept(area.area);\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Call to run an action over every Area that contains a given location.\r\n     */\r\n    public static void forEachAreaThatContains(LocationTag location, Consumer<AreaContainmentObject> action) {\r\n        int x = location.getBlockX(), y = location.getBlockY(), z = location.getBlockZ();\r\n        PerWorldSet set = worlds.get(CoreUtilities.toLowerCase(location.getWorldName()));\r\n        if (set == null) {\r\n            return;\r\n        }\r\n        forEachAreaInSetThatContains(x, y, z, location, set.globalSet, action);\r\n        forEachAreaInSetThatContains(x, y, z, location, set.sets50.get(PerWorldSet.getIndex(x, z, 50, 0)), action);\r\n        forEachAreaInSetThatContains(x, y, z, location, set.sets50_offset.get(PerWorldSet.getIndex(x, z, 50, 25)), action);\r\n        forEachAreaInSetThatContains(x, y, z, location, set.sets200.get(PerWorldSet.getIndex(x, z, 200, 0)), action);\r\n        forEachAreaInSetThatContains(x, y, z, location, set.sets200_offset.get(PerWorldSet.getIndex(x, z, 200, 100)), action);\r\n    }\r\n\r\n    public static void forEachAreaInSetThatIntersects(TrackedArea area2, AreaSet set, Consumer<AreaContainmentObject> action) {\r\n        if (set == null) {\r\n            return;\r\n        }\r\n        for (TrackedArea area : set.list) {\r\n            if (area.mightIntersect(area2)) {\r\n                action.accept(area.area);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void forEachAreaThatIntersects(LocationTag min, LocationTag max, Consumer<AreaContainmentObject> action) {\r\n        CuboidTag.LocationPair pair = new CuboidTag.LocationPair(min, max);\r\n        min = pair.low;\r\n        max = pair.high;\r\n        TrackedArea area2 = new TrackedArea(null, min, max);\r\n        PerWorldSet set = worlds.get(CoreUtilities.toLowerCase(min.getWorldName()));\r\n        if (set == null) {\r\n            return;\r\n        }\r\n        forEachAreaInSetThatIntersects(area2, set.globalSet, action);\r\n        // Loose heuristic for when a regional-indexed loop is probably counterproductive - total looped regions exceeds 10x actual total areas available\r\n        if (pair.xDistance() * pair.zDistance() / (50 * 50) > (set.sets50.size() + set.sets50_offset.size()) * 10) {\r\n            for (AreaSet areaSet : set.sets50.values()) {\r\n                forEachAreaInSetThatIntersects(area2, areaSet, action);\r\n            }\r\n            for (AreaSet areaSet : set.sets50_offset.values()) {\r\n                forEachAreaInSetThatIntersects(area2, areaSet, action);\r\n            }\r\n        }\r\n        else {\r\n            for (int x = area2.lowX - 50; x <= area2.highX + 50; x += 50) {\r\n                for (int z = area2.lowZ - 50; z <= area2.highZ + 50; z += 50) {\r\n                    forEachAreaInSetThatIntersects(area2, set.sets50.get(PerWorldSet.getIndex(x, z, 50, 0)), action);\r\n                    forEachAreaInSetThatIntersects(area2, set.sets50_offset.get(PerWorldSet.getIndex(x, z, 50, 25)), action);\r\n                }\r\n            }\r\n        }\r\n        if (pair.xDistance() * pair.zDistance() / (288 * 200) > (set.sets200.size() + set.sets200_offset.size()) * 10) {\r\n            for (AreaSet areaSet : set.sets200.values()) {\r\n                forEachAreaInSetThatIntersects(area2, areaSet, action);\r\n            }\r\n            for (AreaSet areaSet : set.sets200_offset.values()) {\r\n                forEachAreaInSetThatIntersects(area2, areaSet, action);\r\n            }\r\n        }\r\n        else {\r\n            for (int x = area2.lowX - 200; x <= area2.highX + 200; x += 200) {\r\n                for (int z = area2.lowZ - 200; z <= area2.highZ + 200; z += 200) {\r\n                    forEachAreaInSetThatIntersects(area2, set.sets200.get(PerWorldSet.getIndex(x, z, 200, 0)), action);\r\n                    forEachAreaInSetThatIntersects(area2, set.sets200_offset.get(PerWorldSet.getIndex(x, z, 200, 100)), action);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/PaperAPITools.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.scripts.commands.entity.TeleportCommand;\r\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptContainer;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.Sign;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.event.entity.PlayerDeathEvent;\r\nimport org.bukkit.event.inventory.InventoryType;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\nimport org.bukkit.inventory.*;\r\nimport org.bukkit.inventory.meta.PotionMeta;\r\nimport org.bukkit.scoreboard.Team;\r\nimport org.bukkit.util.Consumer;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.net.URI;\r\nimport java.util.List;\r\nimport java.util.Set;\r\nimport java.util.function.Predicate;\r\n\r\npublic class PaperAPITools {\r\n\r\n    public static PaperAPITools instance = new PaperAPITools();\r\n\r\n    public Inventory createInventory(InventoryHolder holder, int slots, String title) {\r\n        return Bukkit.getServer().createInventory(holder, slots, title);\r\n    }\r\n\r\n    public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) {\r\n        return Bukkit.getServer().createInventory(holder, type, title);\r\n    }\r\n\r\n    public String parseComponent(Object input) {\r\n        if (input == null) {\r\n            return null;\r\n        }\r\n        if (input instanceof String) {\r\n            return (String) input;\r\n        }\r\n        else if (input instanceof BaseComponent[]) {\r\n            return FormattedTextHelper.stringify((BaseComponent[]) input);\r\n        }\r\n        else if (input instanceof BaseComponent) {\r\n            return FormattedTextHelper.stringify((BaseComponent) input);\r\n        }\r\n        else {\r\n            return input.toString();\r\n        }\r\n    }\r\n\r\n    public String getTitle(Inventory inventory) {\r\n        return NMSHandler.instance.getTitle(inventory);\r\n    }\r\n\r\n    public void setCustomName(Entity entity, String name) {\r\n        entity.setCustomName(name);\r\n    }\r\n\r\n    public String getCustomName(Entity entity) {\r\n        return entity.getCustomName();\r\n    }\r\n\r\n    public BaseComponent[] getCustomNameComponent(Entity entity) {\r\n        String customName = entity.getCustomName();\r\n        return customName != null ? FormattedTextHelper.parseSimpleColorsOnly(customName) : null;\r\n    }\r\n\r\n    public void setPlayerListName(Player player, String name) {\r\n        player.setPlayerListName(name);\r\n    }\r\n\r\n    public String getPlayerListName(Player player) {\r\n        return player.getPlayerListName();\r\n    }\r\n\r\n    public String[] getSignLines(Sign sign) {\r\n        return sign.getLines();\r\n    }\r\n\r\n    public void setSignLine(Sign sign, int line, String text) {\r\n        sign.setLine(line, text == null ? \"\" : text);\r\n    }\r\n\r\n    public void sendResourcePack(Player player, String url, String hash, boolean forced, String prompt) {\r\n        byte[] hashData = new byte[20];\r\n        for (int i = 0; i < 20; i++) {\r\n            hashData[i] = (byte) Integer.parseInt(hash.substring(i * 2, i * 2 + 2), 16);\r\n        }\r\n        player.setResourcePack(url, hashData);\r\n    }\r\n\r\n    public void sendSignUpdate(Player player, Location loc, String[] text) {\r\n        player.sendSignChange(loc, text);\r\n    }\r\n\r\n    public String getCustomName(Nameable object) {\r\n        return object.getCustomName();\r\n    }\r\n\r\n    public void setCustomName(Nameable object, String name) {\r\n        object.setCustomName(name);\r\n    }\r\n\r\n    public void sendConsoleMessage(CommandSender sender, String text) {\r\n        sender.spigot().sendMessage(FormattedTextHelper.parse(text, net.md_5.bungee.api.ChatColor.WHITE));\r\n    }\r\n\r\n    public InventoryView openAnvil(Player player, Location loc) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void teleport(Entity entity, Location loc, PlayerTeleportEvent.TeleportCause cause, List<TeleportCommand.EntityState> entityTeleportFlags, List<TeleportCommand.Relative> relativeTeleportFlags) {\r\n        entity.teleport(loc, cause);\r\n    }\r\n\r\n    public void registerBrewingRecipe(String keyName, ItemStack result, String input, String ingredient, ItemScriptContainer itemScriptContainer) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public void clearBrewingRecipes() {\r\n    }\r\n\r\n    public String getBrewingRecipeInputMatcher(NamespacedKey recipeId) {\r\n        return null;\r\n    }\r\n\r\n    public String getBrewingRecipeIngredientMatcher(NamespacedKey recipeId) {\r\n        return null;\r\n    }\r\n\r\n    public RecipeChoice createPredicateRecipeChoice(Predicate<ItemStack> predicate) {\r\n        throw new UnsupportedOperationException();\r\n    }\r\n\r\n    public String getDeathMessage(PlayerDeathEvent event) {\r\n        return event.getDeathMessage();\r\n    }\r\n\r\n    public void setDeathMessage(PlayerDeathEvent event, String message) {\r\n        event.setDeathMessage(message);\r\n    }\r\n\r\n    public void setSkin(Player player, String name) {\r\n        NMSHandler.instance.getProfileEditor().setPlayerSkin(player, name);\r\n    }\r\n\r\n    public void setSkinBlob(Player player, String blob) {\r\n        NMSHandler.instance.getProfileEditor().setPlayerSkinBlob(player, blob);\r\n    }\r\n\r\n    public static MethodHandle WORLD_SPAWN_BUKKIT_CONSUMER = null;\r\n\r\n    // TODO once 1.20 is the minimum supported version, use the modern java.util.Consumer\r\n    public <T extends Entity> T spawnEntity(Location location, Class<T> type, Consumer<T> configure, CreatureSpawnEvent.SpawnReason reason) {\r\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_19)) {\r\n            // Takes the deprecated bukkit consumer on older versions\r\n            if (WORLD_SPAWN_BUKKIT_CONSUMER == null) {\r\n                WORLD_SPAWN_BUKKIT_CONSUMER = ReflectionHelper.getMethodHandle(RegionAccessor.class, \"spawn\", Location.class, Class.class, Consumer.class);\r\n            }\r\n            try {\r\n                return (T) WORLD_SPAWN_BUKKIT_CONSUMER.invoke(location.getWorld(), location, type, configure);\r\n            }\r\n            catch (Throwable e) {\r\n                Debug.echoError(e);\r\n                return null;\r\n            }\r\n        }\r\n        return location.getWorld().spawn(location, type, configure);\r\n    }\r\n\r\n    public void setTeamPrefix(Team team, String prefix) {\r\n        team.setPrefix(prefix);\r\n    }\r\n\r\n    public void setTeamSuffix(Team team, String suffix) {\r\n        team.setSuffix(suffix);\r\n    }\r\n\r\n    public String getTeamPrefix(Team team) {\r\n        return team.getPrefix();\r\n    }\r\n\r\n    public String getTeamSuffix(Team team) {\r\n        return team.getSuffix();\r\n    }\r\n\r\n    public String convertTextToMiniMessage(String text, boolean splitNewlines) {\r\n        return text;\r\n    }\r\n\r\n    public Merchant createMerchant(String title) {\r\n        return Bukkit.createMerchant(title);\r\n    }\r\n\r\n    public String getText(TextDisplay textDisplay) {\r\n        String text = textDisplay.getText();\r\n        return text != null ? text : \"\";\r\n    }\r\n\r\n    public void setText(TextDisplay textDisplay, String text) {\r\n        textDisplay.setText(text);\r\n    }\r\n\r\n    public void kickPlayer(Player player, String message) {\r\n        player.kickPlayer(message);\r\n    }\r\n\r\n    public String getClientBrand(Player player) {\r\n        NetworkInterceptHelper.enable();\r\n        return NMSHandler.playerHelper.getClientBrand(player);\r\n    }\r\n\r\n    public boolean canUseEquipmentSlot(LivingEntity entity, EquipmentSlot slot) {\r\n        return true;\r\n    }\r\n\r\n    // TODO workaround Paper issue - https://github.com/PaperMC/Paper/issues/11732\r\n    public boolean hasCustomName(PotionMeta meta) {\r\n        return meta.hasCustomName();\r\n    }\r\n\r\n    public void setMaterialTags(Material type, Set<NamespacedKey> tags) {\r\n        NMSHandler.blockHelper.setVanillaTags(type, tags);\r\n    }\r\n\r\n    public void addLink(ServerLinks links, String display, URI uri) {\r\n        links.addLink(display, uri);\r\n    }\r\n  \r\n    public double[] getRecentTps() {\r\n        return NMSHandler.instance.getRecentTps();\r\n    }\r\n\r\n    public String getCopperGolemState(CopperGolem copperGolem) {\r\n        return copperGolem.getWeatherState().name();\r\n    }\r\n\r\n    public void setCopperGolemState(ElementTag variant, CopperGolem copperGolem, Mechanism mechanism) {\r\n        if (mechanism.requireEnum(CopperGolem.CopperWeatherState.class)) {\r\n            copperGolem.setWeatherState(variant.asEnum(CopperGolem.CopperWeatherState.class));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/ScoreboardHelper.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.OfflinePlayer;\r\nimport org.bukkit.configuration.ConfigurationSection;\r\nimport org.bukkit.scoreboard.*;\r\n\r\nimport java.util.*;\r\n\r\n/** Helper/manager for scoreboards that creates, saves, and loads scoreboards. */\r\npublic class ScoreboardHelper {\r\n\r\n    public static ScoreboardManager manager = Bukkit.getScoreboardManager();\r\n\r\n    /** A map with scoreboard IDs as keys and scoreboards as values. */\r\n    public static Map<String, Scoreboard> scoreboardMap = new HashMap<>();\r\n\r\n    /** A map with viewer names as keys and scoreboard IDs as values. */\r\n    public static Map<UUID, String> viewerMap = new HashMap<>();\r\n\r\n    /** Called on server startup or /denizen reload saves. */\r\n    public static void _recallScoreboards() {\r\n        for (Map.Entry<String, Scoreboard> entry : scoreboardMap.entrySet()) {\r\n            clearScoreboard(entry.getValue());\r\n        }\r\n        Scoreboard emptyBoard = createScoreboard();\r\n        for (Map.Entry<UUID, String> entry : viewerMap.entrySet()) {\r\n            OfflinePlayer player = Bukkit.getPlayer(entry.getKey());\r\n            if (player != null && player.isOnline()) {\r\n                player.getPlayer().setScoreboard(emptyBoard);\r\n            }\r\n        }\r\n        scoreboardMap.clear();\r\n        viewerMap.clear();\r\n        ConfigurationSection rootSection = Denizen.getInstance().getScoreboards().getConfigurationSection(\"Scoreboards\");\r\n        if (rootSection == null) {\r\n            return;\r\n        }\r\n        Scoreboard board;\r\n        for (String id : rootSection.getKeys(false)) {\r\n            board = createScoreboard(id);\r\n            List<String> viewerList = rootSection.getStringList(id + \".Viewers\");\r\n            for (String viewer : viewerList) {\r\n                if (PlayerTag.matches(viewer)) {\r\n                    PlayerTag player = PlayerTag.valueOf(viewer, CoreUtilities.basicContext);\r\n                    viewerMap.put(player.getUUID(), id);\r\n                    if (player.isOnline()) {\r\n                        player.getPlayerEntity().setScoreboard(board);\r\n                    }\r\n                }\r\n            }\r\n            ConfigurationSection objSection = rootSection.getConfigurationSection(id + \".Objectives\");\r\n            if (objSection == null) {\r\n                return;\r\n            }\r\n            for (String obj : objSection.getKeys(false)) {\r\n                String displaySlot = objSection.getString(obj + \".Display slot\");\r\n                String criteria = objSection.getString(obj + \".Criteria\");\r\n                if (criteria == null) {\r\n                    criteria = \"dummy\";\r\n                }\r\n                if (displaySlot == null) {\r\n                    displaySlot = \"NONE\";\r\n                }\r\n                // TODO: When 1.19 is minimum version: Objective o = board.registerNewObjective(obj, Bukkit.getScoreboardCriteria(criteria), obj);\r\n                Objective o = board.registerNewObjective(obj, criteria);\r\n                if (Argument.valueOf(displaySlot).matchesEnum(DisplaySlot.class)) {\r\n                    o.setDisplaySlot(DisplaySlot.valueOf(displaySlot.toUpperCase()));\r\n                }\r\n                ConfigurationSection scoreSection = objSection.getConfigurationSection(obj + \".Scores\");\r\n                if (scoreSection != null) {\r\n                    for (String scoreName : scoreSection.getKeys(false)) {\r\n                        int scoreInt = scoreSection.getInt(scoreName);\r\n                        addScore(o, scoreName, scoreInt);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    /** Called on server shutdown or /denizen save. */\r\n    public static void _saveScoreboards() {\r\n        try {\r\n            Denizen.getInstance().getScoreboards().set(\"Scoreboards\", null);\r\n            for (Map.Entry<String, Scoreboard> scoreboardEntry : scoreboardMap.entrySet()) {\r\n                String id = scoreboardEntry.getKey();\r\n                List<String> viewerList = new ArrayList<>();\r\n                for (Map.Entry<UUID, String> viewerEntry : viewerMap.entrySet()) {\r\n                    if (id.equalsIgnoreCase(viewerEntry.getValue())) {\r\n                        viewerList.add(viewerEntry.getKey().toString());\r\n                    }\r\n                }\r\n                Denizen.getInstance().getScoreboards().set(\"Scoreboards.\" + id + \".Viewers\", viewerList);\r\n                for (Objective obj : scoreboardEntry.getValue().getObjectives()) {\r\n                    String objPath = \"Scoreboards.\" + id + \".Objectives.\" + obj.getName();\r\n                    Denizen.getInstance().getScoreboards().set(objPath + \".Criteria\", obj.getCriteria());\r\n                    String displaySlot;\r\n                    if (obj.getDisplaySlot() != null) {\r\n                        displaySlot = obj.getDisplaySlot().name();\r\n                    }\r\n                    else {\r\n                        displaySlot = \"NONE\";\r\n                    }\r\n                    Denizen.getInstance().getScoreboards().set(objPath + \".Display slot\", displaySlot);\r\n                    if (obj.isModifiable()) {\r\n                        // There is no method for getting an objective's scores, so iterate through all of the scoreboard's players, check if they have a score for this objective, and save it if they do\r\n                        for (String player : scoreboardEntry.getValue().getEntries()) {\r\n                            int score = obj.getScore(player).getScore();\r\n                            Team team = scoreboardEntry.getValue().getTeam(player);\r\n                            // TODO: Properly save and load teams\r\n                            player = (team != null && team.getPrefix() != null ? team.getPrefix() : \"\") + player + (team != null && team.getSuffix() != null ? team.getSuffix() : \"\");\r\n                            // If a player has no score for this objective, getScore() will return 0, so ignore scores of 0\r\n                            if (score != 0) {\r\n                                Denizen.getInstance().getScoreboards().set(objPath + \".Scores.\" + player, score);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    /** Add a score to an Objective for a name. */\r\n    public static void addScore(Objective o, String playerName, int score) {\r\n        Score sc = o.getScore(playerName);\r\n        // If the score is 0, it won't normally be displayed at first, so force it to be displayed by using setScore() like below on it\r\n        if (score == 0) {\r\n            sc.setScore(1);\r\n            sc.setScore(0);\r\n        }\r\n        else {\r\n            sc.setScore(score);\r\n        }\r\n    }\r\n\r\n    /** Remove a score from an Objective for a name. */\r\n    public static void removeScore(Objective o, String playerName) {\r\n        // There is no method to remove a single score from an objective, as confirmed here: https://bukkit.atlassian.net/browse/BUKKIT-4014\r\n        // So use crazy workaround below (save all scores, clear, and re-add)\r\n        Scoreboard board = o.getScoreboard();\r\n        Map<String, Integer> scoreMap = new HashMap<>();\r\n        for (Score sc : board.getScores(playerName)) {\r\n            if (!sc.getObjective().equals(o)) {\r\n                scoreMap.put(sc.getObjective().getName(), sc.getScore());\r\n            }\r\n        }\r\n        board.resetScores(playerName);\r\n        for (Map.Entry<String, Integer> entry : scoreMap.entrySet()) {\r\n            board.getObjective(entry.getKey()).getScore(playerName).setScore(entry.getValue());\r\n        }\r\n    }\r\n\r\n    /** Clears all the objectives from a Scoreboard, making it empty. */\r\n    public static void clearScoreboard(Scoreboard board) {\r\n        for (Objective o : board.getObjectives()) {\r\n            o.unregister();\r\n        }\r\n    }\r\n\r\n    /** Creates an anonymous new Scoreboard that isn't saved anywhere. */\r\n    public static Scoreboard createScoreboard() {\r\n        return manager.getNewScoreboard();\r\n    }\r\n\r\n    /** Creates a new Scoreboard with a certain id and stories it in the scoreboards map. */\r\n    public static Scoreboard createScoreboard(String id) {\r\n        Scoreboard board = manager.getNewScoreboard();\r\n        scoreboardMap.put(id.toUpperCase(), board);\r\n        return board;\r\n    }\r\n\r\n    /** Deletes a Scoreboard, clearing it and removing it from the scoreboards map, unless it is the server's main scoreboard, in which case it is just cleared. */\r\n    public static void deleteScoreboard(String id) {\r\n        if (id.equalsIgnoreCase(\"main\")) {\r\n            clearScoreboard(getMain());\r\n        }\r\n        else {\r\n            clearScoreboard(getScoreboard(id));\r\n            scoreboardMap.remove(id.toUpperCase());\r\n        }\r\n    }\r\n\r\n    /** Returns the server's main scoreboard, that isn't stored in the scoreboards map because Bukkit already saves it by itself. */\r\n    public static Scoreboard getMain() {\r\n        return manager.getMainScoreboard();\r\n    }\r\n\r\n    /** Returns a Scoreboard from the scoreboards map. */\r\n    public static Scoreboard getScoreboard(String id) {\r\n        return scoreboardMap.get(id.toUpperCase());\r\n    }\r\n\r\n    /** Returns true if the scoreboards map contains a certain scoreboard id. */\r\n    public static boolean hasScoreboard(String id) {\r\n        return scoreboardMap.containsKey(id.toUpperCase());\r\n    }\r\n\r\n    public static void removePlayer(String id, String name) {\r\n        scoreboardMap.get(id.toUpperCase()).resetScores(name);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/Settings.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.PolygonTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.RemoveCommand;\r\nimport com.denizenscript.denizen.tags.core.CustomColorTagBase;\r\nimport com.denizenscript.denizen.utilities.flags.PlayerFlagHandler;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionRefuse;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugSubmitter;\r\nimport org.bukkit.configuration.ConfigurationSection;\r\nimport org.bukkit.configuration.file.FileConfiguration;\r\n\r\nimport java.nio.charset.Charset;\r\n\r\n@ReflectionRefuse\r\npublic class Settings {\r\n\r\n    public static void refillCache() {\r\n        FileConfiguration config = Denizen.getInstance().getConfig();\r\n        // Core\r\n        CoreConfiguration.debugRecordingAllowed = true;\r\n        CoreConfiguration.defaultDebugMode = config.getBoolean(\"Debug.Show\", true);\r\n        CoreConfiguration.shouldShowDebug = CoreConfiguration.defaultDebugMode;\r\n        CoreConfiguration.debugExtraInfo = config.getBoolean(\"Debug.Extra info\", false);\r\n        CoreConfiguration.debugVerbose = config.getBoolean(\"Debug.Verbose\", false);\r\n        CoreConfiguration.debugLoadingInfo = config.getBoolean(\"Debug.Show loading info\", false);\r\n        CoreConfiguration.deprecationWarningRate = config.getLong(\"Debug.Warning rate\", config.getLong(\"Tags.Warning rate\", 10000));\r\n        CoreConfiguration.futureWarningsEnabled = config.getBoolean(\"Debug.Show future warnings\", false);\r\n        CoreConfiguration.allowLog = config.getBoolean(\"Commands.Log.Allow logging\", true);\r\n        CoreConfiguration.allowFileCopy = config.getBoolean(\"Commands.Filecopy.Allow copying files\", true);\r\n        CoreConfiguration.allowWebget = config.getBoolean(\"Commands.Webget.Allow\", true);\r\n        CoreConfiguration.allowSQL = config.getBoolean(\"Commands.SQL.Allow\", true);\r\n        CoreConfiguration.allowRedis = config.getBoolean(\"Commands.Redis.Allow\", true);\r\n        CoreConfiguration.allowMongo = config.getBoolean(\"Commands.Mongo.Allow\", true);\r\n        CoreConfiguration.whileMaxLoops = config.getInt(\"Commands.While.Max loops\", 10000);\r\n        CoreConfiguration.tagTimeout = config.getInt(\"Tags.Timeout\", 10);\r\n        CoreConfiguration.tagTimeoutUnsafe = config.getBoolean(\"Tags.Timeout when unsafe\", false);\r\n        CoreConfiguration.tagTimeoutWhenSilent = config.getBoolean(\"Tags.Timeout when silent\", false);\r\n        CoreConfiguration.scriptQueueSpeed = DurationTag.valueOf(config.getString(\"Scripts.Queue speed\", \"instant\"), CoreUtilities.basicContext).getSeconds();\r\n        CoreConfiguration.allowConsoleRedirection = config.getBoolean(\"Debug.Allow console redirection\", false);\r\n        CoreConfiguration.allowStrangeFileSaves = config.getBoolean(\"Commands.Yaml.Allow saving outside folder\", false);\r\n        CoreConfiguration.skipAllFlagCleanings = config.getBoolean(\"Saves.Skip flag cleaning\", false);\r\n        CoreConfiguration.allowRestrictedActions = config.getBoolean(\"Commands.Security.Allow restricted actions\", false);\r\n        CoreConfiguration.allowWebserver = config.getBoolean(\"Commands.WebServer.Allow\", false);\r\n        CoreConfiguration.webserverRoot = config.getString(\"Commands.WebServer.Webroot\", \"webroot/\");\r\n        CoreConfiguration.allowFileRead = config.getBoolean(\"Commands.File.Allow read\", false);\r\n        CoreConfiguration.allowFileWrite = config.getBoolean(\"Commands.File.Allow write\", false);\r\n        CoreConfiguration.allowFileDeletion = config.getBoolean(\"Commands.Delete.Allow file deletion\", true);\r\n        CoreConfiguration.filePathLimit = config.getString(\"Commands.File.Restrict path\", \"data/\");\r\n        CoreConfiguration.verifyThreadMatches = config.getBoolean(\"Debug.Verify thread\", false);\r\n        CoreConfiguration.queueIdPrefix = config.getBoolean(\"Queues.Id parts.Prefix\", true);\r\n        CoreConfiguration.queueIdNumeric = config.getBoolean(\"Queues.Id parts.Numeric\", true);\r\n        CoreConfiguration.queueIdWords = config.getBoolean(\"Queues.Id parts.Words\", true);\r\n        CoreConfiguration.listFlagsAllowed = config.getBoolean(\"Tags.List flags.I know what im doing and need this\", false);\r\n        CoreConfiguration.allowReflectionFieldReads = config.getBoolean(\"Reflection.Allow reading fields\", true);\r\n        CoreConfiguration.allowReflectedCoreMethods = config.getBoolean(\"Reflection.Allow core methods\", true);\r\n        CoreConfiguration.allowReflectionSet = config.getBoolean(\"Reflection.Allow set command\", false);\r\n        CoreConfiguration.allowReflectionSetPrivate = config.getBoolean(\"Reflection.Allow set private fields\", false);\r\n        CoreConfiguration.allowReflectionSetFinal = config.getBoolean(\"Reflection.Allow set final fields\", false);\r\n        CoreConfiguration.debugLimitPerTick = config.getInt(\"Debug.Limit per tick\", 5000);\r\n        CoreConfiguration.debugTrimLength = config.getInt(\"Debug.Trim length limit\", 1024);\r\n        CoreConfiguration.debugPrefix = config.getString(\"Debug.Prefix\", \"\");\r\n        CoreConfiguration.debugLineLength = config.getInt(\"Debug.Line length\", 300);\r\n        DebugSubmitter.pasteURL = config.getString(\"Debug.Paste URL\", DebugSubmitter.corePasteURL);\r\n        if (DebugSubmitter.pasteURL.equals(\"default\")) {\r\n            DebugSubmitter.pasteURL = DebugSubmitter.corePasteURL;\r\n        }\r\n        String scriptEncoding = config.getString(\"Scripts.Encoding\", \"default\");\r\n        if (scriptEncoding.equalsIgnoreCase(\"default\")) {\r\n            CoreConfiguration.scriptEncoding = null;\r\n        }\r\n        else {\r\n            try {\r\n                CoreConfiguration.scriptEncoding = Charset.forName(scriptEncoding).newDecoder();\r\n            }\r\n            catch (Exception ex) {\r\n                ex.printStackTrace();\r\n            }\r\n        }\r\n        // Spigot\r\n        PolygonTag.preferInclusive = config.getBoolean(\"Tags.Polygon default inclusive\", false);\r\n        allowAsyncPassThrough = config.getBoolean(\"Scripts.Economy.Pass async to main thread\", false);\r\n        skipChunkFlagCleaning = config.getBoolean(\"Saves.Skip chunk flag cleaning\", false);\r\n        nullifySkullSkinIds = config.getBoolean(\"Tags.Nullify skull skin ids\", false);\r\n        worldPlayerDataSaveDelay = (float) DurationTag.valueOf(config.getString(\"Save world player file delay\", \"10s\"), CoreUtilities.basicContext).getSeconds();\r\n        worldPlayerDataMaxCacheTicks = DurationTag.valueOf(config.getString(\"World player data max cache\", \"1h\"), CoreUtilities.basicContext).getTicks();\r\n        cache_overrideHelp = config.getBoolean(\"Debug.Override help\", true);\r\n        cache_useDefaultScriptPath = config.getBoolean(\"Scripts location.Use default script folder\", true);\r\n        cache_showExHelp = config.getBoolean(\"Debug.Ex command help\", true);\r\n        cache_showExDebug = config.getBoolean(\"Debug.Ex command debug\", true);\r\n        cache_getAlternateScriptPath = config.getString(\"Scripts location.Alternative folder path\", \"plugins/Denizen\");\r\n        cache_canRecordStats = config.getBoolean(\"Debug.Stats\", true);\r\n        cache_defaultDebugMode = config.getBoolean(\"Debug.Container default\", true);\r\n        cache_warnOnAsyncPackets = config.getBoolean(\"Debug.Warn on async packets\", false);\r\n        cache_interactQueueSpeed = config.getString(\"Scripts.Interact.Queue speed\", \"0.5s\");\r\n        cache_healthTraitEnabledByDefault = config.getBoolean(\"Traits.Health.Enabled\", false);\r\n        cache_healthTraitRespawnEnabled = config.getBoolean(\"Traits.Health.Respawn.Enabled\", true);\r\n        cache_healthTraitAnimatedDeathEnabled = config.getBoolean(\"Traits.Health.Animated death.Enabled\", true);\r\n        cache_healthTraitRespawnDelay = config.getString(\"Traits.Health.Respawn.Delay\", \"10s\");\r\n        cache_healthTraitBlockDrops = config.getBoolean(\"Traits.Health.Block drops\", false);\r\n        cache_engageTimeoutInSeconds = config.getString(\"Commands.Engage.Timeout\", \"150s\");\r\n        cache_createWorldSymbols = config.getBoolean(\"Commands.CreateWorld.Allow symbols in names\", false);\r\n        cache_createWorldWeirdPaths = config.getBoolean(\"Commands.CreateWorld.Allow weird paths\", false);\r\n        cache_allowServerStop = config.getBoolean(\"Commands.Restart.Allow server stop\", false);\r\n        cache_allowServerRestart = config.getBoolean(\"Commands.Restart.Allow server restart\", true);\r\n        cache_limitPath = config.getString(\"Commands.Yaml.Limit path\", \"none\");\r\n        cache_chatMultipleTargetsFormat = config.getString(\"Commands.Chat.Options.Multiple targets format\", \"%target%, %target%, %target%, and others\");\r\n        cache_chatBystandersRange = config.getDouble(\"Commands.Chat.Options.Range for bystanders\", 5.0);\r\n        cache_chatNoTargetFormat = config.getString(\"Commands.Chat.Formats.No target\", \"[<[talker].name>]: <[message]>\");\r\n        cache_chatToTargetFormat = config.getString(\"Commands.Chat.Formats.To target\", \"[<[talker].name>] -> You: <[message]>\");\r\n        cache_chatWithTargetToBystandersFormat = config.getString(\"Commands.Chat.Formats.With target to bystanders\", \"[<[talker].name>] -> <[target].name>: <[message]>\");\r\n        cache_chatWithTargetsToBystandersFormat = config.getString(\"Commands.Chat.Formats.With targets to bystanders\", \"[<[talker].name>] -> [<[targets]>]: <[message]>\");\r\n        cache_chatAsynchronous = config.getBoolean(\"Triggers.Chat.Use asynchronous event\", false);\r\n        cache_chatToNpcFormat = config.getString(\"Triggers.Chat.Formats.Player to NPC\", \"You -> <npc.nickname>: <text>\");\r\n        cache_chatToNpcOverheardFormat = config.getString(\"Triggers.Chat.Formats.Player to NPC overheard\", \"<player.name> -> <npc.nickname>: <text>\");\r\n        cache_chatToNpcOverhearingRange = config.getDouble(\"Triggers.Chat.Overhearing range\", 4);\r\n        cache_chatMustSeeNPC = config.getBoolean(\"Triggers.Chat.Prerequisites.Must be able to see NPC\", true);\r\n        cache_chatMustLookAtNPC = config.getBoolean(\"Triggers.Chat.Prerequisites.Must be looking in direction of NPC\", true);\r\n        cache_chatGloballyIfFailedChatTriggers = config.getBoolean(\"Triggers.Chat.Appears globally.If triggers failed\", false);\r\n        cache_chatGloballyIfNoChatTriggers = config.getBoolean(\"Triggers.Chat.Appears globally.If triggers missing\", true);\r\n        cache_chatGloballyIfUninteractable = config.getBoolean(\"Triggers.Chat.Appears globally.If NPC uninteractable\", true);\r\n        cache_worldScriptChatEventAsynchronous = config.getBoolean(\"Scripts.World.Events.On player chats.Use asynchronous event\", false);\r\n        cache_worldScriptTimeEventFrequency = DurationTag.valueOf(config.getString(\"Scripts.World.Events.On time changes.Frequency of check\", \"250t\"), CoreUtilities.basicContext);\r\n        cache_blockTagsMaxBlocks = config.getInt(\"Tags.Block tags.Max blocks\", 1000000);\r\n        cache_chatHistoryMaxMessages = config.getInt(\"Tags.Chat history.Max messages\", 10);\r\n        cache_packetInterception = config.getBoolean(\"Packets.Interception\", true);\r\n        cache_packetInterceptAutoInit = config.getBoolean(\"Packets.Auto init\", false);\r\n        cache_commandScriptAutoInit = config.getBoolean(\"Scripts.Command.Auto init\", false);\r\n        cache_legacySpigotNamesSupport = config.getBoolean(\"Scripts.Support legacy Spigot names\", true);\r\n        PlayerFlagHandler.cacheTimeoutSeconds = config.getLong(\"Saves.Offline player cache timeout\", 300);\r\n        PlayerFlagHandler.asyncPreload = config.getBoolean(\"Saves.Load async on login\", true);\r\n        PlayerFlagHandler.saveOnlyWhenWorldSaveOn = config.getBoolean(\"Saves.Only save if world save is on\", false);\r\n        RemoveCommand.alwaysWarnOnMassRemove = config.getBoolean(\"Commands.Remove.Always warn on mass delete\", false);\r\n        ConfigurationSection colorSection = config.getConfigurationSection(\"Colors\");\r\n        if (colorSection != null) {\r\n            CustomColorTagBase.customColors.clear();\r\n            CustomColorTagBase.defaultColor = null;\r\n            for (String key : colorSection.getKeys(false)) {\r\n                CustomColorTagBase.customColorsRaw.put(CoreUtilities.toLowerCase(key), colorSection.getString(key));\r\n            }\r\n            CustomColorTagBase.defaultColorRaw = CustomColorTagBase.customColorsRaw.getOrDefault(\"default\", CustomColorTagBase.defaultColorRaw);\r\n        }\r\n    }\r\n\r\n    public static boolean skipChunkFlagCleaning = false;\r\n\r\n    public static boolean nullifySkullSkinIds = false;\r\n\r\n    public static boolean allowAsyncPassThrough = false;\r\n\r\n    public static float worldPlayerDataSaveDelay = 10;\r\n\r\n    public static long worldPlayerDataMaxCacheTicks = 20 * 60 * 60;\r\n\r\n    public static boolean cache_overrideHelp,\r\n            cache_showExHelp, cache_showExDebug, cache_canRecordStats,\r\n            cache_defaultDebugMode, cache_healthTraitEnabledByDefault, cache_healthTraitAnimatedDeathEnabled,\r\n            cache_healthTraitRespawnEnabled, cache_allowServerStop, cache_allowServerRestart,\r\n            cache_healthTraitBlockDrops, cache_chatAsynchronous, cache_chatMustSeeNPC, cache_chatMustLookAtNPC,\r\n            cache_chatGloballyIfFailedChatTriggers, cache_chatGloballyIfNoChatTriggers,\r\n            cache_chatGloballyIfUninteractable, cache_worldScriptChatEventAsynchronous,\r\n            cache_packetInterception, cache_createWorldSymbols, cache_createWorldWeirdPaths,\r\n            cache_commandScriptAutoInit, cache_packetInterceptAutoInit, cache_warnOnAsyncPackets, cache_legacySpigotNamesSupport;\r\n\r\n    public static volatile boolean cache_useDefaultScriptPath;\r\n    public static volatile String cache_getAlternateScriptPath;\r\n\r\n    public static String cache_healthTraitRespawnDelay,\r\n            cache_engageTimeoutInSeconds, cache_chatMultipleTargetsFormat, cache_chatNoTargetFormat,\r\n            cache_chatToTargetFormat, cache_chatWithTargetToBystandersFormat, cache_chatWithTargetsToBystandersFormat,\r\n            cache_chatToNpcFormat, cache_chatToNpcOverheardFormat, cache_interactQueueSpeed, cache_limitPath;\r\n\r\n    public static int cache_blockTagsMaxBlocks, cache_chatHistoryMaxMessages;\r\n\r\n    public static double cache_chatBystandersRange, cache_chatToNpcOverhearingRange;\r\n\r\n    public static DurationTag cache_worldScriptTimeEventFrequency;\r\n\r\n    public static boolean useDefaultScriptPath() {\r\n        return cache_useDefaultScriptPath;\r\n    }\r\n\r\n    public static String getAlternateScriptPath() {\r\n        return cache_getAlternateScriptPath;\r\n    }\r\n\r\n    public static boolean overrideHelp() {\r\n        return cache_overrideHelp;\r\n    }\r\n\r\n    public static boolean showExHelp() {\r\n        return cache_showExHelp;\r\n    }\r\n\r\n    public static boolean showExDebug() {\r\n        return cache_showExDebug;\r\n    }\r\n\r\n    public static boolean canRecordStats() {\r\n        return cache_canRecordStats;\r\n    }\r\n\r\n    public static String interactQueueSpeed() {\r\n        return cache_interactQueueSpeed;\r\n    }\r\n\r\n    public static boolean healthTraitEnabledByDefault() {\r\n        return cache_healthTraitEnabledByDefault;\r\n    }\r\n\r\n    public static boolean healthTraitBlockDrops() {\r\n        return cache_healthTraitBlockDrops;\r\n    }\r\n\r\n    public static boolean healthTraitRespawnEnabled() {\r\n        return cache_healthTraitRespawnEnabled;\r\n    }\r\n\r\n    public static boolean healthTraitAnimatedDeathEnabled() {\r\n        return cache_healthTraitAnimatedDeathEnabled;\r\n    }\r\n\r\n    public static String healthTraitRespawnDelay() {\r\n        return cache_healthTraitRespawnDelay;\r\n    }\r\n\r\n    /**\r\n     * Whether a certain trigger is enabled by default or not\r\n    */\r\n    public static boolean triggerEnabled(String triggerName) {\r\n        return Denizen.getInstance().getConfig()\r\n                .getBoolean(\"Triggers.\" + String.valueOf(triggerName.charAt(0)).toUpperCase()\r\n                        + CoreUtilities.toLowerCase(triggerName.substring(1)) + \".Enabled\", true);\r\n    }\r\n\r\n    /**\r\n     * Default duration of cooldown set to Denizens for when a trigger is\r\n     * triggered. Not all triggers may use this, it is optional!\r\n    */\r\n    public static double triggerDefaultCooldown(String triggerName) {\r\n        return DurationTag.valueOf(Denizen.getInstance().getConfig()\r\n                .getString(\"Triggers.\" + String.valueOf(triggerName.charAt(0)).toUpperCase()\r\n                        + CoreUtilities.toLowerCase(triggerName.substring(1)) + \".Cooldown\", \"5s\"), CoreUtilities.basicContext).getSeconds();\r\n    }\r\n\r\n    /*\r\n     * This set of nodes defines ranges for different types of\r\n     * interact-script triggers. Not all triggers use a range,\r\n     * as it may not be applicable to the trigger.\r\n    */\r\n\r\n    public static double triggerDefaultRange(String triggerName) {\r\n        return Denizen.getInstance().getConfig()\r\n                .getDouble(\"Triggers.\" + String.valueOf(triggerName.charAt(0)).toUpperCase()\r\n                        + CoreUtilities.toLowerCase(triggerName.substring(1)) + \".Range\", -1);\r\n    }\r\n\r\n    /**\r\n     * Default engage timeout. When NPCs are set to ENGAGE, this is\r\n     * the default timeout that they will auto-DISENGAGE if not otherwise\r\n     * specified. (Default, 150 seconds)\r\n    */\r\n    public static String engageTimeoutInSeconds() {\r\n        return cache_engageTimeoutInSeconds;\r\n    }\r\n\r\n    public static boolean allowStupids() {\r\n        return allowStupid1() && allowStupid2() && allowStupid3();\r\n    }\r\n\r\n    public static boolean allowStupid1() {\r\n        // Unrestricted file access can cause a lot of problems in itself, and encourage a style of script\r\n        // writing that is extremely poor and can be done in much more effective and clean ways.\r\n        // If you believe you need to make use of this config option... strongly consider any possible alternatives.\r\n        //\r\n        // Generally, be aware that if you are not completely clear on exactly how these settings work internally in Java,\r\n        // and what changing them can do, ... you just should not use them.\r\n        // This is for very highly experienced users only.\r\n        return Denizen.getInstance().getConfig()\r\n                .getBoolean(\"Commands.General.Allow unrestricted file access\", false);\r\n    }\r\n\r\n    public static boolean allowStupid2() {\r\n        return Denizen.getInstance().getConfig()\r\n                .getBoolean(\"Commands.General.Confirm allowing unrestricted file access\", false);\r\n    }\r\n\r\n    public static boolean allowStupid3() {\r\n        return Denizen.getInstance().getConfig()\r\n                .getBoolean(\"Commands.General.Unrestricted file access is very bad and dangerous are you sure you want that\", false);\r\n    }\r\n\r\n    public static boolean allowStupidx() {\r\n        return Denizen.getInstance().getConfig()\r\n                .getBoolean(\"Commands.General.Don't change this unrestricted file access option though\", false);\r\n    }\r\n\r\n    public static boolean allowServerStop() {\r\n        return cache_allowServerStop;\r\n    }\r\n\r\n    public static boolean allowServerRestart() {\r\n        return cache_allowServerRestart;\r\n    }\r\n\r\n    public static String fileLimitPath() {\r\n        return cache_limitPath;\r\n    }\r\n\r\n    public static String chatMultipleTargetsFormat() {\r\n        return cache_chatMultipleTargetsFormat;\r\n    }\r\n\r\n    public static double chatBystandersRange() {\r\n        return cache_chatBystandersRange;\r\n    }\r\n\r\n    public static String chatNoTargetFormat() {\r\n        return cache_chatNoTargetFormat;\r\n    }\r\n\r\n    public static String chatToTargetFormat() {\r\n        return cache_chatToTargetFormat;\r\n    }\r\n\r\n    public static String chatWithTargetToBystandersFormat() {\r\n        return cache_chatWithTargetToBystandersFormat;\r\n    }\r\n\r\n    public static String chatWithTargetsToBystandersFormat() {\r\n        return cache_chatWithTargetsToBystandersFormat;\r\n    }\r\n\r\n    /**\r\n     * Whether the Chat Trigger should use an asynchronous Bukkit\r\n     * event or not\r\n    */\r\n    public static boolean chatAsynchronous() {\r\n        return cache_chatAsynchronous;\r\n    }\r\n\r\n    /*\r\n     * The formats in which Chat Trigger input from players appears to\r\n     * themselves and to players who can overhear them\r\n    */\r\n\r\n    public static String chatToNpcFormat() {\r\n        return cache_chatToNpcFormat;\r\n    }\r\n\r\n    public static String chatToNpcOverheardFormat() {\r\n        return cache_chatToNpcOverheardFormat;\r\n    }\r\n\r\n    /**\r\n     * The distance from which a player chatting to an NPC can be overheard\r\n     * by other players\r\n    */\r\n    public static double chatToNpcOverhearingRange() {\r\n        return cache_chatToNpcOverhearingRange;\r\n    }\r\n\r\n    /*\r\n     * Prerequisites for triggering a Chat Trigger\r\n    */\r\n\r\n    public static boolean chatMustSeeNPC() {\r\n        return cache_chatMustSeeNPC;\r\n    }\r\n\r\n    public static boolean chatMustLookAtNPC() {\r\n        return cache_chatMustLookAtNPC;\r\n    }\r\n\r\n    /*\r\n     * Circumstances under which a player's Chat Trigger input should\r\n     * appear in the global chat\r\n    */\r\n\r\n    public static boolean chatGloballyIfFailedChatTriggers() {\r\n        return cache_chatGloballyIfFailedChatTriggers;\r\n    }\r\n\r\n    public static boolean chatGloballyIfNoChatTriggers() {\r\n        return cache_chatGloballyIfNoChatTriggers;\r\n    }\r\n\r\n    public static boolean chatGloballyIfUninteractable() {\r\n        return cache_chatGloballyIfUninteractable;\r\n    }\r\n\r\n    /**\r\n     * Whether the \"on player chats\" world event should use an\r\n     * asynchronous Bukkit event or not\r\n    */\r\n    public static boolean worldScriptChatEventAsynchronous() {\r\n        return cache_worldScriptChatEventAsynchronous;\r\n    }\r\n\r\n    /**\r\n     * The frequency with which the \"on time changes\" world script\r\n     * event will be checked\r\n    */\r\n    public static DurationTag worldScriptTimeEventFrequency() {\r\n        return cache_worldScriptTimeEventFrequency;\r\n    }\r\n\r\n    public static int blockTagsMaxBlocks() {\r\n        return cache_blockTagsMaxBlocks;\r\n    }\r\n\r\n    public static int chatHistoryMaxMessages() {\r\n        return cache_chatHistoryMaxMessages;\r\n    }\r\n\r\n    public static boolean packetInterception() {\r\n        return cache_packetInterception;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/TextWidthHelper.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizencore.utilities.AsciiMatcher;\r\nimport org.bukkit.ChatColor;\r\n\r\nimport java.util.HashMap;\r\n\r\npublic class TextWidthHelper {\r\n\r\n    public static int[] asciiWidthMap = new int[128];\r\n    public static HashMap<Character, Integer> characterWidthMap = new HashMap<>();\r\n\r\n    public static void setWidth(int width, String chars) {\r\n        for (char c : chars.toCharArray()) {\r\n            if (c < 128) {\r\n                asciiWidthMap[c] = width;\r\n            }\r\n            else {\r\n                characterWidthMap.put(c, width);\r\n            }\r\n        }\r\n    }\r\n\r\n    static {\r\n        for (int i = 0; i < 128; i++) {\r\n            asciiWidthMap[i] = 6;\r\n        }\r\n        // Covers all symbols in the default ascii.png texture file\r\n        setWidth(2, \"!,.:;|i'\");\r\n        setWidth(3, \"l`\");\r\n        setWidth(4, \" (){}[]tI\\\"*\");\r\n        setWidth(5, \"<>fkªº▌⌡°ⁿ²\");\r\n        // all other characters are length=6\r\n        setWidth(7, \"@~«»≡≈√\");\r\n        setWidth(8, \"░╢╖╣║╗╝╜∅⌠\");\r\n        setWidth(9, \"▒▓└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┌█▄▐▀\");\r\n    }\r\n\r\n    public static int getWidth(char c) {\r\n        if (c < 128) {\r\n            return asciiWidthMap[c];\r\n        }\r\n        return characterWidthMap.getOrDefault(c, 6);\r\n    }\r\n\r\n    public static AsciiMatcher formatCharCodeMatcher = new AsciiMatcher(\"klmnoKLMNO\");\r\n\r\n    public static int getWidth(String str) {\r\n        return getWidth(false, str);\r\n    }\r\n\r\n    public static int getWidth(boolean wasBold, String str) {\r\n        int maxWidth = 0;\r\n        int total = 0;\r\n        boolean bold = wasBold;\r\n        char[] rawChars = str.toCharArray();\r\n        for (int i = 0; i < rawChars.length; i++) {\r\n            char c = rawChars[i];\r\n            if (c == ChatColor.COLOR_CHAR && (i + 1) < rawChars.length) {\r\n                char c2 = rawChars[i + 1];\r\n                if (c2 == '[') {\r\n                    while (i < rawChars.length && rawChars[i] != ']') {\r\n                        i++;\r\n                    }\r\n                    continue;\r\n                }\r\n                else if (c2 == 'l' || c2 == 'L') {\r\n                    bold = true;\r\n                }\r\n                else if (!formatCharCodeMatcher.isMatch(c2)) {\r\n                    bold = false;\r\n                }\r\n                i++;\r\n                continue;\r\n            }\r\n            total += getWidth(c) + (bold ? 1 : 0);\r\n            if (c == '\\n') {\r\n                if (total > maxWidth) {\r\n                    maxWidth = total;\r\n                }\r\n                total = 0;\r\n            }\r\n        }\r\n        return Math.max(total, maxWidth);\r\n    }\r\n\r\n    public static boolean isBold(boolean wasBold, String str) {\r\n        boolean bold = wasBold;\r\n        char[] rawChars = str.toCharArray();\r\n        for (int i = 0; i < rawChars.length; i++) {\r\n            char c = rawChars[i];\r\n            if (c == ChatColor.COLOR_CHAR && (i + 1) < rawChars.length) {\r\n                char c2 = rawChars[i + 1];\r\n                if (c2 == 'l' || c2 == 'L') {\r\n                    bold = true;\r\n                }\r\n                else if (!formatCharCodeMatcher.isMatch(c2)) {\r\n                    bold = false;\r\n                }\r\n            }\r\n        }\r\n        return bold;\r\n    }\r\n\r\n    public static String splitLines(String str, int width) {\r\n        if (width < 8) {\r\n            return str;\r\n        }\r\n        StringBuilder output = new StringBuilder(str.length() * 2);\r\n        int lineStart = 0;\r\n        boolean bold = false;\r\n        mainloop:\r\n        for (int i = 0; i < str.length(); i++) {\r\n            char c = str.charAt(i);\r\n            if (c == ChatColor.COLOR_CHAR) {\r\n                i++;\r\n                continue;\r\n            }\r\n            if (c == '\\n') {\r\n                String lastLine = str.substring(lineStart, i + 1);\r\n                bold = isBold(bold, lastLine);\r\n                output.append(lastLine);\r\n                lineStart = i + 1;\r\n                continue;\r\n            }\r\n            if (getWidth(bold, str.substring(lineStart, i)) > width) {\r\n                for (int x = i - 1; x > lineStart; x--) {\r\n                    char xc = str.charAt(x);\r\n                    if (xc == ' ') {\r\n                        String lastLine = str.substring(lineStart, x);\r\n                        bold = isBold(bold, lastLine);\r\n                        output.append(lastLine).append(\"\\n\");\r\n                        lineStart = x + 1;\r\n                        i = x;\r\n                        continue mainloop;\r\n                    }\r\n                }\r\n                String lastLine = str.substring(lineStart, i);\r\n                bold = isBold(bold, lastLine);\r\n                output.append(lastLine).append(\"\\n\");\r\n                lineStart = i;\r\n            }\r\n        }\r\n        output.append(str, lineStart, str.length());\r\n        return output.toString();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/Utilities.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.nms.interfaces.BlockHelper;\r\nimport com.denizenscript.denizen.npc.traits.TriggerTrait;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.objects.properties.material.MaterialDirectional;\r\nimport com.denizenscript.denizen.scripts.commands.world.SignCommand;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.AsciiMatcher;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.block.Sign;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.*;\r\nimport org.bukkit.util.BoundingBox;\r\nimport org.bukkit.util.OldEnum;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.io.File;\r\nimport java.io.IOException;\r\nimport java.net.URI;\r\nimport java.net.URISyntaxException;\r\nimport java.util.*;\r\n\r\n/**\r\n * This class has utility methods for various tasks.\r\n */\r\npublic class Utilities {\r\n\r\n    public static NamespacedKey parseNamespacedKey(String input) {\r\n        input = CoreUtilities.toLowerCase(input);\r\n        int colonIndex = input.indexOf(':');\r\n        if (colonIndex != -1) {\r\n            return new NamespacedKey(input.substring(0, colonIndex), cleanseNamespaceID(input.substring(colonIndex + 1)));\r\n        }\r\n        else {\r\n            return NamespacedKey.minecraft(cleanseNamespaceID(input));\r\n        }\r\n    }\r\n\r\n    public static String namespacedKeyToString(NamespacedKey key) {\r\n        return key.getNamespace().equals(NamespacedKey.MINECRAFT) ? key.getKey() : key.toString();\r\n    }\r\n\r\n    public static ListTag registryKeys(Registry<?> registry) {\r\n        return new ListTag(registry.stream().toList(), keyed -> new ElementTag(namespacedKeyToString(keyed.getKey()), true));\r\n    }\r\n\r\n    public static boolean matchesNamespacedKeyButCaseInsensitive(String input) {\r\n        int colonIndex = input.indexOf(':');\r\n        if (colonIndex == -1) {\r\n            return namespaceMatcherButCaseInsensitive.isOnlyMatches(input);\r\n        }\r\n        return namespaceMatcherButCaseInsensitive.isOnlyMatches(input.substring(0, colonIndex)) && namespaceMatcherButCaseInsensitive.isOnlyMatches(input.substring(colonIndex + 1));\r\n    }\r\n\r\n    public static AsciiMatcher namespaceMatcherButCaseInsensitive = new AsciiMatcher(AsciiMatcher.LETTERS_LOWER + AsciiMatcher.LETTERS_UPPER + \".-_/\" + AsciiMatcher.DIGITS);\r\n\r\n    public static boolean matchesNamespacedKey(String input) {\r\n        int colonIndex = input.indexOf(':');\r\n        if (colonIndex == -1) {\r\n            return namespaceMatcher.isOnlyMatches(input);\r\n        }\r\n        return namespaceMatcher.isOnlyMatches(input.substring(0, colonIndex)) && namespaceMatcher.isOnlyMatches(input.substring(colonIndex + 1));\r\n    }\r\n\r\n    public static AsciiMatcher namespaceMatcher = new AsciiMatcher(AsciiMatcher.LETTERS_LOWER + \".-_/\" + AsciiMatcher.DIGITS);\r\n\r\n    public static String cleanseNamespaceID(String input) {\r\n        return namespaceMatcher.trimToMatches(CoreUtilities.toLowerCase(input));\r\n    }\r\n\r\n    public static String getRecipeType(Recipe recipe) {\r\n        if (recipe == null) {\r\n            return null;\r\n        }\r\n        if (recipe instanceof ShapedRecipe) {\r\n            return \"shaped\";\r\n        }\r\n        else if (recipe instanceof ShapelessRecipe) {\r\n            return \"shapeless\";\r\n        }\r\n        else if (recipe instanceof CookingRecipe) {\r\n            if (recipe instanceof FurnaceRecipe) {\r\n                return \"furnace\";\r\n            }\r\n            else if (recipe instanceof BlastingRecipe) {\r\n                return \"blasting\";\r\n            }\r\n            else if (recipe instanceof CampfireRecipe) {\r\n                return \"campfire\";\r\n            }\r\n            else if (recipe instanceof SmokingRecipe) {\r\n                return \"smoking\";\r\n            }\r\n        }\r\n        else if (recipe instanceof StonecuttingRecipe) {\r\n            return \"stonecutting\";\r\n        }\r\n        else if (recipe instanceof SmithingRecipe) {\r\n            return \"smithing\";\r\n        }\r\n        Debug.echoError(\"Failed to determine recipe type for \" + recipe.getClass().getName() + \": \" + recipe);\r\n        return null;\r\n    }\r\n\r\n    public static boolean isRecipeOfType(Recipe recipe, String type) {\r\n        return type == null || (\r\n                (type.equals(\"crafting\") && (recipe instanceof ShapedRecipe || recipe instanceof ShapelessRecipe)) ||\r\n                        (type.equals(\"furnace\") && recipe instanceof FurnaceRecipe) ||\r\n                        (type.equals(\"cooking\") && recipe instanceof CookingRecipe) ||\r\n                        (type.equals(\"blasting\") && recipe instanceof BlastingRecipe) ||\r\n                        (type.equals(\"campfire\") && recipe instanceof CampfireRecipe) ||\r\n                        (type.equals(\"shaped\") && recipe instanceof ShapedRecipe) ||\r\n                        (type.equals(\"shapeless\") && recipe instanceof ShapelessRecipe) ||\r\n                        (type.equals(\"smoking\") && recipe instanceof SmokingRecipe) ||\r\n                        (type.equals(\"stonecutting\") && recipe instanceof StonecuttingRecipe) ||\r\n                        (type.equals(\"smithing\") && recipe instanceof SmithingRecipe));\r\n    }\r\n\r\n    public static boolean canReadFile(File f) {\r\n        if (Settings.allowStupids()) {\r\n            return true;\r\n        }\r\n        try {\r\n            String lown = CoreUtilities.toLowerCase(f.getCanonicalPath()).replace('\\\\', '/');\r\n            if (lown.endsWith(\"/\")) {\r\n                lown = lown.substring(0, lown.length() - 1);\r\n            }\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.log(\"Checking file canRead: \" + lown);\r\n            }\r\n            if (lown.contains(\"denizen/secrets.secret\")) {\r\n                return false;\r\n            }\r\n            int dot = lown.lastIndexOf('.');\r\n            if (dot != -1 && lown.substring(dot + 1).equals(\"secret\")) {\r\n                return false;\r\n            }\r\n            if (!CoreConfiguration.allowStrangeFileSaves &&\r\n                    !f.getCanonicalPath().startsWith(new File(\".\").getCanonicalPath())) {\r\n                return false;\r\n            }\r\n            if (!CoreUtilities.equalsIgnoreCase(Settings.fileLimitPath(), \"none\")\r\n                    && !f.getCanonicalPath().startsWith(new File(\"./\" + Settings.fileLimitPath()).getCanonicalPath())) {\r\n                return false;\r\n            }\r\n            return true;\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n            return false;\r\n        }\r\n    }\r\n\r\n    /** File extensions to just outright forbid generating from scripts, to reduce potential routes for abuse. Most importantly, forbid creation of files that the minecraft server will execute. */\r\n    public static HashSet<String> FORBIDDEN_EXTENSIONS = new HashSet<>(Arrays.asList(\r\n            \"jar\", \"java\", // Java related files\r\n            \"sh\", \"bash\", // Linux scripts\r\n            \"bat\", \"ps1\", \"vb\", \"vbs\", \"vbscript\", \"batch\", \"cmd\", \"com\", \"msc\", \"sct\", \"ws\", \"wsf\", // Windows scripts\r\n            \"exe\", \"scr\", \"msi\", \"dll\", \"bin\", // Windows executables\r\n            \"lnk\", \"reg\", \"rgs\", // other weird Windows files\r\n            \"secret\" // Protected by Denizen\r\n    ));\r\n\r\n    public static boolean isFileCanonicalStringSafeToWrite(String lown) {\r\n        if (lown.contains(\"denizen/config.yml\")) {\r\n            return false;\r\n        }\r\n        if (lown.contains(\"denizen/secrets.secret\")) {\r\n            return false;\r\n        }\r\n        if (lown.contains(\"denizen/scripts/\")) {\r\n            return false;\r\n        }\r\n        if (lown.endsWith(\"plugins/\")) {\r\n            return false;\r\n        }\r\n        int dot = lown.lastIndexOf('.');\r\n        if (dot != -1 && FORBIDDEN_EXTENSIONS.contains(lown.substring(dot + 1))) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    public static boolean canWriteToFile(File f) {\r\n        if (Settings.allowStupids()) {\r\n            return true;\r\n        }\r\n        try {\r\n            String lown = CoreUtilities.toLowerCase(f.getCanonicalPath()).replace('\\\\', '/');\r\n            if (lown.endsWith(\"/\")) {\r\n                lown = lown.substring(0, lown.length() - 1);\r\n            }\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.log(\"Checking file canWrite: \" + lown);\r\n            }\r\n            if (!CoreConfiguration.allowStrangeFileSaves &&\r\n                    !f.getCanonicalPath().startsWith(new File(\".\").getCanonicalPath())) {\r\n                return false;\r\n            }\r\n            if (!CoreUtilities.toLowerCase(Settings.fileLimitPath()).equals(\"none\")\r\n                    && !f.getCanonicalPath().startsWith(new File(\"./\" + Settings.fileLimitPath()).getCanonicalPath())) {\r\n                return false;\r\n            }\r\n            return isFileCanonicalStringSafeToWrite(lown) && isFileCanonicalStringSafeToWrite(lown + \"/\");\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n            return false;\r\n        }\r\n    }\r\n\r\n    public static BlockFace faceFor(Vector vec) {\r\n        for (BlockFace face : BlockFace.values()) {\r\n            if (face.getDirection().distanceSquared(vec) < 0.01) { // floating-point safe check\r\n                return face;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    /**\r\n     * Gets a Location within a range that an entity can walk in.\r\n     *\r\n     * @param location the Location to check with\r\n     * @param range    the range around the Location\r\n     * @return a random Location within range, or null if no Location within range is safe\r\n     */\r\n    public static Location getWalkableLocationNear(Location location, int range) {\r\n        List<Location> locations = new ArrayList<>();\r\n        location = location.getBlock().getLocation();\r\n\r\n        // Loop through each location within the range\r\n        for (double x = -(range); x <= range; x++) {\r\n            for (double y = -(range); y <= range; y++) {\r\n                for (double z = -(range); z <= range; z++) {\r\n                    // Add each block location within range\r\n                    Location loc = location.clone().add(x, y, z);\r\n                    if (checkLocation(location, loc, range) && isWalkable(loc)) {\r\n                        locations.add(loc);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        // No safe Locations found\r\n        if (locations.isEmpty()) {\r\n            return null;\r\n        }\r\n\r\n        // Return a random Location from the list\r\n        return locations.get(CoreUtilities.getRandom().nextInt(locations.size()));\r\n    }\r\n\r\n    public static boolean isWalkable(Location location) {\r\n        if (location.getBlockY() < 1 || location.getBlockY() > 254) {\r\n            return false;\r\n        }\r\n        BlockHelper blockHelper = NMSHandler.blockHelper;\r\n        return location.clone().subtract(0, 1, 0).getBlock().getType().isSolid()\r\n                && !location.getBlock().getType().isSolid()\r\n                && !location.clone().add(0, 1, 0).getBlock().getType().isSolid();\r\n    }\r\n\r\n    /**\r\n     * @param player the player doing the talking\r\n     * @param npc    the npc being talked to\r\n     * @param range  the range, in blocks, that 'bystanders' will hear he chat\r\n     */\r\n    public static void talkToNPC(String message, PlayerTag player, NPCTag npc, double range, ScriptTag script) {\r\n        String replacer = String.valueOf((char) 0x04);\r\n        // Get formats from Settings, and fill in <TEXT>\r\n        String talkFormat = Settings.chatToNpcFormat()\r\n                .replaceAll(\"(?i)<TEXT>\", replacer);\r\n        String bystanderFormat = Settings.chatToNpcOverheardFormat()\r\n                .replaceAll(\"(?i)<TEXT>\", replacer);\r\n\r\n        // Fill in tags\r\n        talkFormat = TagManager.tag(talkFormat, new BukkitTagContext(player, npc, script)).replace(replacer, message);\r\n        bystanderFormat = TagManager.tag(bystanderFormat, new BukkitTagContext(player, npc, script)).replace(replacer, message);\r\n\r\n        // Send message to player\r\n        player.getPlayerEntity().sendMessage(talkFormat);\r\n\r\n        // Send message to bystanders\r\n        for (Player target : Bukkit.getOnlinePlayers()) {\r\n            if (target != player.getPlayerEntity()) {\r\n                if (target.getWorld().equals(player.getPlayerEntity().getWorld())\r\n                        && target.getLocation().distance(player.getPlayerEntity().getLocation()) <= range) {\r\n                    target.sendMessage(bystanderFormat);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Finds the closest NPC to a particular location.\r\n     *\r\n     * @param location The location to find the closest NPC to.\r\n     * @param range    The maximum range to look for the NPC.\r\n     * @return The closest NPC to the location, or null if no NPC was found\r\n     * within the range specified.\r\n     */\r\n    public static NPCTag getClosestNPC_ChatTrigger(Location location, int range) {\r\n        NPC closestNPC = null;\r\n        double closestDistance = Math.pow(range, 2);\r\n        for (NPC npc : CitizensAPI.getNPCRegistry()) {\r\n            if (!npc.isSpawned()) {\r\n                continue;\r\n            }\r\n            Location loc = npc.getStoredLocation();\r\n            if (npc.hasTrait(TriggerTrait.class) && npc.getOrAddTrait(TriggerTrait.class).hasTrigger(\"CHAT\") &&\r\n                    loc.getWorld().equals(location.getWorld())\r\n                    && loc.distanceSquared(location) < closestDistance) {\r\n                closestNPC = npc;\r\n                closestDistance = npc.getStoredLocation().distanceSquared(location);\r\n            }\r\n        }\r\n        if (closestNPC == null) {\r\n            return null;\r\n        }\r\n        return new NPCTag(closestNPC);\r\n    }\r\n\r\n    public static boolean checkLocationWithBoundingBox(Location baseLocation, Entity entity, double theLeeway) {\r\n        if (!checkLocation(baseLocation, entity.getLocation(), theLeeway + 16)) {\r\n            return false;\r\n        }\r\n        BoundingBox box = entity.getBoundingBox();\r\n        double x = Math.max(box.getMinX(), Math.min(baseLocation.getX(), box.getMaxX()));\r\n        double y = Math.max(box.getMinY(), Math.min(baseLocation.getY(), box.getMaxY()));\r\n        double z = Math.max(box.getMinZ(), Math.min(baseLocation.getZ(), box.getMaxZ()));\r\n        double xOff = x - baseLocation.getX();\r\n        double yOff = y - baseLocation.getY();\r\n        double zOff = z - baseLocation.getZ();\r\n        return xOff * xOff + yOff * yOff + zOff * zOff < theLeeway * theLeeway;\r\n    }\r\n\r\n    public static boolean checkLocation(LivingEntity entity, Location theLocation, double theLeeway) {\r\n        return checkLocation(entity.getLocation(), theLocation, theLeeway);\r\n    }\r\n\r\n    public static boolean checkLocation(Location baseLocation, Location theLocation, double theLeeway) {\r\n        if (baseLocation.getWorld() != theLocation.getWorld()) {\r\n            return false;\r\n        }\r\n        return baseLocation.distanceSquared(theLocation) < theLeeway * theLeeway;\r\n    }\r\n\r\n    public static void setSignLines(Sign sign, String[] lines) {\r\n        for (int n = 0; n < 4; n++) {\r\n            PaperAPITools.instance.setSignLine(sign, n, lines[n]);\r\n        }\r\n        sign.update();\r\n    }\r\n\r\n    public static BlockFace chooseSignRotation(Block signBlock) {\r\n        BlockFace[] blockFaces = {BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH};\r\n        for (BlockFace blockFace : blockFaces) {\r\n            Block block = signBlock.getRelative(blockFace);\r\n            Material material = block.getType();\r\n            if (material != Material.AIR && !SignCommand.isAnySign(material)) {\r\n                return blockFace.getOppositeFace();\r\n            }\r\n        }\r\n        return BlockFace.SOUTH;\r\n    }\r\n\r\n    public static BlockFace chooseSignRotation(String direction) {\r\n        String dirUpper = direction.toUpperCase();\r\n        for (BlockFace blockFace : MaterialDirectional.rotatableValidFaces) {\r\n            if (blockFace.name().equals(dirUpper)) {\r\n                return blockFace;\r\n            }\r\n        }\r\n        switch (dirUpper.charAt(0)) {\r\n            case 'N': return BlockFace.NORTH;\r\n            case 'S': return BlockFace.SOUTH;\r\n            case 'E': return BlockFace.EAST;\r\n            case 'W': return BlockFace.WEST;\r\n        }\r\n        return BlockFace.SOUTH;\r\n    }\r\n\r\n    public static void setSignRotation(BlockState signState, String direction) {\r\n        BlockFace bf = chooseSignRotation(direction);\r\n        MaterialTag signMaterial = new MaterialTag(signState.getBlock());\r\n        MaterialDirectional.getFrom(signMaterial).setFacing(bf);\r\n        signState.getBlock().setBlockData(signMaterial.getModernData());\r\n    }\r\n\r\n    /**\r\n     * Extract a file from a zip or jar.\r\n     *\r\n     * @param jarFile  The zip/jar file to use\r\n     * @param fileName Which file to extract\r\n     * @param destDir  Where to extract it to\r\n     */\r\n    public static void extractFile(File jarFile, String fileName, String destDir) {\r\n        java.util.jar.JarFile jar = null;\r\n        try {\r\n            jar = new java.util.jar.JarFile(jarFile);\r\n            java.util.Enumeration myEnum = jar.entries();\r\n            while (myEnum.hasMoreElements()) {\r\n                java.util.jar.JarEntry file = (java.util.jar.JarEntry) myEnum.nextElement();\r\n                if (CoreUtilities.equalsIgnoreCase(file.getName(), fileName)) {\r\n                    java.io.File f = new java.io.File(destDir + \"/\" + file.getName());\r\n                    if (file.isDirectory()) {\r\n                        continue;\r\n                    }\r\n                    java.io.InputStream is = jar.getInputStream(file);\r\n                    java.io.FileOutputStream fos = new java.io.FileOutputStream(f);\r\n                    while (is.available() > 0) {\r\n                        fos.write(is.read());\r\n                    }\r\n                    fos.close();\r\n                    is.close();\r\n                    return;\r\n                }\r\n            }\r\n            Debug.echoError(fileName + \" not found in the jar!\");\r\n        }\r\n        catch (IOException e) {\r\n            Debug.echoError(e);\r\n\r\n        }\r\n        finally {\r\n            if (jar != null) {\r\n                try {\r\n                    jar.close();\r\n                }\r\n                catch (IOException e) {\r\n                    Debug.echoError(e);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    private final static String colors = \"0123456789abcdefklmnorABCDEFKLMNOR\";\r\n\r\n    public static String generateRandomColors(int count) {\r\n        StringBuilder ret = new StringBuilder();\r\n        for (int i = 0; i < count; i++) {\r\n            ret.append(ChatColor.COLOR_CHAR).append(colors.charAt(CoreUtilities.getRandom().nextInt(colors.length())));\r\n        }\r\n        return ret.toString();\r\n    }\r\n\r\n    public static BukkitScriptEntryData getEntryData(ScriptEntry entry) {\r\n        return (BukkitScriptEntryData) entry.entryData;\r\n    }\r\n\r\n    public static WorldTag entryDefaultWorld(ScriptEntry entry, boolean playerFirst) {\r\n        EntityTag entity = entryDefaultEntity(entry, playerFirst);\r\n        if (entity == null) {\r\n            return new WorldTag(Bukkit.getWorlds().get(0));\r\n        }\r\n        return new WorldTag(entity.getWorld());\r\n    }\r\n\r\n    public static LocationTag entryDefaultLocation(ScriptEntry entry, boolean playerFirst) {\r\n        EntityTag entity = entryDefaultEntity(entry, playerFirst);\r\n        if (entity == null) {\r\n            return null;\r\n        }\r\n        return entity.getLocation();\r\n    }\r\n\r\n    public static List<EntityTag> entryDefaultEntityList(ScriptEntry entry, boolean playerFirst) {\r\n        EntityTag entity = entryDefaultEntity(entry, playerFirst);\r\n        if (entity == null) {\r\n            return null;\r\n        }\r\n        return Collections.singletonList(entity);\r\n    }\r\n\r\n    public static EntityTag entryDefaultEntity(ScriptEntry entry, boolean playerFirst) {\r\n        BukkitScriptEntryData entryData = getEntryData(entry);\r\n        if (playerFirst && entryData.hasPlayer() && entryData.getPlayer().isOnline()) {\r\n            return entryData.getPlayer().getDenizenEntity();\r\n        }\r\n        if (entryData.hasNPC() && entryData.getNPC().isSpawned()) {\r\n            return entryData.getNPC().getDenizenEntity();\r\n        }\r\n        if (entryData.hasPlayer() && entryData.getPlayer().isOnline()) {\r\n            return entryData.getPlayer().getDenizenEntity();\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static boolean entryHasPlayer(ScriptEntry entry) {\r\n        return getEntryData(entry).hasPlayer();\r\n    }\r\n\r\n    public static boolean entryHasNPC(ScriptEntry entry) {\r\n        return getEntryData(entry).hasNPC();\r\n    }\r\n\r\n    public static PlayerTag getEntryPlayer(ScriptEntry entry) {\r\n        return getEntryData(entry).getPlayer();\r\n    }\r\n\r\n    public static NPCTag getEntryNPC(ScriptEntry entry) {\r\n        return getEntryData(entry).getNPC();\r\n    }\r\n\r\n    public static boolean isLocationYSafe(Location loc) {\r\n        return isLocationYSafe(loc.getBlockY(), loc.getWorld());\r\n    }\r\n\r\n    public static boolean isLocationYSafe(double y, World world) {\r\n        if (world == null) {\r\n            return true;\r\n        }\r\n        return y >= world.getMinHeight() && y <= world.getMaxHeight();\r\n    }\r\n\r\n    public static ArrayList<Material> allMaterialsThatMatch(String matcherText) {\r\n        ScriptEvent.MatchHelper matcher = ScriptEvent.createMatcher(matcherText);\r\n        ArrayList<Material> mats = new ArrayList<>();\r\n        for (Material material : Material.values()) {\r\n            if (matcher.doesMatch(material.name()) && !material.name().startsWith(\"LEGACY_\")) {\r\n                mats.add(material);\r\n            }\r\n        }\r\n        return mats;\r\n    }\r\n\r\n    // TODO once 1.21 is the minimum supported version, replace with direct registry-based handling\r\n    public static <T> List<T> listTypesRaw(Class<T> type) {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) && Keyed.class.isAssignableFrom(type)) {\r\n            return (List) Bukkit.getRegistry((Class<? extends Keyed>) type).stream().toList();\r\n        }\r\n        return (List) Arrays.asList(((Class<? extends Enum<?>>) type).getEnumConstants());\r\n    }\r\n\r\n    public static ListTag listTypes(Class<?> type) {\r\n        return new ListTag(listTypesRaw(type), Utilities::enumlikeToElement);\r\n    }\r\n\r\n    public static ListTag listLegacyTypes(Class<? extends Keyed> type) {\r\n        List<?> types = NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21) ? Bukkit.getRegistry(type).stream().toList() : Arrays.asList(type.getEnumConstants());\r\n        return new ListTag(types, Utilities::enumLikeToLegacyElement);\r\n    }\r\n\r\n    public static ElementTag enumlikeToElement(Object val) {\r\n        if (val instanceof Enum) {\r\n            return new ElementTag(((Enum<?>) val).name());\r\n        }\r\n        if (val instanceof Keyed) {\r\n            return new ElementTag(namespacedKeyToString(((Keyed) val).getKey()), true);\r\n        }\r\n        return new ElementTag(val.toString());\r\n    }\r\n\r\n    public static ElementTag enumLikeToLegacyElement(Object val) {\r\n        if (val instanceof Enum<?> enumVal) {\r\n            return new ElementTag(enumVal);\r\n        }\r\n        if (val instanceof OldEnum<?> oldEnumVal) {\r\n            return new ElementTag(oldEnumVal.name(), true);\r\n        }\r\n        throw new UnsupportedOperationException(\"Cannot get legacy name element, value isn't an enum: \" + val);\r\n    }\r\n\r\n    public static <T> T elementToEnumlike(ElementTag element, Class<T> type) {\r\n        return elementToEnumlike(element, type, true);\r\n    }\r\n\r\n    public static <T> T elementToEnumlike(ElementTag element, Class<T> type, boolean showWarning) {\r\n        if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_20)) {\r\n            return element.asEnum(type);\r\n        }\r\n        Registry<?> registry = Bukkit.getRegistry((Class<? extends Keyed>) type);\r\n        if (registry == null) {\r\n            return element.asEnum(type);\r\n        }\r\n        T value = (T) registry.get(parseNamespacedKey(element.asString()));\r\n        if (value != null || !Settings.cache_legacySpigotNamesSupport) {\r\n            return value;\r\n        }\r\n        T enumValue = element.asEnum(type);\r\n        if (enumValue != null) {\r\n            if (showWarning) {\r\n                BukkitImplDeprecations.oldSpigotNames.warn();\r\n            }\r\n            return enumValue;\r\n        }\r\n        String updatedName = NMSHandler.instance.updateLegacyName(type, element.asString());\r\n        if (CoreUtilities.equalsIgnoreCase(element.asString(), updatedName)) {\r\n            return null;\r\n        }\r\n        if (showWarning) {\r\n            BukkitImplDeprecations.oldSpigotNames.warn();\r\n        }\r\n        return (T) registry.get(parseNamespacedKey(updatedName));\r\n    }\r\n\r\n    public static <T> T elementToRequiredEnumLike(ElementTag element, Class<T> type, Mechanism mechanism) {\r\n        T converted = elementToEnumlike(element, type);\r\n        if (converted == null) {\r\n            mechanism.echoError(\"Invalid \" + DebugInternals.getClassNameOpti(type) + \" specified.\");\r\n            return null;\r\n        }\r\n        return converted;\r\n    }\r\n\r\n    public static <T> T findBestEnumlike(Class<T> type, String... names) {\r\n        for (String name : names) {\r\n            T val = elementToEnumlike(new ElementTag(name), type, false);\r\n            if (val != null) {\r\n                return val;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static boolean matchesEnumlike(ElementTag element, Class<?> type) {\r\n        return elementToEnumlike(element, type, false) != null;\r\n    }\r\n\r\n    public static boolean requireEnumlike(Mechanism mechanism, Class<?> type) {\r\n        if (!matchesEnumlike(mechanism.getValue(), type)) {\r\n            mechanism.echoError(\"Invalid \" + DebugInternals.getClassNameOpti(type) + \" specified.\");\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    // <--[language]\r\n    // @name Server Links Format\r\n    // @group Minecraft Logic\r\n    // @description\r\n    // Server links are represented in Denizen as <@link ObjectType MapTag>s with the following keys:\r\n    // - link: The address of the link, required.\r\n    // And one of:\r\n    // - type: The type of the link, valid types are listed at <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/ServerLinks.Type.html>.\r\n    // - display: The display name of the link.\r\n    // -->\r\n\r\n    public static ServerLinks replaceServerLinks(ServerLinks serverLinks, ListTag list, TagContext context) {\r\n        serverLinks.getLinks().forEach(serverLinks::removeLink);\r\n        return fillServerLinks(serverLinks, list, context);\r\n    }\r\n\r\n    public static ServerLinks fillServerLinks(ServerLinks serverLinks, ListTag list, TagContext context) {\r\n        for (MapTag map : list.filter(MapTag.class, context)) {\r\n            if (!map.containsKey(\"link\")) {\r\n                Debug.echoError(\"Invalid server links map '\" + map + \"': missing 'link' key!\");\r\n                continue;\r\n            }\r\n            URI uri;\r\n            String strUri = map.getElement(\"link\").asString();\r\n            try {\r\n                uri = new URI(strUri);\r\n            }\r\n            catch (URISyntaxException e) {\r\n                Debug.echoError(\"Invalid server links map '\" + map + \"': invalid 'link' value '\" + strUri + \"'.\");\r\n                continue;\r\n            }\r\n            if (map.containsKey(\"display\")) {\r\n                PaperAPITools.instance.addLink(serverLinks, map.getElement(\"display\").asString(), uri);\r\n            }\r\n            else if (map.containsKey(\"type\")) {\r\n                ServerLinks.Type type = map.getElement(\"type\").asEnum(ServerLinks.Type.class);\r\n                if (type == null) {\r\n                    Debug.echoError(\"Invalid server links map '\" + map + \"': invalid 'type' value '\" + map.getElement(\"type\") + \"'.\");\r\n                    continue;\r\n                }\r\n                serverLinks.addLink(type, uri);\r\n            }\r\n            else {\r\n                Debug.echoError(\"Invalid server links map '\" + map + \"': must have either a 'display' or 'type' key!\");\r\n            }\r\n        }\r\n        return serverLinks;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/VanillaTagHelper.java",
    "content": "package com.denizenscript.denizen.utilities;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Keyed;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.Tag;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.HashSet;\r\nimport java.util.Set;\r\n\r\npublic class VanillaTagHelper {\r\n\r\n    public static HashMap<Material, HashSet<String>> tagsByMaterial = new HashMap<>();\r\n\r\n    public static HashMap<String, HashSet<Material>> materialTagsByKey = new HashMap<>();\r\n\r\n    public static HashMap<EntityType, HashSet<String>> tagsByEntity = new HashMap<>();\r\n\r\n    public static HashMap<String, HashSet<EntityType>> entityTagsByKey = new HashMap<>();\r\n\r\n    static {\r\n        loadTagsCache();\r\n    }\r\n\r\n    public static void addOrUpdateMaterialTag(Tag<Material> tag) {\r\n        if (materialTagsByKey.containsKey(tag.getKey().getKey())) {\r\n            updateMaterialTag(tag);\r\n        }\r\n        else {\r\n            addMaterialTag(tag);\r\n        }\r\n    }\r\n\r\n    public static void addOrUpdateEntityTag(Tag<EntityType> tag) {\r\n        if (entityTagsByKey.containsKey(tag.getKey().getKey())) {\r\n            updateEntityTag(tag);\r\n        }\r\n        else {\r\n            addEntityTag(tag);\r\n        }\r\n    }\r\n\r\n    // TODO: once 1.21 is the minimum supported version, remove this and related methods\r\n    static <T extends Keyed> void update(Tag<T> tag, HashMap<T, HashSet<String>> tagByObj, HashMap<String, HashSet<T>> objByTag) {\r\n        String tagName = Utilities.namespacedKeyToString(tag.getKey());\r\n        Set<T> objs = objByTag.get(tagName);\r\n        if (objs == null) {\r\n            return;\r\n        }\r\n        for (T obj : objs) {\r\n            Set<String> tags = tagByObj.get(obj);\r\n            if (tags.size() == 1) {\r\n                tagByObj.remove(obj);\r\n            }\r\n            else {\r\n                tags.remove(tagName);\r\n            }\r\n        }\r\n        Set<T> newObjs = tag.getValues();\r\n        for (T obj : newObjs) {\r\n            tagByObj.computeIfAbsent(obj, k -> new HashSet<>()).add(tagName);\r\n        }\r\n        objs.clear();\r\n        objs.addAll(newObjs);\r\n    }\r\n\r\n    public static void updateMaterialTag(Tag<Material> tag) {\r\n        update(tag, tagsByMaterial, materialTagsByKey);\r\n    }\r\n\r\n    public static void updateEntityTag(Tag<EntityType> tag) {\r\n        update(tag, tagsByEntity, entityTagsByKey);\r\n    }\r\n\r\n    static <T extends Keyed> void add(Tag<T> tag, HashMap<T, HashSet<String>> tagByObj, HashMap<String, HashSet<T>> objByTag) {\r\n        String tagName = Utilities.namespacedKeyToString(tag.getKey());\r\n        objByTag.computeIfAbsent(tagName, (k) -> new HashSet<>()).addAll(tag.getValues());\r\n        for (T obj : tag.getValues()) {\r\n            tagByObj.computeIfAbsent(obj, (k) -> new HashSet<>()).add(tagName);\r\n        }\r\n    }\r\n\r\n    static void addMaterialTag(Tag<Material> tag) {\r\n        add(tag, tagsByMaterial, materialTagsByKey);\r\n    }\r\n\r\n    static void addEntityTag(Tag<EntityType> tag) {\r\n        add(tag, tagsByEntity, entityTagsByKey);\r\n    }\r\n\r\n    public static void loadTagsCache() {\r\n        tagsByMaterial.clear();\r\n        materialTagsByKey.clear();\r\n        tagsByEntity.clear();\r\n        entityTagsByKey.clear();\r\n        for (Tag<Material> tag : Bukkit.getTags(\"blocks\", Material.class)) {\r\n            addMaterialTag(tag);\r\n        }\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) { // Note: existed on prior versions, but was bugged\r\n            for (Tag<EntityType> tag : Bukkit.getTags(\"entity_types\", EntityType.class)) {\r\n                addEntityTag(tag);\r\n            }\r\n        }\r\n        for (Tag<Material> tag : Bukkit.getTags(\"items\", Material.class)) {\r\n            addMaterialTag(tag);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/BlockSet.java",
    "content": "package com.denizenscript.denizen.utilities.blocks;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\n\r\nimport java.util.HashSet;\r\nimport java.util.List;\r\n\r\npublic interface BlockSet {\r\n\r\n    class InputParams {\r\n\r\n        public Location centerLocation;\r\n\r\n        public boolean noAir;\r\n\r\n        public HashSet<Material> mask;\r\n\r\n        public List<PlayerTag> fakeTo;\r\n\r\n        public DurationTag fakeDuration;\r\n    }\r\n\r\n    FullBlockData[] getBlocks();\r\n\r\n    void setBlocksDelayed(final Runnable runme, final InputParams input, long maxDelayMs);\r\n\r\n    void setBlocks(InputParams input);\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/ChunkCoordinate.java",
    "content": "package com.denizenscript.denizen.utilities.blocks;\r\n\r\nimport com.denizenscript.denizen.objects.ChunkTag;\r\nimport com.denizenscript.denizen.objects.WorldTag;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.Location;\r\n\r\npublic class ChunkCoordinate {\r\n\r\n    public final int x;\r\n\r\n    public final int z;\r\n\r\n    public final String worldName;\r\n\r\n    public ChunkCoordinate(int _x, int _z, String _worldName) {\r\n        x = _x;\r\n        z = _z;\r\n        worldName = _worldName;\r\n    }\r\n\r\n    public ChunkCoordinate(Location location) {\r\n        x = location.getBlockX() >> 4;\r\n        z = location.getBlockZ() >> 4;\r\n        worldName = location.getWorld().getName();\r\n    }\r\n\r\n    public ChunkCoordinate(Chunk chunk) {\r\n        x = chunk.getX();\r\n        z = chunk.getZ();\r\n        worldName = chunk.getWorld().getName();\r\n    }\r\n\r\n    @Override\r\n    public int hashCode() {\r\n        return x + z + worldName.hashCode();\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object other) {\r\n        if (!(other instanceof ChunkCoordinate)) {\r\n            return false;\r\n        }\r\n        return equals((ChunkCoordinate) other);\r\n    }\r\n\r\n    public boolean equals(ChunkCoordinate other) {\r\n        if (other == null) {\r\n            return false;\r\n        }\r\n        return x == other.x && z == other.z && worldName.equals(other.worldName);\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return x + \",\" + z + \",\" + worldName;\r\n    }\r\n\r\n    public ChunkTag getChunk() {\r\n        return new ChunkTag(new WorldTag(worldName), x, z);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/CuboidBlockSet.java",
    "content": "package com.denizenscript.denizen.utilities.blocks;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.NMSVersion;\r\nimport com.denizenscript.denizen.objects.*;\r\nimport com.denizenscript.denizen.scripts.commands.world.SchematicCommand;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\n\r\npublic class CuboidBlockSet implements BlockSet {\r\n\r\n    public static FullBlockData STRUCTURE_VOID = new FullBlockData(Material.STRUCTURE_VOID.createBlockData());\r\n\r\n    public CuboidBlockSet() {\r\n    }\r\n\r\n    public void buildImmediate(AreaContainmentObject area, Location center, HashSet<Material> mask, boolean copyFlags) {\r\n        hasFlags = copyFlags;\r\n        CuboidTag boundary;\r\n        if (area instanceof CuboidTag && ((CuboidTag) area).pairs.size() == 1) {\r\n            boundary = (CuboidTag) area;\r\n        }\r\n        else {\r\n            constraint = area;\r\n            boundary = area.getCuboidBoundary();\r\n        }\r\n        Location low = boundary.pairs.get(0).low;\r\n        Location high = boundary.pairs.get(0).high;\r\n        x_width = (int) ((high.getX() - low.getX()) + 1);\r\n        y_length = (int) ((high.getY() - low.getY()) + 1);\r\n        z_height = (int) ((high.getZ() - low.getZ()) + 1);\r\n        center_x = (int) (center.getX() - low.getX());\r\n        center_y = (int) (center.getY() - low.getY());\r\n        center_z = (int) (center.getZ() - low.getZ());\r\n        blocks = new FullBlockData[x_width * y_length * z_height];\r\n        int index = 0;\r\n        double lowX = low.getBlockX() + 0.5, lowY = low.getBlockY() + 0.5, lowZ = low.getBlockZ() + 0.5;\r\n        Location refLoc = low.clone();\r\n        for (int x = 0; x < x_width; x++) {\r\n            for (int y = 0; y < y_length; y++) {\r\n                for (int z = 0; z < z_height; z++) {\r\n                    refLoc.setX(lowX + x);\r\n                    refLoc.setY(lowY + y);\r\n                    refLoc.setZ(lowZ + z);\r\n                    FullBlockData block = (constraint == null || constraint.doesContainLocation(refLoc)) ? new FullBlockData(refLoc.getBlock(), copyFlags) : STRUCTURE_VOID;\r\n                    if (block != STRUCTURE_VOID && mask != null && !mask.contains(block.data.getMaterial())) {\r\n                        block = STRUCTURE_VOID;\r\n                    }\r\n                    blocks[index++] = block;\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    public void buildDelayed(AreaContainmentObject area, Location center, HashSet<Material> mask, Runnable runme, long maxDelayMs, boolean copyFlags) {\r\n        hasFlags = copyFlags;\r\n        CuboidTag boundary;\r\n        if (area instanceof CuboidTag && ((CuboidTag) area).pairs.size() == 1) {\r\n            boundary = (CuboidTag) area;\r\n        }\r\n        else {\r\n            constraint = area;\r\n            boundary = area.getCuboidBoundary();\r\n        }\r\n        Location low = boundary.pairs.get(0).low;\r\n        Location high = boundary.pairs.get(0).high;\r\n        x_width = (int) ((high.getX() - low.getX()) + 1);\r\n        y_length = (int) ((high.getY() - low.getY()) + 1);\r\n        z_height = (int) ((high.getZ() - low.getZ()) + 1);\r\n        center_x = (int) (center.getX() - low.getX());\r\n        center_y = (int) (center.getY() - low.getY());\r\n        center_z = (int) (center.getZ() - low.getZ());\r\n        final long goal = (long)x_width * y_length * z_height;\r\n        blocks = new FullBlockData[x_width * y_length * z_height];\r\n        double lowX = low.getBlockX() + 0.5, lowY = low.getBlockY() + 0.5, lowZ = low.getBlockZ() + 0.5;\r\n        Location refLoc = low.clone();\r\n        new BukkitRunnable() {\r\n            int index = 0;\r\n            @Override\r\n            public void run() {\r\n                long start = CoreUtilities.monotonicMillis();\r\n                while (index < goal) {\r\n                    long z = index % ((long) z_height);\r\n                    long y = ((index - z) % ((long)y_length * z_height)) / ((long) z_height);\r\n                    long x = (index - y - z) / ((long)y_length * z_height);\r\n                    refLoc.setX(lowX + x);\r\n                    refLoc.setY(lowY + y);\r\n                    refLoc.setZ(lowZ + z);\r\n                    FullBlockData block = (constraint == null || constraint.doesContainLocation(refLoc)) ? new FullBlockData(refLoc.getBlock(), copyFlags) : STRUCTURE_VOID;\r\n                    if (block != STRUCTURE_VOID && mask != null && !mask.contains(block.data.getMaterial())) {\r\n                        block = STRUCTURE_VOID;\r\n                    }\r\n                    blocks[index] = block;\r\n                    index++;\r\n                    if (CoreUtilities.monotonicMillis() - start > maxDelayMs) {\r\n                        return;\r\n                    }\r\n                }\r\n                if (runme != null) {\r\n                    runme.run();\r\n                }\r\n                cancel();\r\n\r\n            }\r\n        }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n    }\r\n\r\n    public AreaContainmentObject constraint = null;\r\n\r\n    public FullBlockData[] blocks = null;\r\n\r\n    public boolean hasFlags = false;\r\n\r\n    public int x_width;\r\n\r\n    public int y_length;\r\n\r\n    public int z_height;\r\n\r\n    public int center_x;\r\n\r\n    public int center_y;\r\n\r\n    public int center_z;\r\n\r\n    public ListTag entities = null;\r\n\r\n    public boolean isModifying = false;\r\n\r\n    public int readingProcesses = 0;\r\n\r\n    public CuboidBlockSet duplicate() {\r\n        CuboidBlockSet result = new CuboidBlockSet();\r\n        result.blocks = blocks.clone();\r\n        result.hasFlags = hasFlags;\r\n        result.x_width = x_width;\r\n        result.y_length = y_length;\r\n        result.z_height = z_height;\r\n        result.center_x = center_x;\r\n        result.center_y = center_y;\r\n        result.center_z = center_z;\r\n        if (entities != null) {\r\n            result.entities = entities.duplicate();\r\n        }\r\n        return result;\r\n    }\r\n\r\n    @Override\r\n    public FullBlockData[] getBlocks() {\r\n        return blocks;\r\n    }\r\n\r\n    public CuboidTag getCuboid(Location loc) {\r\n        Location low = loc.clone().subtract(center_x, center_y, center_z);\r\n        Location high = low.clone().add(x_width - 1, y_length - 1, z_height - 1); // Note: -1 because CuboidTag implicitly includes an extra block by design.\r\n        return new CuboidTag(low, high);\r\n    }\r\n\r\n    public static BlockFace rotateFaceOne(BlockFace face) {\r\n        switch (face) {\r\n            case NORTH:\r\n                return BlockFace.WEST;\r\n            case EAST:\r\n                return BlockFace.NORTH;\r\n            case SOUTH:\r\n                return BlockFace.EAST;\r\n            case WEST:\r\n                return BlockFace.SOUTH;\r\n            case NORTH_EAST:\r\n                return BlockFace.NORTH_WEST;\r\n            case NORTH_WEST:\r\n                return BlockFace.SOUTH_WEST;\r\n            case SOUTH_WEST:\r\n                return BlockFace.SOUTH_EAST;\r\n            case SOUTH_EAST:\r\n                return BlockFace.NORTH_EAST;\r\n            default:\r\n                return BlockFace.SELF;\r\n        }\r\n    }\r\n\r\n    public static HashSet<EntityType> copyTypes = new HashSet<>(Arrays.asList(EntityType.PAINTING, EntityType.ITEM_FRAME, EntityType.ARMOR_STAND));\r\n\r\n    static {\r\n        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20)) {\r\n            copyTypes.add(EntityType.BLOCK_DISPLAY);\r\n            copyTypes.add(EntityType.ITEM_DISPLAY);\r\n            copyTypes.add(EntityType.TEXT_DISPLAY);\r\n        }\r\n    }\r\n\r\n    public void buildEntities(AreaContainmentObject area, Location center) {\r\n        entities = new ListTag();\r\n        for (Entity ent : area.getCuboidBoundary().getEntitiesPossiblyWithin()) {\r\n            if (area.doesContainLocation(ent.getLocation())) {\r\n                if (copyTypes.contains(ent.getType())) {\r\n                    EntityTag entTag = new EntityTag(ent);\r\n                    if (entTag.isPlayer() || entTag.isNPC()) {\r\n                        continue;\r\n                    }\r\n                    MapTag data = new MapTag();\r\n                    data.putObject(\"entity\", entTag.describe(null));\r\n                    data.putObject(\"rotation\", new ElementTag(0));\r\n                    Vector offset = ent.getLocation().toVector().subtract(center.toVector());\r\n                    data.putObject(\"offset\", new LocationTag((String) null, offset.getX(), offset.getY(), offset.getZ(), ent.getLocation().getYaw(), ent.getLocation().getPitch()));\r\n                    entities.addObject(data);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    public void pasteEntities(LocationTag relative) {\r\n        if (entities == null) {\r\n            return;\r\n        }\r\n        for (MapTag data : entities.filter(MapTag.class, CoreUtilities.noDebugContext)) {\r\n            try {\r\n                LocationTag offset = data.getObjectAs(\"offset\", LocationTag.class, CoreUtilities.noDebugContext);\r\n                int rotation = data.getElement(\"rotation\").asInt();\r\n                EntityTag entity = data.getObjectAs(\"entity\", EntityTag.class, CoreUtilities.noDebugContext);\r\n                if (entity == null || offset == null) {\r\n                    continue;\r\n                }\r\n                entity = entity.duplicate();\r\n                offset = offset.clone();\r\n                if (rotation != 0) {\r\n                    ArrayList<Mechanism> mechs = new ArrayList<>(entity.getWaitingMechanisms().size());\r\n                    for (Mechanism mech : entity.getWaitingMechanisms()) {\r\n                        if (mech.getName().equals(\"rotation\")) {\r\n                            String rotationName = mech.getValue().asString();\r\n                            BlockFace face = BlockFace.valueOf(rotationName.toUpperCase());\r\n                            for (int i = 0; i < rotation; i += 90) {\r\n                                face = rotateFaceOne(face);\r\n                            }\r\n                            offset.add(face.getDirection().multiply(0.1)); // Compensate for hanging locations being very stupid\r\n                            mechs.add(new Mechanism(\"rotation\", new ElementTag(face), CoreUtilities.noDebugContext));\r\n                        }\r\n                        else {\r\n                            mechs.add(new Mechanism(mech.getName(), mech.value, CoreUtilities.noDebugContext));\r\n                        }\r\n                    }\r\n                    entity.mechanisms = mechs;\r\n                }\r\n                else {\r\n                    for (Mechanism mechanism : entity.mechanisms) {\r\n                        mechanism.context = CoreUtilities.noDebugContext;\r\n                    }\r\n                }\r\n                Location spawnLoc = relative.clone().add(offset);\r\n                spawnLoc.setYaw(offset.getYaw() - rotation);\r\n                spawnLoc.setPitch(offset.getPitch());\r\n                entity.spawnAt(spawnLoc);\r\n            }\r\n            catch (Exception ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void setBlockSingle(FullBlockData block, int x, int y, int z, InputParams input) {\r\n        if (input.noAir && block.data.getMaterial() == Material.AIR) {\r\n            return;\r\n        }\r\n        if (block.data.getMaterial() == Material.STRUCTURE_VOID) {\r\n            return;\r\n        }\r\n        int finalY = input.centerLocation.getBlockY() + y - center_y;\r\n        if (!Utilities.isLocationYSafe(finalY, input.centerLocation.getWorld())) {\r\n            return;\r\n        }\r\n        Block destBlock = input.centerLocation.clone().add(x - center_x, y - center_y, z - center_z).getBlock();\r\n        if (input.mask != null && !input.mask.contains(destBlock.getType())) {\r\n            return;\r\n        }\r\n        if (input.fakeTo == null) {\r\n            block.set(destBlock, false);\r\n        }\r\n        else {\r\n            FakeBlock.showFakeBlockTo(input.fakeTo, new LocationTag(destBlock.getLocation()), new MaterialTag(block.data), input.fakeDuration, false);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setBlocksDelayed(final Runnable runme, final InputParams input, long maxDelayMs) {\r\n        final long goal = (long)x_width * y_length * z_height;\r\n        new BukkitRunnable() {\r\n            int index = 0;\r\n            @Override\r\n            public void run() {\r\n                SchematicCommand.noPhys = true;\r\n                long start = CoreUtilities.monotonicMillis();\r\n                while (index < goal) {\r\n                    int z = index % (z_height);\r\n                    int y = ((index - z) % (y_length * z_height)) / z_height;\r\n                    int x = (index - y - z) / (y_length * z_height);\r\n                    setBlockSingle(blocks[index], x, y, z, input);\r\n                    index++;\r\n                    if (CoreUtilities.monotonicMillis() - start > maxDelayMs) {\r\n                        SchematicCommand.noPhys = false;\r\n                        return;\r\n                    }\r\n                }\r\n                SchematicCommand.noPhys = false;\r\n                cancel();\r\n                if (runme != null) {\r\n                    runme.run();\r\n                }\r\n            }\r\n        }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n    }\r\n\r\n    @Override\r\n    public void setBlocks(InputParams input) {\r\n        SchematicCommand.noPhys = true;\r\n        int index = 0;\r\n        for (int x = 0; x < x_width; x++) {\r\n            for (int y = 0; y < y_length; y++) {\r\n                for (int z = 0; z < z_height; z++) {\r\n                    setBlockSingle(blocks[index], x, y, z, input);\r\n                    index++;\r\n                }\r\n            }\r\n        }\r\n        SchematicCommand.noPhys = false;\r\n    }\r\n\r\n    public void rotateEntitiesOne() {\r\n        if (entities == null) {\r\n            return;\r\n        }\r\n        ListTag outEntities = new ListTag();\r\n        for (MapTag data : entities.filter(MapTag.class, CoreUtilities.noDebugContext)) {\r\n            LocationTag offset = data.getObjectAs(\"offset\", LocationTag.class, CoreUtilities.noDebugContext);\r\n            int rotation = data.getElement(\"rotation\").asInt();\r\n            offset = new LocationTag((String) null, offset.getZ(), offset.getY(), -offset.getX() + 1, offset.getYaw(), offset.getPitch());\r\n            rotation += 90;\r\n            while (rotation >= 360) {\r\n                rotation -= 360;\r\n            }\r\n            data = data.duplicate();\r\n            data.putObject(\"offset\", offset);\r\n            data.putObject(\"rotation\", new ElementTag(rotation));\r\n            outEntities.addObject(data);\r\n        }\r\n        entities = outEntities;\r\n    }\r\n\r\n    public void rotateOne() {\r\n        rotateEntitiesOne();\r\n        FullBlockData[] bd = new FullBlockData[blocks.length];\r\n        int index = 0;\r\n        int cx = center_x;\r\n        center_x = center_z;\r\n        center_z = x_width - 1 - cx;\r\n        for (int x = 0; x < z_height; x++) {\r\n            for (int y = 0; y < y_length; y++) {\r\n                for (int z = x_width - 1; z >= 0; z--) {\r\n                    bd[index++] = blockAt(z, y, x).rotateOne();\r\n                }\r\n            }\r\n        }\r\n        int xw = x_width;\r\n        x_width = z_height;\r\n        z_height = xw;\r\n        blocks = bd;\r\n    }\r\n\r\n    public void flipEntities(int offsetMultiplier_X, int offsetMultiplier_Z) {\r\n        if (entities == null) {\r\n            return;\r\n        }\r\n        ListTag outEntities = new ListTag();\r\n        for (MapTag data : entities.filter(MapTag.class, CoreUtilities.noDebugContext)) {\r\n            int rotation = data.getElement(\"rotation\").asInt();\r\n            LocationTag offset = data.getObjectAs(\"offset\", LocationTag.class, CoreUtilities.noDebugContext);\r\n            float newYaw = offset.getYaw();\r\n            if (offsetMultiplier_X == -1) {\r\n                newYaw = -1 * (offset.getYaw() - 90) + 270;\r\n            } else if (offsetMultiplier_Z == -1) {\r\n                newYaw = 180 - offset.getYaw();\r\n            }\r\n            while (newYaw < 0 || newYaw >= 360) {\r\n                if (newYaw >= 360) {\r\n                    newYaw -= 360;\r\n                } else {\r\n                    newYaw += 360;\r\n                }\r\n            }\r\n            rotation += (offset.getYaw() - newYaw);\r\n            offset = new LocationTag((String) null, offset.getX() * offsetMultiplier_X, offset.getY(), offset.getZ() * offsetMultiplier_Z, newYaw, offset.getPitch());\r\n            data = data.duplicate();\r\n            data.putObject(\"offset\", offset);\r\n            data.putObject(\"rotation\", new ElementTag(rotation));\r\n            outEntities.addObject(data);\r\n        }\r\n        entities = outEntities;\r\n    }\r\n\r\n    public void flipX() {\r\n        flipEntities(-1, 1);\r\n        FullBlockData[] bd = new FullBlockData[blocks.length];\r\n        int index = 0;\r\n        center_x = x_width - center_x - 1;\r\n        for (int x = x_width - 1; x >= 0; x--) {\r\n            for (int y = 0; y < y_length; y++) {\r\n                for (int z = 0; z < z_height; z++) {\r\n                    bd[index++] = blockAt(x, y, z).flipX();\r\n                }\r\n            }\r\n        }\r\n        blocks = bd;\r\n    }\r\n\r\n    public void flipY() {\r\n        FullBlockData[] bd = new FullBlockData[blocks.length];\r\n        int index = 0;\r\n        center_y = y_length - center_y - 1;\r\n        for (int x = 0; x < x_width; x++) {\r\n            for (int y = y_length - 1; y >= 0; y--) {\r\n                for (int z = 0; z < z_height; z++) {\r\n                    bd[index++] = blockAt(x, y, z).flipY();\r\n                }\r\n            }\r\n        }\r\n        blocks = bd;\r\n    }\r\n\r\n    public void flipZ() {\r\n        flipEntities(1, -1);\r\n        FullBlockData[] bd = new FullBlockData[blocks.length];\r\n        int index = 0;\r\n        center_z = z_height - center_z - 1;\r\n        for (int x = 0; x < x_width; x++) {\r\n            for (int y = 0; y < y_length; y++) {\r\n                for (int z = z_height - 1; z >= 0; z--) {\r\n                    bd[index++] = blockAt(x, y, z).flipZ();\r\n                }\r\n            }\r\n        }\r\n        blocks = bd;\r\n    }\r\n\r\n    public FullBlockData blockAt(double X, double Y, double Z) {\r\n        return blocks[(int) (Z + Y * z_height + X * z_height * y_length)];\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/FakeBlock.java",
    "content": "package com.denizenscript.denizen.utilities.blocks;\n\nimport com.denizenscript.denizen.Denizen;\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.objects.MaterialTag;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\nimport com.denizenscript.denizencore.objects.core.DurationTag;\nimport org.bukkit.Bukkit;\nimport org.bukkit.World;\nimport org.bukkit.scheduler.BukkitRunnable;\nimport org.bukkit.scheduler.BukkitTask;\n\nimport java.util.*;\n\n/**\n * Creates a temporary fake block and shows it to a PlayerTag.\n */\npublic class FakeBlock {\n\n    public static class FakeBlockMap {\n\n        public Map<LocationTag, FakeBlock> byLocation = new HashMap<>();\n\n        public Map<ChunkCoordinate, List<FakeBlock>> byChunk = new HashMap<>();\n\n        public FakeBlock getOrAdd(PlayerTag player, LocationTag location) {\n            location = new LocationTag(location.getBlockX(), location.getBlockY(), location.getBlockZ(), location.getWorldName());\n            FakeBlock block = byLocation.get(location);\n            if (block != null) {\n                return block;\n            }\n            block = new FakeBlock(player, location);\n            byLocation.put(location, block);\n            List<FakeBlock> chunkBlocks = byChunk.computeIfAbsent(block.chunkCoord, k -> new ArrayList<>());\n            chunkBlocks.add(block);\n            return block;\n        }\n\n        public void remove(FakeBlock block) {\n            if (byLocation.remove(block.location) != null) {\n                List<FakeBlock> chunkBlocks = byChunk.get(block.chunkCoord);\n                if (chunkBlocks != null) {\n                    chunkBlocks.remove(block);\n                    if (chunkBlocks.isEmpty()) {\n                        byChunk.remove(block.chunkCoord);\n                    }\n                }\n            }\n        }\n    }\n\n    public final static Map<UUID, FakeBlockMap> blocks = new HashMap<>();\n\n    public static FakeBlock getFakeBlockFor(UUID id, LocationTag location) {\n        FakeBlockMap map = blocks.get(id);\n        if (map == null) {\n            return null;\n        }\n        return map.byLocation.get(location);\n    }\n\n    public static List<FakeBlock> getFakeBlocksFor(UUID id, ChunkCoordinate chunkCoord) {\n        FakeBlockMap map = blocks.get(id);\n        if (map == null) {\n            return null;\n        }\n        return map.byChunk.get(chunkCoord);\n    }\n\n    public final PlayerTag player;\n    public final LocationTag location;\n    public final ChunkCoordinate chunkCoord;\n    public MaterialTag material;\n    public BukkitTask currentTask = null;\n\n    private FakeBlock(PlayerTag player, LocationTag location) {\n        this.player = player;\n        this.location = location;\n        this.chunkCoord = new ChunkCoordinate(location);\n    }\n\n    public static void showFakeBlockTo(List<PlayerTag> players, LocationTag location, MaterialTag material, DurationTag duration, boolean sendNow) {\n        NetworkInterceptHelper.enable();\n        for (PlayerTag player : players) {\n            if (!player.isOnline() || !player.isValid()) {\n                continue;\n            }\n            UUID uuid = player.getPlayerEntity().getUniqueId();\n            FakeBlockMap playerBlocks = blocks.get(uuid);\n            if (playerBlocks == null) {\n                playerBlocks = new FakeBlockMap();\n                blocks.put(uuid, playerBlocks);\n            }\n            FakeBlock block = playerBlocks.getOrAdd(player, location);\n            block.updateBlock(material, duration, sendNow);\n        }\n    }\n\n    public static void stopShowingTo(List<PlayerTag> players, final LocationTag location) {\n        for (PlayerTag player : players) {\n            FakeBlockMap playerBlocks = blocks.get(player.getPlayerEntity().getUniqueId());\n            if (playerBlocks != null) {\n                FakeBlock block = playerBlocks.byLocation.get(location);\n                if (block != null) {\n                    block.cancelBlock();\n                }\n            }\n        }\n    }\n\n    public static HashMap<ChunkCoordinate, BukkitTask> scheduled = new HashMap<>();\n\n    public static void scheduleChunkRefresh(World world, ChunkCoordinate coord) {\n        BukkitTask task = scheduled.get(coord);\n        if (task != null && !task.isCancelled()) {\n            return;\n        }\n        scheduled.put(coord, Bukkit.getScheduler().runTaskLater(Denizen.getInstance(), () -> {\n            world.refreshChunk(coord.x, coord.z);\n            scheduled.remove(coord);\n        }, 1));\n    }\n\n    public void cancelBlock() {\n        if (currentTask != null) {\n            currentTask.cancel();\n            currentTask = null;\n        }\n        material = null;\n        FakeBlockMap mapping = blocks.get(player.getUUID());\n        mapping.remove(this);\n        if (mapping.byChunk.isEmpty()) {\n            blocks.remove(player.getUUID());\n        }\n        if (player.isOnline()) {\n            scheduleChunkRefresh(location.getWorld(), chunkCoord);\n            if (!NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\n                player.getPlayerEntity().sendBlockChange(location, location.getBlock().getBlockData());\n            }\n        }\n    }\n\n    private void updateBlock(MaterialTag material, DurationTag duration, boolean sendNow) {\n        if (currentTask != null) {\n            currentTask.cancel();\n        }\n        this.material = material;\n        if (player.hasChunkLoaded(location.getChunk())) {\n            if (sendNow || !NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {\n                player.getPlayerEntity().sendBlockChange(location, material.getModernData());\n            }\n            scheduleChunkRefresh(location.getWorld(), chunkCoord);\n        }\n        if (duration != null && duration.getTicks() > 0) {\n            currentTask = new BukkitRunnable() {\n                @Override\n                public void run() {\n                    currentTask = null;\n                    cancelBlock();\n                }\n            }.runTaskLater(Denizen.getInstance(), duration.getTicks());\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/FullBlockData.java",
    "content": "package com.denizenscript.denizen.utilities.blocks;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.flags.MapTagBasedFlagTracker;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport org.bukkit.Axis;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.BlockFace;\r\nimport org.bukkit.block.data.*;\r\nimport org.bukkit.block.data.type.*;\r\n\r\nimport java.lang.reflect.Method;\r\nimport java.util.HashMap;\r\nimport java.util.HashSet;\r\nimport java.util.Map;\r\n\r\npublic class FullBlockData {\r\n\r\n    public static void init() {\r\n        HashSet<Class<? extends BlockData>> classesHandled = new HashSet<>();\r\n        for (Material material : Material.values()) {\r\n            if (!material.isBlock() || material.isLegacy()) {\r\n                continue;\r\n            }\r\n            BlockData data = material.createBlockData();\r\n            if (classesHandled.add(data.getClass())) {\r\n                initBlockDataClass(data);\r\n            }\r\n        }\r\n    }\r\n\r\n    /** Trigger a bunch of pointless block data changes, to cause the enum class cache CraftBlockData.ENUM_VALUES to be filled in, thus making it probably safe for async usage. */\r\n    private static void initBlockDataClass(BlockData data) {\r\n        try {\r\n            Class<? extends BlockData> dataClass = data.getClass();\r\n            HashMap<String, Method> setMethods = new HashMap<>();\r\n            for (Method m : dataClass.getMethods()) {\r\n                if (m.getName().startsWith(\"set\") && m.getParameterCount() == 1 && m.getReturnType() == void.class) {\r\n                    setMethods.put(m.getName(), m);\r\n                }\r\n            }\r\n            for (Method m : dataClass.getMethods()) {\r\n                if (m.getName().startsWith(\"get\") && m.getParameterCount() == 0 && m.getReturnType().isEnum()) {\r\n                    Method setter = setMethods.get(\"set\" + m.getName().substring(\"get\".length()));\r\n                    if (setter != null && setter.getParameterTypes()[0] == m.getReturnType()) {\r\n                        setter.setAccessible(true);\r\n                        m.setAccessible(true);\r\n                        Object curVal = m.invoke(data);\r\n                        setter.invoke(data, curVal);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(\"Errored while trying to pre-load BlockData class '\" + data.getClass().getName() + \"'\");\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public FullBlockData(Block block, boolean copyFlags) {\r\n        this(block);\r\n        if (copyFlags) {\r\n            MapTagBasedFlagTracker flagMap = (MapTagBasedFlagTracker) new LocationTag(block.getLocation()).getFlagTracker();\r\n            flags = new MapTag();\r\n            for (String flag : flagMap.listAllFlags()) {\r\n                flags.putObject(flag, flagMap.getRootMap(flag));\r\n            }\r\n            if (flags.isEmpty()) {\r\n                flags = null;\r\n            }\r\n        }\r\n    }\r\n\r\n    public FullBlockData(Block block) {\r\n        this(block.getBlockData());\r\n        tileEntityData = NMSHandler.blockHelper.getNbtData(block);\r\n    }\r\n\r\n    public FullBlockData(BlockData data) {\r\n        this.data = data;\r\n    }\r\n\r\n    public FullBlockData(BlockData data, CompoundBinaryTag tileEntityData, MapTag flags) {\r\n        this.data = data;\r\n        this.tileEntityData = tileEntityData;\r\n        this.flags = flags;\r\n    }\r\n\r\n    public static BlockFace rotateFaceOne(BlockFace face) {\r\n        switch (face) {\r\n            case NORTH:\r\n                return BlockFace.WEST;\r\n            case EAST:\r\n                return BlockFace.NORTH;\r\n            case SOUTH:\r\n                return BlockFace.EAST;\r\n            case WEST:\r\n                return BlockFace.SOUTH;\r\n            case NORTH_EAST:\r\n                return BlockFace.NORTH_WEST;\r\n            case NORTH_WEST:\r\n                return BlockFace.SOUTH_WEST;\r\n            case SOUTH_WEST:\r\n                return BlockFace.SOUTH_EAST;\r\n            case SOUTH_EAST:\r\n                return BlockFace.NORTH_EAST;\r\n            case NORTH_NORTH_EAST:\r\n                return BlockFace.WEST_NORTH_WEST;\r\n            case NORTH_NORTH_WEST:\r\n                return BlockFace.WEST_SOUTH_WEST;\r\n            case SOUTH_SOUTH_WEST:\r\n                return BlockFace.EAST_SOUTH_EAST;\r\n            case SOUTH_SOUTH_EAST:\r\n                return BlockFace.EAST_NORTH_EAST;\r\n            case EAST_NORTH_EAST:\r\n                return BlockFace.NORTH_NORTH_WEST;\r\n            case WEST_NORTH_WEST:\r\n                return BlockFace.SOUTH_SOUTH_WEST;\r\n            case WEST_SOUTH_WEST:\r\n                return BlockFace.SOUTH_SOUTH_EAST;\r\n            case EAST_SOUTH_EAST:\r\n                return BlockFace.NORTH_NORTH_EAST;\r\n        }\r\n        return face;\r\n    }\r\n\r\n    public static Rail.Shape rotateRailShapeOne(Rail.Shape shape) {\r\n        switch (shape) {\r\n            case NORTH_SOUTH:\r\n                return Rail.Shape.EAST_WEST;\r\n            case EAST_WEST:\r\n                return Rail.Shape.NORTH_SOUTH;\r\n            case ASCENDING_EAST:\r\n                return Rail.Shape.ASCENDING_NORTH;\r\n            case ASCENDING_WEST:\r\n                return Rail.Shape.ASCENDING_SOUTH;\r\n            case ASCENDING_NORTH:\r\n                return Rail.Shape.ASCENDING_WEST;\r\n            case ASCENDING_SOUTH:\r\n                return Rail.Shape.ASCENDING_EAST;\r\n            case SOUTH_EAST:\r\n                return Rail.Shape.NORTH_EAST;\r\n            case SOUTH_WEST:\r\n                return Rail.Shape.SOUTH_EAST;\r\n            case NORTH_WEST:\r\n                return Rail.Shape.SOUTH_WEST;\r\n            case NORTH_EAST:\r\n                return Rail.Shape.NORTH_WEST;\r\n        }\r\n        return shape;\r\n    }\r\n\r\n    public FullBlockData rotateOne() {\r\n        if (data instanceof Orientable) {\r\n            BlockData newData = data.clone();\r\n            switch (((Orientable) data).getAxis()) {\r\n                case X:\r\n                    ((Orientable) newData).setAxis(Axis.Z);\r\n                    break;\r\n                case Z:\r\n                    ((Orientable) newData).setAxis(Axis.X);\r\n                    break;\r\n            }\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Rotatable) {\r\n            BlockData newData = data.clone();\r\n            ((Rotatable) newData).setRotation(rotateFaceOne(((Rotatable) data).getRotation()));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Directional) {\r\n            BlockData newData = data.clone();\r\n            ((Directional) newData).setFacing(rotateFaceOne(((Directional) data).getFacing()));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n\r\n        }\r\n        else if (data instanceof Rail) {\r\n            BlockData newData = data.clone();\r\n            ((Rail) newData).setShape(rotateRailShapeOne(((Rail) data).getShape()));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof MultipleFacing) {\r\n            MultipleFacing newData = (MultipleFacing) data.clone();\r\n            for (BlockFace face : ((MultipleFacing) data).getFaces()) {\r\n                newData.setFace(face, false);\r\n            }\r\n            for (BlockFace face : ((MultipleFacing) data).getFaces()) {\r\n                newData.setFace(rotateFaceOne(face), true);\r\n            }\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof RedstoneWire) {\r\n            RedstoneWire newData = (RedstoneWire) data.clone();\r\n            newData.setFace(BlockFace.NORTH, ((RedstoneWire) data).getFace(BlockFace.EAST));\r\n            newData.setFace(BlockFace.WEST, ((RedstoneWire) data).getFace(BlockFace.NORTH));\r\n            newData.setFace(BlockFace.EAST, ((RedstoneWire) data).getFace(BlockFace.SOUTH));\r\n            newData.setFace(BlockFace.SOUTH, ((RedstoneWire) data).getFace(BlockFace.WEST));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Wall) {\r\n            Wall newData = (Wall) data.clone();\r\n            newData.setHeight(BlockFace.NORTH, ((Wall) data).getHeight(BlockFace.EAST));\r\n            newData.setHeight(BlockFace.WEST, ((Wall) data).getHeight(BlockFace.NORTH));\r\n            newData.setHeight(BlockFace.EAST, ((Wall) data).getHeight(BlockFace.SOUTH));\r\n            newData.setHeight(BlockFace.SOUTH, ((Wall) data).getHeight(BlockFace.WEST));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        return this;\r\n    }\r\n\r\n    public static Door.Hinge flipDoorHinge(Door.Hinge hinge) {\r\n        switch (hinge) {\r\n            case LEFT:\r\n                return Door.Hinge.RIGHT;\r\n            case RIGHT:\r\n                return Door.Hinge.LEFT;\r\n        }\r\n        return hinge;\r\n    }\r\n\r\n    public static Chest.Type flipChestType(Chest.Type type) {\r\n        switch (type) {\r\n            case LEFT:\r\n                return Chest.Type.RIGHT;\r\n            case RIGHT:\r\n                return Chest.Type.LEFT;\r\n        }\r\n        return type;\r\n    }\r\n\r\n    public static Bisected.Half flipBisectedHalf(Bisected.Half half) {\r\n        switch (half) {\r\n            case TOP:\r\n                return Bisected.Half.BOTTOM;\r\n            case BOTTOM:\r\n                return Bisected.Half.TOP;\r\n        }\r\n        return half;\r\n    }\r\n\r\n    public static BlockFace flipFaceX(BlockFace face) {\r\n        switch (face) {\r\n            case EAST:\r\n                return BlockFace.WEST;\r\n            case WEST:\r\n                return BlockFace.EAST;\r\n            case NORTH_EAST:\r\n                return BlockFace.NORTH_WEST;\r\n            case NORTH_WEST:\r\n                return BlockFace.NORTH_EAST;\r\n            case SOUTH_WEST:\r\n                return BlockFace.SOUTH_EAST;\r\n            case SOUTH_EAST:\r\n                return BlockFace.SOUTH_WEST;\r\n            case NORTH_NORTH_EAST:\r\n                return BlockFace.NORTH_NORTH_WEST;\r\n            case NORTH_NORTH_WEST:\r\n                return BlockFace.NORTH_NORTH_EAST;\r\n            case SOUTH_SOUTH_WEST:\r\n                return BlockFace.SOUTH_SOUTH_EAST;\r\n            case SOUTH_SOUTH_EAST:\r\n                return BlockFace.SOUTH_SOUTH_WEST;\r\n            case EAST_NORTH_EAST:\r\n                return BlockFace.WEST_NORTH_WEST;\r\n            case WEST_NORTH_WEST:\r\n                return BlockFace.EAST_NORTH_EAST;\r\n            case WEST_SOUTH_WEST:\r\n                return BlockFace.EAST_SOUTH_EAST;\r\n            case EAST_SOUTH_EAST:\r\n                return BlockFace.WEST_SOUTH_WEST;\r\n        }\r\n        return face;\r\n    }\r\n\r\n    public static Rail.Shape flipRailShapeX(Rail.Shape shape) {\r\n        switch (shape) {\r\n            case ASCENDING_EAST:\r\n                return Rail.Shape.ASCENDING_WEST;\r\n            case ASCENDING_WEST:\r\n                return Rail.Shape.ASCENDING_EAST;\r\n            case ASCENDING_NORTH:\r\n                return Rail.Shape.ASCENDING_NORTH;\r\n            case ASCENDING_SOUTH:\r\n                return Rail.Shape.ASCENDING_SOUTH;\r\n            case SOUTH_EAST:\r\n                return Rail.Shape.SOUTH_WEST;\r\n            case SOUTH_WEST:\r\n                return Rail.Shape.SOUTH_EAST;\r\n            case NORTH_WEST:\r\n                return Rail.Shape.NORTH_EAST;\r\n            case NORTH_EAST:\r\n                return Rail.Shape.NORTH_WEST;\r\n        }\r\n        return shape;\r\n    }\r\n\r\n    public static BlockFace flipStairFaceX(Stairs.Shape shape, BlockFace face) {\r\n        switch (shape) {\r\n            case INNER_RIGHT:\r\n            case OUTER_RIGHT:\r\n                switch (face) {\r\n                    case NORTH:\r\n                        return BlockFace.WEST;\r\n                    case EAST:\r\n                        return BlockFace.SOUTH;\r\n                    case SOUTH:\r\n                        return BlockFace.EAST;\r\n                    case WEST:\r\n                        return BlockFace.NORTH;\r\n                }\r\n            case INNER_LEFT:\r\n            case OUTER_LEFT:\r\n                switch (face) {\r\n                    case NORTH:\r\n                        return BlockFace.EAST;\r\n                    case EAST:\r\n                        return BlockFace.NORTH;\r\n                    case SOUTH:\r\n                        return BlockFace.WEST;\r\n                    case WEST:\r\n                        return BlockFace.SOUTH;\r\n                }\r\n            case STRAIGHT:\r\n                switch (face) {\r\n                    case EAST:\r\n                        return BlockFace.WEST;\r\n                    case WEST:\r\n                        return BlockFace.EAST;\r\n                }\r\n        }\r\n        return face;\r\n    }\r\n\r\n    public FullBlockData flipX() {\r\n        if (data instanceof Rotatable) {\r\n            BlockData newData = data.clone();\r\n            ((Rotatable) newData).setRotation(flipFaceX(((Rotatable) data).getRotation()));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Stairs) {\r\n            BlockData newData = data.clone();\r\n            BlockFace face = ((Stairs) data).getFacing();\r\n            Stairs.Shape shape = ((Stairs) data).getShape();\r\n            ((Stairs) newData).setFacing(flipStairFaceX(shape, face));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Directional) {\r\n            BlockData newData = data.clone();\r\n            ((Directional) newData).setFacing(flipFaceX(((Directional) data).getFacing()));\r\n            if (data instanceof Chest) {\r\n                ((Chest) newData).setType(flipChestType(((Chest) data).getType()));\r\n            }\r\n            else if (data instanceof Door) {\r\n                switch (((Door) data).getFacing()) {\r\n                    case NORTH:\r\n                    case SOUTH:\r\n                        ((Door) newData).setHinge(flipDoorHinge(((Door) data).getHinge()));\r\n                        break;\r\n                }\r\n            }\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Rail) {\r\n            BlockData newData = data.clone();\r\n            ((Rail) newData).setShape(flipRailShapeX(((Rail) data).getShape()));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof MultipleFacing) {\r\n            MultipleFacing newData = (MultipleFacing) data.clone();\r\n            for (BlockFace face : ((MultipleFacing) data).getFaces()) {\r\n                newData.setFace(face, false);\r\n            }\r\n            for (BlockFace face : ((MultipleFacing) data).getFaces()) {\r\n                newData.setFace(flipFaceX(face), true);\r\n            }\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof RedstoneWire) {\r\n            RedstoneWire newData = (RedstoneWire) data.clone();\r\n            newData.setFace(BlockFace.NORTH, ((RedstoneWire) data).getFace(BlockFace.NORTH));\r\n            newData.setFace(BlockFace.WEST, ((RedstoneWire) data).getFace(BlockFace.EAST));\r\n            newData.setFace(BlockFace.EAST, ((RedstoneWire) data).getFace(BlockFace.WEST));\r\n            newData.setFace(BlockFace.SOUTH, ((RedstoneWire) data).getFace(BlockFace.SOUTH));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Wall) {\r\n            Wall newData = (Wall) data.clone();\r\n            newData.setHeight(BlockFace.NORTH, ((Wall) data).getHeight(BlockFace.NORTH));\r\n            newData.setHeight(BlockFace.WEST, ((Wall) data).getHeight(BlockFace.EAST));\r\n            newData.setHeight(BlockFace.EAST, ((Wall) data).getHeight(BlockFace.WEST));\r\n            newData.setHeight(BlockFace.SOUTH, ((Wall) data).getHeight(BlockFace.SOUTH));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        return this;\r\n    }\r\n\r\n    public static BlockFace flipFaceY(BlockFace face) {\r\n        switch (face) {\r\n            case DOWN:\r\n                return BlockFace.UP;\r\n            case UP:\r\n                return BlockFace.DOWN;\r\n        }\r\n        return face;\r\n    }\r\n\r\n    public FullBlockData flipY() {\r\n        if (data instanceof Bisected) {\r\n            BlockData newData = data.clone();\r\n            ((Bisected) newData).setHalf(flipBisectedHalf(((Bisected) data).getHalf()));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof MultipleFacing) {\r\n            MultipleFacing newData = (MultipleFacing) data.clone();\r\n            for (BlockFace face : ((MultipleFacing) data).getFaces()) {\r\n                newData.setFace(face, false);\r\n            }\r\n            for (BlockFace face : ((MultipleFacing) data).getFaces()) {\r\n                newData.setFace(flipFaceY(face), true);\r\n            }\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        return this;\r\n    }\r\n\r\n    public static BlockFace flipFaceZ(BlockFace face) {\r\n        switch (face) {\r\n            case NORTH:\r\n                return BlockFace.SOUTH;\r\n            case SOUTH:\r\n                return BlockFace.NORTH;\r\n            case NORTH_EAST:\r\n                return BlockFace.SOUTH_EAST;\r\n            case NORTH_WEST:\r\n                return BlockFace.SOUTH_WEST;\r\n            case SOUTH_WEST:\r\n                return BlockFace.NORTH_WEST;\r\n            case SOUTH_EAST:\r\n                return BlockFace.NORTH_EAST;\r\n            case NORTH_NORTH_EAST:\r\n                return BlockFace.SOUTH_SOUTH_EAST;\r\n            case NORTH_NORTH_WEST:\r\n                return BlockFace.SOUTH_SOUTH_WEST;\r\n            case SOUTH_SOUTH_WEST:\r\n                return BlockFace.NORTH_NORTH_WEST;\r\n            case SOUTH_SOUTH_EAST:\r\n                return BlockFace.NORTH_NORTH_EAST;\r\n            case EAST_NORTH_EAST:\r\n                return BlockFace.EAST_SOUTH_EAST;\r\n            case WEST_NORTH_WEST:\r\n                return BlockFace.WEST_SOUTH_WEST;\r\n            case WEST_SOUTH_WEST:\r\n                return BlockFace.WEST_NORTH_WEST;\r\n            case EAST_SOUTH_EAST:\r\n                return BlockFace.EAST_NORTH_EAST;\r\n        }\r\n        return face;\r\n    }\r\n\r\n    public static Rail.Shape flipRailShapeZ(Rail.Shape shape) {\r\n        switch (shape) {\r\n            case ASCENDING_NORTH:\r\n                return Rail.Shape.ASCENDING_SOUTH;\r\n            case ASCENDING_SOUTH:\r\n                return Rail.Shape.ASCENDING_NORTH;\r\n            case SOUTH_EAST:\r\n                return Rail.Shape.NORTH_EAST;\r\n            case SOUTH_WEST:\r\n                return Rail.Shape.NORTH_WEST;\r\n            case NORTH_WEST:\r\n                return Rail.Shape.SOUTH_WEST;\r\n            case NORTH_EAST:\r\n                return Rail.Shape.SOUTH_EAST;\r\n        }\r\n        return shape;\r\n    }\r\n\r\n    public static BlockFace flipStairFaceZ(Stairs.Shape shape, BlockFace face) {\r\n        switch (shape) {\r\n            case INNER_RIGHT:\r\n            case OUTER_RIGHT:\r\n                switch (face) {\r\n                    case NORTH:\r\n                        return BlockFace.EAST;\r\n                    case EAST:\r\n                        return BlockFace.NORTH;\r\n                    case SOUTH:\r\n                        return BlockFace.WEST;\r\n                    case WEST:\r\n                        return BlockFace.SOUTH;\r\n                }\r\n            case INNER_LEFT:\r\n            case OUTER_LEFT:\r\n                switch (face) {\r\n                    case NORTH:\r\n                        return BlockFace.WEST;\r\n                    case EAST:\r\n                        return BlockFace.SOUTH;\r\n                    case SOUTH:\r\n                        return BlockFace.EAST;\r\n                    case WEST:\r\n                        return BlockFace.NORTH;\r\n                }\r\n            case STRAIGHT:\r\n                switch (face) {\r\n                    case NORTH:\r\n                        return BlockFace.SOUTH;\r\n                    case SOUTH:\r\n                        return BlockFace.NORTH;\r\n                }\r\n        }\r\n        return face;\r\n    }\r\n\r\n    public FullBlockData flipZ() {\r\n        if (data instanceof Rotatable) {\r\n            BlockData newData = data.clone();\r\n            ((Rotatable) newData).setRotation(flipFaceZ(((Rotatable) data).getRotation()));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Stairs) {\r\n            BlockData newData = data.clone();\r\n            BlockFace face = ((Stairs) data).getFacing();\r\n            Stairs.Shape shape = ((Stairs) data).getShape();\r\n            ((Stairs) newData).setFacing(flipStairFaceZ(shape, face));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Directional) {\r\n            BlockData newData = data.clone();\r\n            ((Directional) newData).setFacing(flipFaceZ(((Directional) data).getFacing()));\r\n            if (data instanceof Chest) {\r\n                ((Chest) newData).setType(flipChestType(((Chest) data).getType()));\r\n            }\r\n            else if (data instanceof Door) {\r\n                switch (((Door) data).getFacing()) {\r\n                    case EAST:\r\n                    case WEST:\r\n                        ((Door) newData).setHinge(flipDoorHinge(((Door) data).getHinge()));\r\n                        break;\r\n                }\r\n            }\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Rail) {\r\n            BlockData newData = data.clone();\r\n            ((Rail) newData).setShape(flipRailShapeZ(((Rail) data).getShape()));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof MultipleFacing) {\r\n            MultipleFacing newData = (MultipleFacing) data.clone();\r\n            for (BlockFace face : ((MultipleFacing) data).getFaces()) {\r\n                newData.setFace(face, false);\r\n            }\r\n            for (BlockFace face : ((MultipleFacing) data).getFaces()) {\r\n                newData.setFace(flipFaceZ(face), true);\r\n            }\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof RedstoneWire) {\r\n            RedstoneWire newData = (RedstoneWire) data.clone();\r\n            newData.setFace(BlockFace.NORTH, ((RedstoneWire) data).getFace(BlockFace.SOUTH));\r\n            newData.setFace(BlockFace.WEST, ((RedstoneWire) data).getFace(BlockFace.WEST));\r\n            newData.setFace(BlockFace.EAST, ((RedstoneWire) data).getFace(BlockFace.EAST));\r\n            newData.setFace(BlockFace.SOUTH, ((RedstoneWire) data).getFace(BlockFace.NORTH));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        else if (data instanceof Wall) {\r\n            Wall newData = (Wall) data.clone();\r\n            newData.setHeight(BlockFace.NORTH, ((Wall) data).getHeight(BlockFace.SOUTH));\r\n            newData.setHeight(BlockFace.WEST, ((Wall) data).getHeight(BlockFace.WEST));\r\n            newData.setHeight(BlockFace.EAST, ((Wall) data).getHeight(BlockFace.EAST));\r\n            newData.setHeight(BlockFace.SOUTH, ((Wall) data).getHeight(BlockFace.NORTH));\r\n            return new FullBlockData(newData, tileEntityData, flags);\r\n        }\r\n        return this;\r\n    }\r\n\r\n    public BlockData data;\r\n\r\n    public CompoundBinaryTag tileEntityData;\r\n\r\n    public MapTag flags;\r\n\r\n    public void set(Block block, boolean physics) {\r\n        block.setBlockData(data, physics);\r\n        if (tileEntityData != null) {\r\n            NMSHandler.blockHelper.setNbtData(block, tileEntityData);\r\n        }\r\n        if (flags != null) {\r\n            MapTagBasedFlagTracker flagMap = (MapTagBasedFlagTracker) new LocationTag(block.getLocation()).getFlagTracker();\r\n            for (Map.Entry<StringHolder, ObjectTag> entry : flags.entrySet()) {\r\n                flagMap.setRootMap(entry.getKey().str, entry.getValue().asType(MapTag.class, CoreUtilities.noDebugContext));\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/SpawnableHelper.java",
    "content": "package com.denizenscript.denizen.utilities.blocks;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.World;\r\n\r\nimport java.util.HashSet;\r\n\r\npublic class SpawnableHelper {\r\n\r\n    /**\r\n     * Materials that would be a problem to spawn in/on.\r\n     */\r\n    public static HashSet<Material> DANGEROUS_MATERIALS = new HashSet<>(Utilities.allMaterialsThatMatch(\r\n            \"fire|cactus|water|lava|magma_block|*cauldron|*campfire|*portal\" // Core problems\r\n            + \"|cobweb|ladder|*fence|*door|end_rod|iron_bars|chain|*wall|*_pane\" // Unstable\r\n            + \"*egg|*plate|tripwire|*piston\")); // Could hurt\r\n\r\n    /**\r\n     * Returns true if the location would likely be safe to spawn a player at (based on material solidity at, below, and above the location).\r\n     */\r\n    public static boolean isSpawnable(Location loc) {\r\n        World w = loc.getWorld();\r\n        if (w == null) {\r\n            return false;\r\n        }\r\n        int x = loc.getBlockX();\r\n        int y = loc.getBlockY();\r\n        int z = loc.getBlockZ();\r\n        if (y - 1 <= w.getMinHeight() || y + 1 >= w.getMaxHeight()) {\r\n            return false;\r\n        }\r\n        if (!w.getBlockAt(x, y + 1, z).getType().isAir()) {\r\n            return false;\r\n        }\r\n        Material self = w.getBlockAt(x, y, z).getType();\r\n        if (self.isSolid()) {\r\n            return false;\r\n        }\r\n        if (DANGEROUS_MATERIALS.contains(self)) {\r\n            return false;\r\n        }\r\n        Material below = w.getBlockAt(x, y - 1, z).getType();\r\n        if (!below.isSolid()) {\r\n            return false;\r\n        }\r\n        if (DANGEROUS_MATERIALS.contains(below)) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/blocks/SpongeSchematicHelper.java",
    "content": "package com.denizenscript.denizen.utilities.blocks;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.BlockHelper;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.kyori.adventure.nbt.*;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.util.BlockVector;\r\n\r\nimport java.io.ByteArrayOutputStream;\r\nimport java.io.InputStream;\r\nimport java.io.OutputStream;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.*;\r\nimport java.util.concurrent.ConcurrentHashMap;\r\n\r\npublic class SpongeSchematicHelper {\r\n\r\n    public static String stringifyTag(BinaryTag tag) {\r\n        if (tag instanceof StringBinaryTag stringTag) {\r\n            return stringTag.value();\r\n        }\r\n        else if (tag instanceof ByteArrayBinaryTag byteArrayTag) {\r\n            return new String(byteArrayTag.value(), StandardCharsets.UTF_8);\r\n        }\r\n        return tag.toString();\r\n    }\r\n\r\n    public static ConcurrentHashMap<String, BlockData> blockDataCache = new ConcurrentHashMap<>();\r\n\r\n    public static BlockData unstableParseMaterial(String key) {\r\n        BlockData data;\r\n        try {\r\n            data = NMSHandler.blockHelper.parseBlockData(key);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n            MaterialTag material = MaterialTag.valueOf(BlockHelper.getMaterialNameFromBlockData(key), CoreUtilities.noDebugContext);\r\n            data = (material == null ? new MaterialTag(Material.AIR) : material).getModernData();\r\n        }\r\n        return data;\r\n    }\r\n\r\n    public static class BoolHolder {\r\n        public boolean bool;\r\n    }\r\n\r\n    // Referenced from WorldEdit source and Sponge schematic format v2 documentation\r\n    // Some values are custom and specific to Denizen\r\n    public static CuboidBlockSet fromSpongeStream(InputStream is) {\r\n        boolean isPrimary = Bukkit.isPrimaryThread();\r\n        CuboidBlockSet cbs = new CuboidBlockSet();\r\n        try {\r\n            Map.Entry<String, CompoundBinaryTag> rootTag = BinaryTagIO.reader().readNamed(is, BinaryTagIO.Compression.GZIP);\r\n            if (!rootTag.getKey().equals(\"Schematic\")) {\r\n                throw new Exception(\"Tag 'Schematic' does not exist or is not first!\");\r\n            }\r\n            CompoundBinaryTag schematic = rootTag.getValue();\r\n            if (schematic.contains(\"DenizenEntities\")) {\r\n                String entities = stringifyTag(schematic.get(\"DenizenEntities\"));\r\n                cbs.entities = ListTag.valueOf(entities, CoreUtilities.errorButNoDebugContext);\r\n            }\r\n            short width = getChildTag(schematic, \"Width\", BinaryTagTypes.SHORT).value();\r\n            short length = getChildTag(schematic, \"Length\", BinaryTagTypes.SHORT).value();\r\n            short height = getChildTag(schematic, \"Height\", BinaryTagTypes.SHORT).value();\r\n            int originX = 0;\r\n            int originY = 0;\r\n            int originZ = 0;\r\n            if (schematic.contains(\"DenizenOffset\")) {\r\n                // Note: \"Offset\" contains complete nonsense from WE, so just don't touch it.\r\n                int[] offsetArr = getChildTag(schematic, \"DenizenOffset\", BinaryTagTypes.INT_ARRAY).value();\r\n                originX = offsetArr[0];\r\n                originY = offsetArr[1];\r\n                originZ = offsetArr[2];\r\n            }\r\n            cbs.x_width = width;\r\n            cbs.z_height = length;\r\n            cbs.y_length = height;\r\n            cbs.center_x = originX;\r\n            cbs.center_y = originY;\r\n            cbs.center_z = originZ;\r\n            cbs.blocks = new FullBlockData[width * length * height];\r\n            CompoundBinaryTag paletteTag = getChildTag(schematic, \"Palette\", BinaryTagTypes.COMPOUND);\r\n            HashMap<Integer, BlockData> palette = new HashMap<>(256);\r\n            List<Map.Entry<Integer, String>> latePairs = isPrimary ? null : new ArrayList<>();\r\n            for (String key : paletteTag.keySet()) {\r\n                int id = getChildTag(paletteTag, key, BinaryTagTypes.INT).value();\r\n                if (isPrimary) {\r\n                    palette.put(id, blockDataCache.computeIfAbsent(key, SpongeSchematicHelper::unstableParseMaterial));\r\n                }\r\n                else {\r\n                    BlockData entry = blockDataCache.get(key);\r\n                    if (entry != null) {\r\n                        palette.put(id, entry);\r\n                    }\r\n                    else {\r\n                        latePairs.add(new AbstractMap.SimpleEntry<>(id, key));\r\n                    }\r\n                }\r\n            }\r\n            if (!isPrimary && !latePairs.isEmpty()) {\r\n                BoolHolder bool = new BoolHolder();\r\n                Bukkit.getScheduler().runTask(Denizen.getInstance(), () -> {\r\n                    for (Map.Entry<Integer, String> pair : latePairs) {\r\n                        palette.put(pair.getKey(), blockDataCache.computeIfAbsent(pair.getValue(), SpongeSchematicHelper::unstableParseMaterial));\r\n                    }\r\n                    bool.bool = true;\r\n                });\r\n                for (int i = 0; i < 1000; i++) {\r\n                    Thread.sleep(50);\r\n                    if (bool.bool) {\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            Map<BlockVector, CompoundBinaryTag> tileEntitiesMap = new HashMap<>();\r\n            if (schematic.contains(\"BlockEntities\")) {\r\n                ListBinaryTag tileEntities = getChildTag(schematic, \"BlockEntities\", BinaryTagTypes.LIST);\r\n                for (BinaryTag tag : tileEntities) {\r\n                    if (!(tag instanceof CompoundBinaryTag compoundTag)) {\r\n                        continue;\r\n                    }\r\n                    int[] pos = getChildTag(compoundTag, \"Pos\", BinaryTagTypes.INT_ARRAY).value();\r\n                    int x = pos[0];\r\n                    int y = pos[1];\r\n                    int z = pos[2];\r\n                    BlockVector vec = new BlockVector(x, y, z);\r\n                    tileEntitiesMap.put(vec, compoundTag);\r\n                }\r\n            }\r\n            byte[] blocks = getChildTag(schematic, \"BlockData\", BinaryTagTypes.BYTE_ARRAY).value();\r\n            int i = 0;\r\n            int index = 0;\r\n            while (i < blocks.length) {\r\n                int value = 0;\r\n                int varintLength = 0;\r\n                while (true) {\r\n                    value |= (blocks[i] & 127) << (varintLength++ * 7);\r\n                    if (varintLength > 5) {\r\n                        throw new Exception(\"Schem file blocks tag data corrupted\");\r\n                    }\r\n                    if ((blocks[i] & 128) != 128) {\r\n                        i++;\r\n                        break;\r\n                    }\r\n                    i++;\r\n                }\r\n                FullBlockData block = new FullBlockData(palette.get(value));\r\n                int y = index / (width * length);\r\n                int z = (index % (width * length)) / width;\r\n                int x = (index % (width * length)) % width;\r\n                int cbsIndex = z + y * cbs.z_height + x * cbs.z_height * cbs.y_length;\r\n                BlockVector pt = new BlockVector(x, y, z);\r\n                if (tileEntitiesMap.containsKey(pt)) {\r\n                    block.tileEntityData = tileEntitiesMap.get(pt);\r\n                }\r\n                cbs.blocks[cbsIndex] = block;\r\n                index++;\r\n            }\r\n            if (schematic.contains(\"DenizenFlags\")) {\r\n                CompoundBinaryTag flags = getChildTag(schematic, \"DenizenFlags\", BinaryTagTypes.COMPOUND);\r\n                for (Map.Entry<String, ? extends BinaryTag> flagData : flags) {\r\n                    int flagIndex = Integer.parseInt(flagData.getKey());\r\n                    cbs.blocks[flagIndex].flags = MapTag.valueOf(stringifyTag(flagData.getValue()), CoreUtilities.noDebugContext);\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            throw new RuntimeException(\"Failed to load Sponge-format schematic file\", e);\r\n        }\r\n        return cbs;\r\n    }\r\n\r\n    private static <T extends BinaryTag> T getChildTag(CompoundBinaryTag compoundTag, String key, BinaryTagType<T> expected) throws Exception {\r\n        BinaryTag tag = compoundTag.get(key);\r\n        if (tag == null) {\r\n            throw new Exception(\"Schem file is missing a '\" + key + \"' tag\");\r\n        }\r\n        if (tag.type() != expected) {\r\n            throw new Exception(key + \" tag is not of tag type \" + expected);\r\n        }\r\n        return (T) tag;\r\n    }\r\n\r\n    public static void saveToSpongeStream(CuboidBlockSet blockSet, OutputStream os) {\r\n        try {\r\n            CompoundBinaryTag.Builder schematic = CompoundBinaryTag.builder();\r\n            schematic.putShort(\"Width\", (short) blockSet.x_width);\r\n            schematic.putShort(\"Length\", (short) blockSet.z_height);\r\n            schematic.putShort(\"Height\", (short) blockSet.y_length);\r\n            schematic.putIntArray(\"DenizenOffset\", new int[] {blockSet.center_x, blockSet.center_y, blockSet.center_z});\r\n            if (blockSet.entities != null) {\r\n                schematic.putByteArray(\"DenizenEntities\", blockSet.entities.toString().getBytes(StandardCharsets.UTF_8));\r\n            }\r\n            Map<String, BinaryTag> palette = new HashMap<>();\r\n            ByteArrayOutputStream blocksBuffer = new ByteArrayOutputStream((blockSet.x_width) * (blockSet.y_length) * (blockSet.z_height));\r\n            ListBinaryTag.Builder<CompoundBinaryTag> tileEntities = ListBinaryTag.builder(BinaryTagTypes.COMPOUND);\r\n            int paletteMax = 0;\r\n            for (int y = 0; y < blockSet.y_length; y++) {\r\n                for (int z = 0; z < blockSet.z_height; z++) {\r\n                    for (int x = 0; x < blockSet.x_width; x++) {\r\n                        int cbsIndex = z + y * blockSet.z_height + x * blockSet.z_height * blockSet.y_length;\r\n                        FullBlockData bd = blockSet.blocks[cbsIndex];\r\n                        String dataStr = bd.data.getAsString();\r\n                        BinaryTag blockIdTag = palette.get(dataStr);\r\n                        if (blockIdTag == null) {\r\n                            blockIdTag = IntBinaryTag.intBinaryTag(paletteMax++);\r\n                            palette.put(dataStr, blockIdTag);\r\n                        }\r\n                        int blockId = ((IntBinaryTag) blockIdTag).value();\r\n                        while ((blockId & -128) != 0) {\r\n                            blocksBuffer.write(blockId & 127 | 128);\r\n                            blockId >>>= 7;\r\n                        }\r\n                        blocksBuffer.write(blockId);\r\n                        CompoundBinaryTag rawTag = bd.tileEntityData;\r\n                        if (rawTag != null) {\r\n                            CompoundBinaryTag tileEntityTag = rawTag.putIntArray(\"Pos\", new int[] { x, y, z });\r\n                            tileEntities.add(tileEntityTag);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            schematic.putInt(\"PaletteMax\", paletteMax);\r\n            schematic.put(\"Palette\", CompoundBinaryTag.from(palette));\r\n            schematic.putByteArray(\"BlockData\", blocksBuffer.toByteArray());\r\n            schematic.put(\"BlockEntities\", tileEntities.build());\r\n            if (blockSet.hasFlags) {\r\n                Map<String, BinaryTag> flagMap = new HashMap<>();\r\n                for (int i = 0; i < blockSet.blocks.length; i++) {\r\n                    if (blockSet.blocks[i].flags != null) {\r\n                        flagMap.put(String.valueOf(i), ByteArrayBinaryTag.byteArrayBinaryTag(blockSet.blocks[i].flags.toString().getBytes(StandardCharsets.UTF_8)));\r\n                    }\r\n                }\r\n                if (!flagMap.isEmpty()) {\r\n                    schematic.put(\"DenizenFlags\", CompoundBinaryTag.from(flagMap));\r\n                }\r\n            }\r\n            BinaryTagIO.writer().writeNamed(Map.entry(\"Schematic\", schematic.build()), os, BinaryTagIO.Compression.GZIP);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/CommandEvents.java",
    "content": "package com.denizenscript.denizen.utilities.command;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerCommandSendEvent;\r\n\r\npublic class CommandEvents implements Listener {\r\n\r\n    public CommandEvents() {\r\n        Bukkit.getPluginManager().registerEvents(this, Denizen.getInstance());\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.LOW)\r\n    public void onPlayerCommandSend(PlayerCommandSendEvent event) {\r\n        event.getCommands().remove(\"denizenclickable\");\r\n        event.getCommands().remove(\"denizen:denizenclickable\");\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/DenizenCommandHandler.java",
    "content": "package com.denizenscript.denizen.utilities.command;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.utilities.command.manager.Command;\r\nimport com.denizenscript.denizen.utilities.command.manager.CommandContext;\r\nimport com.denizenscript.denizen.utilities.command.manager.Paginator;\r\nimport com.denizenscript.denizen.utilities.command.manager.exceptions.CommandException;\r\nimport com.denizenscript.denizen.utilities.command.manager.messaging.Messaging;\r\nimport com.denizenscript.denizen.utilities.debugging.DebugConsoleSender;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.scripts.ScriptHelper;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugSubmitter;\r\nimport org.bukkit.command.CommandSender;\r\n\r\nimport java.util.Set;\r\n\r\npublic class DenizenCommandHandler {\r\n\r\n    public DenizenCommandHandler() {\r\n    }\r\n\r\n    // <--[language]\r\n    // @name Denizen Permissions\r\n    // @group Console Commands\r\n    // @description\r\n    // The following is a list of all permission nodes Denizen uses within Bukkit.\r\n    //\r\n    // denizen.clickable     # use the 'denizenclickable' command, which is automatically executed when using <@link command clickable> and for clickable chat triggers\r\n    // denizen.basic         # use the basics of the /denizen command\r\n    // denizen.ex            # use the /ex command\r\n    // denizen.debug         # use the /denizen debug command\r\n    // denizen.submit        # use the /denizen submit command\r\n    //\r\n    // Additionally:\r\n    // denizen.npc.health, denizen.npc.sneak,\r\n    // denizen.npc.effect, denizen.npc.fish, denizen.npc.sleep, denizen.npc.stand,\r\n    // denizen.npc.sit, denizen.npc.nameplate, denizen.npc.nickname, denizen.npc.trigger,\r\n    // denizen.npc.assign, denizen.npc.constants, denizen.npc.pushable\r\n    //\r\n    // However, we recommend just giving op to whoever needs to access Denizen - they can op themselves through Denizen anyway, why not save the trouble?\r\n    // ( EG, /ex execute as_server \"op <player.name>\" )\r\n    //\r\n    // -->\r\n\r\n    // <--[language]\r\n    // @name /denizen submit command\r\n    // @group Console Commands\r\n    // @description\r\n    // Use the '/denizen submit' command with '/denizen debug -r' to record debug output and post it online for assisting developers to see.\r\n    //\r\n    // To begin recording, simply use '/denizen debug -r'.\r\n    // After that, any debug output sent to the console and any player chat will be added to an internal record.\r\n    // Once enabled, you should then fire off scripts and events that aren't working fully.\r\n    // Finally, you use the '/denizen submit' command to take all the recording information and paste it to an online pastebin hosted by the Denizen team.\r\n    // It will give you back a direct link to the full debug output, which you can view yourself and send to other helpers without trouble.\r\n    //\r\n    // There is no limit to the recording size, to prevent any important information from being trimmed away.\r\n    // Be careful not to leave debug recording enabled by accident, as it may eventually begin using up large amounts of memory.\r\n    // (The submit command will automatically disable recording, or you can instead just use '/denizen debug -r' again.)\r\n    //\r\n    // -->\r\n    @Command(\r\n            aliases = {\"denizen\"}, usage = \"submit\",\r\n            desc = \"Submits recorded logs triggered by /denizen debug -r\", modifiers = {\"submit\"},\r\n            min = 1, max = 3, permission = \"denizen.submit\")\r\n    public void submit(CommandContext args, final CommandSender sender) throws CommandException {\r\n        if (!CoreConfiguration.shouldRecordDebug) {\r\n            Messaging.sendError(sender, \"Use /denizen debug -r  to record debug information to be submitted\");\r\n            return;\r\n        }\r\n        Messaging.send(sender, \"Submitting...\");\r\n        DebugSubmitter.submitCurrentRecording((s) -> {\r\n            if (s == null) {\r\n                Messaging.sendError(sender, \"Error while submitting.\");\r\n            }\r\n            else if (s.equals(\"disabled\")) {\r\n                Messaging.sendError(sender, \"Submit failed: not recording.\");\r\n            }\r\n            else {\r\n                Messaging.send(sender, \"Successfully submitted to \" + s);\r\n            }\r\n        });\r\n    }\r\n\r\n    // <--[language]\r\n    // @name /denizen debug command\r\n    // @group Console Commands\r\n    // @description\r\n    // Using the /denizen debug command interfaces with Denizen's debugger to allow control over debug messages.\r\n    //\r\n    // To enable debugging mode, simply type '/denizen debug'.\r\n    // While debug is enabled, all debuggable scripts, and any invoked actions, will output information to the console as they are executed.\r\n    // By default, all scripts are debuggable while the debugger is enabled.\r\n    // To disable a script specifically from debugging, simply add the 'debug:' node with a value of 'false' to your script container.\r\n    // This is typically used to silence particularly spammy scripts. Any kind of script container can be silenced using this method.\r\n    //\r\n    // To stop debugging, simply type the '/denizen debug' command again.\r\n    // This must be used without any additional options. A message will be sent to show the current status of the debugger.\r\n    //\r\n    // Note: you should almost NEVER disable debug entirely. Instead, always disable it on a per-script basis.\r\n    // If debug is globally disabled, that will hide important error messages, not just normal debug output.\r\n    //\r\n    // There are also several options to further help debugging. To use an option, simply attach them to the /denizen debug command.\r\n    // One option, or multiple options can be used. For example: /denizen debug -sbi\r\n    //\r\n    // '-c' enables/disables color. This is sometimes useful when debugging with a non-color console.\r\n    // '-r' enables recording mode. See also: /denizen submit command\r\n    // '-s' enables/disables stacktraces generated by Denizen. We might ask you to enable this when problems arise.\r\n    // '-b' enables/disables the ScriptBuilder debug. When enabled, Denizen will show info on script and argument creation. Warning: Can be spammy.\r\n    // '-n' enables/disables debug trimming. When enabled, messages longer than 1024 characters will be 'snipped'.\r\n    // '-i' enables/disables source information. When enabled, debug will show where it came from (when possible).\r\n    // '-p' enables/disables packet debug logging. When enabled, all packets sent to players (from anywhere) will be logged to console.\r\n    // or, '--pfilter (filter)' to enable packet debug logging with a string contain filter.\r\n    // '-f' enables/disables showing of future warnings. When enabled, future warnings (such as upcoming deprecations) will be displayed in console logs.\r\n    // '-e' enables/disables extra output. This will spam more information about various internal things.\r\n    // '-v' enables/disables advanced ultra-verbose log output. This will *flood* your console super hard.\r\n    // '-o' enables/disables 'override' mode. This will display all script debug, even when 'debug: false' is set for scripts.\r\n    // '-l' enables/disables script loading information. When enabled, '/ex reload' will produce a potentially large amount of debug output.\r\n    //\r\n    // -->\r\n\r\n    /*\r\n     * DENIZEN DEBUG\r\n     */\r\n    @Command(\r\n            aliases = {\"denizen\"}, usage = \"debug (--verbose on) (--ultraverbose on)\",\r\n            desc = \"Toggles debug mode for Denizen.\", modifiers = {\"debug\", \"de\", \"db\", \"dbug\"},\r\n            min = 1, max = 5, permission = \"denizen.debug\", flags = \"scbroevnipfl\")\r\n    public void debug(CommandContext args, CommandSender sender) throws CommandException {\r\n        if (args.getFlags().isEmpty() && args.getValueFlags().isEmpty()) {\r\n            CoreConfiguration.shouldShowDebug = !CoreConfiguration.shouldShowDebug;\r\n            Messaging.sendInfo(sender, \"Denizen debugger is now: \" + (CoreConfiguration.shouldShowDebug ? \"<a>ENABLED\" : \"<c>DISABLED\") + \"<f>.\");\r\n            return;\r\n        }\r\n        CoreConfiguration.shouldShowDebug = true;\r\n        if (args.hasFlag('s')) {\r\n            CoreConfiguration.debugStackTraces = !CoreConfiguration.debugStackTraces;\r\n            Messaging.sendInfo(sender, (CoreConfiguration.debugStackTraces ? \"Denizen debugger is now showing caught \" +\r\n                    \"exception stack traces.\" : \"Denizen debugger is no longer showing caught stack traces.\"));\r\n        }\r\n        if (args.hasFlag('c')) {\r\n            DebugConsoleSender.showColor = !DebugConsoleSender.showColor;\r\n            Messaging.sendInfo(sender, (DebugConsoleSender.showColor ? \"Denizen debugger will now show color.\"\r\n                    : \"Denizen debugger will no longer show color.\"));\r\n        }\r\n        if (args.hasFlag('o')) {\r\n            CoreConfiguration.debugOverride = !CoreConfiguration.debugOverride;\r\n            Messaging.sendInfo(sender, (CoreConfiguration.debugOverride ? \"Denizen debugger is now overriding 'debug: false'.\"\r\n                    : \"Denizen debugger will no longer override 'debug: false'.\"));\r\n        }\r\n        if (args.hasFlag('b')) {\r\n            CoreConfiguration.debugScriptBuilder = !CoreConfiguration.debugScriptBuilder;\r\n            Messaging.sendInfo(sender, (CoreConfiguration.debugScriptBuilder ? \"Denizen debugger is now logging the \" +\r\n                    \"ScriptBuilder.\" : \"Denizen debugger is now hiding ScriptBuilder logging.\"));\r\n        }\r\n        if (args.hasFlag('r')) {\r\n            if (!CoreConfiguration.debugRecordingAllowed) {\r\n                Messaging.sendError(sender, \"Not allowed to record debug currently.\");\r\n                return;\r\n            }\r\n            CoreConfiguration.shouldRecordDebug = !CoreConfiguration.shouldRecordDebug;\r\n            Debug.debugRecording = new StringBuilder();\r\n            Messaging.sendInfo(sender, (CoreConfiguration.shouldRecordDebug ? \"Denizen debugger is now recording. Use /denizen \" +\r\n                    \"submit to finish.\" : \"Denizen debugger recording disabled.\"));\r\n        }\r\n        if (args.hasFlag('e')) {\r\n            CoreConfiguration.debugExtraInfo = !CoreConfiguration.debugExtraInfo;\r\n            Messaging.sendInfo(sender, (CoreConfiguration.debugExtraInfo ? \"Denizen debugger is now showing extra internal information.\" :\r\n                    \"Denizen debugger is no longer showing extra internal information.\"));\r\n        }\r\n        if (args.hasFlag('v') || args.hasValueFlag(\"verbose\")) {\r\n            CoreConfiguration.debugVerbose = !CoreConfiguration.debugVerbose;\r\n            CoreConfiguration.debugUltraVerbose = false;\r\n            Messaging.sendInfo(sender, (CoreConfiguration.debugVerbose ? \"Denizen debugger is now verbose.\" :\r\n                    \"Denizen debugger is no longer verbose.\"));\r\n        }\r\n        if (args.hasValueFlag(\"ultraverbose\")) {\r\n            CoreConfiguration.debugUltraVerbose = !CoreConfiguration.debugUltraVerbose;\r\n            CoreConfiguration.debugVerbose = CoreConfiguration.debugUltraVerbose;\r\n            Messaging.sendInfo(sender, (CoreConfiguration.debugVerbose ? \"Denizen debugger is now ultra-verbose.\" :\r\n                    \"Denizen debugger is no longer ultra-verbose.\"));\r\n        }\r\n        if (args.hasFlag('f')) {\r\n            CoreConfiguration.futureWarningsEnabled = !CoreConfiguration.futureWarningsEnabled;\r\n            Messaging.sendInfo(sender, (CoreConfiguration.futureWarningsEnabled ? \"Denizen debugger is now showing future warnings.\" :\r\n                    \"Denizen debugger will no longer show future warnings.\"));\r\n        }\r\n        if (args.hasFlag('n')) {\r\n            CoreConfiguration.debugShouldTrim = !CoreConfiguration.debugShouldTrim;\r\n            Messaging.sendInfo(sender, (CoreConfiguration.debugShouldTrim ? \"Denizen debugger is now trimming long messages.\"\r\n                    : \"Denizen debugger is no longer trimming long messages.\"));\r\n        }\r\n        if (args.hasFlag('i')) {\r\n            CoreConfiguration.debugShowSources = !CoreConfiguration.debugShowSources;\r\n            Messaging.sendInfo(sender, (CoreConfiguration.debugShowSources ? \"Denizen debugger is now showing source information.\"\r\n                    : \"Denizen debugger is no longer showing source information.\"));\r\n        }\r\n        if (args.hasFlag('p')) {\r\n            NetworkInterceptHelper.enable();\r\n            NMSHandler.debugPackets = !NMSHandler.debugPackets;\r\n            NMSHandler.debugPacketFilter = \"\";\r\n            Messaging.sendInfo(sender, (NMSHandler.debugPackets ? \"Denizen debugger is now showing unfiltered packet logs.\"\r\n                    : \"Denizen debugger is no longer showing packet logs.\"));\r\n        }\r\n        if (args.hasValueFlag(\"pfilter\")) {\r\n            NetworkInterceptHelper.enable();\r\n            NMSHandler.debugPackets = true;\r\n            NMSHandler.debugPacketFilter = CoreUtilities.toLowerCase(args.getFlag(\"pfilter\"));\r\n            Messaging.sendInfo(sender, \"Denizen debug packet log now enabled and filtered.\");\r\n            return;\r\n        }\r\n        if (args.hasFlag('l')) {\r\n            CoreConfiguration.debugLoadingInfo = !CoreConfiguration.debugLoadingInfo;\r\n            Messaging.sendInfo(sender, (CoreConfiguration.debugLoadingInfo ? \"Denizen debugger is now showing script loading information.\"\r\n                    : \"Denizen debugger is no longer showing script loading information.\"));\r\n        }\r\n    }\r\n\r\n    /*\r\n     * DENIZEN DO_NOTHING\r\n     */\r\n    @Command(\r\n            aliases = {\"denizen\"}, usage = \"do_nothing\",\r\n            desc = \"Does nothing, for better server command handling.\", modifiers = {\"do_nothing\"},\r\n            min = 1, max = 3, permission = \"denizen.basic\")\r\n    public void do_nothing(CommandContext args, CommandSender sender) throws CommandException {\r\n        // Do nothing\r\n    }\r\n\r\n    /*\r\n     * DENIZEN VERSION\r\n     */\r\n    @Command(\r\n            aliases = {\"denizen\"}, usage = \"version\",\r\n            desc = \"Shows the currently loaded version of Denizen.\", modifiers = {\"version\"},\r\n            min = 1, max = 3, permission = \"denizen.basic\")\r\n    public void version(CommandContext args, CommandSender sender) throws CommandException {\r\n        Messaging.sendInfo(sender, \"<2>DENIZEN<7>: A high-power scripting engine for Spigot!\");\r\n        Messaging.send(sender, \"\");\r\n        Messaging.send(sender, \"<7>by: <f>the DenizenScript team, with help from many skilled contributors!\");\r\n        Messaging.send(sender, \"<7>chat with us at: <f>https://discord.gg/Q6pZGSR\");\r\n        Messaging.send(sender, \"<7>or learn more at: <f>https://denizenscript.com\");\r\n        Messaging.send(sender, \"<7>version: <f>\" + Denizen.versionTag + \"<7>, core version: <f>\" + DenizenCore.VERSION);\r\n    }\r\n\r\n    /*\r\n     * DENIZEN SAVE\r\n     */\r\n    @Command(\r\n            aliases = {\"denizen\"}, usage = \"save\",\r\n            desc = \"Saves the current Denizen save data to file as needed.\", modifiers = {\"save\"},\r\n            min = 1, max = 3, permission = \"denizen.basic\")\r\n    public void save(CommandContext args, CommandSender sender) throws CommandException {\r\n        DenizenCore.saveAll(false);\r\n        Denizen.getInstance().saveSaves(false);\r\n        Messaging.send(sender, \"Denizen save data saved to file from memory.\");\r\n    }\r\n\r\n    /*\r\n     * DENIZEN RELOAD\r\n     */\r\n    @Command(aliases = {\"denizen\"}, usage = \"reload (saves|notes|config|scripts) (-a)\",\r\n            desc = \"Reloads various Denizen files from disk to memory.\", modifiers = {\"reload\"},\r\n            min = 1, max = 3, permission = \"denizen.basic\", flags = \"a\")\r\n    public void reload(CommandContext args, CommandSender sender) throws CommandException {\r\n        if (args.hasFlag('a')) {\r\n            DenizenCore.implementation.reloadConfig();\r\n            DenizenCore.reloadScripts(false, null);\r\n            DenizenCore.implementation.reloadSaves();\r\n            NoteManager.reload();\r\n            Messaging.send(sender, \"Denizen save data, config, and scripts reloaded from disk to memory.\");\r\n            if (ScriptHelper.hadError()) {\r\n                Messaging.sendError(sender, \"There was an error loading your scripts, check the console for details!\");\r\n            }\r\n            return;\r\n        }\r\n        if (args.length() > 2) {\r\n            if (args.getString(1).equalsIgnoreCase(\"saves\")) {\r\n                DenizenCore.implementation.reloadSaves();\r\n                Messaging.send(sender, \"Denizen save data reloaded from disk to memory.\");\r\n                return;\r\n            }\r\n            else if (args.getString(1).equalsIgnoreCase(\"notes\")) {\r\n                NoteManager.reload();\r\n                Messaging.send(sender, \"Denizen note data reloaded from disk to memory.\");\r\n                return;\r\n            }\r\n            else if (args.getString(1).equalsIgnoreCase(\"config\")) {\r\n                DenizenCore.implementation.reloadConfig();\r\n                Messaging.send(sender, \"Denizen config file reloaded from disk to memory.\");\r\n                return;\r\n            }\r\n            else if (args.getString(1).equalsIgnoreCase(\"scripts\")) {\r\n                DenizenCore.reloadScripts(false, null);\r\n                Messaging.send(sender, \"Denizen/scripts/... reloaded from disk to memory.\");\r\n                if (ScriptHelper.hadError()) {\r\n                    Messaging.sendError(sender, \"There was an error loading your scripts, check the console for details!\");\r\n                }\r\n                Messaging.sendError(sender, \"'/denizen reload scripts' is the old way of doing things ... use '/ex reload' instead!\");\r\n                return;\r\n            }\r\n        }\r\n        Messaging.send(sender, \"\");\r\n        Messaging.send(sender, \"<f>Specify which parts to reload. Valid options are: SAVES, NOTES, CONFIG, SCRIPTS\");\r\n        Messaging.send(sender, \"<b>Example: /denizen reload saves\");\r\n        Messaging.send(sender, \"<f>Use '-a' to reload all parts at once.\");\r\n        Messaging.send(sender, \"<f>Note that you shouldn't use this command generally, instead use '/ex reload' - see also the Beginner's Guide at https://guide.denizenscript.com/\");\r\n        Messaging.send(sender, \"\");\r\n    }\r\n\r\n    /*\r\n     * DENIZEN SCRIPTS\r\n     */\r\n    @Command(\r\n            aliases = {\"denizen\"}, usage = \"scripts (--type assignment|task|...) (--filter string)\",\r\n            desc = \"Lists the currently loaded scripts.\", modifiers = {\"scripts\"},\r\n            min = 1, max = 4, permission = \"denizen.basic\")\r\n    public void scripts(CommandContext args, CommandSender sender) throws CommandException {\r\n        String type = null;\r\n        if (args.hasValueFlag(\"type\")) {\r\n            type = args.getFlag(\"type\");\r\n        }\r\n        String filter = null;\r\n        if (args.hasValueFlag(\"filter\")) {\r\n            filter = args.getFlag(\"filter\");\r\n        }\r\n        Set<String> scripts = ScriptRegistry.scriptContainers.keySet();\r\n        Paginator paginator = new Paginator().header(\"Scripts\");\r\n        paginator.addLine(\"<e>Key: <a>Type  <b>Name\");\r\n        for (String script : scripts) {\r\n            ScriptContainer scriptContainer = ScriptRegistry.getScriptContainer(script);\r\n            if (type != null) {\r\n                if (scriptContainer.getContainerType().equalsIgnoreCase(type)) {\r\n                    if (filter != null) {\r\n                        if (script.contains(filter.toUpperCase())) {\r\n                            paginator.addLine(\"<a>\" + scriptContainer.getContainerType().substring(0, 3) + \"  <b>\" + script);\r\n                        }\r\n                    }\r\n                    else {\r\n                        paginator.addLine(\"<a>\" + scriptContainer.getContainerType().substring(0, 3) + \"  <b>\" + script);\r\n                    }\r\n                }\r\n            }\r\n            else if (filter != null) {\r\n                if (script.contains(filter.toUpperCase())) {\r\n                    paginator.addLine(\"<a>\" + scriptContainer.getContainerType().substring(0, 3) + \"  <b>\" + script);\r\n                }\r\n            }\r\n            else {\r\n                paginator.addLine(\"<a>\" + scriptContainer.getContainerType().substring(0, 3) + \"  <b>\" + script);\r\n            }\r\n        }\r\n        if (!paginator.sendPage(sender, args.getInteger(1, 1))) {\r\n            throw new CommandException(\"The page \" + args.getInteger(1, 1) + \" does not exist!\");\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/ExCommandHandler.java",
    "content": "package com.denizenscript.denizen.utilities.command;\r\n\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.scripts.ScriptBuilder;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.scripts.commands.core.FlagCommand;\r\nimport com.denizenscript.denizencore.scripts.queues.core.InstantQueue;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.ExCommandHelper;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.command.*;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class ExCommandHandler implements CommandExecutor, TabCompleter {\r\n\r\n    public void enableFor(PluginCommand command) {\r\n        command.setExecutor(this);\r\n        command.setTabCompleter(this);\r\n    }\r\n\r\n    @Override\r\n    public boolean onCommand(CommandSender sender, Command cmd, String alias, String[] args) {\r\n\r\n        // <--[language]\r\n        // @name /ex command\r\n        // @group Console Commands\r\n        // @description\r\n        // The '/ex' command is an easy way to run a single denizen script command in-game.\r\n        // 'Ex' is short for 'Execute'.\r\n        // Its syntax, aside from '/ex' is exactly the same as any other Denizen script command.\r\n        // When running a command, some context is also supplied, such as '<player>' if being run by a player (versus the console),\r\n        // as well as '<npc>' if a NPC is selected by using the '/npc sel' command.\r\n        //\r\n        // By default, ex command debug output is sent to the player that ran the ex command (if the command was ran by a player).\r\n        // To avoid this, use '-q' at the start of the ex command.\r\n        // Like: /ex -q narrate \"wow no output\"\r\n        //\r\n        // The '/ex' command creates a new queue each time it's run,\r\n        // meaning for example '/ex define' would do nothing, as the definition will be lost immediately.\r\n        //\r\n        // If you need to sustain a queue between multiple executions, use '/exs' (\"Execute Sustained\").\r\n        // A sustained queue will use the same queue on every execution until the queue stops (normally due to '/exs stop').\r\n        // Be warned that waits will block the sustained queue - eg '/exs wait 10m' will make '/exs' effectively unusable for 10 minutes.\r\n        //\r\n        // Examples:\r\n        // /ex flag <player> test_flag:!\r\n        // /ex run npc_walk_script\r\n        //\r\n        // Need to '/ex' a command as a different player or NPC? Use <@link language The Player and NPC Arguments>.\r\n        //\r\n        // Examples:\r\n        // /ex narrate player:<[aplayer]> 'Your health is <player.health.formatted>.'\r\n        // /ex walk npc:<[some_npc]> <player.cursor_on>\r\n        //\r\n        // -->\r\n\r\n        List<Object> entries = new ArrayList<>();\r\n        String entry = String.join(\" \", args);\r\n        boolean quiet = !Settings.showExDebug();\r\n        if (entry.length() > 3 && entry.startsWith(\"-q \")) {\r\n            quiet = !quiet;\r\n            entry = entry.substring(\"-q \".length());\r\n        }\r\n        if (entry.length() < 2) {\r\n            sender.sendMessage(\"/ex (-q) <denizen script command> (arguments)\");\r\n            return true;\r\n        }\r\n        if (Settings.showExHelp()) {\r\n            if (CoreConfiguration.shouldShowDebug) {\r\n                if (quiet) {\r\n                    sender.sendMessage(ChatColor.YELLOW + \"Executing Denizen script command... check the console for full debug output!\");\r\n                }\r\n            }\r\n            else {\r\n                sender.sendMessage(ChatColor.YELLOW + \"Executing Denizen script command... to see debug, use /denizen debug\");\r\n            }\r\n        }\r\n        entries.add(entry);\r\n        InstantQueue queue = new InstantQueue(\"EXCOMMAND\");\r\n        NPCTag npc = null;\r\n        if (Depends.citizens != null && Depends.citizens.getNPCSelector().getSelected(sender) != null) {\r\n            npc = new NPCTag(Depends.citizens.getNPCSelector().getSelected(sender));\r\n        }\r\n        List<ScriptEntry> scriptEntries = ScriptBuilder.buildScriptEntries(entries, null, new BukkitScriptEntryData(sender instanceof Player player ? new PlayerTag(player) : null, npc));\r\n        queue.addEntries(scriptEntries);\r\n        if (!quiet && sender instanceof Player) {\r\n            queue.debugOutput = s -> sender.spigot().sendMessage(FormattedTextHelper.parse(s.replace(\"<FORCE_ALIGN>\", \"\"), net.md_5.bungee.api.ChatColor.WHITE));\r\n        }\r\n        queue.start();\r\n        return true;\r\n    }\r\n\r\n    static {\r\n        FlagCommand.flagTabCompleters.add(ExCommandHandler::onFlagTabComplete);\r\n    }\r\n\r\n    public static void onFlagTabComplete(AbstractCommand.TabCompletionsBuilder builder) {\r\n        BukkitTagContext context = (BukkitTagContext) builder.context;\r\n        if (context.player != null) {\r\n            for (String flagName : context.player.getFlagTracker().listAllFlags()) {\r\n                if (!flagName.startsWith(\"__\")) {\r\n                    builder.add(flagName);\r\n                }\r\n            }\r\n        }\r\n        if (context.npc != null) {\r\n            for (String flagName : context.npc.getFlagTracker().listAllFlags()) {\r\n                if (!flagName.startsWith(\"__\")) {\r\n                    builder.add(flagName);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public List<String> onTabComplete(CommandSender sender, Command cmd, String alias, String[] rawArgs) {\r\n        BukkitTagContext context = new BukkitTagContext(sender instanceof Player player ? new PlayerTag(player) : null, null, null);\r\n        if (Depends.citizens != null && Depends.citizens.getNPCSelector().getSelected(sender) != null) {\r\n            context.npc = new NPCTag(Depends.citizens.getNPCSelector().getSelected(sender));\r\n        }\r\n        return ExCommandHelper.buildTabCompletions(rawArgs, context);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/ExSustainedCommandHandler.java",
    "content": "package com.denizenscript.denizen.utilities.command;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.utilities.implementation.BukkitScriptEntryData;\r\nimport com.denizenscript.denizencore.scripts.ScriptBuilder;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.queues.core.TimedQueue;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.command.*;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerQuitEvent;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic class ExSustainedCommandHandler implements CommandExecutor, TabCompleter, Listener {\r\n\r\n    public void enableFor(PluginCommand command) {\r\n        command.setExecutor(this);\r\n        command.setTabCompleter(this);\r\n        Bukkit.getPluginManager().registerEvents(this, Denizen.getInstance());\r\n    }\r\n\r\n    public HashMap<UUID, TimedQueue> playerQueues = new HashMap<>();\r\n\r\n    @EventHandler\r\n    public void onPlayerQuit(PlayerQuitEvent event) {\r\n        playerQueues.remove(event.getPlayer().getUniqueId());\r\n    }\r\n\r\n    public TimedQueue getOrMakeQueue(final Player player, boolean quiet) {\r\n        UUID id;\r\n        if (player == null) {\r\n            id = new UUID(0, 0);\r\n        }\r\n        else {\r\n            id = player.getUniqueId();\r\n        }\r\n        TimedQueue queue = playerQueues.get(id);\r\n        if (queue != null && !queue.isStopped) {\r\n            return queue;\r\n        }\r\n        queue = new TimedQueue(\"EX_SUSTAINED_COMMAND\", 0);\r\n        queue.waitWhenEmpty = true;\r\n        playerQueues.put(id, queue);\r\n        return queue;\r\n    }\r\n\r\n    @Override\r\n    public boolean onCommand(CommandSender sender, Command cmd, String alias, String[] args) {\r\n        List<Object> entries = new ArrayList<>();\r\n        String entry = String.join(\" \", args);\r\n        boolean quiet = !Settings.showExDebug();\r\n        if (entry.length() > 3 && entry.startsWith(\"-q \")) {\r\n            quiet = !quiet;\r\n            entry = entry.substring(\"-q \".length());\r\n        }\r\n        if (entry.length() < 2) {\r\n            sender.sendMessage(\"/exs (-q) <denizen script command> (arguments)\");\r\n            return true;\r\n        }\r\n        TimedQueue queue = getOrMakeQueue(sender instanceof Player player ? player : null, quiet);\r\n        if (!quiet && sender instanceof Player) {\r\n            queue.debugOutput = s -> sender.spigot().sendMessage(FormattedTextHelper.parse(s.replace(\"<FORCE_ALIGN>\", \"\"), net.md_5.bungee.api.ChatColor.WHITE));\r\n        }\r\n        else {\r\n            queue.debugOutput = null;\r\n        }\r\n        if (queue.isPaused() || queue.isDelayed()) {\r\n            sender.sendMessage(ChatColor.YELLOW + \"Sustained queue is currently paused or waiting, adding command to queue for later execution.\");\r\n        }\r\n        else if (Settings.showExHelp()) {\r\n            if (CoreConfiguration.shouldShowDebug) {\r\n                if (quiet) {\r\n                    sender.sendMessage(ChatColor.YELLOW + \"Executing Denizen script command... check the console for full debug output!\");\r\n                }\r\n                else {\r\n                    sender.sendMessage(ChatColor.YELLOW + \"Executing Denizen script command...\");\r\n                }\r\n            }\r\n            else {\r\n                sender.sendMessage(ChatColor.YELLOW + \"Executing Denizen script command... to see debug, use /denizen debug\");\r\n            }\r\n        }\r\n        entries.add(entry);\r\n        NPCTag npc = null;\r\n        if (Depends.citizens != null && Depends.citizens.getNPCSelector().getSelected(sender) != null) {\r\n            npc = new NPCTag(Depends.citizens.getNPCSelector().getSelected(sender));\r\n        }\r\n        List<ScriptEntry> scriptEntries = ScriptBuilder.buildScriptEntries(entries, null,\r\n                new BukkitScriptEntryData(sender instanceof Player player ? new PlayerTag(player) : null, npc));\r\n        queue.addEntries(scriptEntries);\r\n        if (!queue.is_started) {\r\n            queue.start();\r\n        }\r\n        else {\r\n            queue.onStart();\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public List<String> onTabComplete(CommandSender sender, Command cmd, String alias, String[] rawArgs) {\r\n        return Denizen.getInstance().exCommand.onTabComplete(sender, cmd, alias, rawArgs);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/NPCCommandHandler.java",
    "content": "package com.denizenscript.denizen.utilities.command;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.npc.traits.*;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.AssignmentScriptContainer;\r\nimport com.denizenscript.denizen.utilities.command.manager.messaging.Messaging;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport net.citizensnpcs.Citizens;\r\nimport net.citizensnpcs.api.command.Command;\r\nimport net.citizensnpcs.api.command.CommandContext;\r\nimport net.citizensnpcs.api.command.Requirements;\r\nimport net.citizensnpcs.api.command.exception.CommandException;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport net.citizensnpcs.trait.Anchors;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.block.data.type.Bed;\r\nimport org.bukkit.block.data.type.Campfire;\r\nimport org.bukkit.block.data.type.Slab;\r\nimport org.bukkit.block.data.type.Stairs;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Player;\r\n\r\npublic class NPCCommandHandler {\r\n\r\n    public NPCCommandHandler(Citizens citizens) {\r\n    }\r\n\r\n    // <--[language]\r\n    // @name /npc pushable command\r\n    // @group Console Commands\r\n    // @description\r\n    // The '/npc pushable' command controls a NPCs Pushable Trait. When a NPC is 'pushable', the NPC\r\n    // will move out of the way when colliding with another LivingEntity.\r\n    //\r\n    // Pushable NPCs have 3 different settings available: Toggled, Returnable, and Delay.\r\n    //\r\n    // When an NPCs Pushable Trait is toggled off, it will not function. Entities which\r\n    // collide may occupy the same space. To toggle pushable on or off, use the Bukkit command:\r\n    // /npc pushable -t\r\n    //\r\n    // Setting the NPC as 'returnable' will automatically navigate the NPC back to\r\n    // its original location after a specified delay. If not returnable, NPCs will retain\r\n    // their position after being moved.\r\n    // /npc pushable -r\r\n    //\r\n    // To change the delay of a returnable NPC, use the following Bukkit Command,\r\n    // specifying the number of seconds in which the delay should be.\r\n    // /npc pushable --delay #\r\n\r\n    // It is possible to use multiple arguments at once. For example:\r\n    // /npc pushable -t -r --delay 10\r\n    //\r\n    // Note: If allowed to move in undesirable areas, the NPC may be un-returnable\r\n    // if the navigator cancels navigation due to being stuck. Care should be taken\r\n    // to ensure a safe area around the NPC.\r\n    //\r\n    // See also: 'pushable trait'\r\n    // -->\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"pushable -t (-r) (--delay #)\", desc = \"Makes an NPC pushable.\",\r\n            flags = \"rt\", modifiers = {\"pushable\", \"push\"}, min = 1, max = 2, permission = \"denizen.npc.pushable\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void pushable(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        PushableTrait trait = npc.getOrAddTrait(PushableTrait.class);\r\n        if (args.hasFlag('r') && !args.hasFlag('t')) {\r\n            trait.setReturnable(!trait.isReturnable());\r\n            Messaging.sendInfo(sender, npc.getName() + (trait.isReturnable() ? \" will \" : \" will not \") + \"return when being pushed\"\r\n                    + (!trait.isReturnable() || trait.isPushable() ? \".\" : \", but is currently not pushable.\"));\r\n            return;\r\n        }\r\n        else if (args.hasValueFlag(\"delay\") && !args.hasFlag('t')) {\r\n            if (args.getFlag(\"delay\").matches(\"\\\\d+\") && args.getFlagInteger(\"delay\") > 0) {\r\n                trait.setDelay(Integer.parseInt(args.getFlag(\"delay\")));\r\n                trait.setReturnable(true);\r\n                Messaging.sendInfo(sender, npc.getName() + \" will return after '\" + args.getFlag(\"delay\") + \"' seconds\"\r\n                        + (trait.isPushable() ? \".\" : \", but is currently not pushable.\"));\r\n            }\r\n            else {\r\n                Messaging.sendError(sender, \"Delay must be a valid number of seconds!\");\r\n            }\r\n            return;\r\n        }\r\n        else if (args.hasFlag('t') && !args.hasValueFlag(\"delay\") && !args.hasFlag('r')) {\r\n            trait.toggle();\r\n            Messaging.sendInfo(sender, npc.getName() + (trait.isPushable() ? \" is\" : \" is not\") + \" currently pushable\" +\r\n                    (trait.isReturnable() && trait.isPushable() ? \" and will return when pushed after '\" + trait.getDelay() + \"' seconds.\" : \".\"));\r\n            return;\r\n        }\r\n        else if (args.hasFlag('t')) {\r\n            trait.toggle();\r\n            if (args.hasFlag('r')) {\r\n                trait.setReturnable(true);\r\n            }\r\n            if (args.hasValueFlag(\"delay\") && args.getFlag(\"delay\").matches(\"\\\\d+\") && args.getFlagInteger(\"delay\") > 0) {\r\n                trait.setDelay(args.getFlagInteger(\"delay\"));\r\n            }\r\n        }\r\n        else if (args.length() > 2) {\r\n            Messaging.send(sender, \"\");\r\n            Messaging.send(sender, \"<f>Use '-t' to toggle pushable state. <b>Example: /npc pushable -t\");\r\n            Messaging.send(sender, \"<f>To have the NPC return to their position when pushed, use '-r'.\");\r\n            Messaging.send(sender, \"<f>Change the return delay with '--delay #'.\");\r\n            Messaging.send(sender, \"\");\r\n        }\r\n        Messaging.sendInfo(sender, npc.getName() + (trait.isPushable() ? \" is\" : \" is not\") + \" currently pushable\" +\r\n                (trait.isReturnable() ? \" and will return when pushed after \" + trait.getDelay() + \" seconds.\" : \".\"));\r\n    }\r\n\r\n    // <--[language]\r\n    // @name /npc constant command\r\n    // @group Console Commands\r\n    // @description\r\n    // The /npc constants command configures a NPC's constants. Uses Denizen's ConstantTrait to keep track of\r\n    // NPC-specific constants. This provides seamless integration with an assignment script's 'Default Constants' in\r\n    // which text variables can be stored and retrieved with the use of 'replaceable tags', or API. Constants set at\r\n    // the NPC level override any constants from the NPC's assignment script.\r\n    //\r\n    // Constants may be used in several ways: Setting, Removing, and Viewing\r\n    //\r\n    // To set a constant, all that is required is a name and value. Use the Bukkit command in the\r\n    // following manner: (Note the use of quotes on multi world values)\r\n    // /npc constant --set constant_name --value 'multi word value'\r\n    //\r\n    // Removing a constant from an NPC only requires a name. Note: It is not possible to remove a\r\n    // constant set by the NPCs Assignment Script, except by modifying the script itself.\r\n    // /npc constant --remove constant_name\r\n    //\r\n    // Viewing constants is easy, just use '/npc constant #', specifying a page number. Constants which\r\n    // have been overridden by the NPC are formatted with a strike-through to indicate this case.\r\n    //\r\n    // To reference a constant value, use the replaceable tag to get the NPCs 'constant' attribute. For example:\r\n    // <npc.constant[constant_name]>. Constants may also have other tags in their value, which will be replaced\r\n    // whenever the constant is used, allowing the use of dynamic information.\r\n    // -->\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"constant --set|remove name --value constant value\",\r\n            desc = \"Views/adds/removes NPC string constants.\", flags = \"r\", modifiers = {\"constants\", \"constant\", \"cons\"},\r\n            min = 1, max = 3, permission = \"denizen.npc.constants\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void constants(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        ConstantsTrait trait = npc.getOrAddTrait(ConstantsTrait.class);\r\n        if (args.hasValueFlag(\"set\")) {\r\n            if (!args.hasValueFlag(\"value\")) {\r\n                throw new CommandException(\"--SET requires use of the '--VALUE \\\"constant value\\\"' argument.\");\r\n            }\r\n            trait.setConstant(args.getFlag(\"set\"), args.getFlag(\"value\"));\r\n            Messaging.sendInfo(sender, npc.getName() + \" has added constant '\" + args.getFlag(\"set\") + \"'.\");\r\n            return;\r\n        }\r\n        else if (args.hasValueFlag(\"remove\")) {\r\n            trait.removeConstant(args.getFlag(\"remove\"));\r\n            Messaging.sendInfo(sender, \"Removed constant '\" + args.getFlag(\"remove\") + \"' from \" + npc.getName() + \"<f>.\");\r\n            return;\r\n        }\r\n        Messaging.send(sender, \"\");\r\n        Messaging.send(sender, \"<f>Use '--set name' to add/set a new NPC-specific constant.\");\r\n        Messaging.send(sender, \"<f>Must also specify '--value \\\"constant value\\\"'.\");\r\n        Messaging.send(sender, \"<b>Example: /npc constant --set constant_1 --value \\\"test value\\\"\");\r\n        Messaging.send(sender, \"<f>Remove NPC-specific constants with '--remove name'\");\r\n        Messaging.send(sender, \"<f>Note: Constants set will override any specified in an\");\r\n        Messaging.send(sender, \"<f>assignment. Constants specified in assignments cannot be\");\r\n        Messaging.send(sender, \"<f>removed with this command.\");\r\n        Messaging.send(sender, \"\");\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"assignment ((--set|--remove|--add) assignment_name) (-c)\",\r\n            desc = \"Controls the assignment for an NPC.\", flags = \"rc\", modifiers = {\"assignment\", \"assign\"},\r\n            min = 1, max = 3, permission = \"denizen.npc.assign\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void assignment(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        Player player = null;\r\n        if (sender instanceof Player) {\r\n            player = (Player) sender;\r\n        }\r\n        if (args.hasFlag('r') || args.hasFlag('c')) {\r\n            if (!npc.hasTrait(AssignmentTrait.class)) {\r\n                Messaging.sendError(sender, \"That NPC has no assignments.\");\r\n                return;\r\n            }\r\n            npc.getOrAddTrait(AssignmentTrait.class).clearAssignments(PlayerTag.mirrorBukkitPlayer(player));\r\n            npc.removeTrait(AssignmentTrait.class);\r\n            Messaging.sendInfo(sender, npc.getName() + \"<f>'s assignments have been cleared.\");\r\n            return;\r\n        }\r\n        if (args.hasValueFlag(\"set\")) {\r\n            String script = args.getFlag(\"set\").replace(\"\\\"\", \"\");\r\n            ScriptContainer container = ScriptRegistry.getScriptContainer(script);\r\n            if (container == null) {\r\n                Messaging.sendError(sender, \"Invalid assignment! Has the script successfully loaded, or has it been misspelled?\");\r\n            }\r\n            else if (!(container instanceof AssignmentScriptContainer)) {\r\n                Messaging.sendError(sender, \"A script with that name exists, but it is not an assignment script!\");\r\n            }\r\n            else {\r\n                AssignmentTrait trait = npc.getOrAddTrait(AssignmentTrait.class);\r\n                trait.clearAssignments(PlayerTag.mirrorBukkitPlayer(player));\r\n                trait.addAssignmentScript((AssignmentScriptContainer) container, PlayerTag.mirrorBukkitPlayer(player));\r\n                Messaging.sendInfo(sender, npc.getName() + \"<f>'s assignment is now just: '\" + container.getName() + \"'.\");\r\n            }\r\n            return;\r\n        }\r\n        else if (args.hasValueFlag(\"add\")) {\r\n            String script = args.getFlag(\"add\").replace(\"\\\"\", \"\");\r\n            ScriptContainer container = ScriptRegistry.getScriptContainer(script);\r\n            AssignmentTrait trait = npc.getOrAddTrait(AssignmentTrait.class);\r\n            if (container == null) {\r\n                Messaging.sendError(sender, \"Invalid assignment! Has the script successfully loaded, or has it been misspelled?\");\r\n            }\r\n            else if (!(container instanceof AssignmentScriptContainer)) {\r\n                Messaging.sendError(sender, \"A script with that name exists, but it is not an assignment script!\");\r\n            }\r\n            else if (trait.addAssignmentScript((AssignmentScriptContainer) container, PlayerTag.mirrorBukkitPlayer(player))) {\r\n                Messaging.sendInfo(sender, npc.getName() + \"<f> is now assigned to '\" + container.getName() + \"'.\");\r\n            }\r\n            else {\r\n                Messaging.sendError(sender, \"That NPC was already assigned that script.\");\r\n            }\r\n            return;\r\n        }\r\n        else if (args.hasValueFlag(\"remove\")) {\r\n            String script = args.getFlag(\"remove\").replace(\"\\\"\", \"\");\r\n            AssignmentTrait trait = npc.getOrAddTrait(AssignmentTrait.class);\r\n            if (trait.removeAssignmentScript(script, PlayerTag.mirrorBukkitPlayer(player))) {\r\n                trait.checkAutoRemove();\r\n                if (npc.hasTrait(AssignmentTrait.class)) {\r\n                    Messaging.sendInfo(sender, npc.getName() + \"<f> is no longer assigned to '\" + script + \"'.\");\r\n                }\r\n                else {\r\n                    Messaging.sendInfo(sender, npc.getName() + \"<f> no longer has any assignment.\");\r\n                }\r\n            }\r\n            else {\r\n                Messaging.sendError(sender, npc.getName() + \"<f> was already not assigned to \" + script + \".\");\r\n            }\r\n            return;\r\n        }\r\n        Messaging.send(sender, \"\");\r\n        Messaging.send(sender, \"<f>Use '--set name' to set a single assignment script to this NPC.\");\r\n        Messaging.send(sender, \"<b>Example: /npc assignment --set \\\"Magic Shop\\\"\");\r\n        Messaging.send(sender, \"<f>Use '--add name' to add an assignment, or '--remove name' to remove one assignment.\");\r\n        Messaging.send(sender, \"<f>Clear all assignments with '-c'.\");\r\n        Messaging.send(sender, \"<f>Note: Assigning a script will fire an 'On Assignment:' action.\");\r\n        Messaging.send(sender, \"\");\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"trigger [trigger name] [(--cooldown [seconds])|(--radius [radius])|(-t)]\",\r\n            desc = \"Controls the various triggers for an NPC.\", flags = \"t\", modifiers = {\"trigger\", \"tr\"},\r\n            min = 1, max = 3, permission = \"denizen.npc.trigger\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void trigger(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        TriggerTrait trait = npc.getOrAddTrait(TriggerTrait.class);\r\n        if ((args.hasValueFlag(\"name\") || (args.argsLength() > 1 && (args.getJoinedStrings(1) != null) && !args.getString(1).matches(\"\\\\d+\")))) {\r\n            // Get the name of the trigger\r\n            String triggerName;\r\n            if (args.hasValueFlag(\"name\")) {\r\n                triggerName = args.getFlag(\"name\");\r\n            }\r\n            else {\r\n                triggerName = args.getJoinedStrings(1);\r\n            }\r\n            // Check to make sure trigger exists\r\n            if (Denizen.getInstance().triggerRegistry.get(triggerName) == null) {\r\n                Messaging.sendError(sender, \"'\" + triggerName.toUpperCase() + \"' trigger does not exist.\");\r\n                Messaging.send(sender, \"<f>Usage: /npc trigger [trigger_name] [(--cooldown #)|(--radius #)|(-t)]\");\r\n                Messaging.send(sender, \"\");\r\n                Messaging.send(sender, \"<f>Use '--name trigger_name' to specify the trigger, and '-t' to toggle it.\");\r\n                Messaging.send(sender, \"<b>Example: /npc trigger --name damage -t\");\r\n                Messaging.send(sender, \"<f>You may also use '--cooldown #' to specify a new cooldown time, and '--radius #' to specify a specific radius, when applicable.\");\r\n                Messaging.send(sender, \"\");\r\n                return;\r\n            }\r\n            // If toggling\r\n            if (args.hasFlag('t')) {\r\n                trait.toggleTrigger(triggerName);\r\n            }\r\n            // If setting cooldown\r\n            if (args.hasValueFlag(\"cooldown\")) {\r\n                trait.setLocalCooldown(triggerName, args.getFlagDouble(\"cooldown\"));\r\n            }\r\n            // If specifying radius\r\n            if (args.hasValueFlag(\"radius\")) {\r\n                trait.setLocalRadius(triggerName, args.getFlagInteger(\"radius\"));\r\n                Messaging.sendInfo(sender, triggerName.toUpperCase() + \" trigger radius is now \" + args.getFlag(\"radius\") + \".\");\r\n            }\r\n            // Show current status of the trigger\r\n            Messaging.sendInfo(sender, triggerName.toUpperCase() + \" trigger \" + (trait.isEnabled(triggerName) ? \"is\" : \"is not\") + \" currently enabled\" +\r\n                    (trait.isEnabled(triggerName) ? \" with a cooldown of '\" + trait.getCooldownDuration(triggerName) + \"' seconds.\" : \".\"));\r\n            return;\r\n        }\r\n        try {\r\n            trait.describe(sender, args.getInteger(1, 1));\r\n        }\r\n        catch (net.citizensnpcs.api.command.exception.CommandException e) {\r\n            throw new CommandException(e.getMessage());\r\n        }\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"nickname [--set nickname]\",\r\n            desc = \"Gives the NPC a nickname, used with a Denizen-compatible Speech Engine.\", modifiers = {\"nickname\", \"nick\", \"ni\"},\r\n            min = 1, max = 3, permission = \"denizen.npc.nickname\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void nickname(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        NicknameTrait trait = npc.getOrAddTrait(NicknameTrait.class);\r\n        if (args.hasValueFlag(\"set\")) {\r\n            trait.setNickname(args.getFlag(\"set\"));\r\n            Messaging.send(sender, \"Nickname set to '\" + args.getFlag(\"set\") + \"'.\");\r\n            return;\r\n        }\r\n        else if (args.hasFlag('r')) {\r\n            trait.setNickname(\"\");\r\n            Messaging.sendInfo(sender, \"Nickname removed.\");\r\n            return;\r\n        }\r\n        if (trait.hasNickname()) {\r\n            Messaging.sendInfo(sender, npc.getName() + \"'s nickname is '\" + trait.getNickname() + \"'.\");\r\n        }\r\n        else {\r\n            Messaging.sendInfo(sender, npc.getName() + \" does not have a nickname!\");\r\n        }\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"sit (--location x,y,z,world) (--anchor anchor_name) (-c)\",\r\n            desc = \"Makes the NPC sit.\", flags = \"c\", modifiers = {\"sit\"},\r\n            min = 1, max = 3, permission = \"denizen.npc.sit\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void sitting(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        if (npc.hasTrait(SneakingTrait.class)) {\r\n            npc.getOrAddTrait(SneakingTrait.class).stand();\r\n            npc.removeTrait(SneakingTrait.class);\r\n        }\r\n        if (npc.hasTrait(SleepingTrait.class)) {\r\n            npc.getOrAddTrait(SleepingTrait.class).wakeUp();\r\n            npc.removeTrait(SleepingTrait.class);\r\n        }\r\n        SittingTrait trait = npc.getOrAddTrait(SittingTrait.class);\r\n        if (args.hasValueFlag(\"location\")) {\r\n            LocationTag location = LocationTag.valueOf(args.getFlag(\"location\"), CoreUtilities.basicContext);\r\n            if (location == null) {\r\n                Messaging.sendError(sender, \"Usage: /npc sit --location x,y,z,world\");\r\n                return;\r\n            }\r\n            trait.sit(location);\r\n            return;\r\n        }\r\n        else if (args.hasValueFlag(\"anchor\")) {\r\n            if (npc.hasTrait(Anchors.class)) {\r\n                Anchors anchors = npc.getOrAddTrait(Anchors.class);\r\n                if (anchors.getAnchor(args.getFlag(\"anchor\")) != null) {\r\n                    trait.sit(anchors.getAnchor(args.getFlag(\"anchor\")).getLocation());\r\n                    Messaging.send(sender, npc.getName() + \" is now sitting.\");\r\n                    return;\r\n                }\r\n            }\r\n            Messaging.sendError(sender, \"NPC \" + npc.getName() + \"<f> does not have the anchor '\" + args.getFlag(\"anchor\") + \"'!\");\r\n            return;\r\n        }\r\n        Location targetLocation;\r\n        if (args.hasFlag('c')) {\r\n            targetLocation = args.getSenderTargetBlockLocation().clone().add(0.5, 0, 0.5);\r\n            targetLocation.setYaw(npc.getStoredLocation().getYaw());\r\n        }\r\n        else {\r\n            targetLocation = npc.getStoredLocation().clone();\r\n            targetLocation.add(0, -0.2, 0);\r\n        }\r\n        if (trait.isSitting()) {\r\n            Messaging.send(sender, npc.getName() + \" is already sitting, use '/npc stand' to stand the NPC back up.\");\r\n            return;\r\n        }\r\n        Block block = targetLocation.getBlock();\r\n        BlockData data = block.getBlockData();\r\n        if (data instanceof Stairs || data instanceof Bed || (data instanceof Slab && ((Slab) data).getType() == Slab.Type.BOTTOM)) {\r\n            targetLocation.setY(targetLocation.getBlockY() + 0.3);\r\n        }\r\n        else if (data instanceof Campfire) {\r\n            targetLocation.setY(targetLocation.getBlockY() + 0.2);\r\n        }\r\n        else if (block.getType().name().endsWith(\"CARPET\")) {\r\n            targetLocation.setY(targetLocation.getBlockY());\r\n        }\r\n        else if (block.getType().isSolid()) {\r\n            targetLocation.setY(targetLocation.getBlockY() + 0.8);\r\n        }\r\n        trait.sit(targetLocation);\r\n        Messaging.send(sender, npc.getName() + \" is now sitting.\");\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"stand\",\r\n            desc = \"Makes the NPC stand.\", modifiers = {\"stand\"},\r\n            min = 1, max = 1, permission = \"denizen.npc.stand\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void standing(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        if (npc.hasTrait(SittingTrait.class)) {\r\n            SittingTrait trait = npc.getOrAddTrait(SittingTrait.class);\r\n            if (!trait.isSitting()) {\r\n                npc.removeTrait(SittingTrait.class);\r\n                Messaging.sendError(sender, npc.getName() + \" is already standing!\");\r\n                return;\r\n            }\r\n            trait.stand();\r\n            npc.removeTrait(SittingTrait.class);\r\n            Messaging.send(sender, npc.getName() + \" is now standing.\");\r\n        }\r\n        else if (npc.hasTrait(SneakingTrait.class)) {\r\n            SneakingTrait trait = npc.getOrAddTrait(SneakingTrait.class);\r\n            if (!trait.isSneaking()) {\r\n                npc.removeTrait(SneakingTrait.class);\r\n                Messaging.sendError(sender, npc.getName() + \" is already standing!\");\r\n                return;\r\n            }\r\n            trait.stand();\r\n            npc.removeTrait(SneakingTrait.class);\r\n            Messaging.send(sender, npc.getName() + \" is now standing.\");\r\n        }\r\n        else if (npc.hasTrait(SleepingTrait.class)) {\r\n            SleepingTrait trait = npc.getOrAddTrait(SleepingTrait.class);\r\n            if (!trait.isSleeping()) {\r\n                npc.removeTrait(SleepingTrait.class);\r\n                Messaging.sendError(sender, npc.getName() + \" is already standing!\");\r\n                return;\r\n            }\r\n            trait.wakeUp();\r\n            npc.removeTrait(SleepingTrait.class);\r\n            Messaging.send(sender, npc.getName() + \" is now standing.\");\r\n        }\r\n        else {\r\n            Messaging.sendError(sender, npc.getName() + \" is already standing!\");\r\n        }\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"sleep (--location x,y,z,world) (--anchor anchor_name)\",\r\n            desc = \"Makes the NPC sleep.\", modifiers = {\"sleep\" },\r\n            min = 1, max = 3, permission = \"denizen.npc.sleep\")\r\n    @Requirements(selected = true, ownership = true, types = { EntityType.VILLAGER, EntityType.PLAYER })\r\n    public void sleeping(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        if (npc.hasTrait(SneakingTrait.class)) {\r\n            npc.getOrAddTrait(SneakingTrait.class).stand();\r\n            npc.removeTrait(SneakingTrait.class);\r\n        }\r\n        if (npc.hasTrait(SittingTrait.class)) {\r\n            npc.getOrAddTrait(SittingTrait.class).stand();\r\n            npc.removeTrait(SittingTrait.class);\r\n        }\r\n        SleepingTrait trait = npc.getOrAddTrait(SleepingTrait.class);\r\n        if (trait.isSleeping()) {\r\n            Messaging.send(sender, npc.getName() + \" was already sleeping, and is now standing!\");\r\n            trait.wakeUp();\r\n            npc.removeTrait(SleepingTrait.class);\r\n            return;\r\n        }\r\n        if (args.hasValueFlag(\"location\")) {\r\n            LocationTag location = LocationTag.valueOf(args.getFlag(\"location\"), CoreUtilities.basicContext);\r\n            if (location == null) {\r\n                Messaging.sendError(sender, \"Usage: /npc sleep --location x,y,z,world\");\r\n                return;\r\n            }\r\n            trait.toSleep(location);\r\n        }\r\n        else if (args.hasValueFlag(\"anchor\")) {\r\n            if (npc.hasTrait(Anchors.class)) {\r\n                Anchors anchors = npc.getOrAddTrait(Anchors.class);\r\n                if (anchors.getAnchor(args.getFlag(\"anchor\")) != null) {\r\n                    trait.toSleep(anchors.getAnchor(args.getFlag(\"anchor\")).getLocation().clone().add(0.5, 0, 0.5));\r\n                    Messaging.send(sender, npc.getName() + \" is now sleeping.\");\r\n                    return;\r\n                }\r\n            }\r\n            Messaging.sendError(sender, \"NPC \" + npc.getName() + \"<f> does not have the anchor '\" + args.getFlag(\"anchor\") + \"'!\");\r\n            return;\r\n        }\r\n        else {\r\n            trait.toSleep();\r\n        }\r\n        if (!trait.isSleeping()) {\r\n            npc.removeTrait(SleepingTrait.class);\r\n        }\r\n        Messaging.send(sender, npc.getName() + \" is now sleeping.\");\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"wakeup\",\r\n            desc = \"Makes the NPC wake up.\", modifiers = {\"wakeup\"},\r\n            min = 1, max = 1, permission = \"denizen.npc.sleep\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void wakingup(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        SleepingTrait trait = npc.getOrAddTrait(SleepingTrait.class);\r\n        if (!trait.isSleeping()) {\r\n            npc.removeTrait(SleepingTrait.class);\r\n            Messaging.sendError(sender, npc.getName() + \" is already awake!\");\r\n            return;\r\n        }\r\n        trait.wakeUp();\r\n        npc.removeTrait(SleepingTrait.class);\r\n        Messaging.send(sender, npc.getName() + \" is no longer sleeping.\");\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"fish (--location x,y,z,world) (--anchor anchor_name) (-c) (--reel_time <duration>) (--cast_time <duration>)\",\r\n            desc = \"Makes the NPC fish, casting at the given location.\", flags = \"c\", modifiers = {\"fish\"},\r\n            min = 1, max = 3, permission = \"denizen.npc.fish\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void startFishing(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        FishingTrait trait = npc.getOrAddTrait(FishingTrait.class);\r\n        if (trait.isFishing()) {\r\n            Messaging.sendError(sender, npc.getName() + \" is already fishing! Use '/npc stopfishing' to stop.\");\r\n            return;\r\n        }\r\n        if (args.hasValueFlag(\"percent\")) {\r\n            trait.setCatchPercent(args.getFlagInteger(\"percent\"));\r\n            Messaging.send(sender, npc.getName() + \" will now catch \" + args.getFlagInteger(\"percent\") + \"% of the fish.\");\r\n        }\r\n        if (args.hasValueFlag(\"reel_time\")) {\r\n            DurationTag duration = DurationTag.valueOf(args.getFlag(\"reel_time\"), CoreUtilities.basicContext);\r\n            if (duration == null) {\r\n                Messaging.sendError(sender, \"Invalid reel duration!\");\r\n                return;\r\n            }\r\n            trait.reelTickRate = duration.getTicksAsInt();\r\n            Messaging.send(sender, \"Set reel rate to \" + duration.formatted(true));\r\n        }\r\n        if (args.hasValueFlag(\"cast_time\")) {\r\n            DurationTag duration = DurationTag.valueOf(args.getFlag(\"cast_time\"), CoreUtilities.basicContext);\r\n            if (duration == null) {\r\n                Messaging.sendError(sender, \"Invalid cast duration!\");\r\n                return;\r\n            }\r\n            trait.reelTickRate = duration.getTicksAsInt();\r\n            Messaging.send(sender, \"Set cast rate to \" + duration.formatted(true) + \".\");\r\n        }\r\n        if (args.hasFlag('c')) {\r\n            trait.startFishing(args.getSenderTargetBlockLocation());\r\n            Messaging.send(sender, npc.getName() + \" is now fishing at your cursor.\");\r\n            return;\r\n        }\r\n        else if (args.hasValueFlag(\"location\")) {\r\n            String[] argsArray = args.getFlag(\"location\").split(\",\");\r\n            if (argsArray.length != 4) {\r\n                Messaging.sendError(sender, \"Usage: /npc fish --location x,y,z,world\");\r\n                return;\r\n            }\r\n            trait.startFishing(LocationTag.valueOf(argsArray[0] + \",\" + argsArray[1] + \",\" + argsArray[2] + \",\" + argsArray[3], CoreUtilities.basicContext));\r\n        }\r\n        else if (args.hasValueFlag(\"anchor\")) {\r\n            if (npc.hasTrait(Anchors.class)) {\r\n                Anchors anchors = npc.getOrAddTrait(Anchors.class);\r\n                if (anchors.getAnchor(args.getFlag(\"anchor\")) != null) {\r\n                    trait.startFishing(anchors.getAnchor(args.getFlag(\"anchor\")).getLocation());\r\n                } else {\r\n                    Messaging.sendError(sender, \"Anchor '\" + args.getFlag(\"anchor\") + \"' is invalid! Did you make a typo?\");\r\n                    return;\r\n                }\r\n            } else {\r\n                Messaging.sendError(sender, \"NPC \" + npc.getName() + \"<f> does not have the anchor '\" + args.getFlag(\"anchor\") + \"'!\");\r\n                return;\r\n            }\r\n        }\r\n        else {\r\n            trait.startFishing();\r\n        }\r\n        Messaging.send(sender, npc.getName() + \" is now fishing.\");\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"stopfishing\",\r\n            desc = \"Makes the NPC stop fishing.\", modifiers = {\"stopfishing\"},\r\n            min = 1, max = 1, permission = \"denizen.npc.fish\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void stopFishing(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        FishingTrait trait = npc.getOrAddTrait(FishingTrait.class);\r\n        if (!trait.isFishing()) {\r\n            npc.removeTrait(FishingTrait.class);\r\n            Messaging.sendError(sender, npc.getName() + \" isn't currently fishing!\");\r\n            return;\r\n        }\r\n        trait.stopFishing();\r\n        npc.removeTrait(FishingTrait.class);\r\n        Messaging.send(sender, npc.getName() + \" is no longer fishing.\");\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"sneak\",\r\n            desc = \"Makes the NPC crouch.\", modifiers = {\"sneak\", \"crouch\"},\r\n            min = 1, max = 1, permission = \"denizen.npc.sneak\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void sneaking(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        if (npc.hasTrait(SleepingTrait.class)) {\r\n            npc.getOrAddTrait(SleepingTrait.class).wakeUp();\r\n            npc.removeTrait(SleepingTrait.class);\r\n        }\r\n        if (npc.hasTrait(SleepingTrait.class)) {\r\n            npc.getOrAddTrait(SleepingTrait.class).wakeUp();\r\n            npc.removeTrait(SleepingTrait.class);\r\n        }\r\n        if (npc.getEntity().getType() != EntityType.PLAYER) {\r\n            Messaging.sendError(sender, npc.getName() + \" needs to be a Player type NPC to sneak!\");\r\n            return;\r\n        }\r\n        SneakingTrait trait = npc.getOrAddTrait(SneakingTrait.class);\r\n        if (trait.isSneaking()) {\r\n            trait.stand();\r\n            Messaging.send(sender, npc.getName() + \" was already sneaking, and is now standing.\");\r\n        }\r\n        else {\r\n            trait.sneak();\r\n            Messaging.send(sender, npc.getName() + \" is now sneaking.\");\r\n        }\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"mirrorskin\",\r\n            desc = \"Makes the NPC mirror the skin of the player looking at it.\", modifiers = {\"mirrorskin\", \"mirror\"},\r\n            min = 1, max = 1, permission = \"denizen.npc.mirror\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void mirror(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        if (npc.getEntity().getType() != EntityType.PLAYER) {\r\n            Messaging.sendError(sender, npc.getName() + \" needs to be a Player type NPC to be a skin-mirror!\");\r\n            return;\r\n        }\r\n        if (!npc.hasTrait(MirrorTrait.class)) {\r\n            npc.getOrAddTrait(MirrorTrait.class).enableMirror();\r\n            Messaging.send(sender, npc.getName() + \" is now mirroring player skins.\");\r\n            return;\r\n        }\r\n        MirrorTrait trait = npc.getOrAddTrait(MirrorTrait.class);\r\n        if (trait.mirror) {\r\n            trait.disableMirror();\r\n            npc.removeTrait(MirrorTrait.class);\r\n            Messaging.send(sender, npc.getName() + \" is no longer mirroring player skins.\");\r\n        }\r\n        else {\r\n            trait.enableMirror();\r\n            Messaging.send(sender, npc.getName() + \" is now mirroring player skins.\");\r\n        }\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"mirrorname\",\r\n            desc = \"Makes the NPC mirror the username of the player looking at it.\", modifiers = {\"mirrorname\"},\r\n            min = 1, max = 1, permission = \"denizen.npc.mirror\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void mirrorName(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        if (!npc.hasTrait(MirrorNameTrait.class)) {\r\n            npc.getOrAddTrait(MirrorNameTrait.class).enableMirror();\r\n            Messaging.send(sender, npc.getName() + \" is now mirroring player names.\");\r\n            return;\r\n        }\r\n        MirrorNameTrait trait = npc.getOrAddTrait(MirrorNameTrait.class);\r\n        if (trait.mirror) {\r\n            trait.disableMirror();\r\n            npc.removeTrait(MirrorNameTrait.class);\r\n            Messaging.send(sender, npc.getName() + \" is no longer mirroring player names.\");\r\n        }\r\n        else {\r\n            trait.enableMirror();\r\n            Messaging.send(sender, npc.getName() + \" is now mirroring player names.\");\r\n        }\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"mirrorequipment\",\r\n            desc = \"Makes the NPC mirror the equipment of the player looking at it.\", modifiers = {\"mirrorequipment\", \"mirrorequip\"},\r\n            min = 1, max = 1, permission = \"denizen.npc.mirror\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void mirrorEquipment(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        if (!npc.hasTrait(MirrorEquipmentTrait.class)) {\r\n            npc.getOrAddTrait(MirrorEquipmentTrait.class).enableMirror();\r\n            Messaging.send(sender, npc.getName() + \" is now mirroring player equipment.\");\r\n            return;\r\n        }\r\n        MirrorEquipmentTrait trait = npc.getOrAddTrait(MirrorEquipmentTrait.class);\r\n        if (trait.mirror) {\r\n            trait.disableMirror();\r\n            npc.removeTrait(MirrorEquipmentTrait.class);\r\n            Messaging.send(sender, npc.getName() + \" is no longer mirroring player equipment.\");\r\n        }\r\n        else {\r\n            trait.enableMirror();\r\n            Messaging.send(sender, npc.getName() + \" is now mirroring player equipment.\");\r\n        }\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"invisible\",\r\n            desc = \"Turns the NPC invisible.\", modifiers = {\"invisible\"},\r\n            min = 1, max = 3, permission = \"denizen.npc.invisible\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void invisible(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        if (!npc.hasTrait(InvisibleTrait.class)) {\r\n            npc.getOrAddTrait(InvisibleTrait.class).setInvisible(true);\r\n            Messaging.send(sender, npc.getName() + \" is now invisible.\");\r\n            return;\r\n        }\r\n        InvisibleTrait trait = npc.getOrAddTrait(InvisibleTrait.class);\r\n        trait.toggle();\r\n        if (trait.isInvisible()) {\r\n            Messaging.send(sender, npc.getName() + \" is now invisible.\");\r\n        }\r\n        else {\r\n            Messaging.send(sender, npc.getName() + \" is no longer invisible.\");\r\n        }\r\n    }\r\n\r\n    @Command(\r\n            aliases = {\"npc\"}, usage = \"health (--set #) (--max #) (-r)\",\r\n            desc = \"Sets the max health for an NPC.\", modifiers = {\"health\", \"he\", \"hp\"},\r\n            min = 1, max = 3, permission = \"denizen.npc.health\", flags = \"sra\")\r\n    @Requirements(selected = true, ownership = true)\r\n    public void health(CommandContext args, CommandSender sender, NPC npc) throws CommandException {\r\n        HealthTrait trait = npc.getOrAddTrait(HealthTrait.class);\r\n        boolean showMore = true;\r\n        if (args.hasValueFlag(\"max\")) {\r\n            trait.setMaxhealth(args.getFlagInteger(\"max\"));\r\n            trait.setHealth();\r\n            Messaging.send(sender, npc.getName() + \"'s health maximum is now \" + trait.getMaxhealth() + \".\");\r\n            showMore = false;\r\n        }\r\n        if (args.hasValueFlag(\"set\")) {\r\n            trait.setHealth(args.getFlagInteger(\"set\"));\r\n        }\r\n        if (args.hasValueFlag(\"respawndelay\")) {\r\n            trait.setRespawnDelay(args.getFlag(\"respawndelay\"));\r\n            Messaging.send(sender, npc.getName() + \"'s respawn delay now \" + trait.getRespawnDelay()\r\n                    + (trait.isRespawnable() ? \".\" : \", but is not currently auto-respawnable upon death.\"));\r\n            showMore = false;\r\n        }\r\n        if (args.hasValueFlag(\"respawnlocation\")) {\r\n            trait.setRespawnLocation(args.getFlag(\"respawnlocation\"));\r\n            Messaging.send(sender, npc.getName() + \"'s respawn location now \" + trait.getRespawnLocationAsString()\r\n                    + (trait.isRespawnable() ? \".\" : \", but is not currently auto-respawnable upon death.\"));\r\n            showMore = false;\r\n        }\r\n        if (args.hasFlag('s')) {\r\n            trait.setRespawnable(!trait.isRespawnable());\r\n            Messaging.send(sender, npc.getName() + (trait.isRespawnable()\r\n                    ? \" will now auto-respawn on death after \" + trait.getRespawnDelay() + \" seconds.\"\r\n                    : \" will no longer auto-respawn on death.\"));\r\n            showMore = false;\r\n        }\r\n        if (args.hasFlag('a')) {\r\n            trait.animateOnDeath(!trait.animatesOnDeath());\r\n            Messaging.send(sender, npc.getName() + (trait.animatesOnDeath()\r\n                    ? \" will now animate on death.\"\r\n                    : \" will no longer animate on death.\"));\r\n            showMore = false;\r\n        }\r\n        else if (args.hasFlag('r')) {\r\n            trait.setHealth();\r\n            Messaging.send(sender, npc.getName() + \"'s health reset to \" + trait.getMaxhealth() + \".\");\r\n            showMore = false;\r\n        }\r\n        if (showMore) {\r\n            Messaging.sendInfo(sender, npc.getName() + \"'s health is '\" + trait.getHealth() + \"/\" + trait.getMaxhealth() + \"'.\");\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/TabCompleteHelper.java",
    "content": "package com.denizenscript.denizen.utilities.command;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.EntityScriptHelper;\r\nimport com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.properties.PropertyParser;\r\nimport com.denizenscript.denizencore.scripts.commands.AbstractCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.util.Map;\r\n\r\npublic class TabCompleteHelper {\r\n\r\n    public static void tabCompleteItems(AbstractCommand.TabCompletionsBuilder tab) {\r\n        int bracket = tab.arg.indexOf('[');\r\n        if (bracket == -1) {\r\n            for (Material material : Material.values()) {\r\n                if (material.isItem()) {\r\n                    tab.add(material.name());\r\n                }\r\n            }\r\n            tab.add(ItemScriptHelper.item_scripts.keySet());\r\n            return;\r\n        }\r\n        String material = tab.arg.substring(0, bracket);\r\n        ItemTag item = ItemTag.valueOf(material, CoreUtilities.noDebugContext);\r\n        tabCompletePropertiesFor(tab, bracket, item);\r\n    }\r\n\r\n    public static void tabCompleteBlockMaterials(AbstractCommand.TabCompletionsBuilder tab) {\r\n        int bracket = tab.arg.indexOf('[');\r\n        if (bracket == -1) {\r\n            for (Material material : Material.values()) {\r\n                if (material.isBlock()) {\r\n                    tab.add(material.name());\r\n                }\r\n            }\r\n            return;\r\n        }\r\n        String material = tab.arg.substring(0, bracket);\r\n        MaterialTag mat = MaterialTag.valueOf(material, CoreUtilities.noDebugContext);\r\n        tabCompletePropertiesFor(tab, bracket, mat);\r\n    }\r\n\r\n    public static void tabCompleteEntityTypes(AbstractCommand.TabCompletionsBuilder tab) {\r\n        int bracket = tab.arg.indexOf('[');\r\n        if (bracket == -1) {\r\n            tab.add(EntityType.values());\r\n            tab.add(EntityScriptHelper.scripts.keySet());\r\n            return;\r\n        }\r\n        String type = tab.arg.substring(0, bracket);\r\n        EntityTag entity = EntityTag.valueOf(type, CoreUtilities.noDebugContext);\r\n        if (entity == null || entity.isUnique()) {\r\n            return;\r\n        }\r\n        tabCompletePropertiesFor(tab, bracket, entity);\r\n    }\r\n\r\n    public static void tabCompletePropertiesFor(AbstractCommand.TabCompletionsBuilder tab, int bracket, ObjectTag object) {\r\n        if (object == null) {\r\n            return;\r\n        }\r\n        int lastSemicolon = tab.arg.lastIndexOf(';');\r\n        if (lastSemicolon == -1) {\r\n            lastSemicolon = bracket;\r\n        }\r\n        String propertyPart = tab.arg.substring(lastSemicolon + 1);\r\n        if (propertyPart.indexOf('=') != -1) {\r\n            return;\r\n        }\r\n        String prefixPart = tab.arg.substring(0, lastSemicolon + 1);\r\n        PropertyParser.ClassPropertiesInfo properties = PropertyParser.propertiesByClass.get(object.getClass());\r\n        for (Map.Entry<String, PropertyParser.PropertyGetter> property : properties.propertiesByMechanism.entrySet()) {\r\n            if (property.getKey().startsWith(propertyPart) && property.getValue().get(object) != null) {\r\n                tab.add(prefixPart + property.getKey());\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/Command.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager;\r\n\r\nimport java.lang.annotation.Retention;\r\nimport java.lang.annotation.RetentionPolicy;\r\n\r\n@Retention(RetentionPolicy.RUNTIME)\r\npublic @interface Command {\r\n\r\n    /**\r\n     * A list of root-level command aliases that will be accepted for this command.\r\n     * For example: <code>{\"blah\", \"bleh\"}</code> would match both /blah and /bleh.\r\n     */\r\n    String[] aliases();\r\n\r\n    /**\r\n     * A short description of the command that will be displayed with the\r\n     * command usage and help.\r\n     */\r\n    String desc();\r\n\r\n    /**\r\n     * Defines the flags available for this command. A flag is a single\r\n     * character such as <code>-f</code> that will alter the behaviour of the\r\n     * command. Each character in this string will be counted as a valid flag:\r\n     * extra flags will be discarded. Accepts * as a catch all.\r\n     */\r\n    String flags() default \"\";\r\n\r\n    /**\r\n     * A longer description of the command which will be displayed in addition to desc\r\n     * in help commands.\r\n     */\r\n    String help() default \"\";\r\n\r\n    /**\r\n     * The maximum number of arguments that the command will accept. Default is\r\n     * <code>-1</code>, or an <b>unlimited</b> number of arguments.\r\n     */\r\n    int max() default -1;\r\n\r\n    /**\r\n     * Minimum number of arguments that are accepted by the command.\r\n     */\r\n    int min() default 0;\r\n\r\n    /**\r\n     * The argument modifiers accepted by the command. Also accepts\r\n     * <code>'*'</code> as a catch all.\r\n     */\r\n    String[] modifiers() default \"\";\r\n\r\n    /**\r\n     * The permission of the command. The command sender will get an error if\r\n     * this is not met.\r\n     */\r\n    String permission() default \"\";\r\n\r\n    /**\r\n     * Command usage string that is displayed when an error occurs with the\r\n     * command processing.\r\n     */\r\n    String usage() default \"\";\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/CommandAnnotationProcessor.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager;\r\n\r\nimport com.denizenscript.denizen.utilities.command.manager.exceptions.CommandException;\r\nimport org.bukkit.command.CommandSender;\r\n\r\nimport java.lang.annotation.Annotation;\r\n\r\npublic interface CommandAnnotationProcessor {\r\n\r\n    /**\r\n     * @return The {@link Annotation} class that this processor will accept.\r\n     */\r\n    Class<? extends Annotation> getAnnotationClass();\r\n\r\n    /**\r\n     * @param sender   The command sender\r\n     * @param context  The context of the command, including arguments\r\n     * @param instance The {@link Annotation} instance\r\n     * @param args     The method arguments\r\n     * @throws CommandException If an exception occurs\r\n     */\r\n    void process(CommandSender sender, CommandContext context, Annotation instance, Object[] args)\r\n            throws CommandException;\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/CommandContext.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager;\r\n\r\nimport com.denizenscript.denizen.utilities.command.manager.exceptions.CommandException;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.command.BlockCommandSender;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.*;\r\nimport java.util.regex.Pattern;\r\n\r\npublic class CommandContext {\r\n\r\n    protected String[] args;\r\n    protected final Set<Character> flags = new HashSet<>();\r\n    private Location location = null;\r\n    private final CommandSender sender;\r\n    protected final Map<String, String> valueFlags = new HashMap<>();\r\n\r\n    public CommandContext(CommandSender sender, String[] args) {\r\n        this.sender = sender;\r\n        int i = 1;\r\n        for (; i < args.length; i++) {\r\n            // initial pass for quotes\r\n            args[i] = args[i].trim();\r\n            if (args[i].length() == 0) {\r\n                // Ignore this\r\n                continue;\r\n            }\r\n            else if (args[i].charAt(0) == '\\'' || args[i].charAt(0) == '\"') {\r\n                char quote = args[i].charAt(0);\r\n                StringBuilder quoted = new StringBuilder(args[i].substring(1)); // remove initial quote\r\n                if (quoted.length() > 0 && quoted.charAt(quoted.length() - 1) == quote) {\r\n                    args[i] = quoted.substring(0, quoted.length() - 1);\r\n                    continue;\r\n                }\r\n                for (int inner = i + 1; inner < args.length; inner++) {\r\n                    if (args[inner].isEmpty()) {\r\n                        continue;\r\n                    }\r\n                    String test = args[inner].trim();\r\n                    quoted.append(\" \").append(test);\r\n                    if (test.charAt(test.length() - 1) == quote) {\r\n                        args[i] = quoted.substring(0, quoted.length() - 1);\r\n                        // remove ending quote\r\n                        for (int j = i + 1; j <= inner; ++j) {\r\n                            args[j] = \"\"; // collapse previous\r\n                        }\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        for (i = 1; i < args.length; ++i) {\r\n            // second pass for flags\r\n            int length = args[i].length();\r\n            if (length == 0) {\r\n                continue;\r\n            }\r\n            if (i + 1 < args.length && length > 2 && VALUE_FLAG.matcher(args[i]).matches()) {\r\n                int inner = i + 1;\r\n                while (args[inner].length() == 0) {\r\n                    // later args may have been quoted\r\n                    if (++inner >= args.length) {\r\n                        inner = -1;\r\n                        break;\r\n                    }\r\n                }\r\n\r\n                if (inner != -1) {\r\n                    valueFlags.put(CoreUtilities.toLowerCase(args[i]).substring(2), args[inner]);\r\n                    args[i] = \"\";\r\n                    args[inner] = \"\";\r\n                }\r\n            }\r\n            else if (FLAG.matcher(args[i]).matches()) {\r\n                for (int k = 1; k < args[i].length(); k++) {\r\n                    flags.add(args[i].charAt(k));\r\n                }\r\n                args[i] = \"\";\r\n            }\r\n        }\r\n        List<String> copied = new ArrayList<>();\r\n        for (String arg : args) {\r\n            arg = arg.trim();\r\n            if (arg == null || arg.isEmpty()) {\r\n                continue;\r\n            }\r\n            copied.add(arg.trim());\r\n        }\r\n        this.args = copied.toArray(new String[0]);\r\n    }\r\n\r\n    public CommandContext(String[] args) {\r\n        this(null, args);\r\n    }\r\n\r\n    public int argsLength() {\r\n        return args.length - 1;\r\n    }\r\n\r\n    public String getCommand() {\r\n        return args[0];\r\n    }\r\n\r\n    public double getDouble(int index) throws NumberFormatException {\r\n        return Double.parseDouble(args[index + 1]);\r\n    }\r\n\r\n    public double getDouble(int index, double def) throws NumberFormatException {\r\n        return index + 1 < args.length ? Double.parseDouble(args[index + 1]) : def;\r\n    }\r\n\r\n    public String getFlag(String ch) {\r\n        return valueFlags.get(ch);\r\n    }\r\n\r\n    public String getFlag(String ch, String def) {\r\n        final String value = valueFlags.get(ch);\r\n        if (value == null) {\r\n            return def;\r\n        }\r\n\r\n        return value;\r\n    }\r\n\r\n    public double getFlagDouble(String ch) throws NumberFormatException {\r\n        return Double.parseDouble(valueFlags.get(ch));\r\n    }\r\n\r\n    public double getFlagDouble(String ch, double def) throws NumberFormatException {\r\n        final String value = valueFlags.get(ch);\r\n        if (value == null) {\r\n            return def;\r\n        }\r\n\r\n        return Double.parseDouble(value);\r\n    }\r\n\r\n    public int getFlagInteger(String ch) throws NumberFormatException {\r\n        return Integer.parseInt(valueFlags.get(ch));\r\n    }\r\n\r\n    public int getFlagInteger(String ch, int def) throws NumberFormatException {\r\n        final String value = valueFlags.get(ch);\r\n        if (value == null) {\r\n            return def;\r\n        }\r\n\r\n        return Integer.parseInt(value);\r\n    }\r\n\r\n    public Set<Character> getFlags() {\r\n        return flags;\r\n    }\r\n\r\n    public int getInteger(int index) throws NumberFormatException {\r\n        return Integer.parseInt(args[index + 1]);\r\n    }\r\n\r\n    public int getInteger(int index, int def) throws NumberFormatException {\r\n        if (index + 1 < args.length) {\r\n            try {\r\n                return Integer.parseInt(args[index + 1]);\r\n            }\r\n            catch (NumberFormatException ex) {\r\n            }\r\n        }\r\n        return def;\r\n    }\r\n\r\n    public String getJoinedStrings(int initialIndex) {\r\n        return getJoinedStrings(initialIndex, ' ');\r\n    }\r\n\r\n    public String getJoinedStrings(int initialIndex, char delimiter) {\r\n        initialIndex = initialIndex + 1;\r\n        StringBuilder buffer = new StringBuilder(args[initialIndex]);\r\n        for (int i = initialIndex + 1; i < args.length; i++) {\r\n            buffer.append(delimiter).append(args[i]);\r\n        }\r\n        return buffer.toString().trim();\r\n    }\r\n\r\n    public String[] getPaddedSlice(int index, int padding) {\r\n        String[] slice = new String[args.length - index + padding];\r\n        System.arraycopy(args, index, slice, padding, args.length - index);\r\n        return slice;\r\n    }\r\n\r\n    public Location getSenderLocation() throws CommandException {\r\n        if (location != null || sender == null) {\r\n            return location;\r\n        }\r\n        if (sender instanceof Player) {\r\n            location = ((Player) sender).getLocation();\r\n        }\r\n        else if (sender instanceof BlockCommandSender) {\r\n            location = ((BlockCommandSender) sender).getBlock().getLocation();\r\n        }\r\n        if (hasValueFlag(\"location\")) {\r\n            location = parseLocation(location, getFlag(\"location\"));\r\n        }\r\n        return location;\r\n    }\r\n\r\n    public String[] getSlice(int index) {\r\n        String[] slice = new String[args.length - index];\r\n        System.arraycopy(args, index, slice, 0, args.length - index);\r\n        return slice;\r\n    }\r\n\r\n    public String getString(int index) {\r\n        return args[index + 1];\r\n    }\r\n\r\n    public String getString(int index, String def) {\r\n        return index + 1 < args.length ? args[index + 1] : def;\r\n    }\r\n\r\n    public Map<String, String> getValueFlags() {\r\n        return valueFlags;\r\n    }\r\n\r\n    public boolean hasFlag(char ch) {\r\n        return flags.contains(ch);\r\n    }\r\n\r\n    public boolean hasValueFlag(String ch) {\r\n        return valueFlags.containsKey(ch);\r\n    }\r\n\r\n    public int length() {\r\n        return args.length;\r\n    }\r\n\r\n    public boolean matches(String command) {\r\n        return CoreUtilities.equalsIgnoreCase(args[0], command);\r\n    }\r\n\r\n    public static Location parseLocation(Location currentLocation, String flag) throws CommandException {\r\n        boolean denizen = flag.startsWith(\"l@\");\r\n        String[] parts = flag.replaceFirst(\"l@\", \"\").split(\"[,]|[:]\");\r\n        if (parts.length > 0) {\r\n            String worldName = currentLocation != null ? currentLocation.getWorld().getName() : \"\";\r\n            double x, y, z;\r\n            float yaw = 0F, pitch = 0F;\r\n            switch (parts.length) {\r\n                case 6:\r\n                    if (denizen) {\r\n                        worldName = parts[5].replaceFirst(\"w@\", \"\");\r\n                    }\r\n                    else {\r\n                        pitch = Float.parseFloat(parts[5]);\r\n                    }\r\n                case 5:\r\n                    if (denizen) {\r\n                        pitch = Float.parseFloat(parts[4]);\r\n                    }\r\n                    else {\r\n                        yaw = Float.parseFloat(parts[4]);\r\n                    }\r\n                case 4:\r\n                    if (denizen && parts.length > 4) {\r\n                        yaw = Float.parseFloat(parts[3]);\r\n                    }\r\n                    else {\r\n                        worldName = parts[3].replaceFirst(\"w@\", \"\");\r\n                    }\r\n                case 3:\r\n                    x = Double.parseDouble(parts[0]);\r\n                    y = Double.parseDouble(parts[1]);\r\n                    z = Double.parseDouble(parts[2]);\r\n                    break;\r\n                default:\r\n                    throw new CommandException(\"Location could not be parsed or was not found.\");\r\n            }\r\n            World world = Bukkit.getWorld(worldName);\r\n            if (world == null) {\r\n                throw new CommandException(\"Location could not be parsed or was not found.\");\r\n            }\r\n            return new Location(world, x, y, z, yaw, pitch);\r\n        }\r\n        else {\r\n            Player search = Bukkit.getPlayerExact(flag);\r\n            if (search == null) {\r\n                throw new CommandException(\"No player could be found by that name.\");\r\n            }\r\n            return search.getLocation();\r\n        }\r\n    }\r\n\r\n    private static final Pattern FLAG = Pattern.compile(\"^-[a-zA-Z]+$\");\r\n    private static final Pattern VALUE_FLAG = Pattern.compile(\"^--[a-zA-Z0-9]+$\");\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/CommandManager.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager;\r\n\r\nimport com.denizenscript.denizen.utilities.command.manager.exceptions.*;\r\nimport com.denizenscript.denizen.utilities.command.manager.messaging.Messaging;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.ArrayListMultimap;\r\nimport com.google.common.collect.ListMultimap;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.command.ConsoleCommandSender;\r\nimport org.bukkit.command.TabCompleter;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.annotation.Annotation;\r\nimport java.lang.reflect.InvocationTargetException;\r\nimport java.lang.reflect.Method;\r\nimport java.lang.reflect.Modifier;\r\nimport java.util.*;\r\n\r\npublic class CommandManager implements TabCompleter {\r\n\r\n    private final Map<Class<? extends Annotation>, CommandAnnotationProcessor> annotationProcessors =\r\n            new HashMap<>();\r\n\r\n    /*\r\n     * Mapping of commands (including aliases) with a description. Root commands\r\n     * are stored under a key of null, whereas child commands are cached under\r\n     * their respective Method. The child map has the key of the command name\r\n     * (one for each alias) with the method.\r\n     */\r\n    private final Map<String, Method> commands = new HashMap<>();\r\n    private Injector injector;\r\n    private final Map<Method, Object> instances = new HashMap<>();\r\n    private final ListMultimap<Method, Annotation> registeredAnnotations = ArrayListMultimap.create();\r\n    private final Set<Method> serverCommands = new HashSet<>();\r\n\r\n    public CommandManager() {\r\n        registerAnnotationProcessor(new RequirementsProcessor());\r\n    }\r\n\r\n    /**\r\n     * Attempt to execute a command using the root {@link Command} given. A list\r\n     * of method arguments may be used when calling the command handler method.\r\n     * <p/>\r\n     * A command handler method should follow the form\r\n     * <code>command(CommandContext args, CommandSender sender)</code> where\r\n     * {@link CommandSender} can be replaced with {@link Player} to only accept\r\n     * players. The method parameters must include the method args given, if\r\n     * any.\r\n     *\r\n     * @param command    The command to execute\r\n     * @param args       The arguments of the command\r\n     * @param sender     The sender of the command\r\n     * @param methodArgs The method arguments to be used when calling the command\r\n     *                   handler\r\n     * @throws CommandException Any exceptions caused from execution of the command\r\n     */\r\n    public void execute(org.bukkit.command.Command command, String[] args, CommandSender sender, Object... methodArgs)\r\n            throws CommandException {\r\n        // must put command into split.\r\n        String[] newArgs = new String[args.length + 1];\r\n        System.arraycopy(args, 0, newArgs, 1, args.length);\r\n        newArgs[0] = CoreUtilities.toLowerCase(command.getName());\r\n\r\n        Object[] newMethodArgs = new Object[methodArgs.length + 1];\r\n        System.arraycopy(methodArgs, 0, newMethodArgs, 1, methodArgs.length);\r\n        executeMethod(newArgs, sender, newMethodArgs);\r\n    }\r\n\r\n    private void executeHelp(String[] args, CommandSender sender) throws CommandException {\r\n        if (!sender.hasPermission(\"denizen.basic\")) {\r\n            throw new NoPermissionsException();\r\n        }\r\n        int page = 1;\r\n        try {\r\n            page = args.length == 3 ? Integer.parseInt(args[2]) : page;\r\n        }\r\n        catch (NumberFormatException e) {\r\n            sendSpecificHelp(sender, args[0], args[2]);\r\n            return;\r\n        }\r\n        sendHelp(sender, args[0], page);\r\n    }\r\n\r\n    // Attempt to execute a command.\r\n    private void executeMethod(String[] args, CommandSender sender, Object[] methodArgs) throws CommandException {\r\n        String cmdName = CoreUtilities.toLowerCase(args[0]);\r\n        String modifier = args.length > 1 ? args[1] : \"\";\r\n        boolean help = CoreUtilities.equalsIgnoreCase(modifier, \"help\");\r\n\r\n        Method method = commands.get(cmdName + \" \" + CoreUtilities.toLowerCase(modifier));\r\n        if (method == null && !help) {\r\n            method = commands.get(cmdName + \" *\");\r\n        }\r\n\r\n        if (method == null && help) {\r\n            executeHelp(args, sender);\r\n            return;\r\n        }\r\n\r\n        if (method == null) {\r\n            throw new UnhandledCommandException();\r\n        }\r\n\r\n        if (!serverCommands.contains(method) && sender instanceof ConsoleCommandSender) {\r\n            throw new ServerCommandException();\r\n        }\r\n\r\n        if (!hasPermission(method, sender)) {\r\n            throw new NoPermissionsException();\r\n        }\r\n\r\n        Command cmd = method.getAnnotation(Command.class);\r\n        CommandContext context = new CommandContext(sender, args);\r\n\r\n        if (context.argsLength() < cmd.min()) {\r\n            throw new CommandUsageException(\"Too few arguments.\", getUsage(args, cmd));\r\n        }\r\n\r\n        if (cmd.max() != -1 && context.argsLength() > cmd.max()) {\r\n            throw new CommandUsageException(\"Too many arguments.\", getUsage(args, cmd));\r\n        }\r\n\r\n        if (!cmd.flags().contains(\"*\")) {\r\n            for (char flag : context.getFlags()) {\r\n                if (cmd.flags().indexOf(String.valueOf(flag)) == -1) {\r\n                    throw new CommandUsageException(\"Unknown flag: \" + flag, getUsage(args, cmd));\r\n                }\r\n            }\r\n        }\r\n\r\n        methodArgs[0] = context;\r\n\r\n        for (Annotation annotation : registeredAnnotations.get(method)) {\r\n            CommandAnnotationProcessor processor = annotationProcessors.get(annotation.annotationType());\r\n            processor.process(sender, context, annotation, methodArgs);\r\n        }\r\n\r\n        Object instance = instances.get(method);\r\n        try {\r\n            method.invoke(instance, methodArgs);\r\n        }\r\n        catch (IllegalArgumentException | IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        catch (InvocationTargetException e) {\r\n            if (e.getCause() instanceof CommandException) {\r\n                throw (CommandException) e.getCause();\r\n            }\r\n            throw new WrappedCommandException(e.getCause());\r\n        }\r\n    }\r\n\r\n    /**\r\n     * A safe version of <code>execute</code> which catches and logs all errors\r\n     * that occur. Returns whether the command handler should print usage or\r\n     * not.\r\n     *\r\n     * @return Whether further usage should be printed\r\n     * @see #execute(org.bukkit.command.Command, String[], CommandSender, Object...)\r\n     */\r\n    public boolean executeSafe(org.bukkit.command.Command command, String[] args, CommandSender sender,\r\n                               Object... methodArgs) {\r\n        try {\r\n            try {\r\n                execute(command, args, sender, methodArgs);\r\n            }\r\n            catch (ServerCommandException ex) {\r\n                Messaging.send(sender, \"You must be ingame to use that command.\");\r\n            }\r\n            catch (CommandUsageException ex) {\r\n                Messaging.sendError(sender, ex.getMessage());\r\n                Messaging.sendError(sender, ex.getUsage());\r\n            }\r\n            catch (UnhandledCommandException ex) {\r\n                return false;\r\n            }\r\n            catch (WrappedCommandException ex) {\r\n                throw ex.getCause();\r\n            }\r\n            catch (CommandException ex) {\r\n                Messaging.sendError(sender, ex.getMessage());\r\n            }\r\n            catch (NumberFormatException ex) {\r\n                Messaging.sendError(sender, \"That is not a valid number.\");\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            if (sender instanceof Player) {\r\n                Messaging.sendError(sender, \"Please report this error: [See console]\");\r\n                Messaging.sendError(sender, ex.getClass().getName() + \": \" + ex.getMessage());\r\n            }\r\n        }\r\n        return true;\r\n    }\r\n\r\n    /**\r\n     * Searches for the closest modifier using Levenshtein distance to the given\r\n     * top level command and modifier.\r\n     *\r\n     * @param command  The top level command\r\n     * @param modifier The modifier to use as the base\r\n     * @return The closest modifier, or empty\r\n     */\r\n    public String getClosestCommandModifier(String command, String modifier) {\r\n        int minDist = Integer.MAX_VALUE;\r\n        command = CoreUtilities.toLowerCase(command);\r\n        String closest = \"\";\r\n        for (String cmd : commands.keySet()) {\r\n            String[] split = cmd.split(\" \");\r\n            if (split.length <= 1 || !split[0].equals(command)) {\r\n                continue;\r\n            }\r\n            int distance = getLevenshteinDistance(modifier, split[1]);\r\n            if (minDist > distance) {\r\n                minDist = distance;\r\n                closest = split[1];\r\n            }\r\n        }\r\n\r\n        return closest;\r\n    }\r\n\r\n    /**\r\n     * Gets the {@link CommandInfo} for the given top level command and\r\n     * modifier, or null if not found.\r\n     *\r\n     * @param rootCommand The top level command\r\n     * @param modifier    The modifier (may be empty)\r\n     * @return The command info for the command\r\n     */\r\n    public CommandInfo getCommand(String rootCommand, String modifier) {\r\n        String joined = rootCommand + ' ' + modifier;\r\n        for (Map.Entry<String, Method> entry : commands.entrySet()) {\r\n            if (!entry.getKey().equalsIgnoreCase(joined) || entry.getValue() == null) {\r\n                continue;\r\n            }\r\n            Command commandAnnotation = entry.getValue().getAnnotation(Command.class);\r\n            if (commandAnnotation == null) {\r\n                continue;\r\n            }\r\n            return new CommandInfo(commandAnnotation);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    /**\r\n     * Gets all modified and root commands from the given root level command.\r\n     * For example, if <code>/npc look</code> and <code>/npc jump</code> were\r\n     * defined, calling <code>getCommands(\"npc\")</code> would return\r\n     * {@link CommandInfo}s for both commands.\r\n     *\r\n     * @param command The root level command\r\n     * @return The list of {@link CommandInfo}s\r\n     */\r\n    public List<CommandInfo> getCommands(String command) {\r\n        List<CommandInfo> cmds = new ArrayList<>();\r\n        command = CoreUtilities.toLowerCase(command);\r\n        for (Map.Entry<String, Method> entry : commands.entrySet()) {\r\n            if (!entry.getKey().startsWith(command) || entry.getValue() == null) {\r\n                continue;\r\n            }\r\n            Command commandAnnotation = entry.getValue().getAnnotation(Command.class);\r\n            if (commandAnnotation == null) {\r\n                continue;\r\n            }\r\n            cmds.add(new CommandInfo(commandAnnotation));\r\n        }\r\n        return cmds;\r\n    }\r\n\r\n    private List<String> getLines(CommandSender sender, String baseCommand) {\r\n        // Ensures that commands with multiple modifiers are only added once\r\n        Set<CommandInfo> processed = new HashSet<>();\r\n        List<String> lines = new ArrayList<>();\r\n        for (CommandInfo info : getCommands(baseCommand)) {\r\n            Command command = info.getCommandAnnotation();\r\n            if (processed.contains(info) || !sender.hasPermission(command.permission())) {\r\n                continue;\r\n            }\r\n            lines.add(format(command, baseCommand));\r\n            if (command.modifiers().length > 1) {\r\n                processed.add(info);\r\n            }\r\n        }\r\n        return lines;\r\n    }\r\n\r\n    private String getUsage(String[] args, Command cmd) {\r\n        return \"/\" + args[0] + \" \" + cmd.usage();\r\n    }\r\n\r\n    /**\r\n     * Checks to see whether there is a command handler for the given command at\r\n     * the root level. This will check aliases as well.\r\n     *\r\n     * @param cmd      The command to check\r\n     * @param modifier The modifier to check (may be empty)\r\n     * @return Whether the command is handled\r\n     */\r\n    public boolean hasCommand(org.bukkit.command.Command cmd, String modifier) {\r\n        String cmdName = CoreUtilities.toLowerCase(cmd.getName());\r\n        return commands.containsKey(cmdName + \" \" + CoreUtilities.toLowerCase(modifier)) || commands.containsKey(cmdName + \" *\");\r\n    }\r\n\r\n    // Returns whether a CommandSender has permission.\r\n    private boolean hasPermission(CommandSender sender, String perm) {\r\n        return sender.hasPermission(perm);\r\n    }\r\n\r\n    // Returns whether a player has access to a command.\r\n    private boolean hasPermission(Method method, CommandSender sender) {\r\n        Command cmd = method.getAnnotation(Command.class);\r\n        return cmd.permission().isEmpty() || hasPermission(sender, cmd.permission()) || hasPermission(sender, \"admin\");\r\n    }\r\n\r\n    @Override\r\n    public List<String> onTabComplete(CommandSender sender, org.bukkit.command.Command command, String alias, String[] args) {\r\n        List<String> results = new ArrayList<>();\r\n        if (args.length <= 1) {\r\n            String search = args.length == 1 ? args[0] : \"\";\r\n            for (String base : commands.keySet()) {\r\n                String[] parts = base.split(\" \");\r\n                String cmd = parts[0];\r\n                if (!cmd.equalsIgnoreCase(command.getName()) || parts.length < 2)\r\n                    continue;\r\n                String modifier = parts[1];\r\n                if (modifier.startsWith(search)) {\r\n                    results.add(modifier);\r\n                }\r\n            }\r\n            return results;\r\n        }\r\n        CommandInfo internalCommand = getCommand(command.getName().toLowerCase(), args[0]);\r\n        if (internalCommand == null) {\r\n            return results;\r\n        }\r\n        String[] newArgs = new String[args.length + 1];\r\n        System.arraycopy(args, 0, newArgs, 1, args.length);\r\n        newArgs[0] = command.getName().toLowerCase();\r\n        CommandContext context = new CommandContext(sender, newArgs); // partial parse\r\n        String flags = internalCommand.commandAnnotation.flags();\r\n        for (int i = 0; i < flags.length(); i++) {\r\n            char c = flags.charAt(i);\r\n            if (!context.hasFlag(c)) {\r\n                results.add(\"-\" + c);\r\n            }\r\n        }\r\n        Collection<String> valueFlags = internalCommand.valueFlags();\r\n        for (String valueFlag : valueFlags) {\r\n            if (!context.hasValueFlag(valueFlag)) {\r\n                results.add(\"--\" + valueFlag);\r\n            }\r\n        }\r\n        return results;\r\n    }\r\n\r\n    /**\r\n     * Register a class that contains commands (methods annotated with\r\n     * {@link Command}). If no dependency {@link Injector} is specified, then\r\n     * only static methods of the class will be registered. Otherwise, new\r\n     * instances the command class will be created and instance methods will be\r\n     * called.\r\n     *\r\n     * @param clazz The class to scan\r\n     * @see #setInjector(Injector)\r\n     */\r\n    public void register(Class<?> clazz) {\r\n        registerMethods(clazz, null);\r\n    }\r\n\r\n    /**\r\n     * Registers an {@link CommandAnnotationProcessor} that can process\r\n     * annotations before a command is executed.\r\n     * <p/>\r\n     * Methods with the {@link Command} annotation will have the rest of their\r\n     * annotations scanned and stored if there is a matching\r\n     * {@link CommandAnnotationProcessor}. Annotations that do not have a\r\n     * processor are discarded. The scanning method uses annotations from the\r\n     * declaring class as a base before narrowing using the method's\r\n     * annotations.\r\n     *\r\n     * @param processor The annotation processor\r\n     */\r\n    public void registerAnnotationProcessor(CommandAnnotationProcessor processor) {\r\n        annotationProcessors.put(processor.getAnnotationClass(), processor);\r\n    }\r\n\r\n    /*\r\n     * Register the methods of a class. This will automatically construct\r\n     * instances as necessary.\r\n     */\r\n    private void registerMethods(Class<?> clazz, Method parent) {\r\n        Object obj = injector != null ? injector.getInstance(clazz) : null;\r\n        registerMethods(clazz, parent, obj);\r\n    }\r\n\r\n    // Register the methods of a class.\r\n    private void registerMethods(Class<?> clazz, Method parent, Object obj) {\r\n        for (Method method : clazz.getMethods()) {\r\n            if (!method.isAnnotationPresent(Command.class)) {\r\n                continue;\r\n            }\r\n            // We want to be able invoke with an instance\r\n            if (!Modifier.isStatic(method.getModifiers())) {\r\n                // Can't register this command if we don't have an instance\r\n                if (obj == null) {\r\n                    continue;\r\n                }\r\n                instances.put(method, obj);\r\n            }\r\n\r\n            Command cmd = method.getAnnotation(Command.class);\r\n            // Cache the aliases too\r\n            for (String alias : cmd.aliases()) {\r\n                for (String modifier : cmd.modifiers()) {\r\n                    commands.put(alias + \" \" + modifier, method);\r\n                }\r\n                if (!commands.containsKey(alias + \" help\")) {\r\n                    commands.put(alias + \" help\", null);\r\n                }\r\n            }\r\n\r\n            List<Annotation> annotations = new ArrayList<>();\r\n            for (Annotation annotation : method.getDeclaringClass().getAnnotations()) {\r\n                Class<? extends Annotation> annotationClass = annotation.annotationType();\r\n                if (annotationProcessors.containsKey(annotationClass)) {\r\n                    annotations.add(annotation);\r\n                }\r\n            }\r\n            for (Annotation annotation : method.getAnnotations()) {\r\n                Class<? extends Annotation> annotationClass = annotation.annotationType();\r\n                if (!annotationProcessors.containsKey(annotationClass)) {\r\n                    continue;\r\n                }\r\n                Iterator<Annotation> itr = annotations.iterator();\r\n                while (itr.hasNext()) {\r\n                    Annotation previous = itr.next();\r\n                    if (previous.annotationType() == annotationClass) {\r\n                        itr.remove();\r\n                    }\r\n                }\r\n                annotations.add(annotation);\r\n            }\r\n\r\n            if (annotations.size() > 0) {\r\n                registeredAnnotations.putAll(method, annotations);\r\n            }\r\n\r\n            Class<?>[] parameterTypes = method.getParameterTypes();\r\n            if (parameterTypes.length <= 1 || parameterTypes[1] == CommandSender.class) {\r\n                serverCommands.add(method);\r\n            }\r\n        }\r\n    }\r\n\r\n    private void sendHelp(CommandSender sender, String name, int page) throws CommandException {\r\n        if (name.equalsIgnoreCase(\"npc\")) {\r\n            name = \"NPC\";\r\n        }\r\n        Paginator paginator = new Paginator().header(capitalize(name) + \" Help\");\r\n        for (String line : getLines(sender, CoreUtilities.toLowerCase(name))) {\r\n            paginator.addLine(line);\r\n        }\r\n        if (!paginator.sendPage(sender, page)) {\r\n            throw new CommandException(\"The page \" + page + \" does not exist.\");\r\n        }\r\n    }\r\n\r\n    private void sendSpecificHelp(CommandSender sender, String rootCommand, String modifier) throws CommandException {\r\n        CommandInfo info = getCommand(rootCommand, modifier);\r\n        if (info == null) {\r\n            throw new CommandException(\"Command /\" + rootCommand + \" \" + modifier + \" not found.\");\r\n        }\r\n        Messaging.send(sender, format(info.getCommandAnnotation(), rootCommand));\r\n        if (info.getCommandAnnotation().help().isEmpty()) {\r\n            return;\r\n        }\r\n        Messaging.send(sender, \"<b>\" + info.getCommandAnnotation().help());\r\n    }\r\n\r\n    public void setInjector(Injector injector) {\r\n        this.injector = injector;\r\n    }\r\n\r\n    public static class CommandInfo {\r\n        private final Command commandAnnotation;\r\n        private List<String> valueFlags;\r\n\r\n        public CommandInfo(Command commandAnnotation) {\r\n            this.commandAnnotation = commandAnnotation;\r\n        }\r\n\r\n        private Collection<String> calculateValueFlags() {\r\n            valueFlags = new ArrayList<>();\r\n            String[] usage = commandAnnotation.usage().replace(\"(\", \"\").replace(\")\", \"\").split(\" \");\r\n            for (String part : usage) {\r\n                if (part.startsWith(\"--\")) {\r\n                    valueFlags.add(part.split(\"\\\\|\")[0].replace(\"--\", \"\"));\r\n                }\r\n            }\r\n            return valueFlags;\r\n        }\r\n\r\n        @Override\r\n        public boolean equals(Object obj) {\r\n            if (this == obj) {\r\n                return true;\r\n            }\r\n            if (obj == null || getClass() != obj.getClass()) {\r\n                return false;\r\n            }\r\n            CommandInfo other = (CommandInfo) obj;\r\n            if (commandAnnotation == null) {\r\n                if (other.commandAnnotation != null) {\r\n                    return false;\r\n                }\r\n            }\r\n            else if (!commandAnnotation.equals(other.commandAnnotation)) {\r\n                return false;\r\n            }\r\n            return true;\r\n        }\r\n\r\n        public Command getCommandAnnotation() {\r\n            return commandAnnotation;\r\n        }\r\n\r\n        @Override\r\n        public int hashCode() {\r\n            return 31 + ((commandAnnotation == null) ? 0 : commandAnnotation.hashCode());\r\n        }\r\n\r\n        public Collection<String> valueFlags() {\r\n            return valueFlags == null ? calculateValueFlags() : valueFlags;\r\n        }\r\n    }\r\n\r\n    private static String capitalize(Object string) {\r\n        String capitalize = string.toString();\r\n        return capitalize.length() == 0 ? \"\" : Character.toUpperCase(capitalize.charAt(0))\r\n                + capitalize.substring(1);\r\n    }\r\n\r\n    private static String format(Command command, String alias) {\r\n        return String.format(COMMAND_FORMAT, alias, (command.usage().isEmpty() ? \"\" : \" \" + command.usage()),\r\n                command.desc());\r\n    }\r\n\r\n    private static int getLevenshteinDistance(String s, String t) {\r\n        if (s == null || t == null) {\r\n            throw new IllegalArgumentException(\"Strings must not be null\");\r\n        }\r\n\r\n        int n = s.length(); // length of s\r\n        int m = t.length(); // length of t\r\n\r\n        if (n == 0) {\r\n            return m;\r\n        }\r\n        else if (m == 0) {\r\n            return n;\r\n        }\r\n\r\n        int[] p = new int[n + 1]; // 'previous' cost array, horizontally\r\n        int[] d = new int[n + 1]; // cost array, horizontally\r\n        int[] _d; // placeholder to assist in swapping p and d\r\n\r\n        // indexes into strings s and t\r\n        int i; // iterates through s\r\n        int j; // iterates through t\r\n\r\n        char t_j; // jth character of t\r\n\r\n        int cost; // cost\r\n\r\n        for (i = 0; i <= n; i++) {\r\n            p[i] = i;\r\n        }\r\n\r\n        for (j = 1; j <= m; j++) {\r\n            t_j = t.charAt(j - 1);\r\n            d[0] = j;\r\n\r\n            for (i = 1; i <= n; i++) {\r\n                cost = s.charAt(i - 1) == t_j ? 0 : 1;\r\n                // minimum of cell to the left+1, to the top+1, diagonally left\r\n                // and up +cost\r\n                d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);\r\n            }\r\n\r\n            // copy current distance counts to 'previous row' distance counts\r\n            _d = p;\r\n            p = d;\r\n            d = _d;\r\n        }\r\n\r\n        // our last action in the above loop was to switch d and p, so p now\r\n        // actually has the most recent cost counts\r\n        return p[n];\r\n    }\r\n\r\n    private static final String COMMAND_FORMAT = \"<7>/<c>%s%s <7>- <e>%s\";\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/Injector.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\n\r\nimport java.lang.reflect.Constructor;\r\nimport java.lang.reflect.InvocationTargetException;\r\n\r\npublic class Injector {\r\n\r\n    private final Class<?>[] argClasses;\r\n    private final Object[] args;\r\n\r\n    public Injector(Object... args) {\r\n        this.args = args;\r\n        argClasses = new Class[args.length];\r\n        for (int i = 0; i < args.length; ++i) {\r\n            argClasses[i] = args[i].getClass();\r\n        }\r\n    }\r\n\r\n    public Object getInstance(Class<?> clazz) {\r\n        try {\r\n            Constructor<?> ctr = clazz.getConstructor(argClasses);\r\n            ctr.setAccessible(true);\r\n            return ctr.newInstance(args);\r\n        }\r\n        catch (NoSuchMethodException e) {\r\n            try {\r\n                return clazz.newInstance();\r\n            }\r\n            catch (Exception ex) {\r\n                Debug.echoError(\"Error initializing commands class \" + clazz + \": \");\r\n                Debug.echoError(ex);\r\n                return null;\r\n            }\r\n        }\r\n        catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {\r\n            Debug.echoError(\"Error initializing commands class \" + clazz + \": \");\r\n            Debug.echoError(e);\r\n            return null;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/Paginator.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager;\r\n\r\nimport com.denizenscript.denizen.utilities.command.manager.messaging.Messaging;\r\nimport org.bukkit.command.CommandSender;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class Paginator {\r\n\r\n    private String header;\r\n    private final List<String> lines = new ArrayList<>();\r\n\r\n    public void addLine(String line) {\r\n        lines.add(line);\r\n    }\r\n\r\n    public Paginator header(String header) {\r\n        this.header = header;\r\n        return this;\r\n    }\r\n\r\n    public boolean sendPage(CommandSender sender, int page) {\r\n        int pages = (int) ((lines.size() / LINES_PER_PAGE == 0) ? 1 : Math.ceil((double) lines.size() / LINES_PER_PAGE));\r\n        if (page < 0 || page > pages) {\r\n            return false;\r\n        }\r\n\r\n        int startIndex = LINES_PER_PAGE * page - LINES_PER_PAGE;\r\n        int endIndex = page * LINES_PER_PAGE;\r\n\r\n        Messaging.send(sender, wrapHeader(\"<e>\" + header + \" <f>\" + page + \"/\" + pages));\r\n\r\n        if (lines.size() < endIndex) {\r\n            endIndex = lines.size();\r\n        }\r\n        for (String line : lines.subList(startIndex, endIndex)) {\r\n            Messaging.send(sender, line);\r\n        }\r\n        return true;\r\n    }\r\n\r\n    public static String wrapHeader(Object string) {\r\n        String highlight = \"<e>\";\r\n        return highlight + \"=====[ \" + string.toString() + highlight + \" ]=====\";\r\n    }\r\n\r\n    private static final int LINES_PER_PAGE = 9;\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/Requirements.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager;\r\n\r\nimport java.lang.annotation.Retention;\r\nimport java.lang.annotation.RetentionPolicy;\r\n\r\n@Retention(RetentionPolicy.RUNTIME)\r\npublic @interface Requirements {\r\n\r\n    // TODO: add Denizen-based requirements/remove this system\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/RequirementsProcessor.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager;\r\n\r\nimport com.denizenscript.denizen.utilities.command.manager.exceptions.CommandException;\r\nimport org.bukkit.command.CommandSender;\r\n\r\nimport java.lang.annotation.Annotation;\r\n\r\npublic class RequirementsProcessor implements CommandAnnotationProcessor {\r\n\r\n    @Override\r\n    public Class<? extends Annotation> getAnnotationClass() {\r\n        return Requirements.class;\r\n    }\r\n\r\n    @Override\r\n    public void process(CommandSender sender, CommandContext context, Annotation instance, Object[] methodArgs)\r\n            throws CommandException {\r\n        // TODO: add Denizen-based requirements/remove this system\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/exceptions/CommandException.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager.exceptions;\r\n\r\npublic class CommandException extends Exception {\r\n\r\n    public CommandException() {\r\n        super();\r\n    }\r\n\r\n    public CommandException(String message) {\r\n        super(message);\r\n    }\r\n\r\n    public CommandException(Throwable t) {\r\n        super(t);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/exceptions/CommandUsageException.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager.exceptions;\r\n\r\npublic class CommandUsageException extends CommandException {\r\n\r\n    protected String usage;\r\n\r\n    public CommandUsageException(String message, String usage) {\r\n        super(message);\r\n        this.usage = usage;\r\n    }\r\n\r\n    public String getUsage() {\r\n        return usage;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/exceptions/NoPermissionsException.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager.exceptions;\r\n\r\npublic class NoPermissionsException extends CommandException {\r\n\r\n    public NoPermissionsException() {\r\n        super(\"You don't have permission to execute that command.\");\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/exceptions/RequirementMissingException.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager.exceptions;\r\n\r\npublic class RequirementMissingException extends CommandException {\r\n\r\n    public RequirementMissingException(String message) {\r\n        super(message);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/exceptions/ServerCommandException.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager.exceptions;\r\n\r\npublic class ServerCommandException extends CommandException {\r\n\r\n    // Placeholder\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/exceptions/UnhandledCommandException.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager.exceptions;\r\n\r\npublic class UnhandledCommandException extends CommandException {\r\n\r\n    // Placeholder\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/exceptions/WrappedCommandException.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager.exceptions;\r\n\r\npublic class WrappedCommandException extends CommandException {\r\n\r\n    public WrappedCommandException(Throwable t) {\r\n        super(t);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/messaging/Colorizer.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager.messaging;\r\n\r\nimport org.bukkit.ChatColor;\r\n\r\nimport java.util.regex.Matcher;\r\nimport java.util.regex.Pattern;\r\n\r\npublic class Colorizer {\r\n\r\n    public static String parseColors(String parsed) {\r\n        Matcher matcher = COLOR_MATCHER.matcher(ChatColor.translateAlternateColorCodes('&', parsed));\r\n        return matcher.replaceAll(GROUP);\r\n    }\r\n\r\n    public static String stripColors(String parsed) {\r\n        Matcher matcher = COLOR_MATCHER.matcher(parsed);\r\n        return matcher.replaceAll(\"\");\r\n    }\r\n\r\n    private static Pattern COLOR_MATCHER;\r\n    private static String GROUP = ChatColor.COLOR_CHAR + \"$1\";\r\n\r\n    static {\r\n        StringBuilder colors = new StringBuilder(\"<([\");\r\n        for (ChatColor color : ChatColor.values()) {\r\n            colors.append(color.getChar());\r\n        }\r\n        colors.append(\"])>\");\r\n        COLOR_MATCHER = Pattern.compile(colors.toString(), Pattern.CASE_INSENSITIVE);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/manager/messaging/Messaging.java",
    "content": "package com.denizenscript.denizen.utilities.command.manager.messaging;\r\n\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.entity.Player;\r\n\r\npublic class Messaging {\r\n\r\n    public static void configure(String messageColour, String highlightColour) {\r\n        MESSAGE_COLOUR = messageColour;\r\n        HIGHLIGHT_COLOUR = highlightColour;\r\n    }\r\n\r\n    private static String prettify(String message) {\r\n        String trimmed = message.trim();\r\n        String messageColor = Colorizer.parseColors(MESSAGE_COLOUR);\r\n        if (!trimmed.isEmpty()) {\r\n            if (trimmed.charAt(0) == ChatColor.COLOR_CHAR) {\r\n                ChatColor test = ChatColor.getByChar(trimmed.substring(1, 2));\r\n                if (test == null) {\r\n                    message = messageColor + message;\r\n                }\r\n            }\r\n            else {\r\n                message = messageColor + message;\r\n            }\r\n        }\r\n        return message;\r\n    }\r\n\r\n    public static void send(CommandSender sender, String msg) {\r\n        sendMessageTo(sender, msg);\r\n    }\r\n\r\n    public static void sendInfo(CommandSender sender, String msg) {\r\n        send(sender, ChatColor.YELLOW + msg);\r\n    }\r\n\r\n    public static void sendError(CommandSender sender, String msg) {\r\n        send(sender, ChatColor.RED + msg);\r\n    }\r\n\r\n    private static void sendMessageTo(CommandSender sender, String rawMessage) {\r\n        if (sender instanceof Player) {\r\n            Player player = (Player) sender;\r\n            rawMessage = rawMessage.replace(\"<player>\", player.getName());\r\n            rawMessage = rawMessage.replace(\"<world>\", player.getWorld().getName());\r\n        }\r\n        rawMessage = Colorizer.parseColors(rawMessage);\r\n        for (String message : rawMessage.split(\"<br>|<n>|\\\\n\")) {\r\n            sender.sendMessage(prettify(message));\r\n        }\r\n    }\r\n\r\n    private static String HIGHLIGHT_COLOUR = ChatColor.YELLOW.toString();\r\n\r\n    private static String MESSAGE_COLOUR = ChatColor.GREEN.toString();\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/scripted/DenizenAliasHelpTopic.java",
    "content": "package com.denizenscript.denizen.utilities.command.scripted;\r\n\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.help.HelpMap;\r\nimport org.bukkit.help.HelpTopic;\r\n\r\npublic class DenizenAliasHelpTopic extends HelpTopic {\r\n\r\n    private final String aliasFor;\r\n    private final HelpMap helpMap;\r\n\r\n    public DenizenAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) {\r\n        this.aliasFor = aliasFor.startsWith(\"/\") ? aliasFor : \"/\" + aliasFor;\r\n        this.helpMap = helpMap;\r\n        this.name = alias.startsWith(\"/\") ? alias : \"/\" + alias;\r\n        if (name.equals(aliasFor)) {\r\n            throw new IllegalArgumentException(\"Command \" + this.name + \" cannot be alias for itself\");\r\n        }\r\n        this.shortText = ChatColor.YELLOW + \"Alias for \" + ChatColor.WHITE + this.aliasFor;\r\n    }\r\n\r\n    public String getFullText(CommandSender forWho) {\r\n        StringBuilder sb = new StringBuilder(this.shortText);\r\n        HelpTopic aliasForTopic = this.helpMap.getHelpTopic(this.aliasFor);\r\n        if (aliasForTopic != null) {\r\n            sb.append(\"\\n\");\r\n            sb.append(aliasForTopic.getFullText(forWho));\r\n        }\r\n\r\n        return sb.toString();\r\n    }\r\n\r\n    public boolean canSee(CommandSender commandSender) {\r\n        if (this.amendedPermission == null) {\r\n            HelpTopic aliasForTopic = this.helpMap.getHelpTopic(this.aliasFor);\r\n            return aliasForTopic != null && aliasForTopic.canSee(commandSender);\r\n        }\r\n        else {\r\n            return commandSender.hasPermission(this.amendedPermission);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/scripted/DenizenCommand.java",
    "content": "package com.denizenscript.denizen.utilities.command.scripted;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.CommandScriptContainer;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.objects.core.ScriptTag;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.SimpleDefinitionProvider;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.citizensnpcs.api.CitizensAPI;\r\nimport net.citizensnpcs.api.npc.NPC;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.command.BlockCommandSender;\r\nimport org.bukkit.command.Command;\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.entity.minecart.CommandMinecart;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class DenizenCommand extends Command {\r\n\r\n    private CommandScriptContainer script;\r\n\r\n    public DenizenCommand(CommandScriptContainer script) {\r\n        super(script.getCommandName(), script.getDescription(), script.getUsage(), script.getAliases());\r\n        String permission = script.getPermission();\r\n        if (permission != null && !permission.isEmpty()) {\r\n            this.setPermission(permission);\r\n            String permissionMessage = script.getPermissionMessage();\r\n            if (permissionMessage != null && !permissionMessage.isEmpty()) {\r\n                this.setPermissionMessage(permissionMessage);\r\n            }\r\n        }\r\n        this.script = script;\r\n    }\r\n\r\n    public boolean canSeeHelp(CommandSender commandSender) {\r\n        if (!script.hasAllowedHelpProcedure()) {\r\n            return true;\r\n        }\r\n        if (!testPermissionSilent(commandSender)) {\r\n            return false;\r\n        }\r\n        Map<String, ObjectTag> context = new HashMap<>();\r\n        PlayerTag player = null;\r\n        if (commandSender instanceof Player) {\r\n            Player pl = (Player) commandSender;\r\n            if (!EntityTag.isNPC(pl)) {\r\n                player = PlayerTag.mirrorBukkitPlayer(pl);\r\n            }\r\n            context.put(\"server\", new ElementTag(false));\r\n        }\r\n        else {\r\n            context.put(\"server\", new ElementTag(true));\r\n        }\r\n        Debug.push3ErrorContexts(script, \"while reading Allowed Help procedure\", player);\r\n        try {\r\n            return script.runAllowedHelpProcedure(player, null, context);\r\n        }\r\n        finally {\r\n            Debug.popErrorContext(3);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean testPermission(CommandSender target) {\r\n        if (testPermissionSilent(target)) {\r\n            return true;\r\n        }\r\n\r\n        String permissionMessage = getPermissionMessage();\r\n        if (permissionMessage == null) {\r\n            target.sendMessage(ChatColor.RED + \"I'm sorry, but you do not have permission to perform this command. \"\r\n                    + \"Please contact the server administrators if you believe that this is in error.\");\r\n        }\r\n        else if (permissionMessage.length() != 0) {\r\n            PlayerTag player = null;\r\n            NPCTag npc = null;\r\n            if (target instanceof Player) {\r\n                Player pl = (Player) target;\r\n                if (EntityTag.isCitizensNPC(pl)) {\r\n                    npc = NPCTag.fromEntity(pl);\r\n                }\r\n                else {\r\n                    player = PlayerTag.mirrorBukkitPlayer(pl);\r\n                }\r\n            }\r\n            if (Depends.citizens != null && npc == null) {\r\n                NPC citizen = CitizensAPI.getDefaultNPCSelector().getSelected(target);\r\n                if (citizen != null) {\r\n                    npc = new NPCTag(citizen);\r\n                }\r\n            }\r\n            BukkitTagContext context = new BukkitTagContext(player, npc, null, false, new ScriptTag(script));\r\n            context.definitionProvider = new SimpleDefinitionProvider();\r\n            context.definitionProvider.addDefinition(\"permission\", new ElementTag(getPermission()));\r\n            Debug.push3ErrorContexts(script, \"while reading Permission Message\", player);\r\n            try {\r\n                for (String line : TagManager.tag(permissionMessage, context).split(\"\\n\")) {\r\n                    target.sendMessage(line);\r\n                }\r\n            }\r\n            finally {\r\n                Debug.popErrorContext(3);\r\n            }\r\n        }\r\n\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean execute(CommandSender commandSender, String commandLabel, String[] arguments) {\r\n        if (!testPermission(commandSender)) {\r\n            return true;\r\n        }\r\n        Map<String, ObjectTag> context = new HashMap<>();\r\n        String raw_args = \"\";\r\n        if (arguments.length > 0) {\r\n            StringBuilder rawArgsBuilder = new StringBuilder();\r\n            for (String arg : arguments) {\r\n                rawArgsBuilder.append(arg).append(' ');\r\n            }\r\n            raw_args = rawArgsBuilder.substring(0, rawArgsBuilder.length() - 1);\r\n        }\r\n        List<String> args = Arrays.asList(ArgumentHelper.buildArgs(raw_args, false));\r\n        context.put(\"args\", new ListTag(args, true));\r\n        context.put(\"raw_args\", new ElementTag(raw_args, true));\r\n        context.put(\"alias\", new ElementTag(commandLabel, true));\r\n        PlayerTag player = null;\r\n        NPCTag npc = null;\r\n        if (commandSender instanceof Player) {\r\n            Player pl = (Player) commandSender;\r\n            if (EntityTag.isCitizensNPC(pl)) {\r\n                npc = NPCTag.fromEntity(pl);\r\n            }\r\n            else {\r\n                player = PlayerTag.mirrorBukkitPlayer(pl);\r\n            }\r\n            context.put(\"server\", new ElementTag(false));\r\n            context.put(\"source_type\", new ElementTag(\"player\"));\r\n        }\r\n        else {\r\n            if (commandSender instanceof BlockCommandSender) {\r\n                context.put(\"command_block_location\", new LocationTag(((BlockCommandSender) commandSender).getBlock().getLocation()));\r\n                context.put(\"server\", new ElementTag(false));\r\n                context.put(\"source_type\", new ElementTag(\"command_block\"));\r\n            }\r\n            else if (commandSender instanceof CommandMinecart) {\r\n                context.put(\"command_minecart\", new EntityTag((CommandMinecart) commandSender));\r\n                context.put(\"server\", new ElementTag(false));\r\n                context.put(\"source_type\", new ElementTag(\"command_minecart\"));\r\n            }\r\n            else {\r\n                context.put(\"server\", new ElementTag(true));\r\n                context.put(\"source_type\", new ElementTag(\"server\"));\r\n            }\r\n        }\r\n        if (Depends.citizens != null && npc == null) {\r\n            NPC citizen = CitizensAPI.getDefaultNPCSelector().getSelected(commandSender);\r\n            if (citizen != null) {\r\n                npc = new NPCTag(citizen);\r\n            }\r\n        }\r\n        script.runCommandScript(player, npc, context);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean isRegistered() {\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public List<String> tabComplete(CommandSender commandSender, String alias, String[] arguments) {\r\n        if (!script.hasTabCompleteProcedure()) {\r\n            return super.tabComplete(commandSender, alias, arguments);\r\n        }\r\n        Map<String, ObjectTag> context = new HashMap<>();\r\n        String raw_args = \"\";\r\n        if (arguments.length > 0) {\r\n            StringBuilder rawArgsBuilder = new StringBuilder();\r\n            for (String arg : arguments) {\r\n                rawArgsBuilder.append(arg).append(' ');\r\n            }\r\n            raw_args = rawArgsBuilder.substring(0, rawArgsBuilder.length() - 1);\r\n        }\r\n        List<String> args = Arrays.asList(ArgumentHelper.buildArgs(raw_args, false));\r\n        context.put(\"args\", new ListTag(args, true));\r\n        context.put(\"raw_args\", new ElementTag(raw_args, true));\r\n        context.put(\"alias\", new ElementTag(alias, true));\r\n        PlayerTag player = null;\r\n        NPCTag npc = null;\r\n        if (commandSender instanceof Player) {\r\n            Player pl = (Player) commandSender;\r\n            if (EntityTag.isCitizensNPC(pl)) {\r\n                npc = NPCTag.fromEntity(pl);\r\n            }\r\n            else {\r\n                player = PlayerTag.mirrorBukkitPlayer(pl);\r\n            }\r\n            context.put(\"server\", new ElementTag(false));\r\n        }\r\n        else {\r\n            context.put(\"server\", new ElementTag(true));\r\n        }\r\n        if (Depends.citizens != null && npc == null) {\r\n            NPC citizen = CitizensAPI.getDefaultNPCSelector().getSelected(commandSender);\r\n            if (citizen != null) {\r\n                npc = new NPCTag(citizen);\r\n            }\r\n        }\r\n        Debug.push3ErrorContexts(script, \"while reading tab completions\", player);\r\n        try {\r\n            return script.runTabCompleteProcedure(player, npc, context, arguments);\r\n        }\r\n        finally {\r\n            Debug.popErrorContext(3);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/scripted/DenizenCommandHelpTopic.java",
    "content": "package com.denizenscript.denizen.utilities.command.scripted;\r\n\r\nimport org.bukkit.command.CommandSender;\r\nimport org.bukkit.help.GenericCommandHelpTopic;\r\n\r\npublic class DenizenCommandHelpTopic extends GenericCommandHelpTopic {\r\n\r\n    private final DenizenCommand denizenCommand;\r\n\r\n    public DenizenCommandHelpTopic(DenizenCommand command) {\r\n        super(command);\r\n        this.denizenCommand = command;\r\n    }\r\n\r\n    @Override\r\n    public boolean canSee(CommandSender sender) {\r\n        return denizenCommand.canSeeHelp(sender);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/command/scripted/DenizenCommandSender.java",
    "content": "package com.denizenscript.denizen.utilities.command.scripted;\r\n\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Server;\r\nimport org.bukkit.command.ConsoleCommandSender;\r\nimport org.bukkit.conversations.Conversation;\r\nimport org.bukkit.conversations.ConversationAbandonedEvent;\r\nimport org.bukkit.permissions.Permission;\r\nimport org.bukkit.permissions.PermissionAttachment;\r\nimport org.bukkit.permissions.PermissionAttachmentInfo;\r\nimport org.bukkit.plugin.Plugin;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.Set;\r\nimport java.util.UUID;\r\n\r\n/**\r\n * Used to send commands and record their output.\r\n */\r\npublic class DenizenCommandSender implements ConsoleCommandSender {\r\n\r\n    private ArrayList<String> output = new ArrayList<>();\r\n\r\n    public boolean silent = false;\r\n\r\n    public ArrayList<String> getOutput() {\r\n        return output;\r\n    }\r\n\r\n    public void clearOutput() {\r\n        output.clear();\r\n    }\r\n\r\n    @Override\r\n    public void sendMessage(String s) {\r\n        output.add(s);\r\n        if (!silent) {\r\n            Bukkit.getServer().getConsoleSender().sendMessage(s);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendMessage(String[] strings) {\r\n        for (String string : strings) {\r\n            sendMessage(string);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendMessage(UUID uuid, String s) {\r\n        output.add(s);\r\n        if (!silent) {\r\n            Bukkit.getServer().getConsoleSender().sendMessage(uuid, s);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendMessage(UUID uuid, String[] strings) {\r\n        output.addAll(Arrays.asList(strings));\r\n        if (!silent) {\r\n            Bukkit.getServer().getConsoleSender().sendMessage(uuid, strings);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Server getServer() {\r\n        return Bukkit.getConsoleSender().getServer();\r\n    }\r\n\r\n    @Override\r\n    public String getName() {\r\n        return Bukkit.getConsoleSender().getName();\r\n    }\r\n\r\n    @Override\r\n    public Spigot spigot() {\r\n        return Bukkit.getServer().getConsoleSender().spigot();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConversing() {\r\n        return Bukkit.getConsoleSender().isConversing();\r\n    }\r\n\r\n    @Override\r\n    public void acceptConversationInput(String s) {\r\n        Bukkit.getConsoleSender().acceptConversationInput(s);\r\n    }\r\n\r\n    @Override\r\n    public boolean beginConversation(Conversation conversation) {\r\n        return Bukkit.getConsoleSender().beginConversation(conversation);\r\n    }\r\n\r\n    @Override\r\n    public void abandonConversation(Conversation conversation) {\r\n        Bukkit.getConsoleSender().abandonConversation(conversation);\r\n    }\r\n\r\n    @Override\r\n    public void abandonConversation(Conversation conversation, ConversationAbandonedEvent conversationAbandonedEvent) {\r\n        Bukkit.getConsoleSender().abandonConversation(conversation, conversationAbandonedEvent);\r\n    }\r\n\r\n    @Override\r\n    public void sendRawMessage(String s) {\r\n        // TODO: Maybe handle this?\r\n        Bukkit.getConsoleSender().sendRawMessage(s);\r\n    }\r\n\r\n    @Override\r\n    public void sendRawMessage(UUID uuid, String s) {\r\n        // TODO: Maybe handle this?\r\n        Bukkit.getConsoleSender().sendRawMessage(uuid, s);\r\n    }\r\n\r\n    @Override\r\n    public boolean isPermissionSet(String s) {\r\n        return Bukkit.getConsoleSender().isPermissionSet(s);\r\n    }\r\n\r\n    @Override\r\n    public boolean isPermissionSet(Permission permission) {\r\n        return Bukkit.getConsoleSender().isPermissionSet(permission);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasPermission(String s) {\r\n        return Bukkit.getConsoleSender().hasPermission(s);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasPermission(Permission permission) {\r\n        return Bukkit.getConsoleSender().hasPermission(permission);\r\n    }\r\n\r\n    @Override\r\n    public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b) {\r\n        return Bukkit.getConsoleSender().addAttachment(plugin, s, b);\r\n    }\r\n\r\n    @Override\r\n    public PermissionAttachment addAttachment(Plugin plugin) {\r\n        return Bukkit.getConsoleSender().addAttachment(plugin);\r\n    }\r\n\r\n    @Override\r\n    public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b, int i) {\r\n        return Bukkit.getConsoleSender().addAttachment(plugin, s, b, i);\r\n    }\r\n\r\n    @Override\r\n    public PermissionAttachment addAttachment(Plugin plugin, int i) {\r\n        return Bukkit.getConsoleSender().addAttachment(plugin, i);\r\n    }\r\n\r\n    @Override\r\n    public void removeAttachment(PermissionAttachment permissionAttachment) {\r\n        Bukkit.getConsoleSender().removeAttachment(permissionAttachment);\r\n    }\r\n\r\n    @Override\r\n    public void recalculatePermissions() {\r\n        Bukkit.getConsoleSender().recalculatePermissions();\r\n    }\r\n\r\n    @Override\r\n    public Set<PermissionAttachmentInfo> getEffectivePermissions() {\r\n        return Bukkit.getConsoleSender().getEffectivePermissions();\r\n    }\r\n\r\n    @Override\r\n    public boolean isOp() {\r\n        return Bukkit.getConsoleSender().isOp();\r\n    }\r\n\r\n    @Override\r\n    public void setOp(boolean b) {\r\n        Bukkit.getConsoleSender().setOp(b);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/debugging/BStatsMetricsLite.java",
    "content": "package com.denizenscript.denizen.utilities.debugging;\r\n\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.configuration.file.YamlConfiguration;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.plugin.Plugin;\r\nimport org.bukkit.plugin.RegisteredServiceProvider;\r\nimport org.bukkit.plugin.ServicePriority;\r\nimport org.json.JSONArray;\r\nimport org.json.JSONObject;\r\n\r\nimport javax.net.ssl.HttpsURLConnection;\r\nimport java.io.*;\r\nimport java.lang.reflect.InvocationTargetException;\r\nimport java.lang.reflect.Method;\r\nimport java.net.URL;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.Collection;\r\nimport java.util.Timer;\r\nimport java.util.TimerTask;\r\nimport java.util.UUID;\r\nimport java.util.logging.Level;\r\nimport java.util.zip.GZIPOutputStream;\r\n\r\n/**\r\n * bStats collects some data for plugin authors.\r\n * <p>\r\n * Check out https://bStats.org/ to learn more about bStats!\r\n */\r\n@SuppressWarnings({\"WeakerAccess\", \"unused\"})\r\npublic class BStatsMetricsLite {\r\n\r\n    // The version of this bStats class\r\n    public static final int B_STATS_VERSION = 1;\r\n\r\n    // The url to which the data is sent\r\n    private static final String URL = \"https://bStats.org/submitData/bukkit\";\r\n\r\n    // Is bStats enabled on this server?\r\n    private boolean enabled;\r\n\r\n    // Should failed requests be logged?\r\n    private static boolean logFailedRequests;\r\n\r\n    // Should the sent data be logged?\r\n    private static boolean logSentData;\r\n\r\n    // Should the response text be logged?\r\n    private static boolean logResponseStatusText;\r\n\r\n    // The uuid of the server\r\n    private static String serverUUID;\r\n\r\n    // The plugin\r\n    private final Plugin plugin;\r\n\r\n    /**\r\n     * Class constructor.\r\n     *\r\n     * @param plugin The plugin which stats should be submitted.\r\n     */\r\n    public BStatsMetricsLite(Plugin plugin) {\r\n        if (plugin == null) {\r\n            throw new IllegalArgumentException(\"Plugin cannot be null!\");\r\n        }\r\n        this.plugin = plugin;\r\n\r\n        // Get the config file\r\n        File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), \"bStats\");\r\n        File configFile = new File(bStatsFolder, \"config.yml\");\r\n        YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);\r\n\r\n        // Check if the config file exists\r\n        if (!config.isSet(\"serverUuid\")) {\r\n\r\n            // Add default values\r\n            config.addDefault(\"enabled\", true);\r\n            // Every server gets it's unique random id.\r\n            config.addDefault(\"serverUuid\", UUID.randomUUID().toString());\r\n            // Should failed request be logged?\r\n            config.addDefault(\"logFailedRequests\", false);\r\n            // Should the sent data be logged?\r\n            config.addDefault(\"logSentData\", false);\r\n            // Should the response text be logged?\r\n            config.addDefault(\"logResponseStatusText\", false);\r\n\r\n            // Inform the server owners about bStats\r\n            config.options().header(\r\n                    \"bStats collects some data for plugin authors like how many servers are using their plugins.\\n\" +\r\n                            \"To honor their work, you should not disable it.\\n\" +\r\n                            \"This has nearly no effect on the server performance!\\n\" +\r\n                            \"Check out https://bStats.org/ to learn more :)\"\r\n            ).copyDefaults(true);\r\n            try {\r\n                config.save(configFile);\r\n            } catch (IOException ignored) { }\r\n        }\r\n\r\n        // Load the data\r\n        serverUUID = config.getString(\"serverUuid\");\r\n        logFailedRequests = config.getBoolean(\"logFailedRequests\", false);\r\n        enabled = config.getBoolean(\"enabled\", true);\r\n        if (enabled) {\r\n            boolean found = false;\r\n            // Search for all other bStats Metrics classes to see if we are the first one\r\n            for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {\r\n                try {\r\n                    service.getField(\"B_STATS_VERSION\"); // Our identifier :)\r\n                    found = true; // We aren't the first\r\n                    break;\r\n                } catch (NoSuchFieldException ignored) { }\r\n            }\r\n            // Register our service\r\n            Bukkit.getServicesManager().register(BStatsMetricsLite.class, this, plugin, ServicePriority.Normal);\r\n            if (!found) {\r\n                // We are the first!\r\n                startSubmitting();\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Checks if bStats is enabled.\r\n     *\r\n     * @return Whether bStats is enabled or not.\r\n     */\r\n    public boolean isEnabled() {\r\n        return enabled;\r\n    }\r\n\r\n    /**\r\n     * Starts the Scheduler which submits our data every 30 minutes.\r\n     */\r\n    private void startSubmitting() {\r\n        final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags\r\n        timer.scheduleAtFixedRate(new TimerTask() {\r\n            @Override\r\n            public void run() {\r\n                if (!plugin.isEnabled()) { // Plugin was disabled\r\n                    timer.cancel();\r\n                    return;\r\n                }\r\n                // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler\r\n                // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;)\r\n                Bukkit.getScheduler().runTask(plugin, () -> submitData());\r\n            }\r\n        }, 1000 * 60 * 5, 1000 * 60 * 30);\r\n        // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start\r\n        // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!\r\n        // WARNING: Just don't do it!\r\n    }\r\n\r\n    /**\r\n     * Gets the plugin specific data.\r\n     * This method is called using Reflection.\r\n     *\r\n     * @return The plugin specific data.\r\n     */\r\n    public JSONObject getPluginData() {\r\n        JSONObject data = new JSONObject();\r\n\r\n        String pluginName = plugin.getDescription().getName();\r\n        String pluginVersion = plugin.getDescription().getVersion();\r\n\r\n        data.put(\"pluginName\", pluginName); // Append the name of the plugin\r\n        data.put(\"pluginVersion\", pluginVersion); // Append the version of the plugin\r\n        JSONArray customCharts = new JSONArray();\r\n        data.put(\"customCharts\", customCharts);\r\n\r\n        return data;\r\n    }\r\n\r\n    /**\r\n     * Gets the server specific data.\r\n     *\r\n     * @return The server specific data.\r\n     */\r\n    private JSONObject getServerData() {\r\n        // Minecraft specific data\r\n        int playerAmount;\r\n        try {\r\n            // Around MC 1.8 the return type was changed to a collection from an array,\r\n            // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;\r\n            Method onlinePlayersMethod = Class.forName(\"org.bukkit.Server\").getMethod(\"getOnlinePlayers\");\r\n            playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class)\r\n                    ? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()\r\n                    : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;\r\n        } catch (Exception e) {\r\n            playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed\r\n        }\r\n        int onlineMode = Bukkit.getOnlineMode() ? 1 : 0;\r\n        String bukkitVersion = Bukkit.getVersion();\r\n\r\n        // OS/Java specific data\r\n        String javaVersion = System.getProperty(\"java.version\");\r\n        String osName = System.getProperty(\"os.name\");\r\n        String osArch = System.getProperty(\"os.arch\");\r\n        String osVersion = System.getProperty(\"os.version\");\r\n        int coreCount = Runtime.getRuntime().availableProcessors();\r\n\r\n        JSONObject data = new JSONObject();\r\n\r\n        data.put(\"serverUUID\", serverUUID);\r\n\r\n        data.put(\"playerAmount\", playerAmount);\r\n        data.put(\"onlineMode\", onlineMode);\r\n        data.put(\"bukkitVersion\", bukkitVersion);\r\n\r\n        data.put(\"javaVersion\", javaVersion);\r\n        data.put(\"osName\", osName);\r\n        data.put(\"osArch\", osArch);\r\n        data.put(\"osVersion\", osVersion);\r\n        data.put(\"coreCount\", coreCount);\r\n\r\n        return data;\r\n    }\r\n\r\n    /**\r\n     * Collects the data and sends it afterwards.\r\n     */\r\n    private void submitData() {\r\n        final JSONObject data = getServerData();\r\n\r\n        JSONArray pluginData = new JSONArray();\r\n        // Search for all other bStats Metrics classes to get their plugin data\r\n        for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {\r\n            try {\r\n                service.getField(\"B_STATS_VERSION\"); // Our identifier :)\r\n\r\n                for (RegisteredServiceProvider<?> provider : Bukkit.getServicesManager().getRegistrations(service)) {\r\n                    try {\r\n                        pluginData.put(provider.getService().getMethod(\"getPluginData\").invoke(provider.getProvider()));\r\n                    } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {\r\n                    }\r\n                }\r\n            } catch (NoSuchFieldException ignored) { }\r\n        }\r\n\r\n        data.put(\"plugins\", pluginData);\r\n\r\n        // Create a new thread for the connection to the bStats server\r\n        new Thread(() -> {\r\n            try {\r\n                // Send the data\r\n                sendData(plugin, data);\r\n            } catch (Exception e) {\r\n                // Something went wrong! :(\r\n                if (logFailedRequests) {\r\n                    plugin.getLogger().log(Level.WARNING, \"Could not submit plugin stats of \" + plugin.getName(), e);\r\n                }\r\n            }\r\n        }).start();\r\n    }\r\n\r\n    /**\r\n     * Sends the data to the bStats server.\r\n     *\r\n     * @param plugin Any plugin. It's just used to get a logger instance.\r\n     * @param data The data to send.\r\n     * @throws Exception If the request failed.\r\n     */\r\n    private static void sendData(Plugin plugin, JSONObject data) throws Exception {\r\n        if (data == null) {\r\n            throw new IllegalArgumentException(\"Data cannot be null!\");\r\n        }\r\n        if (Bukkit.isPrimaryThread()) {\r\n            throw new IllegalAccessException(\"This method must not be called from the main thread!\");\r\n        }\r\n        if (logSentData) {\r\n            plugin.getLogger().info(\"Sending data to bStats: \" + data);\r\n        }\r\n        HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();\r\n\r\n        // Compress the data to save bandwidth\r\n        byte[] compressedData = compress(data.toString());\r\n\r\n        // Add headers\r\n        connection.setRequestMethod(\"POST\");\r\n        connection.addRequestProperty(\"Accept\", \"application/json\");\r\n        connection.addRequestProperty(\"Connection\", \"close\");\r\n        connection.addRequestProperty(\"Content-Encoding\", \"gzip\"); // We gzip our request\r\n        connection.addRequestProperty(\"Content-Length\", String.valueOf(compressedData.length));\r\n        connection.setRequestProperty(\"Content-Type\", \"application/json\"); // We send our data in JSON format\r\n        connection.setRequestProperty(\"User-Agent\", \"MC-Server/\" + B_STATS_VERSION);\r\n\r\n        // Send data\r\n        connection.setDoOutput(true);\r\n        DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());\r\n        outputStream.write(compressedData);\r\n        outputStream.flush();\r\n        outputStream.close();\r\n\r\n        InputStream inputStream = connection.getInputStream();\r\n        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));\r\n\r\n        StringBuilder builder = new StringBuilder();\r\n        String line;\r\n        while ((line = bufferedReader.readLine()) != null) {\r\n            builder.append(line);\r\n        }\r\n        bufferedReader.close();\r\n        if (logResponseStatusText) {\r\n            plugin.getLogger().info(\"Sent data to bStats and received response: \" + builder);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Gzips the given String.\r\n     *\r\n     * @param str The string to gzip.\r\n     * @return The gzipped String.\r\n     * @throws IOException If the compression failed.\r\n     */\r\n    private static byte[] compress(final String str) throws IOException {\r\n        if (str == null) {\r\n            return null;\r\n        }\r\n        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();\r\n        GZIPOutputStream gzip = new GZIPOutputStream(outputStream);\r\n        gzip.write(str.getBytes(StandardCharsets.UTF_8));\r\n        gzip.close();\r\n        return outputStream.toByteArray();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/debugging/Debug.java",
    "content": "package com.denizenscript.denizen.utilities.debugging;\r\n\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debuggable;\r\n\r\n@Deprecated\r\npublic class Debug {\r\n    @Deprecated\r\n    public static void report(Debuggable caller, String name, String report) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.report(caller, name, report);\r\n    }\r\n    @Deprecated\r\n    public static void report(Debuggable caller, String name, Object... values) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.report(caller, name, values);\r\n    }\r\n    @Deprecated\r\n    public static void echoDebug(Debuggable caller, com.denizenscript.denizencore.utilities.debugging.Debug.DebugElement element) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.echoDebug(caller, element);\r\n    }\r\n    @Deprecated\r\n    public static void echoDebug(Debuggable caller, com.denizenscript.denizencore.utilities.debugging.Debug.DebugElement element, String string) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.echoDebug(caller, element, string);\r\n    }\r\n    @Deprecated\r\n    public static void echoDebug(Debuggable caller, String message) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.echoDebug(caller, message);\r\n    }\r\n    @Deprecated\r\n    public static void echoApproval(String message) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.echoApproval(message);\r\n    }\r\n    @Deprecated\r\n    public static void echoError(String message) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.echoError(message);\r\n    }\r\n    @Deprecated\r\n    public static void echoError(ScriptEntry source, String message) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.echoError(source, message);\r\n    }\r\n    @Deprecated\r\n    public static void echoError(Throwable ex) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.echoError(ex);\r\n    }\r\n    @Deprecated\r\n    public static void echoError(ScriptEntry source, Throwable ex) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.echoError(source, ex);\r\n    }\r\n    @Deprecated\r\n    public static void log(String message) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.log(\"<OUTDATED-PLUGIN>\", message);\r\n    }\r\n    @Deprecated\r\n    public static void log(String caller, String message) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.log(caller, message);\r\n    }\r\n    @Deprecated\r\n    public static void log(com.denizenscript.denizencore.utilities.debugging.Debug.DebugElement element, String message) {\r\n        com.denizenscript.denizencore.utilities.debugging.Debug.log(element, message);\r\n    }\r\n    @Deprecated\r\n    public static boolean shouldDebug(Debuggable caller) {\r\n        return com.denizenscript.denizencore.utilities.debugging.Debug.shouldDebug(caller);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/debugging/DebugConsoleSender.java",
    "content": "package com.denizenscript.denizen.utilities.debugging;\r\n\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.command.CommandSender;\r\n\r\npublic class DebugConsoleSender {\r\n\r\n    public static boolean showColor = true;\r\n\r\n    static CommandSender commandSender = null;\r\n\r\n    public static void sendMessage(String string) {\r\n        if (commandSender == null) {\r\n            commandSender = Bukkit.getServer().getConsoleSender();\r\n        }\r\n        //                                                                       \"[HH:mm:ss INFO]: \"\r\n        string = CoreConfiguration.debugPrefix + string.replace(\"<FORCE_ALIGN>\", \"                 \");\r\n        if (showColor) {\r\n            PaperAPITools.instance.sendConsoleMessage(commandSender, string);\r\n        }\r\n        else {\r\n            commandSender.sendMessage(ChatColor.stripColor(string));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/debugging/DebugSubmit.java",
    "content": "package com.denizenscript.denizen.utilities.debugging;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugSubmitter;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.World;\r\nimport org.bukkit.configuration.file.YamlConfiguration;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.plugin.Plugin;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.UUID;\r\n\r\n/**\r\n * Spigot helper for the core DebugSubmitter.\r\n */\r\npublic class DebugSubmit {\r\n\r\n    public static void init() {\r\n        DebugSubmitter.pasteTitleGetter = () -> \"Denizen Debug Logs From \" + ChatColor.stripColor(Bukkit.getServer().getMotd());\r\n        DebugSubmitter.debugHeaderLines.add(DebugSubmit::getCoreHeader);\r\n    }\r\n\r\n    public static String getCoreHeader() {\r\n        try {\r\n            // Build a list of plugins\r\n            StringBuilder pluginlist = new StringBuilder();\r\n            int newlineLength = 0;\r\n            int pluginCount = Bukkit.getPluginManager().getPlugins().length;\r\n            for (Plugin pl : Bukkit.getPluginManager().getPlugins()) {\r\n                String temp = ((char) 0x01) + (pl.isEnabled() ? \"2\" : \"4\") + pl.getName() + \": \" + pl.getDescription().getVersion() + \", \";\r\n                pluginlist.append(temp);\r\n                newlineLength += temp.length();\r\n                if (newlineLength > 80) {\r\n                    newlineLength = 0;\r\n                    pluginlist.append(\"\\n\");\r\n                }\r\n            }\r\n            // Build a list of worlds\r\n            StringBuilder worldlist = new StringBuilder();\r\n            newlineLength = 0;\r\n            int worldCount = Bukkit.getWorlds().size();\r\n            for (World w : Bukkit.getWorlds()) {\r\n                String temp = w.getName() + \", \";\r\n                worldlist.append(temp);\r\n                newlineLength += temp.length();\r\n                if (newlineLength > 80) {\r\n                    newlineLength = 0;\r\n                    worldlist.append(\"\\n\");\r\n                }\r\n            }\r\n            // Build a list of players\r\n            StringBuilder playerlist = new StringBuilder();\r\n            newlineLength = 0;\r\n            int playerCount = Bukkit.getOnlinePlayers().size();\r\n            for (Player pla : Bukkit.getOnlinePlayers()) {\r\n                String temp = pla.getDisplayName() + ChatColor.GRAY + \"(\" + pla.getName() + \"), \";\r\n                playerlist.append(temp);\r\n                newlineLength += temp.length();\r\n                if (newlineLength > 80) {\r\n                    newlineLength = 0;\r\n                    playerlist.append(\"\\n\");\r\n                }\r\n            }\r\n            // Prevent errors if the debug was submitted by the server\r\n            if (playerlist.length() < 2) {\r\n                playerlist.append(\"No Online Players, \");\r\n            }\r\n            int plNormal = 0, plNull = 0, pl3 = 0, pl0 = 0, plWeird = 0;\r\n            try {\r\n                for (UUID id : PlayerTag.getAllPlayers().values()) {\r\n                    if (id == null) {\r\n                        plNull++;\r\n                    }\r\n                    else if (id.version() == 4) {\r\n                        plNormal++;\r\n                    }\r\n                    else if (id.version() == 3) {\r\n                        pl3++;\r\n                    }\r\n                    else if (id.version() == 0) {\r\n                        pl0++;\r\n                    }\r\n                    else {\r\n                        plWeird++;\r\n                    }\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            String playerSet = (plNormal > 0 ? plNormal + \" normal, \" : \"\") + (plNull > 0 ? plNull + \" null, \" : \"\")\r\n                    + (pl3 > 0 ? pl3 + \" v3, \" : \"\") + (pl0 > 0 ? pl0 + \" v0, \" : \"\") + (plWeird > 0 ? plWeird + \" other, \" : \"\");\r\n            if (playerSet.length() > 2) {\r\n                playerSet = playerSet.substring(0, playerSet.length() - 2);\r\n            }\r\n            // Gather other setting info\r\n            boolean proxied = false;\r\n            String modeSuffix = \"\";\r\n            if (Bukkit.getServer().spigot().getConfig().getBoolean(\"settings.bungeecord\")) {\r\n                modeSuffix = \" (BungeeCord)\";\r\n                proxied = true;\r\n            }\r\n            else if (Denizen.supportsPaper) {\r\n                String paperMode = getPaperOnlineMode();\r\n                if (paperMode != null) {\r\n                    modeSuffix = paperMode;\r\n                    proxied = true;\r\n                }\r\n            }\r\n            String onlineMode = (Bukkit.getServer().getOnlineMode() ? ChatColor.GREEN + \"online\" : (proxied ? ChatColor.YELLOW : ChatColor.RED) + \"offline\") + modeSuffix;\r\n            return \"Server Version: \" + Bukkit.getServer().getName() + \" version \" + Bukkit.getServer().getVersion()\r\n                    + \"\\nActive Plugins (\" + pluginCount + \"): \" + pluginlist.substring(0, pluginlist.length() - 2)\r\n                    + \"\\nLoaded Worlds (\" + worldCount + \"): \" + worldlist.substring(0, worldlist.length() - 2)\r\n                    + \"\\nOnline Players (\" + playerCount + \"): \" + playerlist.substring(0, playerlist.length() - 2)\r\n                    + \"\\nTotal Players Ever: \" + PlayerTag.getAllPlayers().size() + \" (\" + playerSet + \")\"\r\n                    + \"\\nMode: \" + onlineMode;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return \"(Error Building Header)\";\r\n        }\r\n    }\r\n\r\n    public static YamlConfiguration paperConfig;\r\n\r\n    public static String getPaperOnlineMode() {\r\n        boolean isEnabled, isOnline;\r\n        try {\r\n            Class config = Class.forName(\"io.papermc.paper.configuration.GlobalConfiguration\");\r\n            Object instance = ReflectionHelper.getFieldValue(config, \"instance\", null);\r\n            Object proxies = ReflectionHelper.getFieldValue(config, \"proxies\", instance);\r\n            Object velocity = ReflectionHelper.getFieldValue(proxies.getClass(), \"velocity\", proxies);\r\n            Field velField = ReflectionHelper.getFields(velocity.getClass()).get(\"enabled\");\r\n            velField.setAccessible(true);\r\n            isEnabled = velField.getBoolean(velocity);\r\n            Field onlineField = ReflectionHelper.getFields(velocity.getClass()).get(\"onlineMode\");\r\n            onlineField.setAccessible(true);\r\n            isOnline = onlineField.getBoolean(velocity);\r\n        }\r\n        catch (ClassNotFoundException ignore) {\r\n            isEnabled = getPaperConfigKey(\"settings.velocity-support.enabled\");\r\n            isOnline = getPaperConfigKey(\"settings.velocity-support.online-mode\");\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n        if (isEnabled) {\r\n            return isOnline ? ChatColor.GREEN + \" (Velocity: online)\" : ChatColor.RED + \" (Velocity: offline)\";\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static boolean getPaperConfigKey(String key) {\r\n        if (!Denizen.supportsPaper) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (paperConfig == null) {\r\n                paperConfig = (YamlConfiguration) ReflectionHelper.getFields(Class.forName(\"com.destroystokyo.paper.PaperConfig\")).get(\"config\").get(null);\r\n            }\r\n            return paperConfig.getBoolean(key);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/debugging/StatsRecord.java",
    "content": "package com.denizenscript.denizen.utilities.debugging;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizencore.scripts.ScriptRegistry;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.Deprecations;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Bukkit;\r\n\r\nimport java.io.BufferedReader;\r\nimport java.io.InputStreamReader;\r\nimport java.net.HttpURLConnection;\r\nimport java.net.URL;\r\nimport java.net.URLEncoder;\r\nimport java.nio.charset.StandardCharsets;\r\n\r\npublic class StatsRecord extends Thread {\r\n\r\n    public static void trigger() {\r\n        StatsRecord recorder = new StatsRecord();\r\n        recorder.gather();\r\n        recorder.start();\r\n    }\r\n\r\n    public void gather() {\r\n        String denizenVersion = Denizen.getInstance().coreImplementation.getImplementationVersion();\r\n        // We don't need the real value of the MOTD / port, but they're useful for differentiating, so use them to generate a hash\r\n        String differentiator = CoreUtilities.hash_md5((Bukkit.getServer().getMotd() + Bukkit.getServer().getPort()).getBytes(StandardCharsets.UTF_8));\r\n        String deprecations = String.join(\"\\n\", Deprecations.firedRecently.keySet());\r\n        Deprecations.firedRecently.clear();\r\n        String mcVersion = Bukkit.getVersion();\r\n        int mcPart = mcVersion.indexOf(\"(MC: \");\r\n        int endPart = mcPart == -1 ? -1 : mcVersion.indexOf(\")\", mcPart);\r\n        mcVersion = (endPart == -1) ? \"\" : mcVersion.substring(mcPart + \"(MC: \".length(), endPart);\r\n        content = \"postid=pluginstats&plugin=Denizen\"\r\n                + \"&differentiator=\" + differentiator\r\n                + \"&pl_plugin_version=\" + URLEncoder.encode(denizenVersion)\r\n                + \"&pl_platform=\" + URLEncoder.encode(Bukkit.getName())\r\n                + \"&pl_minecraft_version=\" + URLEncoder.encode(mcVersion)\r\n                + \"&pl_player_count=\" + Bukkit.getOnlinePlayers().size()\r\n                + \"&pl_script_count=\" + ScriptRegistry.scriptContainers.size()\r\n                + \"&pl_deprecations=\" + URLEncoder.encode(deprecations);\r\n    }\r\n\r\n    public String content;\r\n\r\n    @Override\r\n    public void run() {\r\n        BufferedReader in = null;\r\n        try {\r\n            // Open a connection to the stats server\r\n            URL url = new URL(\"https://stats.mcmonkey.org/Stats/Submit\");\r\n            HttpURLConnection uc = (HttpURLConnection) url.openConnection();\r\n            uc.setDoInput(true);\r\n            uc.setDoOutput(true);\r\n            uc.setConnectTimeout(10000);\r\n            uc.connect();\r\n            // Safely connected at this point\r\n            // Create the final message pack and upload it\r\n            uc.getOutputStream().write(content.getBytes(StandardCharsets.UTF_8));\r\n            // Wait for a response from the server\r\n            in = new BufferedReader(new InputStreamReader(uc.getInputStream()));\r\n            // Record the response\r\n            String Result = in.readLine();\r\n            // TODO: Use return?\r\n            // Close the connection\r\n            in.close();\r\n        }\r\n        catch (Exception e) {\r\n            if (CoreConfiguration.debugOverride) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        finally {\r\n            try {\r\n                if (in != null) {\r\n                    in.close();\r\n                }\r\n            }\r\n            catch (Exception e) {\r\n                if (CoreConfiguration.debugOverride) {\r\n                    Debug.echoError(e);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/depends/Depends.java",
    "content": "package com.denizenscript.denizen.utilities.depends;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.google.common.io.ByteArrayDataOutput;\r\nimport com.google.common.io.ByteStreams;\r\nimport net.citizensnpcs.Citizens;\r\nimport net.milkbowl.vault.chat.Chat;\r\nimport net.milkbowl.vault.economy.Economy;\r\nimport net.milkbowl.vault.permission.Permission;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.plugin.Plugin;\r\nimport org.bukkit.plugin.RegisteredServiceProvider;\r\n\r\npublic class Depends {\r\n\r\n    public static Citizens citizens = null;\r\n\r\n    public static Economy economy = null;\r\n    public static Permission permissions = null;\r\n    public static Chat chat = null;\r\n    public static Plugin vault = null;\r\n\r\n    public static void initialize() {\r\n        setupBungee();\r\n        setupVault();\r\n        setupCitizens();\r\n    }\r\n\r\n    public static void setupVault() {\r\n        vault = Bukkit.getServer().getPluginManager().getPlugin(\"Vault\");\r\n        setupEconomy();\r\n        setupPermissions();\r\n        setupChat();\r\n    }\r\n\r\n    public static void setupBungee() {\r\n        Bukkit.getMessenger().registerOutgoingPluginChannel(Denizen.getInstance(), \"BungeeCord\");\r\n    }\r\n\r\n    public static void bungeeSendPlayer(Player player, String server) {\r\n        ByteArrayDataOutput out = ByteStreams.newDataOutput();\r\n        out.writeUTF(\"Connect\");\r\n        out.writeUTF(server);\r\n        player.sendPluginMessage(Denizen.getInstance(), \"BungeeCord\", out.toByteArray());\r\n    }\r\n\r\n    public static boolean setupEconomy() {\r\n        if (vault == null || !vault.isEnabled()) {\r\n            return false;\r\n        }\r\n        try {\r\n            RegisteredServiceProvider<Economy> rsp = Bukkit.getServer().getServicesManager().getRegistration(Economy.class);\r\n            if (rsp == null) {\r\n                return false;\r\n            }\r\n            economy = rsp.getProvider();\r\n        }\r\n        catch (Exception e) {\r\n        }\r\n        return economy != null;\r\n    }\r\n\r\n    public static boolean setupChat() {\r\n        if (vault == null || !vault.isEnabled()) {\r\n            return false;\r\n        }\r\n        try {\r\n            RegisteredServiceProvider<Chat> rsp = Bukkit.getServer().getServicesManager().getRegistration(Chat.class);\r\n            chat = rsp.getProvider();\r\n        }\r\n        catch (Exception e) {\r\n        }\r\n        return chat != null;\r\n    }\r\n\r\n    public static boolean setupPermissions() {\r\n        if (vault == null || !vault.isEnabled()) {\r\n            return false;\r\n        }\r\n        try {\r\n            RegisteredServiceProvider<Permission> rsp = Bukkit.getServer().getServicesManager().getRegistration(Permission.class);\r\n            permissions = rsp.getProvider();\r\n        }\r\n        catch (Exception e) {\r\n        }\r\n        return permissions != null;\r\n    }\r\n\r\n    public static boolean setupCitizens() {\r\n        Plugin plugin = Bukkit.getServer().getPluginManager().getPlugin(\"Citizens\");\r\n        if (plugin == null || !plugin.isEnabled()) {\r\n            return false;\r\n        }\r\n        try {\r\n            citizens = (Citizens) Bukkit.getServer().getPluginManager().getPlugin(\"Citizens\");\r\n        }\r\n        catch (Exception e) {\r\n        }\r\n        return citizens != null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/AreaEffectCloudHelper.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport org.bukkit.Color;\r\nimport org.bukkit.Particle;\r\nimport org.bukkit.entity.AreaEffectCloud;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.potion.PotionData;\r\nimport org.bukkit.potion.PotionEffect;\r\nimport org.bukkit.potion.PotionEffectType;\r\nimport org.bukkit.potion.PotionType;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\n\r\nimport java.util.List;\r\n\r\npublic class AreaEffectCloudHelper {\r\n    private AreaEffectCloud entity;\r\n\r\n    public AreaEffectCloudHelper(Entity entity) {\r\n        this.entity = (AreaEffectCloud) entity;\r\n    }\r\n\r\n    ////////////////\r\n    // Base Potion Data\r\n    /////////\r\n\r\n    private PotionData getBPData() {\r\n        return entity.getBasePotionData();\r\n    }\r\n\r\n    public String getBPName() {\r\n        return getBPData().getType().name();\r\n    }\r\n\r\n    public boolean getBPUpgraded() {\r\n        return getBPData().isUpgraded();\r\n    }\r\n\r\n    public boolean getBPExtended() {\r\n        return getBPData().isExtended();\r\n    }\r\n\r\n    public void setBP(PotionType type, boolean extended, boolean upgraded) {\r\n        entity.setBasePotionData(new PotionData(type, extended, upgraded));\r\n    }\r\n\r\n    ////////////////\r\n    // Particles\r\n    /////////\r\n\r\n    public Color getColor() {\r\n        return entity.getColor();\r\n    }\r\n\r\n    public void setColor(Color color) {\r\n        entity.setColor(color);\r\n    }\r\n\r\n    public String getParticle() {\r\n        return entity.getParticle().name();\r\n    }\r\n\r\n    public void setParticle(String name) {\r\n        Particle particle = Utilities.elementToEnumlike(new ElementTag(name, true), Particle.class);\r\n        if (particle != null) {\r\n            entity.setParticle(particle);\r\n        }\r\n    }\r\n\r\n    ////////////////\r\n    // Radius\r\n    /////////\r\n\r\n    public float getRadius() {\r\n        return entity.getRadius();\r\n    }\r\n\r\n    public float getRadiusOnUse() {\r\n        return entity.getRadiusOnUse();\r\n    }\r\n\r\n    public float getRadiusPerTick() {\r\n        return entity.getRadiusPerTick();\r\n    }\r\n\r\n    public void setRadius(float radius) {\r\n        entity.setRadius(radius);\r\n    }\r\n\r\n    public void setRadiusOnUse(float radius) {\r\n        entity.setRadiusOnUse(radius);\r\n    }\r\n\r\n    public void setRadiusPerTick(float radius) {\r\n        entity.setRadiusPerTick(radius);\r\n    }\r\n\r\n    ////////////////\r\n    // Duration\r\n    /////////\r\n\r\n    public long getDuration() {\r\n        return (long) entity.getDuration();\r\n    }\r\n\r\n    public long getDurationOnUse() {\r\n        return (long) entity.getDurationOnUse();\r\n    }\r\n\r\n    public long getReappDelay() {\r\n        return (long) entity.getReapplicationDelay();\r\n    }\r\n\r\n    public long getWaitTime() {\r\n        return (long) entity.getWaitTime();\r\n    }\r\n\r\n    public void setDuration(int ticks) {\r\n        entity.setDuration(ticks);\r\n    }\r\n\r\n    public void setDurationOnUse(int ticks) {\r\n        entity.setDurationOnUse(ticks);\r\n    }\r\n\r\n    public void setReappDelay(int ticks) {\r\n        entity.setReapplicationDelay(ticks);\r\n    }\r\n\r\n    public void setWaitTime(int ticks) {\r\n        entity.setWaitTime(ticks);\r\n    }\r\n\r\n    ////////////////\r\n    // Custom Effects\r\n    /////////\r\n\r\n    public List<PotionEffect> getCustomEffects() {\r\n        return entity.getCustomEffects();\r\n    }\r\n\r\n    public boolean hasCustomEffects() {\r\n        return entity.hasCustomEffects();\r\n    }\r\n\r\n    public void clearEffects() {\r\n        entity.clearCustomEffects();\r\n    }\r\n\r\n    public void removeEffect(PotionEffectType type) {\r\n        entity.removeCustomEffect(type);\r\n    }\r\n\r\n    public void addEffect(PotionEffect effect, boolean override) {\r\n        entity.addCustomEffect(effect, override);\r\n    }\r\n\r\n    ////////////////\r\n    // Misc\r\n    /////////\r\n\r\n    public ProjectileSource getSource() {\r\n        return entity.getSource();\r\n    }\r\n\r\n    public void setSource(ProjectileSource source) {\r\n        entity.setSource(source);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/BossBarHelper.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.boss.BarColor;\r\nimport org.bukkit.boss.BarStyle;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.*;\r\n\r\npublic class BossBarHelper {\r\n\r\n    private static final Map<UUID, List<BossBar>> bossBars = new HashMap<>();\r\n\r\n    public static void showSimpleBossBar(Player player, String title, double progress) {\r\n        UUID uuid = player.getUniqueId();\r\n        if (!bossBars.containsKey(uuid)) {\r\n            bossBars.put(uuid, new ArrayList<>());\r\n        }\r\n        List<BossBar> playerBars = bossBars.get(uuid);\r\n        if (!playerBars.isEmpty()) {\r\n            Iterator<BossBar> iterator = playerBars.iterator();\r\n            while (iterator.hasNext()) {\r\n                BossBar bossBar = iterator.next();\r\n                bossBar.removePlayer(player);\r\n                iterator.remove();\r\n            }\r\n        }\r\n        BossBar bossBar = Bukkit.createBossBar(title, BarColor.PURPLE, BarStyle.SOLID);\r\n        bossBar.setProgress(progress);\r\n        bossBar.addPlayer(player);\r\n        bossBar.setVisible(true);\r\n        playerBars.add(bossBar);\r\n    }\r\n\r\n    public static void removeSimpleBossBar(Player player) {\r\n        UUID uuid = player.getUniqueId();\r\n        if (bossBars.containsKey(uuid) && !bossBars.get(uuid).isEmpty()) {\r\n            Iterator<BossBar> iterator = bossBars.get(uuid).iterator();\r\n            while (iterator.hasNext()) {\r\n                BossBar bossBar = iterator.next();\r\n                bossBar.removePlayer(player);\r\n                iterator.remove();\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/DenizenEntityType.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.enums.CustomEntityType;\r\nimport com.denizenscript.denizen.nms.interfaces.CustomEntity;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class DenizenEntityType {\r\n\r\n    private static final Map<String, DenizenEntityType> registeredTypes = new HashMap<>();\r\n    private final EntityType bukkitEntityType;\r\n    private final String name;\r\n    private final String lowercaseName;\r\n    private final double gravity;\r\n    public final CustomEntityType customEntityType;\r\n\r\n    static {\r\n        for (EntityType entityType : EntityType.values()) {\r\n            registeredTypes.put(entityType.name(), new DenizenEntityType(entityType));\r\n        }\r\n    }\r\n\r\n    // <--[language]\r\n    // @name Denizen Entity Types\r\n    // @group Useful Lists\r\n    // @description\r\n    // Along with the default EntityTypes <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/EntityType.html>,\r\n    // Denizen also adds in a few altered entities:\r\n    // - FAKE_ARROW: For use when you want an arrow to stay spawned at a location for any reason.\r\n    // - FAKE_PLAYER: Spawns a fake player (non-Citizens NPC).\r\n    //   Use with the mechanisms \"name\" and \"skin\" to alter the respective properties.\r\n    // -->\r\n\r\n    private DenizenEntityType(EntityType entityType) {\r\n        this.bukkitEntityType = entityType;\r\n        this.name = entityType.name();\r\n        this.lowercaseName = CoreUtilities.toLowerCase(name);\r\n        this.gravity = Gravity.getGravity(entityType);\r\n        this.customEntityType = null;\r\n    }\r\n\r\n    private DenizenEntityType(String name, Class<? extends CustomEntity> entityType) {\r\n        this(name, entityType, 0.115);\r\n    }\r\n\r\n    private DenizenEntityType(String name, Class<? extends CustomEntity> entityType, double gravity) {\r\n        EntityType bukkitEntityType = EntityType.UNKNOWN;\r\n        if (entityType != null) {\r\n            for (EntityType type : EntityType.values()) {\r\n                Class<? extends Entity> clazz = type.getEntityClass();\r\n                if (clazz != null && clazz.isAssignableFrom(entityType)) {\r\n                    bukkitEntityType = type;\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n        this.bukkitEntityType = bukkitEntityType;\r\n        this.name = CoreUtilities.toUpperCase(name);\r\n        this.lowercaseName = CoreUtilities.toLowerCase(name);\r\n        this.gravity = gravity;\r\n        this.customEntityType = CustomEntityType.valueOf(CoreUtilities.toUpperCase(name));\r\n    }\r\n\r\n    public Entity spawnNewEntity(Location location, ArrayList<Mechanism> mechanisms, String scriptName, CreatureSpawnEvent.SpawnReason reason) {\r\n        try {\r\n            if (name.equals(\"DROPPED_ITEM\")) {\r\n                ItemStack itemStack = new ItemStack(Material.STONE);\r\n                for (Mechanism mechanism : mechanisms) {\r\n                    if (mechanism.matches(\"item\") && mechanism.requireObject(ItemTag.class)) {\r\n                        itemStack = mechanism.valueAsType(ItemTag.class).getItemStack();\r\n                        break;\r\n                    }\r\n                }\r\n                return location.getWorld().dropItem(location, itemStack);\r\n            }\r\n            else if (!isCustom()) {\r\n                return SpawnEntityHelper.spawn(location, bukkitEntityType, mechanisms, scriptName, reason);\r\n            }\r\n            else {\r\n                switch (customEntityType) {\r\n                    case FAKE_ARROW:\r\n                        return NMSHandler.customEntityHelper.spawnFakeArrow(location);\r\n                    case FAKE_PLAYER:\r\n                        if (Settings.packetInterception()) {\r\n                            String name = null;\r\n                            String skin = null;\r\n                            String blob = null;\r\n                            for (Mechanism mechanism : new ArrayList<>(mechanisms)) {\r\n                                if (mechanism.matches(\"name\")) {\r\n                                    name = mechanism.getValue().asString();\r\n                                    mechanisms.remove(mechanism);\r\n                                }\r\n                                else if (mechanism.matches(\"skin\")) {\r\n                                    skin = mechanism.getValue().asString();\r\n                                    mechanisms.remove(mechanism);\r\n                                }\r\n                                else if (mechanism.matches(\"skin_blob\")) {\r\n                                    blob = mechanism.getValue().asString();\r\n                                    mechanisms.remove(mechanism);\r\n                                }\r\n                                if (name != null && (skin != null || blob != null)) {\r\n                                    break;\r\n                                }\r\n                            }\r\n                            NetworkInterceptHelper.enable();\r\n                            return NMSHandler.customEntityHelper.spawnFakePlayer(location, name, skin, blob, true);\r\n                        }\r\n                        break;\r\n                    case ITEM_PROJECTILE:\r\n                        BukkitImplDeprecations.itemProjectile.warn();\r\n                        ItemStack itemStack = new ItemStack(Material.STONE);\r\n                        for (Mechanism mechanism : mechanisms) {\r\n                            if (mechanism.matches(\"item\") && mechanism.requireObject(ItemTag.class)) {\r\n                                itemStack = mechanism.valueAsType(ItemTag.class).getItemStack();\r\n                            }\r\n                        }\r\n                        return NMSHandler.customEntityHelper.spawnItemProjectile(location, itemStack);\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public String getName() {\r\n        return this.name;\r\n    }\r\n\r\n    public String getLowercaseName() {\r\n        return this.lowercaseName;\r\n    }\r\n\r\n    public double getGravity() {\r\n        return this.gravity;\r\n    }\r\n\r\n    public EntityType getBukkitEntityType() {\r\n        return bukkitEntityType;\r\n    }\r\n\r\n    public static void registerEntityType(String name, Class<? extends CustomEntity> entityType) {\r\n        registeredTypes.put(CoreUtilities.toUpperCase(name), new DenizenEntityType(name, entityType));\r\n    }\r\n\r\n    public static boolean isRegistered(String name) {\r\n        return registeredTypes.containsKey(CoreUtilities.toUpperCase(name));\r\n    }\r\n\r\n    public static DenizenEntityType getByName(String name) {\r\n        return registeredTypes.get(CoreUtilities.toUpperCase(name));\r\n    }\r\n\r\n    public static DenizenEntityType getByEntity(Entity entity) {\r\n        if (entity instanceof CustomEntity) {\r\n            return getByName(((CustomEntity) entity).getEntityTypeName());\r\n        }\r\n        else {\r\n            return getByName(entity.getType().name());\r\n        }\r\n    }\r\n\r\n    public boolean isCustom() {\r\n        return customEntityType != null;\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return getName();\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/EntityAttachmentHelper.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.HashSet;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic class EntityAttachmentHelper {\r\n\r\n    public static HashMap<UUID, PlayerAttachMap> attachedEntityToData = new HashMap<>();\r\n    public static HashMap<UUID, EntityAttachedToMap> toEntityToData = new HashMap<>();\r\n\r\n    public static class AttachmentData {\r\n\r\n        public EntityTag attached, to;\r\n\r\n        public boolean offsetRelative;\r\n\r\n        public float yawAngleOffset, pitchAngleOffset;\r\n\r\n        public Location positionalOffset;\r\n\r\n        public HashMap<UUID, Vector> visiblePositions = new HashMap<>();\r\n\r\n        public boolean syncServer;\r\n\r\n        public boolean noRotate, noPitch;\r\n\r\n        public BukkitTask checkTask;\r\n\r\n        public UUID forPlayer;\r\n\r\n        public Vector fixedForOffset(Vector offset, float yaw, float pitch) {\r\n            if (offsetRelative) {\r\n                return offset.clone().add(EntityAttachmentHelper.fixOffset(positionalOffset.toVector(), -yaw + yawAngleOffset, pitch + pitchAngleOffset));\r\n            }\r\n            else {\r\n                return offset.clone().add(positionalOffset.toVector());\r\n            }\r\n        }\r\n\r\n        public void doServerSync() {\r\n            Location goal = to.getLocation();\r\n            if (positionalOffset != null) {\r\n                goal = fixedForOffset(goal.toVector(), goal.getYaw(), goal.getPitch()).toLocation(goal.getWorld());\r\n            }\r\n            if (noRotate) {\r\n                Location attachLoc = attached.getLocation();\r\n                goal.setYaw(attachLoc.getYaw());\r\n                goal.setPitch(attachLoc.getPitch());\r\n            }\r\n            else if (noPitch) {\r\n                goal.setPitch(attached.getLocation().getPitch());\r\n            }\r\n            attached.teleport(goal);\r\n        }\r\n\r\n        public void startTask() {\r\n            if (checkTask != null) {\r\n                checkTask.cancel();\r\n            }\r\n            BukkitRunnable runnable = new BukkitRunnable() {\r\n                int ticks = 0;\r\n                @Override\r\n                public void run() {\r\n                    if (!attached.isValid() || !to.isValid()) {\r\n                        cancelAndRemove();\r\n                        return;\r\n                    }\r\n                    if (ticks++ >= 20 * 10) { // Run a forcetele every 10 seconds to guarantee sync\r\n                        visiblePositions.clear();\r\n                        ticks = 0;\r\n                    }\r\n                    if (syncServer) {\r\n                        doServerSync();\r\n                    }\r\n                }\r\n            };\r\n            runnable.run();\r\n            checkTask = runnable.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    // Run a forcetele one second later just to guarantee sync for lagging clients\r\n                    visiblePositions.clear();\r\n                }\r\n            }.runTaskLater(Denizen.getInstance(), 20);\r\n        }\r\n\r\n        public void removeFrom(PlayerAttachMap map) {\r\n            if (map != null) {\r\n                if (forPlayer == null) {\r\n                    map.everyoneAttachment = null;\r\n                }\r\n                else {\r\n                    if (map.playerToAttachment != null) {\r\n                        map.playerToAttachment.remove(forPlayer);\r\n                    }\r\n                    map.autoNull();\r\n                }\r\n            }\r\n        }\r\n\r\n        public void cancelAndRemove() {\r\n            if (checkTask != null) {\r\n                checkTask.cancel();\r\n            }\r\n            checkTask = null;\r\n            EntityAttachedToMap map = toEntityToData.get(to.getUUID());\r\n            if (map != null) {\r\n                PlayerAttachMap subMap = map.attachedToMap.get(attached.getUUID());\r\n                if (subMap != null) {\r\n                    removeFrom(subMap);\r\n                    if (subMap.everyoneAttachment == null && subMap.playerToAttachment == null) {\r\n                        map.attachedToMap.remove(attached.getUUID());\r\n                        if (map.attachedToMap.isEmpty()) {\r\n                            toEntityToData.remove(to.getUUID());\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            PlayerAttachMap attachMap = attachedEntityToData.get(attached.getUUID());\r\n            if (attachMap != null) {\r\n                removeFrom(attachMap);\r\n                if (attachMap.everyoneAttachment == null && attachMap.playerToAttachment == null) {\r\n                    attachedEntityToData.remove(attached.getUUID());\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    public static class PlayerAttachMap {\r\n\r\n        public EntityTag attached;\r\n\r\n        public AttachmentData everyoneAttachment;\r\n\r\n        public HashMap<UUID, AttachmentData> playerToAttachment;\r\n\r\n        public AttachmentData getAttachment(UUID player) {\r\n            if (playerToAttachment == null) {\r\n                return everyoneAttachment;\r\n            }\r\n            AttachmentData data = playerToAttachment.get(player);\r\n            if (data != null) {\r\n                return data;\r\n            }\r\n            return everyoneAttachment;\r\n        }\r\n\r\n        public void cancelAll() {\r\n            if (everyoneAttachment != null) {\r\n                everyoneAttachment.cancelAndRemove();\r\n            }\r\n            if (playerToAttachment != null) {\r\n                for (AttachmentData attachment : new HashSet<>(playerToAttachment.values())) {\r\n                    attachment.cancelAndRemove();\r\n                }\r\n            }\r\n        }\r\n\r\n        public void autoNull() {\r\n            if (playerToAttachment != null && playerToAttachment.isEmpty()) {\r\n                playerToAttachment = null;\r\n            }\r\n        }\r\n    }\r\n\r\n    public static class EntityAttachedToMap {\r\n\r\n        public HashMap<UUID, PlayerAttachMap> attachedToMap = new HashMap<>();\r\n    }\r\n\r\n    public static byte adaptedCompressedAngle(byte angle, float offset) {\r\n        float angleF = ((float) angle) * (360F / 256F);\r\n        return compressAngle(angleF + offset);\r\n    }\r\n\r\n    public static float normalizeAngle(float angle) {\r\n        angle %= 360;\r\n        if (angle > 180) {\r\n            angle -= 360;\r\n        }\r\n        if (angle < -180) {\r\n            angle += 360;\r\n        }\r\n        return angle;\r\n    }\r\n\r\n    public static byte compressAngle(float angle) {\r\n        return (byte)((int)(normalizeAngle(angle) * (256F / 360F)));\r\n    }\r\n\r\n    public static Vector fixOffset(Vector offset, double yaw, double pitch) {\r\n        yaw = Math.toRadians(yaw);\r\n        pitch = Math.toRadians(pitch);\r\n        Vector offsetPatched = offset.clone();\r\n        // x rotation\r\n        double cosPitch = Math.cos(pitch);\r\n        double sinPitch = Math.sin(pitch);\r\n        double y1 = (offsetPatched.getY() * cosPitch) - (offsetPatched.getZ() * sinPitch);\r\n        double z1 = (offsetPatched.getY() * sinPitch) + (offsetPatched.getZ() * cosPitch);\r\n        offsetPatched.setY(y1);\r\n        offsetPatched.setZ(z1);\r\n        // y rotation\r\n        double cosYaw = Math.cos(yaw);\r\n        double sinYaw = Math.sin(yaw);\r\n        double x2 = (offsetPatched.getX() * cosYaw) + (offsetPatched.getZ() * sinYaw);\r\n        double z2 = (offsetPatched.getX() * -sinYaw) + (offsetPatched.getZ() * cosYaw);\r\n        offsetPatched.setX(x2);\r\n        offsetPatched.setZ(z2);\r\n        return offsetPatched;\r\n    }\r\n\r\n    public static void removeAttachment(UUID attachedId, UUID forPlayer) {\r\n        PlayerAttachMap map = attachedEntityToData.get(attachedId);\r\n        if (map == null) {\r\n            return;\r\n        }\r\n        if (forPlayer == null) {\r\n            attachedEntityToData.remove(attachedId);\r\n            map.cancelAll();\r\n        }\r\n        else {\r\n            if (map.playerToAttachment != null) {\r\n                AttachmentData data = map.playerToAttachment.get(forPlayer);\r\n                if (data != null) {\r\n                    data.cancelAndRemove();\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void addPlayerAttachMap(Map<UUID, PlayerAttachMap> target, AttachmentData attachment) {\r\n        PlayerAttachMap map = target.get(attachment.attached.getUUID());\r\n        if (map == null) {\r\n            map = new PlayerAttachMap();\r\n            map.attached = attachment.attached;\r\n            target.put(attachment.attached.getUUID(), map);\r\n        }\r\n        if (attachment.forPlayer == null) {\r\n            map.everyoneAttachment = attachment;\r\n        }\r\n        else {\r\n            if (map.playerToAttachment == null) {\r\n                map.playerToAttachment = new HashMap<>();\r\n            }\r\n            map.playerToAttachment.put(attachment.forPlayer, attachment);\r\n        }\r\n    }\r\n\r\n    public static void registerAttachment(AttachmentData attachment) {\r\n        NetworkInterceptHelper.enable();\r\n        removeAttachment(attachment.attached.getUUID(), attachment.forPlayer);\r\n        attachment.startTask();\r\n        EntityAttachedToMap toMap = toEntityToData.get(attachment.to.getUUID());\r\n        if (toMap == null) {\r\n            toMap = new EntityAttachedToMap();\r\n            toEntityToData.put(attachment.to.getUUID(), toMap);\r\n        }\r\n        addPlayerAttachMap(attachedEntityToData, attachment);\r\n        addPlayerAttachMap(toMap.attachedToMap, attachment);\r\n        if (attachment.syncServer) {\r\n            attachment.doServerSync();\r\n        }\r\n    }\r\n\r\n    public static void forceAttachMove(EntityTag attached, EntityTag to, Vector offset, boolean matchRotation) {\r\n        removeAttachment(attached.getUUID(), null);\r\n        if (to == null) {\r\n            return;\r\n        }\r\n        AttachmentData data = new AttachmentData();\r\n        data.attached = attached;\r\n        data.to = to;\r\n        data.positionalOffset = offset == null ? null : offset.toLocation(null);\r\n        data.offsetRelative = matchRotation;\r\n        registerAttachment(data);\r\n    }\r\n\r\n    public static boolean denyOriginalPacketSend(UUID player, UUID entity) {\r\n        PlayerAttachMap attached = EntityAttachmentHelper.attachedEntityToData.get(entity);\r\n        if (attached == null) {\r\n            return false;\r\n        }\r\n        AttachmentData data = attached.getAttachment(player);\r\n        if (data == null) {\r\n            return false;\r\n        }\r\n        if (data.to.getUUID().equals(player)) {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/EntityMetadataCommandHelper.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.*;\r\nimport java.util.function.BiConsumer;\r\nimport java.util.function.Predicate;\r\n\r\npublic record EntityMetadataCommandHelper(Predicate<Entity> getter, BiConsumer<EntityTag, Boolean> setter, Map<UUID, Map<UUID, Boolean>> packetOverrides) {\r\n\r\n    public EntityMetadataCommandHelper(Predicate<Entity> getter, BiConsumer<EntityTag, Boolean> setter) {\r\n        this(getter, setter, new HashMap<>());\r\n    }\r\n\r\n    public void setForPlayers(List<PlayerTag> players, EntityTag target, Predicate<PlayerTag> stateSupplier) {\r\n        if (target == null || target.getUUID() == null || players == null) {\r\n            return;\r\n        }\r\n        NetworkInterceptHelper.enable();\r\n        boolean wasEntityAdded = !packetOverrides.containsKey(target.getUUID());\r\n        Map<UUID, Boolean> playerMap = packetOverrides.computeIfAbsent(target.getUUID(), k -> new HashMap<>());\r\n        for (PlayerTag player : players) {\r\n            boolean state = stateSupplier.test(player);\r\n            Boolean oldState = playerMap.put(player.getUUID(), state);\r\n            if ((wasEntityAdded || oldState == null || oldState != state) && player.isOnline()) {\r\n                NMSHandler.packetHelper.sendEntityMetadataFlagsUpdate(player.getPlayerEntity(), target.getBukkitEntity());\r\n            }\r\n        }\r\n    }\r\n\r\n    public Boolean getState(Entity entity, UUID player, boolean fakeOnly) {\r\n        if (entity == null) {\r\n            return null;\r\n        }\r\n        if (player != null) {\r\n            Map<UUID, Boolean> playerMap = packetOverrides.get(entity.getUniqueId());\r\n            if (playerMap != null && playerMap.containsKey(player)) {\r\n                return playerMap.get(player);\r\n            }\r\n        }\r\n        if (fakeOnly) {\r\n            return null;\r\n        }\r\n        return getter.test(entity);\r\n    }\r\n\r\n    public boolean noOverrides() {\r\n        return packetOverrides.isEmpty();\r\n    }\r\n\r\n    public enum Action {TRUE, FALSE, TOGGLE, RESET}\r\n\r\n    public void execute(ScriptEntry scriptEntry, List<EntityTag> targets, Action action, List<PlayerTag> forPlayers) {\r\n        if (targets == null) {\r\n            targets = Utilities.entryDefaultEntityList(scriptEntry, true);\r\n            if (targets == null) {\r\n                throw new InvalidArgumentsRuntimeException(\"Must specify valid targets.\");\r\n            }\r\n        }\r\n        switch (action) {\r\n            case TRUE, FALSE -> {\r\n                boolean state = action == Action.TRUE;\r\n                if (forPlayers == null) {\r\n                    for (EntityTag target : targets) {\r\n                        setter.accept(target, state);\r\n                    }\r\n                }\r\n                else {\r\n                    for (EntityTag target : targets) {\r\n                        setForPlayers(forPlayers, target, player -> state);\r\n                    }\r\n                }\r\n            }\r\n            case TOGGLE -> {\r\n                if (forPlayers == null) {\r\n                    for (EntityTag target : targets) {\r\n                        setter.accept(target, !getState(target.getBukkitEntity(), null, false));\r\n                    }\r\n                }\r\n                else {\r\n                    for (EntityTag target : targets) {\r\n                        setForPlayers(forPlayers, target, player -> !getState(target.getBukkitEntity(), player.getUUID(), false));\r\n                    }\r\n                }\r\n            }\r\n            case RESET -> {\r\n                for (EntityTag target : targets) {\r\n                    Map<UUID, Boolean> playerMap = packetOverrides.get(target.getUUID());\r\n                    if (playerMap == null) {\r\n                        return;\r\n                    }\r\n                    Set<UUID> playersToUpdate = new HashSet<>();\r\n                    if (forPlayers == null) {\r\n                        playersToUpdate.addAll(playerMap.keySet());\r\n                        packetOverrides.remove(target.getUUID());\r\n                    }\r\n                    else {\r\n                        for (PlayerTag player : forPlayers) {\r\n                            if (playerMap.remove(player.getUUID()) != null) {\r\n                                playersToUpdate.add(player.getUUID());\r\n                            }\r\n                        }\r\n                        if (playerMap.isEmpty()) {\r\n                            packetOverrides.remove(target.getUUID());\r\n                        }\r\n                    }\r\n                    if (playersToUpdate.isEmpty()) {\r\n                        return;\r\n                    }\r\n                    for (Player player : NMSHandler.entityHelper.getPlayersThatSee(target.getBukkitEntity())) {\r\n                        if (playersToUpdate.contains(player.getUniqueId())) {\r\n                            NMSHandler.packetHelper.sendEntityMetadataFlagsUpdate(player, target.getBukkitEntity());\r\n                        }\r\n                    }\r\n                    if (playersToUpdate.contains(target.getUUID())) {\r\n                        NMSHandler.packetHelper.sendEntityMetadataFlagsUpdate(target.as(Player.class), target.getBukkitEntity());\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/FakeEntity.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\n\r\nimport java.util.*;\r\nimport java.util.function.Consumer;\r\n\r\npublic class FakeEntity {\r\n\r\n    public static class FakeEntityMap {\r\n\r\n        public Map<Integer, FakeEntity> byId = new HashMap<>();\r\n\r\n        public void remove(FakeEntity entity) {\r\n            byId.remove(entity.id);\r\n        }\r\n    }\r\n\r\n    public final static Map<UUID, FakeEntityMap> playersToEntities = new HashMap<>();\r\n    public final static Map<UUID, FakeEntity> idsToEntities = new HashMap<>();\r\n\r\n    public static FakeEntity getFakeEntityFor(UUID uuid, int id) {\r\n        FakeEntityMap map = playersToEntities.get(uuid);\r\n        if (map == null) {\r\n            return null;\r\n        }\r\n        return map.byId.get(id);\r\n    }\r\n\r\n    public List<PlayerTag> players;\r\n    public int id;\r\n    public EntityTag entity;\r\n    public LocationTag location;\r\n    public BukkitTask currentTask = null;\r\n    public Consumer<PlayerTag> triggerSpawnPacket;\r\n    public Runnable triggerUpdatePacket;\r\n    public Runnable triggerDestroyPacket;\r\n    public UUID overrideUUID;\r\n\r\n    public FakeEntity(List<PlayerTag> player, LocationTag location, int id) {\r\n        this.players = player;\r\n        this.location = location;\r\n        this.id = id;\r\n    }\r\n\r\n    public static FakeEntity showFakeEntityTo(List<PlayerTag> players, EntityTag typeToSpawn, LocationTag location, DurationTag duration, EntityTag vehicle) {\r\n        NetworkInterceptHelper.enable();\r\n        FakeEntity fakeEntity = NMSHandler.playerHelper.sendEntitySpawn(players, typeToSpawn.getEntityType(), location, typeToSpawn.mechanisms == null ? null : new ArrayList<>(typeToSpawn.mechanisms), -1, null, true);\r\n        if (vehicle != null) {\r\n            NMSHandler.playerHelper.addFakePassenger(players, vehicle.getBukkitEntity(), fakeEntity);\r\n        }\r\n        idsToEntities.put(fakeEntity.overrideUUID == null ? fakeEntity.entity.getUUID() : fakeEntity.overrideUUID, fakeEntity);\r\n        for (PlayerTag player : players) {\r\n            UUID uuid = player.getPlayerEntity().getUniqueId();\r\n            FakeEntity.FakeEntityMap playerEntities = playersToEntities.get(uuid);\r\n            if (playerEntities == null) {\r\n                playerEntities = new FakeEntity.FakeEntityMap();\r\n                playersToEntities.put(uuid, playerEntities);\r\n            }\r\n            playerEntities.byId.put(fakeEntity.id, fakeEntity);\r\n        }\r\n        fakeEntity.updateEntity(fakeEntity.entity, duration);\r\n        return fakeEntity;\r\n    }\r\n\r\n    public void cancelEntity() {\r\n        if (currentTask != null) {\r\n            currentTask.cancel();\r\n            currentTask = null;\r\n        }\r\n        idsToEntities.remove(overrideUUID == null ? entity.getUUID() : overrideUUID);\r\n        if (triggerDestroyPacket != null) {\r\n            triggerDestroyPacket.run();\r\n        }\r\n        else {\r\n            for (PlayerTag player : players) {\r\n                if (player.isOnline()) {\r\n                    NMSHandler.playerHelper.sendEntityDestroy(player.getPlayerEntity(), entity.getBukkitEntity());\r\n                }\r\n            }\r\n        }\r\n        for (PlayerTag player : players) {\r\n            FakeEntity.FakeEntityMap mapping = playersToEntities.get(player.getUUID());\r\n            mapping.remove(this);\r\n        }\r\n        entity.isFakeValid = false;\r\n    }\r\n\r\n    private void updateEntity(EntityTag entity, DurationTag duration) {\r\n        if (currentTask != null) {\r\n            currentTask.cancel();\r\n        }\r\n        this.entity = entity;\r\n        if (duration != null && duration.getTicks() > 0) {\r\n            currentTask = new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    currentTask = null;\r\n                    cancelEntity();\r\n                }\r\n            }.runTaskLater(Denizen.getInstance(), duration.getTicks());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/Gravity.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.Registry;\r\nimport org.bukkit.entity.EntityType;\r\n\r\npublic class Gravity {\r\n\r\n    // TODO once 1.20 is the minimum supported version can reference the enum directly\r\n    public static final EntityType EXPERIENCE_BOTTLE_ENTITY_TYPE = Registry.ENTITY_TYPE.get(NamespacedKey.minecraft(\"experience_bottle\"));\r\n\r\n    public static double getGravity(EntityType entityType) {\r\n        if (entityType == EXPERIENCE_BOTTLE_ENTITY_TYPE) {\r\n            return 0.157;\r\n        }\r\n        switch (entityType) {\r\n            case ARROW:\r\n                return 0.118;\r\n            case SNOWBALL:\r\n                return 0.076;\r\n            case EGG:\r\n                return 0.074;\r\n            default:\r\n                return 0.115;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/HideEntitiesHelper.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerJoinEvent;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.util.*;\r\n\r\npublic class HideEntitiesHelper {\r\n\r\n    public static class PlayerHideMap {\r\n\r\n        public UUID player;\r\n\r\n        public HashSet<UUID> entitiesHidden = new HashSet<>();\r\n\r\n        public HashSet<UUID> overridinglyShow = new HashSet<>();\r\n\r\n        public HashSet<String> matchersHidden = new HashSet<>();\r\n\r\n        public boolean shouldHideViaMatcher(Entity entity) {\r\n            if (entity == null) {\r\n                return false;\r\n            }\r\n            if (!matchersHidden.isEmpty()) {\r\n                if (overridinglyShow.contains(entity.getUniqueId())) {\r\n                    return false;\r\n                }\r\n                EntityTag entityTag = new EntityTag(entity);\r\n                for (String matchable : matchersHidden) {\r\n                    if (entityTag.tryAdvancedMatcher(matchable, CoreUtilities.noDebugContext)) {\r\n                        if (entity instanceof Player) {\r\n                            Player thisPlayer = Bukkit.getPlayer(player);\r\n                            if (thisPlayer != null && thisPlayer.canSee((Player) entity)) {\r\n                                thisPlayer.hidePlayer(Denizen.getInstance(), (Player) entity);\r\n                            }\r\n                        }\r\n                        return true;\r\n                    }\r\n                }\r\n            }\r\n            return false;\r\n        }\r\n\r\n        public boolean shouldHide(Entity entity) {\r\n            if (entity == null) {\r\n                return false;\r\n            }\r\n            if (overridinglyShow.contains(entity.getUniqueId())) {\r\n                return false;\r\n            }\r\n            if (entitiesHidden.contains(entity.getUniqueId())) {\r\n                return true;\r\n            }\r\n            if (defaultHidden.contains(entity.getUniqueId())) {\r\n                return true;\r\n            }\r\n            return shouldHideViaMatcher(entity);\r\n        }\r\n    }\r\n\r\n    public static HashMap<UUID, PlayerHideMap> playerHides = new HashMap<>();\r\n\r\n    public static HashSet<UUID> defaultHidden = new HashSet<>();\r\n\r\n    public static boolean hasAnyHides() {\r\n        return !playerHides.isEmpty() || !defaultHidden.isEmpty();\r\n    }\r\n\r\n    public static PlayerHideMap getPlayerMapFor(UUID player) {\r\n        PlayerHideMap map = playerHides.get(player);\r\n        if (map == null) {\r\n            map = new PlayerHideMap();\r\n            map.player = player;\r\n            playerHides.put(player, map);\r\n        }\r\n        return map;\r\n    }\r\n\r\n    public static boolean playerShouldHide(UUID player, Entity ent) {\r\n        PlayerHideMap map = playerHides.get(player);\r\n        if (map == null) {\r\n            return defaultHidden.contains(ent.getUniqueId()) && !player.equals(ent.getUniqueId());\r\n        }\r\n        return map.shouldHide(ent);\r\n    }\r\n\r\n    public static boolean addHide(UUID player, UUID entity) {\r\n        NetworkInterceptHelper.enable();\r\n        ensurePlayerHiding();\r\n        if (player == null) {\r\n            return defaultHidden.add(entity);\r\n        }\r\n        PlayerHideMap map = getPlayerMapFor(player);\r\n        map.overridinglyShow.remove(entity);\r\n        return map.entitiesHidden.add(entity);\r\n    }\r\n\r\n    public static void hideEntity(Player player, Entity entity) {\r\n        if (addHide(player == null ? null : player.getUniqueId(), entity.getUniqueId())) {\r\n            if (player == null) {\r\n                for (Player pl : Bukkit.getOnlinePlayers()) {\r\n                    NMSHandler.entityHelper.sendHidePacket(pl, entity);\r\n                }\r\n            }\r\n            else {\r\n                NMSHandler.entityHelper.sendHidePacket(player, entity);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static boolean removeHide(UUID player, UUID entity) {\r\n        NetworkInterceptHelper.enable();\r\n        if (player == null) {\r\n            return defaultHidden.remove(entity);\r\n        }\r\n        PlayerHideMap map = playerHides.get(player);\r\n        if (defaultHidden.contains(entity) || (map != null && map.shouldHideViaMatcher(Bukkit.getEntity(entity)))) {\r\n            if (map == null) {\r\n                map = new PlayerHideMap();\r\n                map.player = player;\r\n                playerHides.put(player, map);\r\n            }\r\n            map.entitiesHidden.remove(entity);\r\n            return map.overridinglyShow.add(entity);\r\n        }\r\n        if (map == null) {\r\n            return false;\r\n        }\r\n        boolean result = map.entitiesHidden.remove(entity);\r\n        if (result && map.entitiesHidden.isEmpty() && map.overridinglyShow.isEmpty() && map.matchersHidden.isEmpty()) {\r\n            playerHides.remove(player);\r\n        }\r\n        return result;\r\n    }\r\n\r\n    public static void unhideEntity(Player player, Entity entity) {\r\n        if (removeHide(player == null ? null : player.getUniqueId(), entity.getUniqueId())) {\r\n            if (player == null) {\r\n                for (Player pl : Bukkit.getOnlinePlayers()) {\r\n                    NMSHandler.entityHelper.sendShowPacket(pl, entity);\r\n                }\r\n            }\r\n            else {\r\n                NMSHandler.entityHelper.sendShowPacket(player, entity);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static class EnforcePlayerHides implements Listener {\r\n\r\n        @EventHandler\r\n        public void onPlayerJoin(PlayerJoinEvent event) {\r\n            for (UUID id : defaultHidden) {\r\n                Player pTarget = Bukkit.getPlayer(id);\r\n                if (pTarget != null) {\r\n                    event.getPlayer().hidePlayer(Denizen.getInstance(), pTarget);\r\n                }\r\n            }\r\n            final Player pl = event.getPlayer();\r\n            PlayerHideMap map = playerHides.get(pl.getUniqueId());\r\n            if (map == null) {\r\n                return;\r\n            }\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    if (pl.isOnline()) {\r\n                        if (map.matchersHidden != null) {\r\n                            for (Entity entity : pl.getWorld().getEntities()) {\r\n                                if (map.shouldHide(entity)) {\r\n                                    NMSHandler.entityHelper.sendHidePacket(pl, entity);\r\n                                }\r\n                            }\r\n                        }\r\n                        else {\r\n                            for (UUID id : map.entitiesHidden) {\r\n                                Entity ent = Bukkit.getEntity(id);\r\n                                if (ent != null) {\r\n                                    NMSHandler.entityHelper.sendHidePacket(pl, ent);\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }.runTaskLater(Denizen.getInstance(), 5);\r\n        }\r\n    }\r\n\r\n    public static EnforcePlayerHides EPH = null;\r\n\r\n    public static void ensurePlayerHiding() {\r\n        if (EPH == null) {\r\n            EPH = new EnforcePlayerHides();\r\n            Bukkit.getPluginManager().registerEvents(EPH, Denizen.getInstance());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/Position.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport org.bukkit.entity.Entity;\r\n\r\nimport java.util.List;\r\n\r\npublic class Position {\r\n\r\n    public static void mount(List<Entity> entities) {\r\n        Entity lastEntity = null;\r\n        for (Entity entity : entities) {\r\n            if (entity != null) {\r\n                if (lastEntity != null && entity != lastEntity) {\r\n                    if (!entity.getPassengers().contains(lastEntity)) {\r\n                        lastEntity.teleport(entity.getLocation());\r\n                        entity.addPassenger(lastEntity);\r\n                    }\r\n                }\r\n                lastEntity = entity;\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void dismount(List<Entity> entities) {\r\n        for (Entity entity : entities) {\r\n            if (entity != null) {\r\n                entity.leaveVehicle();\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/SpawnEntityHelper.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.EntityScriptHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.util.Consumer;\r\n\r\nimport java.util.ArrayList;\r\n\r\n/**\r\n * Applies mechanisms to an entity before spawning it\r\n */\r\npublic class SpawnEntityHelper {\r\n\r\n    public static <T extends Entity> Entity spawn(Location location, EntityType bukkitEntityType, final ArrayList<Mechanism> mechanisms, final String scriptName, final CreatureSpawnEvent.SpawnReason reason) {\r\n        Consumer<? extends Entity> consumer = (Consumer<Entity>) bukkitEntity -> {\r\n            if (scriptName != null) {\r\n                EntityScriptHelper.setEntityScript(bukkitEntity, scriptName);\r\n            }\r\n            EntityTag entity = new EntityTag(bukkitEntity);\r\n            for (Mechanism mechanism : new ArrayList<>(mechanisms)) {\r\n                if (EntityTag.earlyValidMechanisms.contains(CoreUtilities.toLowerCase(mechanism.getName()))) {\r\n                    entity.safeAdjustDuplicate(mechanism);\r\n                    //mechanisms.remove(mechanism);\r\n                }\r\n            }\r\n        };\r\n        return PaperAPITools.instance.spawnEntity(location, (Class<T>) bukkitEntityType.getEntityClass(), (Consumer<T>) consumer, reason);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/entity/Velocity.java",
    "content": "package com.denizenscript.denizen.utilities.entity;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.util.Vector;\r\n\r\npublic class Velocity {\r\n\r\n    /**\r\n     * Calculates the vector between two locations' vectors\r\n     * <p/>\r\n     * Original code by SethBling, edited to be a bit\r\n     * more accurate.\r\n     *\r\n     * @param from       The origin's vector\r\n     * @param to         The destination's vector\r\n     * @param gravity    The gravity value of the entity\r\n     * @param heightGain The gain in height\r\n     * @return A vector\r\n     */\r\n    public static Vector calculate(Vector from, Vector to, double gravity, double heightGain) {\r\n        // Block locations\r\n        int endGain = to.getBlockY() - from.getBlockY();\r\n        double horizDist = Math.sqrt(distanceSquared(from, to));\r\n        if (horizDist == 0) {\r\n            if (to.getY() > from.getY()) {\r\n                return new Vector(0, 1, 0);\r\n            }\r\n            return new Vector(0, -1, 0);\r\n        }\r\n        double maxGain = heightGain > (endGain + heightGain) ? heightGain : (endGain + heightGain);\r\n        // Solve quadratic equation for velocity\r\n        double a = -horizDist * horizDist / (4 * maxGain);\r\n        double b = horizDist;\r\n        double c = -endGain;\r\n\r\n        double slope = -b / (2 * a) - Math.sqrt(b * b - 4 * a * c) / (2 * a);\r\n\r\n        // Vertical velocity\r\n        double vy = Math.sqrt(maxGain * (gravity + 0.0013675090252708 * heightGain));\r\n        // Horizontal velocity\r\n        double vh = vy / slope;\r\n        // Calculate horizontal direction\r\n        int dx = to.getBlockX() - from.getBlockX();\r\n        int dz = to.getBlockZ() - from.getBlockZ();\r\n        double mag = Math.sqrt(dx * dx + dz * dz);\r\n        double dirx = dx / mag;\r\n        double dirz = dz / mag;\r\n        // Horizontal velocity components\r\n        double vx = vh * dirx;\r\n        double vz = vh * dirz;\r\n        return new Vector(vx, vy, vz);\r\n    }\r\n\r\n    private static double distanceSquared(Vector from, Vector to) {\r\n        double dx = to.getBlockX() - from.getBlockX();\r\n        double dz = to.getBlockZ() - from.getBlockZ();\r\n        return dx * dx + dz * dz;\r\n    }\r\n\r\n    public static Vector randomSpread(Vector from, double spreadFactor) {\r\n        double length = from.length();\r\n        Vector fromNormal = from.clone().multiply(1.0 / length);\r\n        Location ref = new Location(null, 0, 0, 0).setDirection(fromNormal);\r\n        // Random point_around_z from origin\r\n        double rLen = CoreUtilities.getRandom().nextDouble() * spreadFactor * 0.0175; // 0.0175 = the approximate offset length of 1 degree at 1 unit distance forward\r\n        double randomAngle = CoreUtilities.getRandom().nextDouble() * (Math.PI * 2);\r\n        double cosR = Math.cos(randomAngle);\r\n        double sinR = Math.sin(randomAngle);\r\n        double x = -(rLen * sinR);\r\n        double y = rLen * cosR;\r\n        // rotate_around_x[pitch]\r\n        double cosPitch = Math.cos(Math.toRadians(ref.getPitch()));\r\n        double sinPitch = Math.sin(Math.toRadians(ref.getPitch()));\r\n        double y2 = y * cosPitch;\r\n        double z2 = y * sinPitch;\r\n        // rotate_around_y[-yaw]\r\n        double yaw = -EntityHelper.normalizeYaw(ref.getYaw());\r\n        double cosYaw = Math.cos(Math.toRadians(yaw));\r\n        double sinYaw = Math.sin(Math.toRadians(yaw));\r\n        double x3 = (x * cosYaw) + (z2 * sinYaw);\r\n        double z3 = (x * -sinYaw) + (z2 * cosYaw);\r\n        return fromNormal.add(new Vector(x3, y2, z3)).normalize().multiply(length);\r\n    }\r\n\r\n    public static double launchAngle(Location from, Vector to, double v, double elev, double g) {\r\n        Vector victor = from.toVector().subtract(to);\r\n        double dist = Math.sqrt(Math.pow(victor.getX(), 2) + Math.pow(victor.getZ(), 2));\r\n        double v2 = Math.pow(v, 2);\r\n        double v4 = Math.pow(v, 4);\r\n        double derp = g * (g * Math.pow(dist, 2) + 2 * elev * v2);\r\n        if (v4 < derp) {\r\n            // Max optimal (won't hit!)\r\n            return Math.atan((2 * g * elev + v2) / (2 * g * elev + 2 * v2));\r\n        }\r\n        else {\r\n            return Math.atan((v2 - Math.sqrt(v4 - derp)) / (g * dist));\r\n        }\r\n    }\r\n\r\n    public static double hangtime(double launchAngle, double v, double elev, double g) {\r\n        double a = v * Math.sin(launchAngle);\r\n        double b = -2 * g * elev;\r\n        if (Math.pow(a, 2) + b < 0) {\r\n            return 0;\r\n        }\r\n        return (a + Math.sqrt(Math.pow(a, 2) + b)) / g;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/flags/DataPersistenceFlagTracker.java",
    "content": "package com.denizenscript.denizen.utilities.flags;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.utilities.DataPersistenceHelper;\r\nimport com.denizenscript.denizencore.flags.MapTagBasedFlagTracker;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.utilities.AsciiMatcher;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.persistence.PersistentDataHolder;\r\nimport org.bukkit.persistence.PersistentDataType;\r\n\r\nimport java.util.Collection;\r\n\r\npublic class DataPersistenceFlagTracker extends MapTagBasedFlagTracker {\r\n\r\n    public DataPersistenceFlagTracker(PersistentDataHolder holder) {\r\n        this.holder = holder;\r\n    }\r\n\r\n    public DataPersistenceFlagTracker(PersistentDataHolder holder, String keyPrefix) {\r\n        this.holder = holder;\r\n        this.keyPrefix = keyPrefix;\r\n    }\r\n\r\n    public PersistentDataHolder holder;\r\n\r\n    public String keyPrefix = \"flag_\";\r\n\r\n    public static AsciiMatcher allowedKeyText = new AsciiMatcher(AsciiMatcher.LETTERS_LOWER + AsciiMatcher.DIGITS + \"_/.-\");\r\n\r\n    public static String cleanKeyName(String input) {\r\n        return allowedKeyText.trimToMatches(CoreUtilities.toLowerCase(input));\r\n    }\r\n\r\n    @Override\r\n    public MapTag getRootMap(String key) {\r\n        return (MapTag) DataPersistenceHelper.getDenizenKey(holder, keyPrefix + cleanKeyName(key));\r\n    }\r\n\r\n    @Override\r\n    public void setRootMap(String key, MapTag map) {\r\n        if (map == null) {\r\n            DataPersistenceHelper.removeDenizenKey(holder, keyPrefix + cleanKeyName(key));\r\n            return;\r\n        }\r\n        if (map.containsKey(expirationString) || map.getObject(valueString) instanceof MapTag) {\r\n            holder.getPersistentDataContainer().set(expireNeededKey, PersistentDataType.STRING, \"true\");\r\n        }\r\n        DataPersistenceHelper.setDenizenKey(holder, keyPrefix + cleanKeyName(key), map);\r\n    }\r\n\r\n    @Override\r\n    public Collection<String> listAllFlags() {\r\n        return NMSHandler.instance.containerListFlags(holder.getPersistentDataContainer(), keyPrefix);\r\n    }\r\n\r\n    public static NamespacedKey expireNeededKey = new NamespacedKey(Denizen.getInstance(), \"expire_flag_check_needed\");\r\n\r\n    @Override\r\n    public void doTotalClean() {\r\n        if (!holder.getPersistentDataContainer().has(expireNeededKey, PersistentDataType.STRING)) {\r\n            return;\r\n        }\r\n        boolean containsAnyToCheck = false;\r\n        for (NamespacedKey key : holder.getPersistentDataContainer().getKeys()) {\r\n            if (!key.getNamespace().equals(\"denizen\") || !key.getKey().startsWith(\"flag_\")) {\r\n                continue;\r\n            }\r\n            ObjectTag map = DataPersistenceHelper.getDenizenKey(holder, key.getKey());\r\n            if (!(map instanceof MapTag)) {\r\n                continue;\r\n            }\r\n            if (isExpired(((MapTag) map).getObject(expirationString))) {\r\n                holder.getPersistentDataContainer().remove(key);\r\n                containsAnyToCheck = true;\r\n                continue;\r\n            }\r\n            ObjectTag subValue = ((MapTag) map).getObject(valueString);\r\n            if (subValue instanceof MapTag) {\r\n                if (doClean((MapTag) subValue)) {\r\n                    holder.getPersistentDataContainer().set(key, DataPersistenceHelper.PERSISTER_TYPE, map);\r\n                }\r\n                containsAnyToCheck = true;\r\n            }\r\n        }\r\n        if (!containsAnyToCheck) {\r\n            holder.getPersistentDataContainer().remove(expireNeededKey);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/flags/LocationFlagSearchHelper.java",
    "content": "package com.denizenscript.denizen.utilities.flags;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.persistence.PersistentDataContainer;\r\n\r\nimport java.util.List;\r\nimport java.util.function.Consumer;\r\n\r\npublic class LocationFlagSearchHelper {\r\n\r\n    public static void getFlaggedLocations(Chunk chunk, String flagName, Consumer<Location> handleLocation) {\r\n        int subKeyIndex = flagName.indexOf('.');\r\n        String fullPath = flagName;\r\n        if (subKeyIndex != -1) {\r\n            flagName = flagName.substring(0, subKeyIndex);\r\n        }\r\n        PersistentDataContainer container = chunk.getPersistentDataContainer();\r\n        Location ref = new Location(chunk.getWorld(), 0, 0, 0);\r\n        for (NamespacedKey key : container.getKeys()) {\r\n            if (key.getNamespace().equals(\"denizen\") && key.getKey().startsWith(\"flag_tracker_\") && key.getKey().endsWith(flagName)) {\r\n                List<String> split = CoreUtilities.split(key.getKey(), '_', 6);\r\n                if (split.size() == 6 && split.get(5).equals(flagName)) {\r\n                    ref.setX(Integer.parseInt(split.get(2)));\r\n                    ref.setY(Integer.parseInt(split.get(3)));\r\n                    ref.setZ(Integer.parseInt(split.get(4)));\r\n                    if (new LocationTag(ref).getFlagTracker().hasFlag(fullPath)) {\r\n                        handleLocation.accept(ref);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/flags/PlayerFlagHandler.java",
    "content": "package com.denizenscript.denizen.utilities.flags;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.flags.AbstractFlagTracker;\r\nimport com.denizenscript.denizencore.flags.SavableMapFlagTracker;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.AsyncPlayerPreLoginEvent;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.io.File;\r\nimport java.lang.ref.SoftReference;\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\nimport java.util.concurrent.CompletableFuture;\r\nimport java.util.concurrent.Future;\r\nimport java.util.concurrent.TimeUnit;\r\nimport java.util.concurrent.atomic.AtomicBoolean;\r\n\r\npublic class PlayerFlagHandler implements Listener {\r\n\r\n    public static long cacheTimeoutSeconds = 300;\r\n\r\n    public static boolean asyncPreload = false;\r\n\r\n    public static boolean saveOnlyWhenWorldSaveOn = false;\r\n\r\n    public static class CachedPlayerFlag {\r\n\r\n        public long lastAccessed;\r\n\r\n        public SavableMapFlagTracker tracker;\r\n\r\n        public AtomicBoolean savingNow = new AtomicBoolean(false), loadingNow = new AtomicBoolean(false);\r\n\r\n        public boolean shouldExpire() {\r\n            if (cacheTimeoutSeconds == -1) {\r\n                return false;\r\n            }\r\n            if (cacheTimeoutSeconds == 0) {\r\n                return true;\r\n            }\r\n            return lastAccessed + (cacheTimeoutSeconds * 1000) < CoreUtilities.monotonicMillis();\r\n        }\r\n    }\r\n\r\n    public static File dataFolder;\r\n\r\n    public static HashMap<UUID, CachedPlayerFlag> playerFlagTrackerCache = new HashMap<>();\r\n\r\n    public static HashMap<UUID, SoftReference<CachedPlayerFlag>> secondaryPlayerFlagTrackerCache = new HashMap<>();\r\n\r\n    private static ArrayList<UUID> toClearCache = new ArrayList<>();\r\n\r\n    public static void cleanSecondaryCache() {\r\n        toClearCache.clear();\r\n        for (Map.Entry<UUID, SoftReference<CachedPlayerFlag>> entry : secondaryPlayerFlagTrackerCache.entrySet()) {\r\n            // NOTE: This call will make the GC think the value is still needed, thus the 10 minute cleanup timer to allow the GC to know these are unimportant\r\n            if (entry.getValue().get() == null) {\r\n                toClearCache.add(entry.getKey());\r\n            }\r\n        }\r\n        for (UUID id : toClearCache) {\r\n            secondaryPlayerFlagTrackerCache.remove(id);\r\n        }\r\n    }\r\n\r\n    private static int secondaryCleanTicker = 0;\r\n\r\n    public static void cleanCache() {\r\n        if (cacheTimeoutSeconds == -1) {\r\n            return;\r\n        }\r\n        if (secondaryCleanTicker++ > 10) {\r\n            cleanSecondaryCache();\r\n        }\r\n        long timeNow = CoreUtilities.monotonicMillis();\r\n        for (Map.Entry<UUID, CachedPlayerFlag> entry : playerFlagTrackerCache.entrySet()) {\r\n            if (cacheTimeoutSeconds > 0 && entry.getValue().lastAccessed + (cacheTimeoutSeconds * 1000) < timeNow) {\r\n                continue;\r\n            }\r\n            if (Bukkit.getPlayer(entry.getKey()) != null) {\r\n                entry.getValue().lastAccessed = timeNow;\r\n                continue;\r\n            }\r\n            saveThenExpire(entry.getKey(), entry.getValue());\r\n        }\r\n    }\r\n\r\n    public static void saveThenExpire(UUID id, CachedPlayerFlag cache) {\r\n        if (saveOnlyWhenWorldSaveOn && !Bukkit.getWorlds().get(0).isAutoSave()) {\r\n            return;\r\n        }\r\n        BukkitRunnable expireTask = new BukkitRunnable() {\r\n            @Override\r\n            public void run() {\r\n                if (cache.shouldExpire()) {\r\n                    playerFlagTrackerCache.remove(id);\r\n                    secondaryPlayerFlagTrackerCache.put(id, new SoftReference<>(cache));\r\n                }\r\n            }\r\n        };\r\n        if (cache.savingNow.get() || cache.loadingNow.get()) {\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    CachedPlayerFlag newCache = playerFlagTrackerCache.get(id);\r\n                    if (newCache != null) {\r\n                        saveThenExpire(id, newCache);\r\n                    }\r\n                }\r\n            }.runTaskLater(Denizen.getInstance(), 10);\r\n            return;\r\n        }\r\n        if (!cache.tracker.modified) {\r\n            expireTask.runTaskLater(Denizen.getInstance(), 1);\r\n            return;\r\n        }\r\n        cache.tracker.modified = false;\r\n        String text = cache.tracker.toString();\r\n        cache.savingNow.set(true);\r\n        new BukkitRunnable() {\r\n            @Override\r\n            public void run() {\r\n                try {\r\n                    saveFlags(id, text);\r\n                }\r\n                catch (Throwable ex) {\r\n                    Debug.echoError(ex);\r\n                }\r\n                cache.savingNow.set(false);\r\n                expireTask.runTaskLater(Denizen.getInstance(), 1);\r\n            }\r\n        }.runTaskAsynchronously(Denizen.getInstance());\r\n    }\r\n\r\n    public static void loadFlags(UUID id, CachedPlayerFlag cache) {\r\n        try {\r\n            cache.tracker = SavableMapFlagTracker.loadFlagFile(new File(dataFolder, id.toString()).getPath(), false);\r\n        }\r\n        finally {\r\n            cache.loadingNow.set(false);\r\n        }\r\n    }\r\n\r\n    public static AbstractFlagTracker getTrackerFor(UUID id) {\r\n        CachedPlayerFlag cache = playerFlagTrackerCache.get(id);\r\n        if (cache == null) {\r\n            SoftReference<CachedPlayerFlag> softRef = secondaryPlayerFlagTrackerCache.get(id);\r\n            if (softRef != null) {\r\n                cache = softRef.get();\r\n                if (cache != null) {\r\n                    cache.lastAccessed = CoreUtilities.monotonicMillis();\r\n                    if (CoreConfiguration.debugVerbose) {\r\n                        Debug.echoError(\"Verbose - (getTrackerFor) flag tracker updated from soft to main for \" + id);\r\n                    }\r\n                    playerFlagTrackerCache.put(id, cache);\r\n                    secondaryPlayerFlagTrackerCache.remove(id);\r\n                    return cache.tracker;\r\n                }\r\n            }\r\n            cache = new CachedPlayerFlag();\r\n            cache.lastAccessed = CoreUtilities.monotonicMillis();\r\n            cache.loadingNow.set(true);\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.echoError(\"Verbose - (getTrackerFor) flag tracker created for \" + id);\r\n            }\r\n            playerFlagTrackerCache.put(id, cache);\r\n            loadFlags(id, cache);\r\n            if (cache.tracker != null && !CoreConfiguration.skipAllFlagCleanings) {\r\n                cache.tracker.doTotalClean();\r\n            }\r\n        }\r\n        else {\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.echoError(\"Verbose - (getTrackerFor) flag tracker was cached for \" + id);\r\n            }\r\n            if (cache.loadingNow.get()) {\r\n                long start = CoreUtilities.monotonicMillis();\r\n                while (cache.loadingNow.get()) {\r\n                    if (CoreConfiguration.debugVerbose) {\r\n                        Debug.echoError(\"Verbose - (getTrackerFor) flag tracker is loading, so waiting, for \" + id + \" ... at \" + (CoreUtilities.monotonicMillis() - start) + \"ms\");\r\n                    }\r\n                    if (CoreUtilities.monotonicMillis() - start > 15 * 1000) {\r\n                        Debug.echoError(\"Flag loading timeout, errors may follow\");\r\n                        playerFlagTrackerCache.remove(id);\r\n                        return null;\r\n                    }\r\n                    try {\r\n                        Thread.sleep(1);\r\n                    }\r\n                    catch (InterruptedException ex) {\r\n                        Debug.echoError(ex);\r\n                        return cache.tracker;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        return cache.tracker;\r\n    }\r\n\r\n    public static Future loadAsync(UUID id) { // Note: this method is called sync, but triggers an async load\r\n        try {\r\n            CachedPlayerFlag cache = playerFlagTrackerCache.get(id);\r\n            if (cache != null) {\r\n                if (CoreConfiguration.debugVerbose) {\r\n                    Debug.echoError(\"Verbose - (loadAsync) flag tracker ignored due to cache for \" + id);\r\n                }\r\n                return null;\r\n            }\r\n            SoftReference<CachedPlayerFlag> softRef = secondaryPlayerFlagTrackerCache.get(id);\r\n            if (softRef != null) {\r\n                cache = softRef.get();\r\n                if (cache != null) {\r\n                    cache.lastAccessed = CoreUtilities.monotonicMillis();\r\n                    if (CoreConfiguration.debugVerbose) {\r\n                        Debug.echoError(\"Verbose - (loadAsync) flag tracker updated from softref to main for \" + id);\r\n                    }\r\n                    playerFlagTrackerCache.put(id, cache);\r\n                    secondaryPlayerFlagTrackerCache.remove(id);\r\n                    return null;\r\n                }\r\n            }\r\n            CachedPlayerFlag newCache = new CachedPlayerFlag();\r\n            newCache.lastAccessed = CoreUtilities.monotonicMillis();\r\n            newCache.loadingNow.set(true);\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.echoError(\"Verbose - (loadAsync) flag tracker created \" + id);\r\n            }\r\n            playerFlagTrackerCache.put(id, newCache);\r\n            CompletableFuture future = new CompletableFuture();\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    loadFlags(id, newCache);\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.instance, () -> {\r\n                        if (CoreConfiguration.debugVerbose) {\r\n                            Debug.echoError(\"Verbose - flag tracker async loaded \" + id);\r\n                        }\r\n                        if (newCache.tracker != null && !CoreConfiguration.skipAllFlagCleanings) {\r\n                            newCache.tracker.doTotalClean();\r\n                        }\r\n                    });\r\n                    future.complete(null);\r\n                }\r\n            }.runTaskAsynchronously(Denizen.getInstance());\r\n            return future;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static void saveAllNow(boolean lockUntilDone) {\r\n        for (Map.Entry<UUID, CachedPlayerFlag> entry : playerFlagTrackerCache.entrySet()) {\r\n            CachedPlayerFlag flags = entry.getValue();\r\n            if (flags.tracker.modified) {\r\n                if (!lockUntilDone && flags.savingNow.get() || flags.loadingNow.get()) {\r\n                    continue;\r\n                }\r\n                while (flags.savingNow.get() || flags.loadingNow.get()) {\r\n                    try {\r\n                        Thread.sleep(10);\r\n                    }\r\n                    catch (InterruptedException ex) {\r\n                        Debug.echoError(ex);\r\n                    }\r\n                }\r\n                flags.savingNow.set(true);\r\n                flags.tracker.modified = false;\r\n                final UUID id = entry.getKey();\r\n                final String data = flags.tracker.toString();\r\n                Runnable doSave = () -> {\r\n                    saveFlags(id, data);\r\n                    flags.savingNow.set(false);\r\n                };\r\n                if (lockUntilDone) {\r\n                    doSave.run();\r\n                }\r\n                else {\r\n                    DenizenCore.runAsync(doSave);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void saveFlags(UUID id, String flagData) {\r\n        CoreUtilities.journallingFileSave(new File(dataFolder, id.toString() + \".dat\").getPath(), flagData);\r\n    }\r\n\r\n    @EventHandler\r\n    public void onPlayerLogin(AsyncPlayerPreLoginEvent event) {\r\n        if (!asyncPreload) {\r\n            return;\r\n        }\r\n        if (!Denizen.hasTickedOnce) {\r\n            return;\r\n        }\r\n        UUID id = event.getUniqueId();\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            Future<Future> future = Bukkit.getScheduler().callSyncMethod(Denizen.getInstance(), () -> {\r\n                return loadAsync(id);\r\n            });\r\n            try {\r\n                Future newFuture = future.get(15, TimeUnit.SECONDS);\r\n                if (newFuture != null) {\r\n                    newFuture.get(15, TimeUnit.SECONDS);\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void reloadAllFlagsNow() {\r\n        playerFlagTrackerCache.clear();\r\n        secondaryPlayerFlagTrackerCache.clear();\r\n        for (Player player : Bukkit.getOnlinePlayers()) {\r\n            getTrackerFor(player.getUniqueId());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/flags/WorldFlagHandler.java",
    "content": "package com.denizenscript.denizen.utilities.flags;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizencore.flags.SavableMapFlagTracker;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.World;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.WorldLoadEvent;\r\nimport org.bukkit.event.world.WorldUnloadEvent;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\npublic class WorldFlagHandler implements Listener {\r\n\r\n    public static HashMap<String, SavableMapFlagTracker> worldFlagTrackers = new HashMap<>();\r\n\r\n    public WorldFlagHandler() {\r\n        Bukkit.getPluginManager().registerEvents(this, Denizen.getInstance());\r\n    }\r\n\r\n    public void init() {\r\n        for (World world : Bukkit.getWorlds()) {\r\n            loadWorldFlags(world);\r\n        }\r\n    }\r\n\r\n    public void saveAll(boolean lockUntilDone) {\r\n        for (Map.Entry<String, SavableMapFlagTracker> flagTracker : worldFlagTrackers.entrySet()) {\r\n            if (flagTracker.getValue().modified) {\r\n                flagTracker.getValue().saveToFile(flagPathFor(flagTracker.getKey()), lockUntilDone);\r\n                flagTracker.getValue().modified = false;\r\n            }\r\n        }\r\n    }\r\n\r\n    public void shutdown() {\r\n        saveAll(true);\r\n        worldFlagTrackers.clear();\r\n    }\r\n\r\n    public static String flagPathFor(String worldName) {\r\n        return Bukkit.getWorldContainer().getPath() + \"/\" + worldName + \"/denizen_flags\";\r\n    }\r\n\r\n    public static void loadWorldFlags(World world) {\r\n        if (worldFlagTrackers.containsKey(world.getName())) {\r\n            return;\r\n        }\r\n        worldFlagTrackers.put(world.getName(), SavableMapFlagTracker.loadFlagFile(flagPathFor(world.getName()), true));\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\r\n    public void onWorldLoad(WorldLoadEvent event) {\r\n        loadWorldFlags(event.getWorld());\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)\r\n    public void onWorldUnload(WorldUnloadEvent event) {\r\n        SavableMapFlagTracker flags = worldFlagTrackers.remove(event.getWorld().getName());\r\n        if (flags != null && flags.modified) {\r\n            flags.modified = false;\r\n            flags.saveToFile(flagPathFor(event.getWorld().getName()), true);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/implementation/BukkitScriptEntryData.java",
    "content": "package com.denizenscript.denizen.utilities.implementation;\r\n\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.YamlConfiguration;\r\nimport org.bukkit.entity.Entity;\r\n\r\npublic class BukkitScriptEntryData extends ScriptEntryData {\r\n    private PlayerTag player;\r\n    private NPCTag npc;\r\n\r\n    public BukkitScriptEntryData(PlayerTag player, NPCTag npc) {\r\n        this.player = player;\r\n        this.npc = npc;\r\n    }\r\n\r\n    public BukkitScriptEntryData(EntityTag entity) {\r\n        if (entity == null) {\r\n            return;\r\n        }\r\n        if (entity.isCitizensNPC()) {\r\n            this.npc = entity.getDenizenNPC();\r\n        }\r\n        if (entity.isPlayer()) {\r\n            this.player = entity.getDenizenPlayer();\r\n        }\r\n    }\r\n\r\n    public BukkitScriptEntryData(Entity entity) {\r\n        this(entity == null ? null : new EntityTag(entity));\r\n    }\r\n\r\n    public PlayerTag getPlayer() {\r\n        return player;\r\n    }\r\n\r\n    public NPCTag getNPC() {\r\n        return npc != null && npc.getCitizen() != null ? npc : null;\r\n    }\r\n\r\n    public boolean hasNPC() {\r\n        return npc != null && npc.getCitizen() != null;\r\n    }\r\n\r\n    public boolean hasPlayer() {\r\n        return player != null;\r\n    }\r\n\r\n    public void setPlayer(PlayerTag player) {\r\n        this.player = player;\r\n    }\r\n\r\n    public void setNPC(NPCTag npc) {\r\n        this.npc = npc;\r\n    }\r\n\r\n    @Override\r\n    public void transferDataFrom(ScriptEntryData scriptEntryData) {\r\n        if (scriptEntryData == null) {\r\n            return;\r\n        }\r\n        player = ((BukkitScriptEntryData) scriptEntryData).getPlayer();\r\n        npc = ((BukkitScriptEntryData) scriptEntryData).getNPC();\r\n        scriptEntry = scriptEntryData.scriptEntry;\r\n\r\n    }\r\n\r\n    @Override\r\n    public TagContext getTagContext() {\r\n        return new BukkitTagContext(player, npc, scriptEntry,\r\n                scriptEntry == null || scriptEntry.shouldDebug(),\r\n                scriptEntry != null ? scriptEntry.getScript() : null);\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        if (npc == null && player == null) {\r\n            return \"\";\r\n        }\r\n        else if (npc == null) {\r\n            return \"player=p@\" + player.getName();\r\n        }\r\n        else if (player == null) {\r\n            return \"npc=n@\" + npc.getId();\r\n        }\r\n        else {\r\n            return \"player=p@\" + player.getName() + \"   npc=n@\" + npc.getId();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public YamlConfiguration save() {\r\n        YamlConfiguration out = new YamlConfiguration();\r\n        if (hasPlayer()) {\r\n            out.set(\"player\", getPlayer().savable());\r\n        }\r\n        if (hasNPC()) {\r\n            out.set(\"npc\", getNPC().savable());\r\n        }\r\n        return out;\r\n    }\r\n\r\n    @Override\r\n    public void load(YamlConfiguration config) {\r\n        String player = config.getString(\"player\", null);\r\n        if (player != null) {\r\n            setPlayer(PlayerTag.valueOf(player, CoreUtilities.errorButNoDebugContext));\r\n        }\r\n        String npc = config.getString(\"npc\", null);\r\n        if (npc != null) {\r\n            setNPC(NPCTag.valueOf(npc, CoreUtilities.errorButNoDebugContext));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/implementation/DenizenCoreImplementation.java",
    "content": "package com.denizenscript.denizen.utilities.implementation;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.bukkit.ScriptReloadEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.NPCTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.*;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.debugging.DebugConsoleSender;\r\nimport com.denizenscript.denizen.utilities.depends.Depends;\r\nimport com.denizenscript.denizen.utilities.flags.PlayerFlagHandler;\r\nimport com.denizenscript.denizen.utilities.maps.DenizenMapManager;\r\nimport com.denizenscript.denizencore.DenizenImplementation;\r\nimport com.denizenscript.denizencore.flags.FlaggableObject;\r\nimport com.denizenscript.denizencore.objects.Argument;\r\nimport com.denizenscript.denizencore.objects.ObjectFetcher;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.VectorObject;\r\nimport com.denizenscript.denizencore.objects.notable.Notable;\r\nimport com.denizenscript.denizencore.objects.notable.NoteManager;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntry;\r\nimport com.denizenscript.denizencore.scripts.ScriptEntryData;\r\nimport com.denizenscript.denizencore.scripts.containers.ScriptContainer;\r\nimport com.denizenscript.denizencore.scripts.queues.ScriptQueue;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.DefinitionProvider;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.debugging.StrongWarning;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\n\r\nimport java.io.File;\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\n\r\npublic class DenizenCoreImplementation implements DenizenImplementation {\r\n\r\n    @Override\r\n    public File getScriptFolder() { // Note: can be called async\r\n        File file;\r\n        // Get the script directory\r\n        if (Settings.useDefaultScriptPath()) {\r\n            file = new File(Denizen.getInstance().getDataFolder() + File.separator + \"scripts\");\r\n        }\r\n        else {\r\n            file = new File(Settings.getAlternateScriptPath().replace(\"/\", File.separator));\r\n        }\r\n        return file;\r\n    }\r\n\r\n    @Override\r\n    public String getImplementationVersion() {\r\n        return Denizen.versionTag;\r\n    }\r\n\r\n    @Override\r\n    public String getImplementationName() {\r\n        return \"Spigot\";\r\n    }\r\n\r\n    @Override\r\n    public void preScriptReload() {\r\n        // Remove all recipes added by Denizen item scripts\r\n        ItemScriptHelper.removeDenizenRecipes();\r\n        // Remove all registered commands added by Denizen command scripts\r\n        CommandScriptHelper.removeDenizenCommands();\r\n        // Remove all registered economy scripts if needed\r\n        if (Depends.vault != null) {\r\n            EconomyScriptContainer.cleanup();\r\n        }\r\n        // Give map image downloads a new chance\r\n        DenizenMapManager.failedUrls.clear();\r\n    }\r\n\r\n    @Override\r\n    public void onScriptReload() {\r\n        Depends.setupEconomy();\r\n        Bukkit.getServer().getPluginManager().callEvent(new ScriptReloadEvent());\r\n    }\r\n\r\n    @Override\r\n    public String queueHeaderInfo(ScriptEntry scriptEntry) {\r\n        BukkitScriptEntryData data = ((BukkitScriptEntryData) scriptEntry.entryData);\r\n        if (data.hasPlayer() && data.hasNPC()) {\r\n            return \" with player '\" + data.getPlayer().getName() + \"' and NPC '\" + data.getNPC().getId() + \"/\" + data.getNPC().getName() + \"'\";\r\n        }\r\n        else if (data.hasPlayer()) {\r\n            return \" with player '\" + data.getPlayer().getName() + \"'\";\r\n        }\r\n        else if (data.hasNPC()) {\r\n            return \" with NPC '\" + data.getNPC().getId() + \"/\" + data.getNPC().getName() + \"'\";\r\n        }\r\n        return \"\";\r\n    }\r\n\r\n    @Override\r\n    public boolean needsHandleArgPrefix(String prefix) {\r\n        return prefix.equals(\"player\") || prefix.equals(\"npc\");\r\n    }\r\n\r\n    // <--[language]\r\n    // @name The Player and NPC Arguments\r\n    // @group Script Command System\r\n    // @description\r\n    // The \"player:<player>\" and \"npc:<npc>\" arguments are special meta-arguments that are available for all commands, but are only useful for some.\r\n    // They are written like:\r\n    // - give stick player:<server.flag[some_player]>\r\n    // or:\r\n    // - sit npc:<entry[save].created_npc>\r\n    //\r\n    // Denizen tracks a \"linked player\" and a \"linked NPC\" in queues and the commands within.\r\n    // Many commands automatically operate on the linked player/NPC default or exclusively\r\n    // (for example, \"give\" defaults to giving items to the linked player but that can be changed with the \"to\" argument,\r\n    // \"sit\" exclusively makes the linked NPC sit, and that cannot be changed except by the global NPC argument).\r\n    //\r\n    // When the player argument is used, it sets the linked player for the specific command it's on.\r\n    // This is only useful for commands that default to operating on the linked player.\r\n    // This can also be useful with the \"run\" command to link a specific player to the new queue.\r\n    //\r\n    // The NPC argument is essentially equivalent to the player argument, but for the linked NPC instead of the linked player.\r\n    //\r\n    // These arguments will also affect tags (mainly \"<player>\" and \"<npc>\") in the same command line (regardless of argument order).\r\n    // If you need to use the original player/NPC in a tag on the same line, use the define command to track it.\r\n    //\r\n    // You can also modify the linked player or NPC for an entire queue using the fake-definitions '__player' and '__npc', for example:\r\n    // <code>\r\n    // - foreach <server.players> as:__player:\r\n    //     - narrate \"Hi <player.name> isn't it nice how the player is linked here\"\r\n    // </code>\r\n    //\r\n    // -->\r\n\r\n    public static StrongWarning invalidPlayerArg = new StrongWarning(\"invalidPlayerArg\", \"The 'player:' arg should not be used in commands like define/flag/yaml/... just input the player directly instead.\");\r\n    public static StrongWarning invalidNpcArg = new StrongWarning(\"invalidNpcArg\", \"The 'npc:' arg should not be used in commands like define/flag/yaml/... just input the npc directly instead.\");\r\n    public static HashSet<String> invalidPlayerArgCommands = new HashSet<>(Arrays.asList(\"DEFINE\", \"FLAG\", \"YAML\"));\r\n\r\n    @Override\r\n    public boolean handleCustomArgs(ScriptEntry scriptEntry, Argument arg) {\r\n        if (arg.matchesPrefix(\"player\")) {\r\n            if (invalidPlayerArgCommands.contains(scriptEntry.getCommandName())) {\r\n                invalidPlayerArg.warn(scriptEntry);\r\n            }\r\n            Debug.echoDebug(scriptEntry, \"...replacing the linked player with \" + arg.getValue());\r\n            String value = TagManager.tag(arg.getValue(), scriptEntry.getContext());\r\n            PlayerTag player = PlayerTag.valueOf(value, scriptEntry.context);\r\n            if (player == null || !player.isValid()) {\r\n                Debug.echoError(scriptEntry, value + \" is an invalid player!\");\r\n            }\r\n            ((BukkitScriptEntryData) scriptEntry.entryData).setPlayer(player);\r\n            ((BukkitTagContext) scriptEntry.context).player = player;\r\n            return true;\r\n        }\r\n        else if (arg.matchesPrefix(\"npc\")) {\r\n            if (invalidPlayerArgCommands.contains(scriptEntry.getCommandName())) {\r\n                invalidNpcArg.warn(scriptEntry);\r\n            }\r\n            Debug.echoDebug(scriptEntry, \"...replacing the linked NPC with \" + arg.getValue());\r\n            String value = TagManager.tag(arg.getValue(), scriptEntry.getContext());\r\n            NPCTag npc = NPCTag.valueOf(value, scriptEntry.context);\r\n            if (npc == null || !npc.isValid()) {\r\n                Debug.echoError(scriptEntry, value + \" is an invalid NPC!\");\r\n                return false;\r\n            }\r\n            ((BukkitScriptEntryData) scriptEntry.entryData).setNPC(npc);\r\n            ((BukkitTagContext) scriptEntry.context).npc = npc;\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void refreshScriptContainers() {\r\n        CommandScriptHelper.commandScripts.clear();\r\n        InventoryScriptHelper.inventoryScripts.clear();\r\n        EntityScriptHelper.scripts.clear();\r\n        ItemScriptHelper.item_scripts.clear();\r\n        ItemScriptHelper.item_scripts_by_hash_id.clear();\r\n    }\r\n\r\n    @Override\r\n    public TagContext getTagContext(ScriptContainer container) {\r\n        return new BukkitTagContext(container);\r\n    }\r\n\r\n    @Override\r\n    public TagContext getTagContext(ScriptEntry scriptEntry) {\r\n        return new BukkitTagContext(scriptEntry);\r\n    }\r\n\r\n    @Override\r\n    public ScriptEntryData getEmptyScriptEntryData() {\r\n        return new BukkitScriptEntryData(null, null);\r\n    }\r\n\r\n    @Override\r\n    public String cleanseLogString(String input) {\r\n        return cleanseLog(input);\r\n    }\r\n\r\n    public static String cleanseLog(String input) {\r\n        char esc_char = (char) 0x1b;\r\n        String esc = String.valueOf(esc_char);\r\n        String repc = String.valueOf(ChatColor.COLOR_CHAR);\r\n        if (input.indexOf(esc_char) != -1) {\r\n            input = CoreUtilities.replace(input, esc + \"[0;30;22m\", repc + \"0\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;34;22m\", repc + \"1\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;32;22m\", repc + \"2\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;36;22m\", repc + \"3\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;31;22m\", repc + \"4\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;35;22m\", repc + \"5\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;33;22m\", repc + \"6\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;37;22m\", repc + \"7\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;30;1m\", repc + \"8\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;34;1m\", repc + \"9\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;32;1m\", repc + \"a\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;36;1m\", repc + \"b\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;31;1m\", repc + \"c\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;35;1m\", repc + \"d\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;33;1m\", repc + \"e\");\r\n            input = CoreUtilities.replace(input, esc + \"[0;37;1m\", repc + \"f\");\r\n            input = CoreUtilities.replace(input, esc + \"[5m\", repc + \"k\");\r\n            input = CoreUtilities.replace(input, esc + \"[21m\", repc + \"l\");\r\n            input = CoreUtilities.replace(input, esc + \"[9m\", repc + \"m\");\r\n            input = CoreUtilities.replace(input, esc + \"[4m\", repc + \"n\");\r\n            input = CoreUtilities.replace(input, esc + \"[3m\", repc + \"o\");\r\n            input = CoreUtilities.replace(input, esc + \"[m\", repc + \"r\");\r\n        }\r\n        return input;\r\n    }\r\n\r\n    @Override\r\n    public void preTagExecute() {\r\n        try {\r\n            NMSHandler.instance.disableAsyncCatcher();\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(\"Running not-Spigot?!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void postTagExecute() {\r\n        try {\r\n            NMSHandler.instance.undisableAsyncCatcher();\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(\"Running not-Spigot?!\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean canWriteToFile(File f) {\r\n        return Utilities.canWriteToFile(f);\r\n    }\r\n\r\n    public static ChatColor[] DEBUG_FRIENDLY_COLORS = new ChatColor[] {\r\n            ChatColor.AQUA, ChatColor.BLUE, ChatColor.DARK_AQUA, ChatColor.DARK_BLUE, ChatColor.DARK_GREEN,\r\n            ChatColor.DARK_PURPLE, ChatColor.GOLD, ChatColor.GRAY, ChatColor.GREEN,\r\n            ChatColor.LIGHT_PURPLE, ChatColor.WHITE, ChatColor.YELLOW\r\n    };\r\n\r\n    @Override\r\n    public String getRandomColor() {\r\n        return DEBUG_FRIENDLY_COLORS[CoreUtilities.getRandom().nextInt(DEBUG_FRIENDLY_COLORS.length)].toString();\r\n    }\r\n\r\n    @Override\r\n    public boolean canReadFile(File f) {\r\n        return Utilities.canReadFile(f);\r\n    }\r\n\r\n    @Override\r\n    public File getDataFolder() {\r\n        return Denizen.getInstance().getDataFolder();\r\n    }\r\n\r\n    // <--[extension]\r\n    // @name Flag System Extension\r\n    // @target_type language\r\n    // @target_name Flag System\r\n    // @description\r\n    // ItemTags, rather than using the flag command, are primarily flagged via <@link command inventory> with the 'flag' argument, or via <@link tag ItemTag.with_flag>, or via the item script container.\r\n    //\r\n    // Additionally, flags be searched for with tags like <@link tag server.online_players_flagged>, <@link tag server.players_flagged>, <@link tag server.spawned_npcs_flagged>, <@link tag server.npcs_flagged>, ...\r\n    // Flags can also be required by script event lines, as explained at <@link language Script Event Switches>.\r\n    // Item flags can also be used as a requirement in <@link command take>.\r\n    //\r\n    // Note that some internal flags exist, and are prefixed with '__' to avoid conflict with normal user flags.\r\n    // This includes:\r\n    // - '__raw' and '__clear' which are part of a fake-flag system used for forcibly setting raw data to a flaggable object,\r\n    // - '__scripts', '__time', etc. which is where some object-type flags are stored inside of server flags,\r\n    // - '__interact_step' which is used for interact script steps, related to <@link command zap>,\r\n    // - '__interact_cooldown' which is used for interact script cooldowns, related to <@link command cooldown>.\r\n    //\r\n    // -->\r\n\r\n    // <--[extension]\r\n    // @name Flag Command Extension\r\n    // @target_type command\r\n    // @target_name Flag\r\n    // @Tags\r\n    // <server.online_players_flagged[<flag_name>]>\r\n    // <server.players_flagged[<flag_name>]>\r\n    // <server.spawned_npcs_flagged[<flag_name>]>\r\n    // <server.npcs_flagged[<flag_name>]>\r\n    // -->\r\n\r\n    @Override\r\n    public FlaggableObject simpleWordToFlaggable(String word, ScriptEntry entry) {\r\n        if (CoreUtilities.equalsIgnoreCase(word, \"player\")) {\r\n            return Utilities.getEntryPlayer(entry);\r\n        }\r\n        if (CoreUtilities.equalsIgnoreCase(word, \"npc\")) {\r\n            return Utilities.getEntryNPC(entry);\r\n        }\r\n        if (!word.contains(\"@\")) {\r\n            Notable noted = NoteManager.getSavedObject(word);\r\n            if (noted instanceof LocationTag) {\r\n                return (LocationTag) noted;\r\n            }\r\n        }\r\n        ObjectTag obj = ObjectFetcher.pickObjectFor(word, entry.context);\r\n        if (obj instanceof FlaggableObject) {\r\n            return (FlaggableObject) obj;\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static BukkitScriptEntryData getScriptEntryData(ScriptQueue queue) {\r\n        if (queue.getLastEntryExecuted() != null) {\r\n            return (BukkitScriptEntryData) queue.getLastEntryExecuted().entryData;\r\n        }\r\n        else if (queue.getEntries().size() > 0) {\r\n            return (BukkitScriptEntryData) queue.getEntries().get(0).entryData;\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ObjectTag getSpecialDef(String def, ScriptQueue queue) {\r\n        if (def.equals(\"__player\")) {\r\n            BukkitScriptEntryData data = getScriptEntryData(queue);\r\n            if (data == null) {\r\n                return null;\r\n            }\r\n            return data.getPlayer();\r\n        }\r\n        else if (def.equals(\"__npc\")) {\r\n            BukkitScriptEntryData data = getScriptEntryData(queue);\r\n            if (data == null) {\r\n                return null;\r\n            }\r\n            return data.getNPC();\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public boolean setSpecialDef(String def, ScriptQueue queue, ObjectTag value) {\r\n        if (def.equals(\"__player\")) {\r\n            BukkitScriptEntryData baseData = getScriptEntryData(queue);\r\n            if (baseData == null) {\r\n                return true;\r\n            }\r\n            PlayerTag player = value == null ? null : value.asType(PlayerTag.class, baseData.getTagContext());\r\n            if (queue.getLastEntryExecuted() != null) {\r\n                ((BukkitScriptEntryData) queue.getLastEntryExecuted().entryData).setPlayer(player);\r\n            }\r\n            for (ScriptEntry entry : queue.getEntries()) {\r\n                ((BukkitScriptEntryData) entry.entryData).setPlayer(player);\r\n            }\r\n            return true;\r\n        }\r\n        else if (def.equals(\"__npc\")) {\r\n            BukkitScriptEntryData baseData = getScriptEntryData(queue);\r\n            if (baseData == null) {\r\n                return true;\r\n            }\r\n            NPCTag npc = value == null ? null : value.asType(NPCTag.class, baseData.getTagContext());\r\n            if (queue.getLastEntryExecuted() != null) {\r\n                ((BukkitScriptEntryData) queue.getLastEntryExecuted().entryData).setNPC(npc);\r\n            }\r\n            for (ScriptEntry entry : queue.getEntries()) {\r\n                ((BukkitScriptEntryData) entry.entryData).setNPC(npc);\r\n            }\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void addExtraErrorHeaders(StringBuilder headerBuilder, ScriptEntry source) {\r\n        BukkitScriptEntryData data = Utilities.getEntryData(source);\r\n        if (data.hasPlayer()) {\r\n            headerBuilder.append(\" with player '<A>\").append(data.getPlayer().getName()).append(\"<LR>'\");\r\n        }\r\n        if (data.hasNPC()) {\r\n            headerBuilder.append(\" with NPC '<A>\").append(data.getNPC().debuggable()).append(\"<LR>'\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String applyDebugColors(String uncolored) {\r\n        if (!CoreUtilities.contains(uncolored, '<')) {\r\n            return uncolored;\r\n        }\r\n        return uncolored\r\n                .replace(\"<Y>\", ChatColor.YELLOW.toString())\r\n                .replace(\"<O>\", ChatColor.GOLD.toString()) // 'orange'\r\n                .replace(\"<G>\", ChatColor.DARK_GRAY.toString())\r\n                .replace(\"<LG>\", ChatColor.GRAY.toString())\r\n                .replace(\"<GR>\", ChatColor.GREEN.toString())\r\n                .replace(\"<A>\", ChatColor.AQUA.toString())\r\n                .replace(\"<R>\", ChatColor.DARK_RED.toString())\r\n                .replace(\"<LR>\", ChatColor.RED.toString())\r\n                .replace(\"<LP>\", ChatColor.LIGHT_PURPLE.toString())\r\n                .replace(\"<W>\", ChatColor.WHITE.toString());\r\n    }\r\n\r\n    @Override\r\n    public void doFinalDebugOutput(String rawText) {\r\n        DebugConsoleSender.sendMessage(rawText);\r\n    }\r\n\r\n    // <--[extension]\r\n    // @name Format Script Definitions\r\n    // @target_type language\r\n    // @target_name Format Script Containers\r\n    // @description\r\n    // '<[name]>' is available as a special def as well for use with the 'on player chats' event to fill the player's name properly.\r\n    // Note that 'special' means special: these tags behave a little funny in certain circumstances.\r\n    // In particular, these can't be used as real tags in some cases, including for example when determining a format script in the 'player chats' event.\r\n    // -->\r\n    @Override\r\n    public void addFormatScriptDefinitions(DefinitionProvider definitionProvider, TagContext context) {\r\n        BukkitTagContext bukkitContext = (BukkitTagContext) context;\r\n        String name = null;\r\n        if (bukkitContext.npc != null) {\r\n            name = bukkitContext.npc.getName();\r\n        }\r\n        else if (bukkitContext.player != null) {\r\n            name = bukkitContext.player.getName();\r\n        }\r\n        if (name != null) {\r\n            definitionProvider.addDefinition(\"name\", name);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String stripColor(String text) {\r\n        return ChatColor.stripColor(text);\r\n    }\r\n\r\n    @Override\r\n    public void reloadConfig() {\r\n        Denizen.getInstance().reloadConfig();\r\n    }\r\n\r\n    @Override\r\n    public void reloadSaves() {\r\n        Denizen.getInstance().reloadSaves();\r\n        PlayerFlagHandler.reloadAllFlagsNow();\r\n    }\r\n\r\n    @Override\r\n    public VectorObject getVector(double x, double y, double z) {\r\n        return new LocationTag(null, x, y, z);\r\n    }\r\n\r\n    @Override\r\n    public VectorObject vectorize(ObjectTag obj, TagContext context) {\r\n        return obj.asType(LocationTag.class, context);\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/inventory/InventoryTrackerSystem.java",
    "content": "package com.denizenscript.denizen.utilities.inventory;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.InventoryTag;\r\nimport com.denizenscript.denizen.scripts.containers.core.InventoryScriptHelper;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.inventory.InventoryCloseEvent;\r\nimport org.bukkit.event.inventory.InventoryOpenEvent;\r\nimport org.bukkit.inventory.Inventory;\r\n\r\nimport java.util.HashMap;\r\n\r\npublic class InventoryTrackerSystem implements Listener {\r\n\r\n    public static HashMap<Long, InventoryTag> idTrackedInventories = new HashMap<>(512);\r\n\r\n    public static long temporaryInventoryIdCounter = 0;\r\n\r\n    public static HashMap<Inventory, InventoryTag> temporaryInventoryLinks = new HashMap<>(512);\r\n\r\n    public static HashMap<Inventory, InventoryTag> retainedInventoryLinks = new HashMap<>(512);\r\n\r\n    public static InventoryTag getTagFormFor(Inventory inventory) {\r\n        if (inventory == null) {\r\n            return null;\r\n        }\r\n        InventoryTag result = temporaryInventoryLinks.get(inventory);\r\n        if (result != null) {\r\n            return result;\r\n        }\r\n        return retainedInventoryLinks.get(inventory);\r\n    }\r\n\r\n    public static boolean isGenericTrackable(InventoryTag tagForm) {\r\n        if (tagForm == null || tagForm.getIdType() == null) {\r\n            return false;\r\n        }\r\n        return tagForm.getIdType().equals(\"generic\") || tagForm.getIdType().equals(\"script\");\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.MONITOR)\r\n    public void onPlayerOpensInventory(InventoryOpenEvent event) {\r\n        if (event.isCancelled()) {\r\n            return;\r\n        }\r\n        InventoryTag tagForm = getTagFormFor(event.getInventory());\r\n        if (isGenericTrackable(tagForm)) {\r\n            trackTemporaryInventory(event.getInventory(), tagForm);\r\n            retainedInventoryLinks.put(event.getInventory(), tagForm);\r\n        }\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.MONITOR)\r\n    public void onPlayerCloseInventory(InventoryCloseEvent event) {\r\n        Inventory inv = event.getInventory();\r\n        Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> {\r\n            if (inv.getViewers().isEmpty()) {\r\n                InventoryTag removed = retainedInventoryLinks.remove(inv);\r\n                if (removed != null && removed.uniquifier != null) {\r\n                    idTrackedInventories.remove(removed.uniquifier);\r\n                    temporaryInventoryLinks.put(inv, removed);\r\n                }\r\n            }\r\n        }, 1);\r\n    }\r\n\r\n    public static void trackTemporaryInventory(Inventory inventory, InventoryTag tagForm) {\r\n        if (inventory == null || tagForm == null) {\r\n            return;\r\n        }\r\n        if (!isGenericTrackable(tagForm)) {\r\n            return;\r\n        }\r\n        if (InventoryScriptHelper.notedInventories.containsKey(inventory)) {\r\n            return;\r\n        }\r\n        if (tagForm.uniquifier == null) {\r\n            tagForm.uniquifier = temporaryInventoryIdCounter++;\r\n        }\r\n        if (!idTrackedInventories.containsKey(tagForm.uniquifier)) {\r\n            idTrackedInventories.put(tagForm.uniquifier, tagForm);\r\n            temporaryInventoryLinks.put(inventory, tagForm);\r\n        }\r\n    }\r\n\r\n    public static void setup() {\r\n        Bukkit.getScheduler().scheduleSyncRepeatingTask(Denizen.getInstance(), () -> {\r\n            if (idTrackedInventories.size() > 300) {\r\n                idTrackedInventories.clear();\r\n                for (InventoryTag temp : temporaryInventoryLinks.values()) {\r\n                    idTrackedInventories.put(temp.uniquifier, temp);\r\n                }\r\n                for (InventoryTag retained : retainedInventoryLinks.values()) {\r\n                    idTrackedInventories.put(retained.uniquifier, retained);\r\n                    temporaryInventoryLinks.put(retained.getInventory(), retained);\r\n                }\r\n            }\r\n            InventoryTrackerSystem.temporaryInventoryLinks.clear();\r\n        }, 20, 20);\r\n        Bukkit.getPluginManager().registerEvents(new InventoryTrackerSystem(), Denizen.getInstance());\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/inventory/InventoryViewUtil.java",
    "content": "package com.denizenscript.denizen.utilities.inventory;\n\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport org.bukkit.entity.HumanEntity;\nimport org.bukkit.event.inventory.InventoryType;\nimport org.bukkit.inventory.Inventory;\nimport org.bukkit.inventory.InventoryView;\nimport org.bukkit.inventory.ItemStack;\n\nimport java.lang.invoke.MethodHandle;\n\npublic class InventoryViewUtil {\n\n    private static <T> T get(MethodHandle methodHandle, InventoryView view) {\n        try {\n            return (T) methodHandle.invoke(view);\n        }\n        catch (Throwable e) {\n            Debug.echoError(e);\n            return null;\n        }\n    }\n\n    private static <T> T get(MethodHandle methodHandle, InventoryView view, int num) {\n        try {\n            return (T) methodHandle.invoke(view, num);\n        }\n        catch (Throwable e) {\n            Debug.echoError(e);\n            return null;\n        }\n    }\n\n    private static final MethodHandle GET_TOP_INVENTORY = ReflectionHelper.getMethodHandle(InventoryView.class, \"getTopInventory\");\n\n    public static Inventory getTopInventory(InventoryView view) {\n        return get(GET_TOP_INVENTORY, view);\n    }\n\n    private static final MethodHandle GET_PLAYER = ReflectionHelper.getMethodHandle(InventoryView.class, \"getPlayer\");\n\n    public static HumanEntity getPlayer(InventoryView view) {\n        return get(GET_PLAYER, view);\n    }\n\n    private static final MethodHandle GET_TYPE = ReflectionHelper.getMethodHandle(InventoryView.class, \"getType\");\n\n    public static InventoryType getType(InventoryView view) {\n        return get(GET_TYPE, view);\n    }\n\n    private static final MethodHandle GET_ITEM = ReflectionHelper.getMethodHandle(InventoryView.class, \"getItem\", int.class);\n\n    public static ItemStack getItem(InventoryView view, int slot) {\n        return get(GET_ITEM, view, slot);\n    }\n\n    private static final MethodHandle GET_INVENTORY = ReflectionHelper.getMethodHandle(InventoryView.class, \"getInventory\", int.class);\n\n    public static Inventory getInventory(InventoryView view, int slot) {\n        return get(GET_INVENTORY, view, slot);\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/inventory/RecipeHelper.java",
    "content": "package com.denizenscript.denizen.utilities.inventory;\r\n\r\nimport org.bukkit.Material;\r\nimport org.bukkit.inventory.*;\r\n\r\nimport java.util.*;\r\n\r\npublic class RecipeHelper {\r\n\r\n    public static class ShapeHelper {\r\n        public ShapeHelper(ShapedRecipe recipe) {\r\n            choiceMap = recipe.getChoiceMap();\r\n            shapeText = recipe.getShape();\r\n            int len = shapeText.length;\r\n            while (len > 0 && shapeText[len - 1].isEmpty()) {\r\n                len--;\r\n            }\r\n            while (len > 0 && shapeText[0].isEmpty()) {\r\n                shapeText = Arrays.copyOfRange(shapeText, 1, len);\r\n                len--;\r\n            }\r\n            height = len;\r\n            width = 0;\r\n            for (int i = 0; i < len; i++) {\r\n                width = Math.max(width, shapeText[i].length());\r\n            }\r\n            if (height <= 0 || width <= 0) {\r\n                throw new RuntimeException(\"ShapedRecipe malformed.\");\r\n            }\r\n        }\r\n\r\n        public int width;\r\n        public int height;\r\n        public String[] shapeText;\r\n        public Map<Character, RecipeChoice> choiceMap;\r\n    }\r\n\r\n    public static int tryRecipeMatch(ItemStack[] matrix, int matrixWidth, ShapeHelper shape, int offsetX, int offsetY) {\r\n        int max = 64;\r\n        for (int shapeX = 0; shapeX < shape.width; shapeX++) {\r\n            for (int shapeY = 0; shapeY < shape.height; shapeY++) {\r\n                if (shape.shapeText[shapeY].length() <= shapeX) {\r\n                    continue;\r\n                }\r\n                int x = offsetX + shapeX;\r\n                int y = offsetY + shapeY;\r\n                int matrixIndex = x + y * matrixWidth;\r\n                ItemStack matrixItem = matrix[matrixIndex];\r\n                RecipeChoice choices = shape.choiceMap.get(shape.shapeText[shapeY].charAt(shapeX));\r\n                if (choices != null) {\r\n                    if (matrixItem == null || !choices.test(matrixItem)) {\r\n                        return 0;\r\n                    }\r\n                    max = Math.min(max, matrixItem.getAmount());\r\n                }\r\n            }\r\n        }\r\n        return max;\r\n    }\r\n\r\n    public static int getShapedQuantity(CraftingInventory inventory, ShapeHelper shape) {\r\n        ItemStack[] matrix = inventory.getMatrix();\r\n        int matrixWidth = matrix.length == 9 ? 3 : 2;\r\n        int canMoveX = matrixWidth - shape.width;\r\n        int canMoveY = matrixWidth - shape.height;\r\n        for (int offsetX = 0; offsetX <= canMoveX; offsetX++) {\r\n            for (int offsetY = 0; offsetY <= canMoveY; offsetY++) {\r\n                int result = tryRecipeMatch(matrix, matrixWidth, shape, offsetX, offsetY);\r\n                if (result > 0) {\r\n                    return result;\r\n                }\r\n            }\r\n        }\r\n        return 0;\r\n    }\r\n\r\n    public static boolean tryRemoveSingle(List<ItemStack> items, List<RecipeChoice> choices) {\r\n        HashSet<Integer> used = new HashSet<>();\r\n        mainLoop:\r\n        for (RecipeChoice choice : choices) {\r\n            for (int i = 0; i < items.size(); i++) {\r\n                ItemStack item = items.get(i);\r\n                if (choice.test(item) && !used.contains(i)) {\r\n                    used.add(i);\r\n                    if (item.getAmount() == 1) {\r\n                        items.remove(i);\r\n                    }\r\n                    else {\r\n                        item.setAmount(item.getAmount() - 1);\r\n                    }\r\n                    continue mainLoop;\r\n                }\r\n            }\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    public static int getShapelessQuantity(CraftingInventory inventory, ShapelessRecipe recipe) {\r\n        List<ItemStack> items = new ArrayList<>();\r\n        for (ItemStack item : inventory.getMatrix()) {\r\n            if (item != null && item.getType() != Material.AIR) {\r\n                items.add(item.clone());\r\n            }\r\n        }\r\n        int amount = 0;\r\n        while (tryRemoveSingle(items, recipe.getChoiceList())) {\r\n            amount++;\r\n        }\r\n        return amount;\r\n    }\r\n\r\n    public static int getMaximumOutputQuantity(Recipe recipe, CraftingInventory inventory) {\r\n        if (recipe instanceof ShapedRecipe) {\r\n            return getShapedQuantity(inventory, new ShapeHelper((ShapedRecipe) recipe));\r\n        }\r\n        else if (recipe instanceof ShapelessRecipe) {\r\n            return getShapelessQuantity(inventory, (ShapelessRecipe) recipe);\r\n        }\r\n        else {\r\n            return 1;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/inventory/SlotHelper.java",
    "content": "package com.denizenscript.denizen.utilities.inventory;\r\n\r\nimport com.denizenscript.denizencore.events.ScriptEvent;\r\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.inventory.InventoryHolder;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.PlayerInventory;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\n\r\n/**\r\n * Helper for player inventory slots.\r\n * <p>\r\n * These values are based on indices within the inventory contents array of a player.\r\n * <p>\r\n * These values are *NOT* equivalent to arbitrary Minecraft \"slot ID\" values, which are commonly mentioned online but have no connection to anything.\r\n * <p>\r\n * All values are Minecraft indices (0-based).\r\n */\r\npublic class SlotHelper {\r\n\r\n    // Player hotbar: 0-8 (9 items counting 0)\r\n    // Player inventory 9-35 (3 rows of 9 items)\r\n\r\n    public static final int BOOTS = 36; // MC ID: 100\r\n\r\n    public static final int LEGGINGS = 37; // MC ID: 101\r\n\r\n    public static final int CHESTPLATE = 38; // MC ID: 102\r\n\r\n    public static final int HELMET = 39; // MC ID: 103\r\n\r\n    public static final int OFFHAND = 40; // MC ID: -106 // wtf mojang? Negative?\r\n\r\n    /**\r\n     * Returns the slot an item in the player's inventory is at (for events like PlayerItemDamageEvent where the exact item is available).\r\n     * Returns -1 if unknown.\r\n     */\r\n    public static int slotForItem(PlayerInventory inventory, ItemStack item) {\r\n        if (item.equals(inventory.getChestplate())) {\r\n            return CHESTPLATE;\r\n        }\r\n        if (item.equals(inventory.getLeggings())) {\r\n            return LEGGINGS;\r\n        }\r\n        if (item.equals(inventory.getBoots())) {\r\n            return BOOTS;\r\n        }\r\n        if (item.equals(inventory.getHelmet())) {\r\n            return HELMET;\r\n        }\r\n        if (item.equals(inventory.getItemInMainHand())) {\r\n            return inventory.getHeldItemSlot();\r\n        }\r\n        if (item.equals(inventory.getItemInOffHand())) {\r\n            return OFFHAND;\r\n        }\r\n        ItemStack[] contents = inventory.getContents();\r\n        for (int i = 0; i < contents.length; i++) {\r\n            if (item.equals(contents[i])) {\r\n                return i;\r\n            }\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    // <--[language]\r\n    // @name Slot Inputs\r\n    // @group Useful Lists\r\n    // @description\r\n    // Whenever a script component requires a slot ID (such as the take command, when using '- take slot:#')\r\n    // you can give the slot ID input as either a number of the 1-based index (where the first slot is 1, the second is 2, etc.)\r\n    // If the slot given is 'hand', for a player the held item slot will be used, for any other entity the slot will be 1.\r\n    // OR you can give the following names (valid for player inventories only):\r\n    // BOOTS: equivalent to 37\r\n    // LEGGINGS: equivalent to 38\r\n    // CHESTPLATE: equivalent to 39\r\n    // HELMET: equivalent to 40\r\n    // OFFHAND: equivalent to 41\r\n    //\r\n    // Note that some common alternate spellings may be automatically accepted as well.\r\n    //\r\n    // -->\r\n    public static EquipmentSlot indexToEquipSlot(int index) {\r\n        switch (index) {\r\n            case BOOTS:\r\n                return EquipmentSlot.FEET;\r\n            case LEGGINGS:\r\n                return EquipmentSlot.LEGS;\r\n            case CHESTPLATE:\r\n                return EquipmentSlot.CHEST;\r\n            case HELMET:\r\n                return EquipmentSlot.HEAD;\r\n            case OFFHAND:\r\n                return EquipmentSlot.OFF_HAND;\r\n            default:\r\n                return null;\r\n        }\r\n    }\r\n\r\n    public static int equipSlotToIndex(EquipmentSlot slot) {\r\n        switch (slot) {\r\n            case FEET:\r\n                return BOOTS;\r\n            case LEGS:\r\n                return LEGGINGS;\r\n            case CHEST:\r\n                return CHESTPLATE;\r\n            case HEAD:\r\n                return HELMET;\r\n            case OFF_HAND:\r\n                return OFFHAND;\r\n            default:\r\n                return -1;\r\n        }\r\n    }\r\n\r\n    public static final HashMap<String, Integer> nameIndexMap = new HashMap<>();\r\n    public static final List<String>[] indexNameMap = new List[50];\r\n\r\n    public static void registerSlotName(String name, int index) {\r\n        nameIndexMap.put(name, index);\r\n        nameIndexMap.put(name + \"s\", index);\r\n        List<String> list = indexNameMap[index];\r\n        if (list == null) {\r\n            list = new ArrayList<>();\r\n            indexNameMap[index] = list;\r\n        }\r\n        list.add(name);\r\n        list.add(name + \"s\");\r\n    }\r\n\r\n    static {\r\n        registerSlotName(\"boot\", BOOTS);\r\n        registerSlotName(\"feet\", BOOTS);\r\n        registerSlotName(\"foot\", BOOTS);\r\n        registerSlotName(\"shoe\", BOOTS);\r\n        registerSlotName(\"leg\", LEGGINGS);\r\n        registerSlotName(\"legging\", LEGGINGS);\r\n        registerSlotName(\"chest\", CHESTPLATE);\r\n        registerSlotName(\"chestplate\", CHESTPLATE);\r\n        registerSlotName(\"helmet\", HELMET);\r\n        registerSlotName(\"head\", HELMET);\r\n        registerSlotName(\"offhand\", OFFHAND);\r\n    }\r\n\r\n    public static int nameToIndexFor(String name, InventoryHolder holder) {\r\n        return nameToIndex(name, holder instanceof Entity ? (Entity) holder : null);\r\n    }\r\n\r\n    /**\r\n     * Converts a user given slot name (or number) to a valid internal slot index.\r\n     * Will subtract 1 from a user-given number, per Denizen standard for user input (1-based slot index).\r\n     */\r\n    public static int nameToIndex(String name, Entity entity) {\r\n        name = name.toLowerCase().replace(\"_\", \"\");\r\n        if (name.equals(\"hand\")) {\r\n            return entity instanceof Player ? ((Player) entity).getInventory().getHeldItemSlot() : 0;\r\n        }\r\n        Integer matched = nameIndexMap.get(name);\r\n        if (matched != null) {\r\n            return matched;\r\n        }\r\n        if (ArgumentHelper.matchesInteger(name)) {\r\n            return Integer.parseInt(name) - 1;\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    public static boolean doesMatch(String text, Entity entity, int slot) {\r\n        ScriptEvent.MatchHelper matcher = ScriptEvent.createMatcher(text);\r\n        if (slot >= 0 && slot < indexNameMap.length) {\r\n            List<String> names = indexNameMap[slot];\r\n            if (names != null) {\r\n                for (String name : names) {\r\n                    if (matcher.doesMatch(name)) {\r\n                        return true;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (entity instanceof Player && slot == ((Player) entity).getInventory().getHeldItemSlot()) {\r\n            if (matcher.doesMatch(\"hand\")) {\r\n                return true;\r\n            }\r\n        }\r\n        if (matcher.doesMatch(String.valueOf(slot + 1))) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/maps/DenizenMapManager.java",
    "content": "package com.denizenscript.denizen.utilities.maps;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.AsciiMatcher;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.NaturalOrderComparator;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.configuration.ConfigurationSection;\r\nimport org.bukkit.configuration.file.YamlConfiguration;\r\nimport org.bukkit.map.MapRenderer;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport javax.imageio.stream.FileImageOutputStream;\r\nimport java.io.BufferedInputStream;\r\nimport java.io.File;\r\nimport java.io.IOException;\r\nimport java.net.MalformedURLException;\r\nimport java.net.URL;\r\nimport java.net.URLConnection;\r\nimport java.util.*;\r\n\r\npublic class DenizenMapManager {\r\n\r\n    private final static Map<Integer, DenizenMapRenderer> mapRenderers = new HashMap<>();\r\n    private final static Map<String, String> downloadedByUrl = new HashMap<>();\r\n    private final static File imagesFolder = new File(Denizen.getInstance().getDataFolder(), \"images\");\r\n    private final static File imageDownloads = new File(imagesFolder, \"downloaded\");\r\n    private final static File mapsFile = new File(Denizen.getInstance().getDataFolder(), \"maps.yml\");\r\n\r\n    private static int downloadCount = (imageDownloads.exists() ? imageDownloads.listFiles().length : 0) + 1;\r\n\r\n    private static YamlConfiguration mapsConfig;\r\n\r\n    public static void reloadMaps() {\r\n        Map<Integer, List<MapRenderer>> oldMapRenderers = new HashMap<>();\r\n        for (Map.Entry<Integer, DenizenMapRenderer> entry : mapRenderers.entrySet()) {\r\n            DenizenMapRenderer renderer = entry.getValue();\r\n            oldMapRenderers.put(entry.getKey(), renderer.getOldRenderers());\r\n            renderer.deactivate();\r\n        }\r\n        mapRenderers.clear();\r\n        downloadedByUrl.clear();\r\n        mapsConfig = YamlConfiguration.loadConfiguration(mapsFile);\r\n        ConfigurationSection mapsSection = mapsConfig.getConfigurationSection(\"MAPS\");\r\n        if (mapsSection == null) {\r\n            return;\r\n        }\r\n        for (String key : mapsSection.getKeys(false)) {\r\n            int mapId = Integer.parseInt(key);\r\n            MapView mapView = Bukkit.getServer().getMap((short) mapId); // TODO: ??? (deprecated short method)\r\n            if (mapView == null) {\r\n                Debug.echoError(\"Map #\" + key + \" does not exist. Has it been removed? Deleting from maps.yml...\");\r\n                mapsSection.set(key, null);\r\n                continue;\r\n            }\r\n            ConfigurationSection objectsData = mapsSection.getConfigurationSection(key + \".objects\");\r\n            List<MapRenderer> oldRenderers;\r\n            if (oldMapRenderers.containsKey(mapId)) {\r\n                oldRenderers = oldMapRenderers.get(mapId);\r\n            }\r\n            else {\r\n                oldRenderers = mapView.getRenderers();\r\n                for (MapRenderer oldRenderer : oldRenderers) {\r\n                    mapView.removeRenderer(oldRenderer);\r\n                }\r\n            }\r\n            boolean contextual = mapsSection.getBoolean(key + \".contextual\", true);\r\n            DenizenMapRenderer renderer = new DenizenMapRenderer(oldRenderers, mapsSection.getBoolean(key + \".auto update\", false), contextual);\r\n            renderer.displayOriginal = mapsSection.getBoolean(key + \".original\", true);\r\n            List<String> objects = new ArrayList<>(objectsData.getKeys(false));\r\n            objects.sort(new NaturalOrderComparator());\r\n            for (String objectKey : objects) {\r\n                ConfigurationSection objectConfig = objectsData.getConfigurationSection(objectKey);\r\n                String type = objectConfig.getString(\"type\").toUpperCase();\r\n                String xTag = objectConfig.getString(\"x\");\r\n                String yTag = objectConfig.getString(\"y\");\r\n                String visibilityTag = objectConfig.getString(\"visibility\");\r\n                boolean debug = objectConfig.getString(\"debug\", \"false\").equalsIgnoreCase(\"true\");\r\n                MapObject object = null;\r\n                switch (type) {\r\n                    case \"CURSOR\":\r\n                        object = new MapCursor(xTag, yTag, visibilityTag, debug, objectConfig.getString(\"direction\"), objectConfig.getString(\"cursor\"));\r\n                        break;\r\n                    case \"IMAGE\":\r\n                        String file = objectConfig.getString(\"image\");\r\n                        int width = objectConfig.getInt(\"width\", 0);\r\n                        int height = objectConfig.getInt(\"height\", 0);\r\n                        object = new MapImage(renderer, xTag, yTag, visibilityTag, debug, file, width, height);\r\n                        break;\r\n                    case \"TEXT\":\r\n                        object = new MapText(xTag, yTag, visibilityTag, debug, objectConfig.getString(\"text\"), objectConfig.getString(\"color\"),\r\n                                objectConfig.getString(\"font\"), objectConfig.getString(\"size\"), objectConfig.getString(\"style\"));\r\n                        break;\r\n                    case \"DOT\":\r\n                        object = new MapDot(xTag, yTag, visibilityTag, debug, objectConfig.getString(\"radius\"), objectConfig.getString(\"color\"));\r\n                        break;\r\n                }\r\n                if (object != null) {\r\n                    object.worldCoordinates = objectConfig.getString(\"world_coordinates\", \"false\").equalsIgnoreCase(\"true\");\r\n                    object.showPastEdge = objectConfig.getString(\"show_past_edge\", \"false\").equalsIgnoreCase(\"true\");\r\n                    renderer.addObject(object);\r\n                }\r\n            }\r\n            mapView.addRenderer(renderer);\r\n            mapRenderers.put(mapId, renderer);\r\n        }\r\n        for (Map.Entry<Integer, List<MapRenderer>> entry : oldMapRenderers.entrySet()) {\r\n            int id = entry.getKey();\r\n            if (!mapRenderers.containsKey(id)) {\r\n                MapView mapView = Bukkit.getServer().getMap((short) id); // TODO: ??? (deprecated short method)\r\n                if (mapView != null) {\r\n                    for (MapRenderer renderer : entry.getValue()) {\r\n                        mapView.addRenderer(renderer);\r\n                    }\r\n                }\r\n                // If it's null, the server no longer has the map - don't do anything about it\r\n            }\r\n        }\r\n        ConfigurationSection downloadedImages = mapsConfig.getConfigurationSection(\"DOWNLOADED\");\r\n        if (downloadedImages == null) {\r\n            return;\r\n        }\r\n        for (String image : downloadedImages.getKeys(false)) {\r\n            downloadedByUrl.put(CoreUtilities.toLowerCase(downloadedImages.getString(image)), image.replace(\"DOT\", \".\"));\r\n        }\r\n    }\r\n\r\n    public static void saveMaps() {\r\n        for (Map.Entry<Integer, DenizenMapRenderer> entry : mapRenderers.entrySet()) {\r\n            if (entry.getValue().isActive()) {\r\n                mapsConfig.set(\"MAPS.\" + entry.getKey(), entry.getValue().getSaveData());\r\n            }\r\n        }\r\n        for (Map.Entry<String, String> entry : downloadedByUrl.entrySet()) {\r\n            mapsConfig.set(\"DOWNLOADED.\" + entry.getValue().replace(\".\", \"DOT\"), entry.getKey());\r\n        }\r\n        try {\r\n            mapsConfig.save(mapsFile);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    public static void setMap(MapView map, DenizenMapRenderer denizenMapRenderer) {\r\n        List<MapRenderer> oldRenderers = map.getRenderers();\r\n        for (MapRenderer renderer : oldRenderers) {\r\n            map.removeRenderer(renderer);\r\n        }\r\n        map.addRenderer(denizenMapRenderer);\r\n        mapRenderers.put(map.getId(), denizenMapRenderer);\r\n        denizenMapRenderer.hasChanged = true;\r\n    }\r\n\r\n    public static DenizenMapRenderer getDenizenRenderer(MapView map) {\r\n        int mapId = map.getId();\r\n        DenizenMapRenderer dmr;\r\n        if (!mapRenderers.containsKey(mapId)) {\r\n            boolean contextual = map.isTrackingPosition() || map.isUnlimitedTracking();\r\n            dmr = new DenizenMapRenderer(map.getRenderers(), false, contextual);\r\n            setMap(map, dmr);\r\n        }\r\n        else {\r\n            dmr = mapRenderers.get(mapId);\r\n        }\r\n        return dmr;\r\n    }\r\n\r\n    public static List<MapRenderer> removeDenizenRenderers(MapView map) {\r\n        List<MapRenderer> oldRenderers = new ArrayList<>();\r\n        for (MapRenderer renderer : map.getRenderers()) {\r\n            if (renderer instanceof DenizenMapRenderer) {\r\n                map.removeRenderer(renderer);\r\n                oldRenderers.addAll(((DenizenMapRenderer) renderer).getOldRenderers());\r\n                ((DenizenMapRenderer) renderer).deactivate();\r\n                mapRenderers.remove(map.getId());\r\n            }\r\n        }\r\n        return oldRenderers;\r\n    }\r\n\r\n    public static String getActualFile(String file) {\r\n        String fileLower = CoreUtilities.toLowerCase(file);\r\n        if (!fileLower.startsWith(\"http://\") && !fileLower.startsWith(\"https://\")) {\r\n            File f = new File(imagesFolder, file);\r\n            if (!Utilities.canReadFile(f)) {\r\n                Debug.echoError(\"Cannot read from that file path due to security settings in Denizen/config.yml.\");\r\n                return null;\r\n            }\r\n            return f.getPath();\r\n        }\r\n        else {\r\n            try {\r\n                return downloadImage(new URL(file));\r\n            }\r\n            catch (MalformedURLException e) {\r\n                Debug.echoError(\"URL is malformed: \" + file);\r\n                return null;\r\n            }\r\n        }\r\n    }\r\n\r\n    public static HashSet<String> failedUrls = new HashSet<>();\r\n\r\n    public static AsciiMatcher allowedExtensionText = new AsciiMatcher(AsciiMatcher.LETTERS_LOWER + AsciiMatcher.DIGITS);\r\n\r\n    private static String downloadImage(URL url) {\r\n        try {\r\n            if (failedUrls.contains(url.toString())) {\r\n                return null;\r\n            }\r\n            if (!imageDownloads.exists()) {\r\n                imageDownloads.mkdirs();\r\n            }\r\n            String urlString = CoreUtilities.toLowerCase(url.toString());\r\n            if (downloadedByUrl.containsKey(urlString)) {\r\n                File image = new File(imageDownloads, downloadedByUrl.get(urlString));\r\n                if (image.exists()) {\r\n                    return image.getPath();\r\n                }\r\n            }\r\n            URLConnection connection = url.openConnection();\r\n            BufferedInputStream in = new BufferedInputStream(connection.getInputStream());\r\n            int lastDot = urlString.lastIndexOf('.');\r\n            String fileName = String.format(\"%0\" + (6 - String.valueOf(downloadCount).length()) + \"d\", downloadCount)\r\n                    + (lastDot > 0 ? \".\" + allowedExtensionText.trimToMatches(urlString.substring(lastDot)) : \"\");\r\n            File output = new File(imageDownloads, fileName);\r\n            FileImageOutputStream out = new FileImageOutputStream(output);\r\n            int i;\r\n            while ((i = in.read()) != -1) {\r\n                out.write(i);\r\n            }\r\n            out.flush();\r\n            out.close();\r\n            in.close();\r\n            downloadedByUrl.put(urlString, fileName);\r\n            downloadCount++;\r\n            return output.getPath();\r\n        }\r\n        catch (IOException e) {\r\n            failedUrls.add(url.toString());\r\n            Debug.echoError(e);\r\n        }\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/maps/DenizenMapRenderer.java",
    "content": "package com.denizenscript.denizen.utilities.maps;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapRenderer;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport java.util.*;\r\n\r\npublic class DenizenMapRenderer extends MapRenderer {\r\n\r\n    public List<MapObject> mapObjects = new ArrayList<>();\r\n\r\n    private List<MapRenderer> oldMapRenderers;\r\n\r\n    public boolean autoUpdate;\r\n\r\n    public boolean displayOriginal = true;\r\n\r\n    private boolean active;\r\n\r\n    public boolean hasChanged = true;\r\n\r\n    public DenizenMapRenderer(List<MapRenderer> oldMapRenderers, boolean autoUpdate, boolean contextual) {\r\n        super(contextual);\r\n        this.oldMapRenderers = oldMapRenderers;\r\n        if (oldMapRenderers.size() == 1 && oldMapRenderers.get(0) instanceof DenizenMapRenderer) {\r\n            this.oldMapRenderers = ((DenizenMapRenderer) oldMapRenderers.get(0)).oldMapRenderers;\r\n        }\r\n        this.autoUpdate = autoUpdate;\r\n        this.active = true;\r\n    }\r\n\r\n    public void addObject(MapObject object) {\r\n        if (!active) {\r\n            throw new IllegalStateException(\"DenizenMapRenderer is not active\");\r\n        }\r\n        mapObjects.add(object);\r\n    }\r\n\r\n    public List<MapRenderer> getOldRenderers() {\r\n        return oldMapRenderers;\r\n    }\r\n\r\n    public void deactivate() {\r\n        if (!active) {\r\n            throw new IllegalStateException(\"Already deactivated\");\r\n        }\r\n        this.active = false;\r\n        mapObjects.clear();\r\n        oldMapRenderers.clear();\r\n    }\r\n\r\n    public boolean isActive() {\r\n        return active;\r\n    }\r\n\r\n    public Map<String, Object> getSaveData() {\r\n        if (!active) {\r\n            throw new IllegalStateException(\"DenizenMapRenderer is not active\");\r\n        }\r\n        Map<String, Object> data = new HashMap<>();\r\n        Map<String, Object> objects = new HashMap<>();\r\n        for (int i = 0; i < mapObjects.size(); i++) {\r\n            Map<String, Object> objectData = mapObjects.get(i).getSaveData();\r\n            objects.put(String.valueOf(i), objectData);\r\n        }\r\n        data.put(\"objects\", objects);\r\n        data.put(\"contextual\", isContextual());\r\n        data.put(\"auto update\", autoUpdate);\r\n        data.put(\"original\", displayOriginal);\r\n        return data;\r\n    }\r\n\r\n    @Override\r\n    public void render(MapView mapView, MapCanvas mapCanvas, Player player) {\r\n        if (!Denizen.getInstance().isEnabled()) {\r\n            // Special case for shutdown borko\r\n            return;\r\n        }\r\n        if (!active) {\r\n            mapView.removeRenderer(this);\r\n            return;\r\n        }\r\n        if (!autoUpdate && !hasChanged && !isContextual()) {\r\n            return;\r\n        }\r\n        try {\r\n            while (mapCanvas.getCursors().size() > 0) {\r\n                mapCanvas.getCursors().removeCursor(mapCanvas.getCursors().getCursor(0));\r\n            }\r\n            if (displayOriginal) {\r\n                for (MapRenderer oldR : oldMapRenderers) {\r\n                    oldR.render(mapView, mapCanvas, player);\r\n                }\r\n            }\r\n            UUID uuid = player.getUniqueId();\r\n            PlayerTag p = PlayerTag.mirrorBukkitPlayer(player);\r\n            for (MapObject object : mapObjects) {\r\n                if (autoUpdate) {\r\n                    object.lastMap = mapView;\r\n                    object.update(p, uuid);\r\n                }\r\n                if (object.isVisibleTo(p)) {\r\n                    object.render(mapView, mapCanvas, p, uuid);\r\n                }\r\n            }\r\n            hasChanged = false;\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n            mapView.removeRenderer(this);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/maps/MapCursor.java",
    "content": "package com.denizenscript.denizen.utilities.maps;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic class MapCursor extends MapObject {\r\n\r\n    protected String directionTag;\r\n    protected String typeTag;\r\n    protected Map<UUID, org.bukkit.map.MapCursor> cursors = new HashMap<>();\r\n\r\n    public MapCursor(String xTag, String yTag, String visibilityTag, boolean debug, String directionTag, String typeTag) {\r\n        super(xTag, yTag, visibilityTag, debug);\r\n        this.directionTag = directionTag;\r\n        this.typeTag = typeTag;\r\n    }\r\n\r\n    public byte getDirection(PlayerTag player) {\r\n        return yawToDirection(Double.parseDouble(tag(directionTag, player)));\r\n    }\r\n\r\n    public org.bukkit.map.MapCursor.Type getType(PlayerTag player) {\r\n        return Utilities.elementToEnumlike(new ElementTag(tag(typeTag, player)), org.bukkit.map.MapCursor.Type.class);\r\n    }\r\n\r\n    private byte yawToDirection(double yaw) {\r\n        return (byte) (Math.floor((yaw / 22.5) + 0.5) % 16);\r\n    }\r\n\r\n    @Override\r\n    public void update(PlayerTag player, UUID uuid) {\r\n        super.update(player, uuid);\r\n        if (cursors.containsKey(uuid)) {\r\n            org.bukkit.map.MapCursor cursor = cursors.get(uuid);\r\n            cursor.setX((byte) getX(player));\r\n            cursor.setY((byte) getY(player));\r\n            cursor.setVisible(isVisibleTo(player));\r\n            cursor.setDirection(getDirection(player));\r\n            cursor.setType(getType(player));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Map<String, Object> getSaveData() {\r\n        Map<String, Object> data = super.getSaveData();\r\n        data.put(\"type\", \"CURSOR\");\r\n        data.put(\"direction\", directionTag);\r\n        data.put(\"cursor\", typeTag);\r\n        return data;\r\n    }\r\n\r\n    @Override\r\n    public void render(MapView mapView, MapCanvas mapCanvas, PlayerTag player, UUID uuid) {\r\n        try {\r\n            int x = (getX(player) - 64) * 2;\r\n            int z = (getY(player) - 64) * 2;\r\n            if (x < -127 || z < -127 || x > 127 || z > 127) {\r\n                if (showPastEdge) {\r\n                    x = Math.max(Math.min(x, 127), -127);\r\n                    z = Math.max(Math.min(z, 127), -127);\r\n                }\r\n                else {\r\n                    return;\r\n                }\r\n            }\r\n            org.bukkit.map.MapCursor cursor = new org.bukkit.map.MapCursor((byte) x, (byte) z, getDirection(player), getType(player).getValue(), isVisibleTo(player));\r\n            mapCanvas.getCursors().addCursor(cursor);\r\n            cursors.put(uuid, cursor);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/maps/MapDot.java",
    "content": "package com.denizenscript.denizen.utilities.maps;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic class MapDot extends MapObject {\r\n\r\n    protected String radiusTag;\r\n    protected String colorTag;\r\n\r\n    public MapDot(String xTag, String yTag, String visibilityTag, boolean debug, String radiusTag, String colorTag) {\r\n        super(xTag, yTag, visibilityTag, debug);\r\n        this.radiusTag = radiusTag;\r\n        this.colorTag = colorTag;\r\n    }\r\n\r\n    @Override\r\n    public Map<String, Object> getSaveData() {\r\n        Map<String, Object> data = super.getSaveData();\r\n        data.put(\"type\", \"DOT\");\r\n        data.put(\"radius\", radiusTag);\r\n        data.put(\"color\", colorTag);\r\n        return data;\r\n    }\r\n\r\n    @Override\r\n    public void render(MapView mapView, MapCanvas mapCanvas, PlayerTag player, UUID uuid) {\r\n        try {\r\n            int baseX = getX(player);\r\n            int baseY = getY(player);\r\n            int radius = (int) Double.parseDouble(tag(radiusTag, player));\r\n            ColorTag color = ColorTag.valueOf(tag(colorTag, player), getTagContext(player));\r\n            byte colorId = MapImage.matchColor(color.getAWTColor());\r\n            int max = radius == 0 ? 1 : radius;\r\n            for (int x = -radius; x < max; x++) {\r\n                int finalX = baseX + x;\r\n                if (finalX < 0 || finalX > 127) {\r\n                    continue;\r\n                }\r\n                for (int y = -radius; y < max; y++) {\r\n                    int finalY = baseY + y;\r\n                    if (finalY < 0 || finalY > 127) {\r\n                        continue;\r\n                    }\r\n                    if (((x + 0.5) * (x + 0.5)) + ((y + 0.5) * (y + 0.5)) <= (max * max)) {\r\n                        mapCanvas.setPixel(finalX, finalY, colorId);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/maps/MapImage.java",
    "content": "package com.denizenscript.denizen.utilities.maps;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapPalette;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.*;\r\nimport java.awt.image.BufferedImage;\r\nimport java.awt.image.ColorModel;\r\nimport java.awt.image.ImageConsumer;\r\nimport java.lang.reflect.Field;\r\nimport java.util.HashMap;\r\nimport java.util.Hashtable;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic class MapImage extends MapObject {\r\n\r\n    public byte[] cachedImageData = null;\r\n    public Image imageForCache = null;\r\n    public Image image;\r\n    public ImageIcon imageIcon;\r\n    public int width = 0;\r\n    public int height = 0;\r\n    public String fileTag;\r\n    public String actualFile = null;\r\n    public boolean disabled = false;\r\n    public DenizenMapRenderer renderer;\r\n\r\n    public MapImage(DenizenMapRenderer renderer, String xTag, String yTag, String visibilityTag, boolean debug, String fileTag, int width, int height) {\r\n        super(xTag, yTag, visibilityTag, debug);\r\n        this.fileTag = fileTag;\r\n        if (width > 0 || height > 0) {\r\n            this.width = width > 0 ? width : 0;\r\n            this.height = height > 0 ? height : 0;\r\n        }\r\n        this.renderer = renderer;\r\n    }\r\n\r\n    @Override\r\n    public Map<String, Object> getSaveData() {\r\n        Map<String, Object> data = super.getSaveData();\r\n        data.put(\"type\", \"IMAGE\");\r\n        data.put(\"width\", width);\r\n        data.put(\"height\", height);\r\n        data.put(\"image\", fileTag);\r\n        return data;\r\n    }\r\n\r\n    @Override\r\n    public void render(MapView mapView, MapCanvas mapCanvas, PlayerTag player, UUID uuid) {\r\n        try {\r\n            if (actualFile == null) {\r\n                actualFile = DenizenMapManager.getActualFile(fileTag);\r\n                if (actualFile == null) {\r\n                    disabled = true;\r\n                    return;\r\n                }\r\n                imageIcon = new ImageIcon(actualFile);\r\n                image = imageIcon.getImage();\r\n                image.getSource().addConsumer(new ImageConsumer() {\r\n                    @Override\r\n                    public void setDimensions(int width, int height) {\r\n                    }\r\n\r\n                    @Override\r\n                    public void setProperties(Hashtable<?, ?> props) {\r\n                    }\r\n\r\n                    @Override\r\n                    public void setColorModel(ColorModel model) {\r\n                    }\r\n\r\n                    @Override\r\n                    public void setHints(int hintflags) {\r\n                    }\r\n\r\n                    @Override\r\n                    public void setPixels(int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize) {\r\n                        // When the internal pixels are updated, the cache is no longer current.\r\n                        cachedImageData = null;\r\n                        renderer.hasChanged = true;\r\n                    }\r\n\r\n                    @Override\r\n                    public void setPixels(int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize) {\r\n                    }\r\n\r\n                    @Override\r\n                    public void imageComplete(int status) {\r\n                    }\r\n                });\r\n                if (width == 0) {\r\n                    width = image.getWidth(null);\r\n                }\r\n                if (height == 0) {\r\n                    height = image.getHeight(null);\r\n                }\r\n                if (width == -1 || height == -1) {\r\n                    Debug.echoError(\"Image loading failed (bad width/height) for image \" + fileTag);\r\n                    disabled = true;\r\n                    return;\r\n                }\r\n                disabled = false;\r\n            }\r\n            if (disabled) {\r\n                return;\r\n            }\r\n            // Use custom functions to draw image to allow transparency and reduce lag intensely\r\n            byte[] bytes;\r\n            if (cachedImageData == null || image != imageForCache) {\r\n                bytes = imageToBytes(image, width, height);\r\n                if (bytes == null) {\r\n                    Debug.echoError(\"Image loading failed (bad imageToBytes) for image \" + fileTag);\r\n                    disabled = true;\r\n                    return;\r\n                }\r\n                cachedImageData = bytes;\r\n                imageForCache = image;\r\n            }\r\n            else {\r\n                bytes = cachedImageData;\r\n            }\r\n            int x = getX(player);\r\n            int y = getY(player);\r\n            NMSHandler.packetHelper.setMapData(mapCanvas, bytes, x, y, this);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    private static final Color[] bukkitColors;\r\n\r\n    static {\r\n        Color[] colors = null;\r\n        try {\r\n            Field field = MapPalette.class.getDeclaredField(\"colors\");\r\n            field.setAccessible(true);\r\n            colors = (Color[]) field.get(null);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        bukkitColors = colors;\r\n    }\r\n\r\n    public static byte[] imageToBytes(Image image, int width, int height) {\r\n        BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\r\n        Graphics2D graphics = temp.createGraphics();\r\n        graphics.drawImage(image, 0, 0, width, height, null);\r\n        graphics.dispose();\r\n        int[] pixels = new int[width * height];\r\n        temp.getRGB(0, 0, width, height, pixels, 0, width);\r\n        byte[] result = new byte[width * height];\r\n        for (int i = 0; i < pixels.length; i++) {\r\n            result[i] = matchColor(new Color(pixels[i], true));\r\n        }\r\n        return result;\r\n    }\r\n\r\n    public static HashMap<Color, Byte> colorCache = new HashMap<>(1024);\r\n\r\n    public static byte matchColor(Color color) {\r\n        if (color.getAlpha() < 128) {\r\n            return 0;\r\n        }\r\n        Byte result = colorCache.get(color);\r\n        if (result != null) {\r\n            return result;\r\n        }\r\n        int index = 0;\r\n        double best = -1;\r\n        for (int i = 4; i < bukkitColors.length; i++) {\r\n            double distance = getDistance(color, bukkitColors[i]);\r\n            if (distance < best || best == -1) {\r\n                best = distance;\r\n                index = i;\r\n            }\r\n        }\r\n        byte gotten = (byte) (index < 128 ? index : -129 + (index - 127));\r\n        if (colorCache.size() < 1024 * 16) {\r\n            colorCache.put(color, gotten);\r\n        }\r\n        return gotten;\r\n    }\r\n\r\n    public static double getDistance(Color c1, Color c2) {\r\n        double rmean = (c1.getRed() + c2.getRed()) / 2.0;\r\n        double r = c1.getRed() - c2.getRed();\r\n        double g = c1.getGreen() - c2.getGreen();\r\n        int b = c1.getBlue() - c2.getBlue();\r\n        double weightR = 2 + rmean / 256.0;\r\n        double weightG = 4.0;\r\n        double weightB = 2 + (255 - rmean) / 256.0;\r\n        return weightR * r * r + weightG * g * g + weightB * b * b;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/maps/MapObject.java",
    "content": "package com.denizenscript.denizen.utilities.maps;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.tags.BukkitTagContext;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic abstract class MapObject {\r\n\r\n    protected String xTag;\r\n    protected String yTag;\r\n    protected String visibilityTag;\r\n    protected Map<UUID, Boolean> currentVisibility = new HashMap<>();\r\n    protected boolean debug;\r\n    public boolean showPastEdge = false;\r\n\r\n    public MapView lastMap;\r\n\r\n    public boolean worldCoordinates = false;\r\n\r\n    public MapObject(String xTag, String yTag, String visibilityTag, boolean debug) {\r\n        this.xTag = xTag;\r\n        this.yTag = yTag;\r\n        this.visibilityTag = visibilityTag;\r\n        this.debug = debug;\r\n    }\r\n\r\n    public void update(PlayerTag player, UUID uuid) {\r\n        currentVisibility.put(uuid, tag(visibilityTag, player).equalsIgnoreCase(\"true\"));\r\n    }\r\n\r\n    public int getX(PlayerTag player) {\r\n        int x = (int) Double.parseDouble(tag(xTag, player));\r\n        if (worldCoordinates && lastMap != null) {\r\n            float f = (float) (x - lastMap.getCenterX()) / (2 << (lastMap.getScale().getValue()));\r\n            x = ((int) ((f * 2.0F) + 0.5D)) + 64;\r\n            if (showPastEdge) {\r\n                x = Math.max(Math.min(x, 127), 0);\r\n            }\r\n        }\r\n        return x;\r\n    }\r\n\r\n    public int getY(PlayerTag player) {\r\n        int y = (int) Double.parseDouble(tag(yTag, player));\r\n        if (worldCoordinates && lastMap != null) {\r\n            float f1 = (float) (y - lastMap.getCenterZ()) / (2 << (lastMap.getScale().getValue()));\r\n            y = ((int) ((f1 * 2.0F) + 0.5D)) + 64;\r\n            if (showPastEdge) {\r\n                y = Math.max(Math.min(y, 127), 0);\r\n            }\r\n        }\r\n        return y;\r\n    }\r\n\r\n    public boolean isVisibleTo(PlayerTag player) {\r\n        if (!currentVisibility.containsKey(player.getUUID())) {\r\n            currentVisibility.put(player.getUUID(), tag(visibilityTag, player).equalsIgnoreCase(\"true\"));\r\n        }\r\n        return currentVisibility.get(player.getUUID());\r\n    }\r\n\r\n    public TagContext getTagContext(PlayerTag player) {\r\n        return new BukkitTagContext(player, player.getSelectedNPC(), null, debug, null);\r\n    }\r\n\r\n    protected String tag(String arg, PlayerTag player) {\r\n        // Short, reusable TagManager call\r\n        return TagManager.tag(arg, getTagContext(player));\r\n    }\r\n\r\n    public Map<String, Object> getSaveData() {\r\n        Map<String, Object> data = new HashMap<>();\r\n        data.put(\"x\", xTag);\r\n        data.put(\"y\", yTag);\r\n        data.put(\"visibility\", visibilityTag);\r\n        data.put(\"debug\", debug ? \"true\" : \"false\");\r\n        data.put(\"world_coordinates\", worldCoordinates ? \"true\" : \"false\");\r\n        data.put(\"show_past_edge\", showPastEdge ? \"true\" : \"false\");\r\n        return data;\r\n    }\r\n\r\n    public abstract void render(MapView mapView, MapCanvas mapCanvas, PlayerTag player, UUID uuid);\r\n\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/maps/MapText.java",
    "content": "package com.denizenscript.denizen.utilities.maps;\r\n\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.ListTag;\r\nimport com.denizenscript.denizencore.tags.TagContext;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapView;\r\nimport org.bukkit.map.MinecraftFont;\r\n\r\nimport java.awt.*;\r\nimport java.awt.image.BufferedImage;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic class MapText extends MapObject {\r\n\r\n    protected String textTag, colorTag, fontTag, sizeTag, styleTag;\r\n    protected Map<UUID, String> playerTexts = new HashMap<>();\r\n\r\n    public MapText(String xTag, String yTag, String visibilityTag, boolean debug, String textTag, String colorTag, String fontTag, String sizeTag, String styleTag) {\r\n        super(xTag, yTag, visibilityTag, debug);\r\n        this.textTag = textTag;\r\n        this.colorTag = colorTag;\r\n        this.fontTag = fontTag;\r\n        this.sizeTag = sizeTag;\r\n        this.styleTag = styleTag;\r\n    }\r\n\r\n    @Override\r\n    public void update(PlayerTag player, UUID uuid) {\r\n        super.update(player, uuid);\r\n        playerTexts.put(uuid, tag(textTag, player));\r\n    }\r\n\r\n    public String getText(PlayerTag player) {\r\n        return playerTexts.get(player.getPlayerEntity().getUniqueId());\r\n    }\r\n\r\n    public void setText(String textTag) {\r\n        this.textTag = textTag;\r\n    }\r\n\r\n    @Override\r\n    public Map<String, Object> getSaveData() {\r\n        Map<String, Object> data = super.getSaveData();\r\n        data.put(\"type\", \"TEXT\");\r\n        data.put(\"text\", textTag);\r\n        data.put(\"color\", colorTag);\r\n        data.put(\"font\", fontTag);\r\n        data.put(\"size\", sizeTag);\r\n        data.put(\"style\", styleTag);\r\n        return data;\r\n    }\r\n\r\n    @Override\r\n    public void render(MapView mapView, MapCanvas mapCanvas, PlayerTag player, UUID uuid) {\r\n        try {\r\n            if (!playerTexts.containsKey(uuid)) {\r\n                playerTexts.put(uuid, tag(textTag, player));\r\n            }\r\n            ColorTag color = ColorTag.valueOf(colorTag == null ? \"black\" : tag(colorTag, player), getTagContext(player));\r\n            if (fontTag == null) {\r\n                byte b = MapImage.matchColor(color.getAWTColor());\r\n                String text = ((char) 167) + Byte.toString(b) + ((char) 59) + getText(player);\r\n                mapCanvas.drawText(getX(player), getY(player), MinecraftFont.Font, text);\r\n                return;\r\n            }\r\n            int style = Font.PLAIN;\r\n            if (styleTag != null) {\r\n                TagContext context = getTagContext(player);\r\n                ListTag styles = TagManager.tagObject(styleTag, context).asType(ListTag.class, context);\r\n                for (String styleStr : styles) {\r\n                    String styleLower = CoreUtilities.toLowerCase(styleStr);\r\n                    switch (styleLower) {\r\n                        case \"bold\" -> style |= Font.BOLD;\r\n                        case \"italic\" -> style |= Font.ITALIC;\r\n                    }\r\n                }\r\n            }\r\n            int size = sizeTag != null ? TagManager.tagObject(sizeTag, getTagContext(player)).asElement().asInt() : 10;\r\n            BufferedImage image = new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB);\r\n            Graphics2D graphics = image.createGraphics();\r\n            graphics.setFont(new Font(tag(fontTag, player), style, size));\r\n            graphics.setColor(color.getAWTColor());\r\n            FontMetrics metrics = graphics.getFontMetrics();\r\n            int y = getY(player) + metrics.getAscent() - metrics.getDescent() - metrics.getLeading();\r\n            graphics.drawString(getText(player), getX(player), y);\r\n            graphics.dispose();\r\n            mapCanvas.drawImage(0, 0, image);\r\n\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/midi/MidiUtil.java",
    "content": "package com.denizenscript.denizen.utilities.midi;\r\n\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\n\r\nimport javax.sound.midi.InvalidMidiDataException;\r\nimport javax.sound.midi.MidiSystem;\r\nimport javax.sound.midi.MidiUnavailableException;\r\nimport javax.sound.midi.Receiver;\r\nimport javax.sound.midi.Sequencer;\r\nimport java.io.File;\r\nimport java.io.IOException;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Utility for playing midi files for players to hear.\r\n *\r\n * @author authorblues, patched by mcmonkey\r\n */\r\npublic class MidiUtil {\r\n    public static Map<String, Receiver> receivers = new HashMap<>();\r\n\r\n    public static void startSequencer(File file, float tempo, NoteBlockReceiver receiver)\r\n            throws InvalidMidiDataException, IOException, MidiUnavailableException {\r\n\r\n        Sequencer sequencer = MidiSystem.getSequencer(false);\r\n        sequencer.setSequence(MidiSystem.getSequence(file));\r\n        sequencer.addMetaEventListener(receiver);\r\n        sequencer.open();\r\n\r\n        receiver.setSequencer(sequencer);\r\n\r\n        // Set desired tempo\r\n        sequencer.setTempoFactor(tempo);\r\n\r\n        sequencer.getTransmitter().setReceiver(receiver);\r\n        sequencer.start();\r\n    }\r\n\r\n    public static NoteBlockReceiver playMidi(File file, float tempo, float volume, List<EntityTag> entities) {\r\n        try {\r\n            NoteBlockReceiver receiver = new NoteBlockReceiver(entities, entities.get(0).getUUID().toString());\r\n            receiver.VOLUME_RANGE = volume;\r\n            // If there is already a midi file being played for one of the entities,\r\n            // stop playing it\r\n            for (EntityTag entity : entities) {\r\n                stopMidi(entity.getUUID().toString());\r\n            }\r\n            receivers.put(entities.get(0).getUUID().toString(), receiver);\r\n            startSequencer(file, tempo, receiver);\r\n            return receiver;\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static NoteBlockReceiver playMidi(File file, float tempo, float volume, LocationTag location) {\r\n        try {\r\n            NoteBlockReceiver receiver = new NoteBlockReceiver(location, location.identify());\r\n            receiver.VOLUME_RANGE = volume;\r\n            // If there is already a midi file being played for this location,\r\n            // stop playing it\r\n            stopMidi(location.identify());\r\n            receivers.put(location.identify(), receiver);\r\n            startSequencer(file, tempo, receiver);\r\n            return receiver;\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static void stopMidi(String object) {\r\n        if (receivers.containsKey(object)) {\r\n            receivers.get(object).close();\r\n        }\r\n    }\r\n\r\n    public static void stopMidi(List<EntityTag> entities) {\r\n        for (EntityTag entity : entities) {\r\n            stopMidi(entity.getUUID().toString());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/midi/NoteBlockReceiver.java",
    "content": "package com.denizenscript.denizen.utilities.midi;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Maps;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Sound;\r\nimport org.bukkit.SoundCategory;\r\n\r\nimport javax.sound.midi.*;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Midi Receiver for processing note events.\r\n *\r\n * @author authorblues, patched by mcmonkey\r\n */\r\npublic class NoteBlockReceiver implements Receiver, MetaEventListener {\r\n    public float VOLUME_RANGE = 10.0f;\r\n\r\n    private List<EntityTag> entities;\r\n    private LocationTag location;\r\n    private Map<Integer, Integer> channelPatches;\r\n    public String key;\r\n    public Sequencer sequencer;\r\n    public boolean closing = false;\r\n\r\n    public NoteBlockReceiver(List<EntityTag> entities, String _Key) {\r\n        this.entities = entities;\r\n        this.location = null;\r\n        this.channelPatches = Maps.newHashMap();\r\n        this.key = _Key;\r\n    }\r\n\r\n    public NoteBlockReceiver(LocationTag location, String _Key) {\r\n        this.entities = null;\r\n        this.location = location;\r\n        this.channelPatches = Maps.newHashMap();\r\n        this.key = _Key;\r\n    }\r\n\r\n    public void setSequencer(Sequencer sequencer) {\r\n        this.sequencer = sequencer;\r\n    }\r\n\r\n    @Override\r\n    public void meta(MetaMessage meta) {\r\n        if (meta.getType() == 47) { // Track completion\r\n            close();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void send(MidiMessage m, long time) {\r\n        if (closing) {\r\n            return;\r\n        }\r\n        if (m instanceof ShortMessage) {\r\n            ShortMessage smessage = (ShortMessage) m;\r\n            int chan = smessage.getChannel();\r\n\r\n            switch (smessage.getCommand()) {\r\n                case ShortMessage.PROGRAM_CHANGE:\r\n                    int patch = smessage.getData1();\r\n                    channelPatches.put(chan, patch);\r\n                    break;\r\n\r\n                case ShortMessage.NOTE_ON:\r\n                    this.playNote(smessage);\r\n                    break;\r\n\r\n                case ShortMessage.NOTE_OFF:\r\n                    break;\r\n\r\n                case ShortMessage.STOP:\r\n                    close();\r\n                    break;\r\n            }\r\n        }\r\n    }\r\n\r\n    public void playNote(ShortMessage message) {\r\n        int channel = message.getChannel();\r\n        // If this is a percussion channel, return\r\n        if (channel == 9) {\r\n            return;\r\n        }\r\n        if (channelPatches == null) {\r\n            Debug.echoError(\"Trying to play notes on closed midi NoteBlockReceiver!\");\r\n            return;\r\n        }\r\n        // get the correct instrument\r\n        Integer patch = channelPatches.get(channel);\r\n        // get pitch and volume from the midi message\r\n        float pitch = (float) ToneUtil.midiToPitch(message);\r\n        float volume = VOLUME_RANGE * (message.getData2() / 127.0f);\r\n        Sound instrument = patch == null ? defaultMidiInstrument : getMidiInstrumentFromPatch(patch);\r\n        Runnable actualPlay = () -> {\r\n            if (location != null) {\r\n                location.getWorld().playSound(location, instrument, volume, pitch);\r\n            }\r\n            else if (entities != null && !entities.isEmpty()) {\r\n                for (int i = 0; i < entities.size(); i++) {\r\n                    EntityTag entity = entities.get(i);\r\n                    if (entity.isSpawned()) {\r\n                        if (entity.isPlayer()) {\r\n                            entity.getPlayer().playSound(entity.getPlayer(), instrument, SoundCategory.RECORDS, volume, pitch);\r\n                        }\r\n                        else {\r\n                            entity.getLocation().getWorld().playSound(entity.getLocation(), instrument, SoundCategory.RECORDS, volume, pitch);\r\n                        }\r\n                    }\r\n                    else {\r\n                        entities.remove(i);\r\n                        i--;\r\n                    }\r\n                }\r\n            }\r\n            else {\r\n                this.close();\r\n            }\r\n        };\r\n        if (Bukkit.isPrimaryThread()) {\r\n            actualPlay.run();\r\n        }\r\n        else {\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), actualPlay);\r\n        }\r\n    }\r\n\r\n    public Runnable onFinish = null;\r\n\r\n    @Override\r\n    public void close() {\r\n        if (closing) {\r\n            return;\r\n        }\r\n        closing = true;\r\n        Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> {\r\n            try {\r\n                MidiUtil.receivers.remove(key);\r\n                if (sequencer != null) {\r\n                    sequencer.close();\r\n                    sequencer = null;\r\n                }\r\n                channelPatches.clear();\r\n                channelPatches = null;\r\n                entities = null;\r\n                location = null;\r\n                if (onFinish != null) {\r\n                    onFinish.run();\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }, 1);\r\n    }\r\n\r\n    private static final int[] instruments = { // Last revised 2023/02/24 for MC 1.19.3 instrument list (previously revised for MC 1.12)\r\n            0, 0, 0, 0, 0, 0, 0, 5,         // 8\r\n            9, 9, 9, 9, 9, 6, 0, 9,         // 16\r\n            9, 0, 0, 0, 0, 0, 0, 5,         // 24\r\n            5, 5, 5, 5, 5, 5, 5, 1,         // 32\r\n            1, 1, 1, 1, 1, 1, 1, 5,         // 40\r\n            1, 5, 5, 5, 5, 5, 5, 5,         // 48\r\n            5, 5, 5, 8, 8, 8, 8, 8,         // 56\r\n            8, 8, 8, 8, 8, 8, 8, 8,         // 64\r\n            8, 8, 8, 8, 8, 8, 8, 8,         // 72\r\n            8, 8, 8, 8, 8, 8, 8, 8,         // 80\r\n            0, 1, 2, 3, 4, 5, 6, 7,         // 88\r\n            8, 9, 10, 11, 12, 13, 14, 15,   // 96\r\n            0, 0, 0, 0, 0, 0, 0, 5,         // 104\r\n            5, 5, 5, 9, 8, 5, 8, 6,         // 112\r\n            6, 3, 3, 2, 2, 2, 6, 5,         // 120\r\n            1, 1, 1, 6, 1, 2, 4, 7,         // 128\r\n    };\r\n\r\n    public static Sound getMidiInstrumentFromPatch(int patch) {\r\n        switch (instruments[patch]) {\r\n            case 0: return Sound.BLOCK_NOTE_BLOCK_HARP;\r\n            case 1: return Sound.BLOCK_NOTE_BLOCK_BASS;\r\n            case 2: return Sound.BLOCK_NOTE_BLOCK_SNARE;\r\n            case 3: return Sound.BLOCK_NOTE_BLOCK_HAT;\r\n            case 4: return Sound.BLOCK_NOTE_BLOCK_BASEDRUM;\r\n            case 5: return Sound.BLOCK_NOTE_BLOCK_GUITAR;\r\n            case 6: return Sound.BLOCK_NOTE_BLOCK_BELL;\r\n            case 7: return Sound.BLOCK_NOTE_BLOCK_CHIME;\r\n            case 8: return Sound.BLOCK_NOTE_BLOCK_FLUTE;\r\n            case 9: return Sound.BLOCK_NOTE_BLOCK_XYLOPHONE;\r\n            case 10: return Sound.BLOCK_NOTE_BLOCK_PLING;\r\n            case 11: return Sound.BLOCK_NOTE_BLOCK_DIDGERIDOO;\r\n            case 12: return Sound.BLOCK_NOTE_BLOCK_COW_BELL;\r\n            case 13: return Sound.BLOCK_NOTE_BLOCK_BANJO;\r\n            case 14: return Sound.BLOCK_NOTE_BLOCK_IRON_XYLOPHONE;\r\n            case 15: return Sound.BLOCK_NOTE_BLOCK_BIT;\r\n            default: return defaultMidiInstrument;\r\n        }\r\n    }\r\n\r\n    public static final Sound defaultMidiInstrument = Sound.BLOCK_NOTE_BLOCK_HARP;\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/midi/ToneUtil.java",
    "content": "package com.denizenscript.denizen.utilities.midi;\r\n\r\nimport javax.sound.midi.ShortMessage;\r\n\r\n/**\r\n * Utility for converting between different representations of a tone.\r\n * Calculations provided by github.com/sk89q/craftbook\r\n *\r\n * @author authorblues\r\n */\r\n\r\npublic class ToneUtil {\r\n\r\n    public static double noteToPitch(byte note) {\r\n\r\n        return (float) Math.pow(2.0D, (note - 12) / 12.0D);\r\n    }\r\n\r\n    // converts midi events into Note objects\r\n    public static byte midiToNote(ShortMessage smsg) {\r\n\r\n        int semitone = smsg.getData1();\r\n\r\n        if (semitone < 54) {\r\n            return (byte) ((semitone - 6) % (18 - 6));\r\n        }\r\n        else if (semitone > 78) {\r\n            return (byte) ((semitone - 6) % (18 - 6) + 12);\r\n        }\r\n        else {\r\n            return (byte) (semitone - 54);\r\n        }\r\n    }\r\n\r\n    // converts midi events into pitch\r\n    public static double midiToPitch(ShortMessage smsg) {\r\n\r\n        return noteToPitch(midiToNote(smsg));\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/nbt/CustomNBT.java",
    "content": "package com.denizenscript.denizen.utilities.nbt;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.objects.properties.entity.EntityDisabledSlots.Action;\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport net.kyori.adventure.nbt.*;\nimport org.bukkit.Material;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.inventory.EquipmentSlot;\nimport org.bukkit.inventory.ItemStack;\n\nimport java.util.*;\n\npublic class CustomNBT {\n\n    public static final String KEY_DENIZEN = \"Denizen NBT\";\n    public static final String KEY_CAN_PLACE_ON = \"CanPlaceOn\";\n    public static final String KEY_CAN_DESTROY = \"CanDestroy\";\n    public static final String KEY_DISABLED_SLOTS = \"DisabledSlots\";\n\n    private static final Map<EquipmentSlot, Integer> slotMap;\n\n    static {\n        slotMap = new HashMap<>();\n        slotMap.put(EquipmentSlot.HAND, 0);\n        slotMap.put(EquipmentSlot.FEET, 1);\n        slotMap.put(EquipmentSlot.LEGS, 2);\n        slotMap.put(EquipmentSlot.CHEST, 3);\n        slotMap.put(EquipmentSlot.HEAD, 4);\n        slotMap.put(EquipmentSlot.OFF_HAND, 5);\n    }\n\n    /*\n     * Some static methods for dealing with Minecraft NBT data, which is used to store\n     * custom NBT.\n     */\n\n    // TODO: once 1.20 is the minimum supported version, remove this\n    public static List<Material> getNBTMaterials(ItemStack itemStack, String key) {\n        if (itemStack == null || itemStack.getType() == Material.AIR) {\n            return null;\n        }\n        CompoundBinaryTag compoundTag = NMSHandler.itemHelper.getNbtData(itemStack);\n        List<Material> materials = new ArrayList<>();\n        if (compoundTag.contains(key)) {\n            ListBinaryTag temp = compoundTag.getList(key, BinaryTagTypes.STRING);\n            for (BinaryTag tag : temp) {\n                materials.add(Material.matchMaterial(((StringBinaryTag) tag).value()));\n            }\n        }\n        return materials;\n    }\n\n    // TODO: once 1.20 is the minimum supported version, remove this\n    public static ItemStack setNBTMaterials(ItemStack itemStack, String key, List<Material> materials) {\n        if (itemStack == null || itemStack.getType() == Material.AIR) {\n            return null;\n        }\n        CompoundBinaryTag compoundTag = NMSHandler.itemHelper.getNbtData(itemStack);\n        CompoundBinaryTag modifiedCompoundTag = compoundTag.remove(key);\n        if (materials.isEmpty()) {\n            return NMSHandler.itemHelper.setNbtData(itemStack, modifiedCompoundTag);\n        }\n        ListBinaryTag.Builder<StringBinaryTag> internalMaterials = ListBinaryTag.builder(BinaryTagTypes.STRING);\n        for (Material material : materials) {\n            internalMaterials.add(StringBinaryTag.stringBinaryTag(material.getKey().toString()));\n        }\n        modifiedCompoundTag = modifiedCompoundTag.put(key, internalMaterials.build());\n        return NMSHandler.itemHelper.setNbtData(itemStack, modifiedCompoundTag);\n    }\n\n    public static ItemStack addCustomNBT(ItemStack itemStack, String key, String value, String basekey) {\n        if (itemStack == null || itemStack.getType() == Material.AIR) {\n            return null;\n        }\n        CompoundBinaryTag customData = ItemRawNBT.compoundOrEmpty(NMSHandler.itemHelper.getCustomData(itemStack));\n        CompoundBinaryTag modifiedDenizenData = customData.getCompound(basekey).putString(CoreUtilities.toLowerCase(key), value);\n        CompoundBinaryTag modifiedCustomData = customData.put(basekey, modifiedDenizenData);\n        return NMSHandler.itemHelper.setCustomData(itemStack, modifiedCustomData);\n    }\n\n    // TODO: once 1.20 is the minimum supported version, remove this\n    public static ItemStack clearNBT(ItemStack itemStack, String key) {\n        if (itemStack == null || itemStack.getType() == Material.AIR) {\n            return null;\n        }\n        CompoundBinaryTag compoundTag = NMSHandler.itemHelper.getNbtData(itemStack);\n        return NMSHandler.itemHelper.setNbtData(itemStack, compoundTag.remove(key));\n    }\n\n    public static ItemStack removeCustomNBT(ItemStack itemStack, String key, String basekey) {\n        if (itemStack == null || itemStack.getType() == Material.AIR) {\n            return null;\n        }\n        CompoundBinaryTag customData = NMSHandler.itemHelper.getCustomData(itemStack);\n        if (customData == null) {\n            return itemStack;\n        }\n        CompoundBinaryTag denizenData = customData.getCompound(basekey, null);\n        if (denizenData == null) {\n            return itemStack;\n        }\n        CompoundBinaryTag modifiedDenizenData = denizenData.remove(CoreUtilities.toLowerCase(key));\n        CompoundBinaryTag modifiedCustomData = modifiedDenizenData.isEmpty() ? customData.remove(basekey) : customData.put(basekey, modifiedDenizenData);\n        return NMSHandler.itemHelper.setCustomData(itemStack, modifiedCustomData.isEmpty() ? null : modifiedCustomData);\n    }\n\n    public static boolean hasCustomNBT(ItemStack itemStack, String key, String basekey) {\n        if (itemStack == null || itemStack.getType() == Material.AIR) {\n            return false;\n        }\n        CompoundBinaryTag customData = NMSHandler.itemHelper.getCustomData(itemStack);\n        if (customData == null) {\n            return false;\n        }\n        return customData.getCompound(basekey).contains(CoreUtilities.toLowerCase(key));\n    }\n\n    public static String getCustomNBT(ItemStack itemStack, String key, String basekey) {\n        if (itemStack == null || itemStack.getType() == Material.AIR || key == null) {\n            return null;\n        }\n        CompoundBinaryTag customData = NMSHandler.itemHelper.getCustomData(itemStack);\n        if (customData == null) {\n            return null;\n        }\n        CompoundBinaryTag denizenData = customData.getCompound(basekey, null);\n        if (denizenData == null) {\n            return null;\n        }\n        return denizenData.getString(CoreUtilities.toLowerCase(key), null);\n    }\n\n    public static List<String> listNBT(ItemStack itemStack, String basekey) {\n        List<String> nbt = new ArrayList<>();\n        if (itemStack == null || itemStack.getType() == Material.AIR) {\n            return nbt;\n        }\n        CompoundBinaryTag customData = NMSHandler.itemHelper.getCustomData(itemStack);\n        if (customData == null) {\n            return nbt;\n        }\n        CompoundBinaryTag denizenData = customData.getCompound(basekey, null);\n        if (denizenData == null) {\n            return nbt;\n        }\n        nbt.addAll(denizenData.keySet());\n        return nbt;\n    }\n\n    public static void addCustomNBT(Entity entity, String key, int value) {\n        if (entity == null) {\n            return;\n        }\n        CompoundBinaryTag compoundTag = NMSHandler.entityHelper.getNbtData(entity);\n        NMSHandler.entityHelper.setNbtData(entity, compoundTag.putInt(key, value));\n    }\n\n    public static void removeCustomNBT(Entity entity, String key) {\n        if (entity == null) {\n            return;\n        }\n        CompoundBinaryTag compoundTag = NMSHandler.entityHelper.getNbtData(entity);\n        NMSHandler.entityHelper.setNbtData(entity, compoundTag.remove(key));\n    }\n\n    public static int getCustomIntNBT(Entity entity, String key) {\n        if (entity == null) {\n            return 0;\n        }\n        CompoundBinaryTag compoundTag = NMSHandler.entityHelper.getNbtData(entity);\n        // Return contents of the tag\n        return compoundTag.getInt(key);\n    }\n\n    public static void setDisabledSlots(Entity entity, Map<EquipmentSlot, Set<Action>> map) {\n        int sum = 0;\n        for (Map.Entry<EquipmentSlot, Set<Action>> entry : map.entrySet()) {\n            if (!slotMap.containsKey(entry.getKey())) {\n                continue;\n            }\n            for (Action action : entry.getValue()) {\n                sum += 1 << (slotMap.get(entry.getKey()) + action.getId());\n            }\n        }\n        addCustomNBT(entity, KEY_DISABLED_SLOTS, sum);\n    }\n\n    public static Map<EquipmentSlot, Set<Action>> getDisabledSlots(Entity entity) {\n        if (entity == null) {\n            return null;\n        }\n        Map<EquipmentSlot, Set<Action>> map = new HashMap<>();\n        CompoundBinaryTag compoundTag = NMSHandler.entityHelper.getNbtData(entity);\n        int disabledSlots = compoundTag.getInt(KEY_DISABLED_SLOTS);\n        if (disabledSlots == 0) {\n            return map;\n        }\n        slotLoop:\n        for (EquipmentSlot slot : slotMap.keySet()) {\n            for (Action action : Action.values()) {\n                int matchedSlot = disabledSlots & 1 << slotMap.get(slot) + action.getId();\n                if (matchedSlot != 0) {\n                    Set<Action> set = map.computeIfAbsent(slot, k -> new HashSet<>());\n                    set.add(action);\n                    disabledSlots -= matchedSlot;\n                    if (disabledSlots == 0) {\n                        break slotLoop;\n                    }\n                }\n            }\n        }\n        return map;\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/nbt/LeatherColorer.java",
    "content": "package com.denizenscript.denizen.utilities.nbt;\r\n\r\nimport com.denizenscript.denizen.objects.properties.bukkit.BukkitColorExtensions;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport org.bukkit.inventory.meta.LeatherArmorMeta;\r\n\r\npublic class LeatherColorer {\r\n\r\n    public static void colorArmor(ItemTag item, String colorArg) {\r\n        if (item == null) {\r\n            return;\r\n        }\r\n        if (ColorTag.matches(colorArg)) {\r\n            try {\r\n                LeatherArmorMeta meta = (LeatherArmorMeta) item.getItemMeta();\r\n                meta.setColor(BukkitColorExtensions.getColor(ColorTag.valueOf(colorArg, CoreUtilities.basicContext)));\r\n                item.setItemMeta(meta);\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(\"Unable to color '\" + item.identify() + \"'.\");\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/packets/DenizenPacketHandler.java",
    "content": "package com.denizenscript.denizen.utilities.packets;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.player.*;\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInResourcePackStatus;\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInSteerVehicle;\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.commands.server.ExecuteCommand;\r\nimport com.denizenscript.denizencore.DenizenCore;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.HashSet;\r\nimport java.util.UUID;\r\nimport java.util.concurrent.Callable;\r\nimport java.util.concurrent.FutureTask;\r\n\r\npublic class DenizenPacketHandler {\r\n\r\n    public static DenizenPacketHandler instance;\r\n\r\n    public static HashSet<UUID> forceNoclip = new HashSet<>();\r\n\r\n    public void receivePacket(final Player player, final PacketInResourcePackStatus resourcePackStatus) {\r\n        if (!ResourcePackStatusScriptEvent.instance.eventData.isEnabled) {\r\n            return;\r\n        }\r\n        Bukkit.getScheduler().runTask(Denizen.getInstance(), () -> {\r\n            ResourcePackStatusScriptEvent event = ResourcePackStatusScriptEvent.instance;\r\n            event.status = new ElementTag(resourcePackStatus.getStatus());\r\n            event.player = PlayerTag.mirrorBukkitPlayer(player);\r\n            event.fire();\r\n        });\r\n    }\r\n\r\n    public boolean receivePacket(final Player player, final PacketInSteerVehicle steerVehicle, Runnable allow) {\r\n        if (PlayerSteersEntityScriptEvent.instance.eventData.isEnabled) {\r\n            Runnable process = () -> {\r\n                if (!player.isInsideVehicle()) {\r\n                    return;\r\n                }\r\n                PlayerSteersEntityScriptEvent event = PlayerSteersEntityScriptEvent.instance;\r\n                event.player = PlayerTag.mirrorBukkitPlayer(player);\r\n                event.entity = new EntityTag(player.getVehicle());\r\n                event.sideways = new ElementTag(steerVehicle.getLeftwardInput());\r\n                event.forward = new ElementTag(steerVehicle.getForwardInput());\r\n                event.jump = new ElementTag(steerVehicle.getJumpInput());\r\n                event.dismount = new ElementTag(steerVehicle.getDismountInput());\r\n                event.cancelled = false;\r\n                event.modifyCancellation = (c) -> event.cancelled = c;\r\n                event.fire();\r\n                if (!event.cancelled) {\r\n                    allow.run();\r\n                }\r\n            };\r\n            if (Bukkit.isPrimaryThread()) {\r\n                process.run();\r\n            }\r\n            else {\r\n                Bukkit.getScheduler().runTask(Denizen.getInstance(), process);\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static boolean isHoldingRaisable(Player player) {\r\n        return PlayerRaiseLowerItemScriptEvent.raisableItems.contains(player.getEquipment().getItemInMainHand().getType())\r\n            || PlayerRaiseLowerItemScriptEvent.raisableItems.contains(player.getEquipment().getItemInOffHand().getType());\r\n    }\r\n\r\n    public void receivePlacePacket(final Player player) {\r\n        if (!PlayerRaiseLowerItemScriptEvent.instance.eventData.isEnabled) {\r\n            return;\r\n        }\r\n        if (isHoldingRaisable(player)) {\r\n            Bukkit.getScheduler().runTask(Denizen.getInstance(), () -> {\r\n                PlayerRaiseLowerItemScriptEvent.signalDidRaise(player);\r\n            });\r\n        }\r\n    }\r\n\r\n    public void receiveDigPacket(final Player player) {\r\n        if (Denizen.supportsPaper || !PlayerRaiseLowerItemScriptEvent.instance.eventData.isEnabled) {\r\n            return;\r\n        }\r\n        if (isHoldingRaisable(player)) {\r\n            Bukkit.getScheduler().runTask(Denizen.getInstance(), () -> {\r\n                PlayerRaiseLowerItemScriptEvent.signalDidLower(player, \"lower\");\r\n            });\r\n        }\r\n    }\r\n\r\n    public boolean shouldInterceptChatPacket() {\r\n        return !ExecuteCommand.silencedPlayers.isEmpty()\r\n                || PlayerReceivesMessageScriptEvent.instance.loaded\r\n                || PlayerReceivesActionbarScriptEvent.instance.loaded;\r\n    }\r\n\r\n    public PlayerReceivesMessageScriptEvent sendPacket(final Player player, final PacketOutChat chat) {\r\n        if (!chat.isActionbar() && ExecuteCommand.silencedPlayers.contains(player.getUniqueId())) {\r\n            PlayerReceivesMessageScriptEvent event = (PlayerReceivesMessageScriptEvent) PlayerReceivesMessageScriptEvent.instance.clone();\r\n            event.cancelled = true;\r\n            return event;\r\n        }\r\n        if (chat.getMessage() == null) {\r\n            return null;\r\n        }\r\n        final PlayerReceivesMessageScriptEvent event = chat.isActionbar() ? PlayerReceivesActionbarScriptEvent.instance : PlayerReceivesMessageScriptEvent.instance;\r\n        if (event.loaded) {\r\n            Callable<PlayerReceivesMessageScriptEvent> eventCall = () -> {\r\n                event.reset();\r\n                event.message = new ElementTag(chat.getMessage());\r\n                event.rawJson = new ElementTag(chat.getRawJson());\r\n                event.system = new ElementTag(chat.isSystem());\r\n                event.player = PlayerTag.mirrorBukkitPlayer(player);\r\n                return event.triggerNow();\r\n            };\r\n            try {\r\n                if (DenizenCore.isMainThread()) {\r\n                    return eventCall.call();\r\n                }\r\n                else {\r\n                    FutureTask<PlayerReceivesMessageScriptEvent> futureTask = new FutureTask<>(eventCall);\r\n                    Bukkit.getScheduler().runTask(Denizen.getInstance(), futureTask);\r\n                    return futureTask.get();\r\n                }\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(e);\r\n                return null;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/packets/HideParticles.java",
    "content": "package com.denizenscript.denizen.utilities.packets;\r\n\r\nimport org.bukkit.Particle;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.HashSet;\r\nimport java.util.UUID;\r\n\r\npublic class HideParticles {\r\n\r\n    public static HashMap<UUID, HashSet<Particle>> hidden = new HashMap<>();\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/packets/ItemChangeMessage.java",
    "content": "package com.denizenscript.denizen.utilities.packets;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.block.BlockBreakEvent;\r\nimport org.bukkit.event.inventory.InventoryOpenEvent;\r\nimport org.bukkit.event.player.PlayerInteractEvent;\r\nimport org.bukkit.event.player.PlayerItemHeldEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic class ItemChangeMessage implements Listener {\r\n\r\n    static {\r\n        Denizen.getInstance().getServer().getPluginManager().registerEvents(new ItemChangeMessage(),\r\n                Denizen.getInstance());\r\n    }\r\n\r\n    private static final Map<UUID, Integer> slotChanged = new HashMap<>();\r\n\r\n    public static void sendMessage(Player player, String message) {\r\n        ItemStack item = player.getEquipment().getItemInMainHand();\r\n        // If the player is holding air, force a light gray stained glass pane,\r\n        // which is probably the least intrusive\r\n        if (item == null || item.getType() == Material.AIR) {\r\n            item = new ItemStack(Material.LIGHT_GRAY_STAINED_GLASS_PANE);\r\n        }\r\n        else {\r\n            item = item.clone();\r\n        }\r\n        ItemTag itemTag = new ItemTag(item);\r\n        NMSHandler.itemHelper.setDisplayName(itemTag, message);\r\n        int slot = player.getInventory().getHeldItemSlot() + 36;\r\n        NMSHandler.packetHelper.setSlot(player, slot, item, true);\r\n        slotChanged.put(player.getUniqueId(), slot);\r\n    }\r\n\r\n    public static void resetItem(Player player) {\r\n        if (player == null) {\r\n            return;\r\n        }\r\n        UUID uuid = player.getUniqueId();\r\n        if (slotChanged.containsKey(uuid)) {\r\n            int slot = slotChanged.get(uuid);\r\n            ItemStack itemStack = player.getEquipment().getItemInMainHand();\r\n            NMSHandler.packetHelper.setSlot(player, slot, itemStack, true);\r\n            slotChanged.remove(uuid);\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerItemHeld(PlayerItemHeldEvent event) {\r\n        resetItem(event.getPlayer());\r\n    }\r\n\r\n    @EventHandler\r\n    public void inventoryOpen(InventoryOpenEvent event) {\r\n        if (event.getPlayer() instanceof Player) {\r\n            resetItem((Player) event.getPlayer());\r\n        }\r\n    }\r\n\r\n    // Breaking blocks with tools and such will reset the item automatically...\r\n    // sadly, this sends the display name of the real item when that happens.\r\n    // This also occurs when right clicking\r\n    // TODO: find a super hacky way around that?\r\n    // TODO: Use a packet override for the above\r\n\r\n    @EventHandler\r\n    public void blockBreak(BlockBreakEvent event) {\r\n        Player player = event.getPlayer();\r\n        if (player != null) {\r\n            slotChanged.remove(player.getUniqueId());\r\n        }\r\n    }\r\n\r\n    @EventHandler\r\n    public void playerInteract(PlayerInteractEvent event) {\r\n        Player player = event.getPlayer();\r\n        if (player != null) {\r\n            slotChanged.remove(player.getUniqueId());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/packets/NetworkInterceptCodeGen.java",
    "content": "package com.denizenscript.denizen.utilities.packets;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizencore.utilities.codegen.CodeGenUtil;\r\nimport com.denizenscript.denizencore.utilities.codegen.MethodGenerator;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.objectweb.asm.ClassWriter;\r\nimport org.objectweb.asm.Label;\r\nimport org.objectweb.asm.Opcodes;\r\nimport org.objectweb.asm.Type;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.invoke.MethodHandles;\r\nimport java.lang.reflect.Constructor;\r\nimport java.lang.reflect.Method;\r\nimport java.lang.reflect.Modifier;\r\nimport java.lang.reflect.Parameter;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class NetworkInterceptCodeGen {\r\n\r\n    public static MethodHandle generateInstance = null;\r\n\r\n    public static void generateClass(Class<?> denClass, Class<?> abstractClass, Class<?> nmsClass) {\r\n        try {\r\n            Constructor<?> origConstructor = denClass.getConstructors()[0];\r\n            // ====== Build class ======\r\n            String className = \"com/denizenscript/denizen/network_intercept_codegen/GeneratedInterceptor\";\r\n            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);\r\n            cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, Type.getInternalName(denClass), new String[0]);\r\n            cw.visitSource(\"GENERATED_INTERCEPTOR\", null);\r\n            // ====== Build constructor ======\r\n            {\r\n                MethodGenerator gen = MethodGenerator.generateMethod(className, cw, Opcodes.ACC_PUBLIC, \"<init>\", Type.getConstructorDescriptor(origConstructor));\r\n                gen.loadThis();\r\n                int p = 0;\r\n                for (Parameter param : origConstructor.getParameters()) {\r\n                    MethodGenerator.Local local = gen.addLocal(\"arg\" + (p++), param.getType());\r\n                    gen.loadLocal(local);\r\n                }\r\n                gen.invokeSpecial(origConstructor);\r\n                gen.returnNone();\r\n                gen.end();\r\n            }\r\n            // ====== Find public methods ======\r\n            for (Method method : nmsClass.getDeclaredMethods()) {\r\n                int modifier = method.getModifiers();\r\n                if (Modifier.isPublic(modifier) && !Modifier.isFinal(modifier) && !Modifier.isStatic(modifier)) {\r\n                    boolean hasMethod = false;\r\n                    try {\r\n                        abstractClass.getDeclaredMethod(method.getName(), method.getParameterTypes());\r\n                        hasMethod = true;\r\n                    }\r\n                    catch (NoSuchMethodException ignore) {}\r\n                    if (!hasMethod) {\r\n                        if (NMSHandler.debugPackets) {\r\n                            Debug.log(\"Must override \" + method + \" --- \" + method.getName() + \", returns \" + method.getReturnType() + \" is \" + modifier\r\n                                    + \", Public=\" + Modifier.isPublic(modifier) + \", final=\" + Modifier.isFinal(modifier));\r\n                        }\r\n                        MethodGenerator gen = MethodGenerator.generateMethod(className, cw, Opcodes.ACC_PUBLIC, method.getName(), Type.getMethodDescriptor(method));\r\n                        List<MethodGenerator.Local> locals = new ArrayList<>();\r\n                        int id = 1;\r\n                        for (Class<?> type : method.getParameterTypes()) {\r\n                            if (NMSHandler.debugPackets) {\r\n                                Debug.log(\"Var \" + id + \" is type \" + type.getName());\r\n                            }\r\n                            locals.add(gen.addLocal(\"arg_\" + (id++), type));\r\n                        }\r\n                        Label returnLabel = new Label();\r\n                        gen.loadThis();\r\n                        gen.loadInstanceField(Type.getInternalName(abstractClass), \"oldListener\", Type.getDescriptor(nmsClass));\r\n                        gen.jumpIfNullTo(returnLabel);\r\n                        gen.loadThis();\r\n                        gen.loadInstanceField(Type.getInternalName(abstractClass), \"oldListener\", Type.getDescriptor(nmsClass));\r\n                        locals.forEach(gen::loadLocal);\r\n                        gen.invokeVirtual(method);\r\n                        Class<?> returnType = method.getReturnType();\r\n                        gen.returnValue(returnType);\r\n\r\n                        gen.advanceAndLabel(returnLabel);\r\n                        if (returnType != Void.TYPE) {\r\n                            if (returnType.isPrimitive()) {\r\n                                if (returnType == Long.TYPE) {\r\n                                    gen.mv.visitInsn(Opcodes.LCONST_0);\r\n                                }\r\n                                else if (returnType == Float.TYPE) {\r\n                                    gen.mv.visitInsn(Opcodes.FCONST_0);\r\n                                }\r\n                                else if (returnType == Double.TYPE) {\r\n                                    gen.mv.visitInsn(Opcodes.DCONST_0);\r\n                                }\r\n                                else {\r\n                                    gen.mv.visitInsn(Opcodes.ICONST_0);\r\n                                }\r\n                            }\r\n                            else {\r\n                                gen.loadNull();\r\n                            }\r\n                        }\r\n                        gen.returnValue(returnType);\r\n                        gen.end();\r\n                    }\r\n                }\r\n            }\r\n            // ====== Compile and return ======\r\n            cw.visitEnd();\r\n            byte[] compiled = cw.toByteArray();\r\n            Class<?> generatedClass = CodeGenUtil.loader.define(className.replace('/', '.'), compiled);\r\n            Constructor<?> ctor = generatedClass.getDeclaredConstructor(origConstructor.getParameterTypes());\r\n            ctor.setAccessible(true);\r\n            generateInstance = MethodHandles.lookup().unreflectConstructor(ctor);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static Object generateAppropriateInterceptor(Object netMan, Object player, Class<?> denClass, Class<?> abstractClass, Class<?> nmsClass) {\r\n        if (generateInstance == null) {\r\n            generateClass(denClass, abstractClass, nmsClass);\r\n        }\r\n        try {\r\n            return generateInstance.invoke(netMan, player);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/packets/NetworkInterceptHelper.java",
    "content": "package com.denizenscript.denizen.utilities.packets;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.EventPriority;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.player.PlayerJoinEvent;\r\nimport org.bukkit.event.player.PlayerQuitEvent;\r\n\r\npublic class NetworkInterceptHelper implements Listener {\r\n\r\n    public static boolean isEnabled = false;\r\n\r\n    public static void enable() {\r\n        if (isEnabled) {\r\n            return;\r\n        }\r\n        isEnabled = true;\r\n        DenizenPacketHandler.instance = new DenizenPacketHandler();\r\n        Bukkit.getPluginManager().registerEvents(new NetworkInterceptHelper(), Denizen.getInstance());\r\n        for (Player player : Bukkit.getOnlinePlayers()) {\r\n            NMSHandler.packetHelper.setNetworkManagerFor(player);\r\n        }\r\n        NMSHandler.packetHelper.enableNetworkManager();\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.LOWEST)\r\n    public void onPlayerJoin(PlayerJoinEvent event) {\r\n        NMSHandler.packetHelper.setNetworkManagerFor(event.getPlayer());\r\n    }\r\n\r\n    @EventHandler(priority = EventPriority.LOWEST)\r\n    public void onPlayerQuit(PlayerQuitEvent event) {\r\n        if (!event.getPlayer().isOnline()) { // Workaround: Paper misfires this event extra times after the player is already gone.\r\n            event.setQuitMessage(null); // Block the message too since it's obviously not valid for the message to show a second time.\r\n            // Also note that Paper literally has a commit that just removes a warning that would have helped catch issues like this because I guess they just like having errors https://i.alexgoodwin.media/i/misc/a8f5c3.png\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/world/GameRuleReflect.java",
    "content": "package com.denizenscript.denizen.utilities.world;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.NMSVersion;\nimport com.denizenscript.denizen.utilities.Utilities;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport org.bukkit.GameRule;\nimport org.bukkit.Keyed;\nimport org.bukkit.World;\n\nimport java.lang.invoke.MethodHandle;\n\npublic class GameRuleReflect {\n\n    private static final MethodHandle GAMERULE_VALUES = ReflectionHelper.getMethodHandle(GameRule.class, \"values\");\n    private static final MethodHandle GAMERULE_GET_BY_NAME = ReflectionHelper.getMethodHandle(GameRule.class, \"getByName\", String.class);\n    private static final MethodHandle GAMERULE_GET_NAME = ReflectionHelper.getMethodHandle(GameRule.class, \"getName\");\n    private static final MethodHandle GAMERULE_GET_TYPE = ReflectionHelper.getMethodHandle(GameRule.class, \"getType\");\n\n    public static final boolean MODERN_GAMERULE_NAMING = NMSHandler.getVersion().isAtLeast(NMSVersion.v1_21);\n    public static final GameRule<Boolean> WEATHER_CYCLE_GAMERULE = getByName(MODERN_GAMERULE_NAMING ? \"advance_weather\" : \"doWeatherCycle\");\n\n    public static GameRule<?>[] values() {\n        try {\n            return (GameRule<?>[]) GAMERULE_VALUES.invokeExact();\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static <T> GameRule<T> getByName(String name) {\n        try {\n            return (GameRule<T>) GAMERULE_GET_BY_NAME.invokeExact(name);\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static String getName(GameRule<?> gameRule) {\n        if (MODERN_GAMERULE_NAMING) {\n            return Utilities.namespacedKeyToString(((Keyed) gameRule).getKey());\n        }\n        try {\n            return (String) GAMERULE_GET_NAME.invokeExact(gameRule);\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static Class<?> getType(GameRule<?> gameRule) {\n        try {\n            return (Class<?>) GAMERULE_GET_TYPE.invokeExact(gameRule);\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    // TODO 1.21.11: certain gamemodes (e.g. max_minecart_speed) behave badly for some reason\n    public static <T> T getValue(World world, GameRule<T> gameRule) {\n        try {\n            return world.getGameRuleValue(gameRule);\n        }\n        catch (IllegalArgumentException e) {\n            if (e.getMessage().equals(\"Tried to access invalid game rule\")) {\n                return null;\n            }\n            throw e;\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/world/PathFinder.java",
    "content": "package com.denizenscript.denizen.utilities.world;\r\n\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.citizensnpcs.api.ai.NavigatorParameters;\r\nimport net.citizensnpcs.api.astar.AStarMachine;\r\nimport net.citizensnpcs.api.astar.pathfinder.*;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n/**\r\n * TODO: Potentially a separate implementation?\r\n */\r\npublic class PathFinder {\r\n\r\n    public static AStarMachine ASTAR = AStarMachine.createWithDefaultStorage();\r\n\r\n    public static List<LocationTag> getPath(Location start, Location dest) {\r\n        VectorGoal goal = new VectorGoal(dest, 1);\r\n        Path plan = (Path) ASTAR.runFully(goal, new VectorNode(goal, start, new ChunkBlockSource(start, 100), new NavigatorParameters().examiner(new MinecraftBlockExaminer())), 50000);\r\n        if (plan == null) {\r\n            Debug.verboseLog(\"PathFinder: No path found from \" + start + \" to \" + dest + \" (return null)\");\r\n            return new ArrayList<>();\r\n        }\r\n        if (plan.isComplete()) {\r\n            Debug.verboseLog(\"PathFinder: path from \" + start + \" to \" + dest + \" is instantly completed\");\r\n            return new ArrayList<>();\r\n        }\r\n        else {\r\n            List<LocationTag> path = new ArrayList<>();\r\n            while (!plan.isComplete()) {\r\n                Vector v = plan.getCurrentVector();\r\n                path.add(new LocationTag(start.getWorld(), v.getX(), v.getY(), v.getZ()));\r\n                plan.update(null);\r\n            }\r\n            return path;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/world/VoidGenerator.java",
    "content": "package com.denizenscript.denizen.utilities.world;\r\n\r\nimport org.bukkit.block.Biome;\r\nimport org.bukkit.generator.BiomeProvider;\r\nimport org.bukkit.generator.ChunkGenerator;\r\nimport org.bukkit.generator.WorldInfo;\r\n\r\nimport java.util.List;\r\n\r\npublic class VoidGenerator extends ChunkGenerator {\r\n\r\n    final boolean setVoidBiome;\r\n\r\n    public VoidGenerator(boolean setVoidBiome) {\r\n        this.setVoidBiome = setVoidBiome;\r\n    }\r\n\r\n    public static class VoidBiomeProvider extends BiomeProvider {\r\n\r\n        public static final List<Biome> VOID_BIOME_LIST = List.of(Biome.THE_VOID);\r\n\r\n        @Override\r\n        public Biome getBiome(WorldInfo worldInfo, int i, int i1, int i2) {\r\n            return Biome.THE_VOID;\r\n        }\r\n\r\n        @Override\r\n        public List<Biome> getBiomes(WorldInfo worldInfo) {\r\n            return VOID_BIOME_LIST;\r\n        }\r\n    }\r\n\r\n    public static final VoidBiomeProvider VOID_BIOME_PROVIDER = new VoidBiomeProvider();\r\n\r\n    @Override\r\n    public BiomeProvider getDefaultBiomeProvider(WorldInfo worldInfo) {\r\n        return setVoidBiome ? VOID_BIOME_PROVIDER : null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/java/com/denizenscript/denizen/utilities/world/WorldListChangeTracker.java",
    "content": "package com.denizenscript.denizen.utilities.world;\r\n\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.Listener;\r\nimport org.bukkit.event.world.WorldInitEvent;\r\nimport org.bukkit.event.world.WorldLoadEvent;\r\nimport org.bukkit.event.world.WorldUnloadEvent;\r\n\r\npublic class WorldListChangeTracker implements Listener {\r\n\r\n    public static int changes;\r\n\r\n    @EventHandler\r\n    public void onWorldLoaded(WorldLoadEvent event) {\r\n        changes++;\r\n    }\r\n\r\n    @EventHandler\r\n    public void onWorldUnloaded(WorldUnloadEvent event) {\r\n        changes++;\r\n    }\r\n\r\n    @EventHandler\r\n    public void onWorldInit(WorldInitEvent event) {\r\n        changes++;\r\n    }\r\n}\r\n"
  },
  {
    "path": "plugin/src/main/resources/config.yml",
    "content": "# Denizen config.\r\n\r\nDebug:\r\n    # Whether debug information about Denizen should EVER appear in the server console by default\r\n    # NOTE: You almost ALWAYS want this ON! Leave it at 'true'! To hide debug from scripts, set \"debug: false\" on the script containers that work fine.\r\n    # Disabling this can prevent your logs from showing error messages and other important notices!\r\n    Show: true\r\n    # Whether the '/ex' command should tell you to check debug when you use it.\r\n    Ex command help: true\r\n    # Whether the '/ex' command should show debug by default. (if set to false, this inverts the '-q' option)\r\n    Ex command debug: true\r\n    # The max length of a line of debug output. Shorten this if your console is hard to read.\r\n    Line length: 300\r\n    # The max length of debug output. After this length, all further output will be cut off.\r\n    Trim length limit: 4096\r\n    # Whether to permit the \"system.redirect_logging\" mechanism\r\n    Allow console redirection: false\r\n    # Whether to send basic statistic information to the Denizen central server.\r\n    # This is purely to maintain basic information on things like how many servers run any given version of Denizen.\r\n    # Please do not disable this unless it is throwing errors (Please report any errors you receive as well).\r\n    Stats: true\r\n    # Whether to force the help command to be the default bukkit help (required to fix issues with command script help options).\r\n    Override help: true\r\n    # The default debug mode for script containers.\r\n    # This should almost always be left at 'true'.\r\n    Container default: true\r\n    # How many debug messages can appear within the span of a single tick before they simply get blocked.\r\n    # This is available as a backup safety feature to prevent debug output from crashing a server.\r\n    # Set the value lower to better protect this, set the value higher to willfully debug a very very long script.\r\n    Limit per tick: 5000\r\n    # Whether to show some extra info output (will spam your console).\r\n    Extra info: false\r\n    # Whether to show extra advanced detailed 'verbose' info output (will FLOOD your console!).\r\n    Verbose: false\r\n    # Optional text to prefix all debug output.\r\n    Prefix: \"\"\r\n    # Whether to show full information about scripts being loaded (at startup and \"/ex reload\" usage).\r\n    Show loading info: false\r\n    # Whether to show deprecation warnings that are scheduled as future warnings.\r\n    Show future warnings: false\r\n    # Enable this to show a warning in logs when packets are sent asynchronously.\r\n    # Async packets are largely unsupported with a few exceptions, but some plugins send them regardless.\r\n    # This setting helps you catch those plugins if enabled.\r\n    Warn on async packets: false\r\n    # Rate between when slowed warnings appear (in milliseconds). This is used for minor warnings such as newer deprecation notices, to avoid error message flood.\r\n    Warning rate: 10000\r\n    # If set true, core systems will check what thread is performing certain critical actions, and give a warning if the wrong one is used.\r\n    Verify thread: false\r\n    # Optionally you can set an alternate URL here to any valid instance of https://github.com/DenizenScript/DenizenPastingWebsite - 'default' corresponds to https://paste.denizenscript.com/New/Log\r\n    Paste URL: default\r\n\r\nScripts:\r\n    # Whether backwards compatibility for older Spigot names should be enabled (e.g. 'GENERIC_MAX_HEALTH' as an attribute instead of 'max_health').\r\n    # This is not needed if your scripts all use modern names. Take caution where names may be stored (eg in flag data).\r\n    Support legacy Spigot names: true\r\n    World:\r\n        Events:\r\n            On player chats:\r\n                # Whether to use the dangerous 'async' chat event (not recommended!)\r\n                Use asynchronous event: false\r\n            On time changes:\r\n                Frequency of check: 250t\r\n    Interact:\r\n        # Default speed for interact script queues\r\n        Queue speed: instant\r\n    # Default speed for new queues (via run command usually)\r\n    Queue speed: instant\r\n    # What character encoding to use. 'default' indicates system default, 'utf8' is suggested as a preferred encoding in most cases.\r\n    Encoding: utf8\r\n    Command:\r\n        # If set to 'true', the command scripts engine will automatically initialize when the server loads.\r\n        # If false, it will only initialize once at least one command script is loaded.\r\n        # This defaults to false to avoid any possible interference on servers that don't use command scripts.\r\n        Auto init: false\r\n    Economy:\r\n        # If enabled, async calls to economy scripts or PlaceholderAPI tags will be sent through to the main thread.\r\n        # This will remove the illegal-async-access warning, at the cost of risking thread lockup or other issues.\r\n        # If you're wondering why async is blocked, see explanation @ https://i.alexgoodwin.media/i/misc/2fdd06.png\r\n        # IF YOU HAVE ASYNC WARNINGS FROM DENIZEN, THIS IS NOT HOW TO FIX IT.\r\n        # YOU SHOULD YELL AT THE DEVELOPER OF WHATEVER PLUGIN IS TRYING TO MAKE SYNC-ONLY CALLS ASYNC.\r\n        # ASYNC USAGES OF THE BUKKIT API ARE NEVER ALLOWED EXCEPT WHERE EXPLICITLY DOCUMENTED OTHERWISE.\r\n        Pass async to main thread: false\r\n\r\n# Settings related to queues.\r\nQueues:\r\n    # Toggle which parts of a queue ID are used for the queue ID generator.\r\n    # At least one (or both) of \"numeric\" or \"words\" must be enabled (otherwise, queue IDs cannot be unique).\r\n    Id parts:\r\n        # Whether to include the queue prefix - usually the script name.\r\n        Prefix: true\r\n        # Whether to include the numeric ID - incremental number, guaranteed unique.\r\n        # Be warned that disabling this reduces the guarantee of unique IDs.\r\n        Numeric: true\r\n        # Whether to include random words - helps easily distinguish queues in logs. Can create partial uniqueness if numeric is turned off.\r\n        Words: true\r\n\r\n# The default options for new Denizen NPCs\r\n# Whether to spawn NPCs with given traits, and what settings to have by default\r\nTraits:\r\n    Health:\r\n        Enabled: false\r\n        # Whether to block drops from NPCs that have the health trait by default.\r\n        Block drops: false\r\n        Respawn:\r\n            # Whether the health trait should control respawn by default.\r\n            Enabled: false\r\n            # How long after death before ethe NPC should respawn by default.\r\n            Delay: 10s\r\n        Animated death:\r\n            # Whether the NPC should play a death animation by default.\r\n            Enabled: false\r\n\r\n# Whether triggers are on by default, and their individual settings\r\nTriggers:\r\n    Chat:\r\n        Enabled: true\r\n        Use asynchronous event: false\r\n        # Note: cooldowns, ranges, etc. for all triggers should be edited in your script's \"trigger\" command, not in the config\r\n        Cooldown: 2s\r\n        Range: 3\r\n        Overhearing range: 4\r\n        Prerequisites:\r\n            Must be looking in direction of NPC: true\r\n            Must be able to see NPC: true\r\n        Formats:\r\n            Player to NPC: \"You -> <npc.nickname>: <text>\"\r\n            Player to NPC overheard: \"<player.name> -> <npc.nickname>: <text>\"\r\n        Appears globally:\r\n            If triggers missing: true\r\n            If triggers failed: true\r\n            If NPC uninteractable: true\r\n    Click:\r\n        Enabled: true\r\n        Cooldown: 2s\r\n        # Set to a positive value to set a click radius limit. Cannot exceed player click range (hand reach).\r\n        Range: -1\r\n    Damage:\r\n        Enabled: false\r\n        Cooldown: 0.5s\r\n    Proximity:\r\n        Enabled: false\r\n        Cooldown: 1s\r\n        Range: 10\r\n\r\nCommands:\r\n    Engage:\r\n        # the default timeout for engage, if not specifically set otherwise\r\n        Timeout: 150s\r\n    While:\r\n        # how many times the WHILE command can loop before it gives up\r\n        # Set to 0 for infinite\r\n        Max loops: 10000\r\n    Chat:\r\n        # settings for the Chat command, these override Citizens2 settings\r\n        Options:\r\n            Multiple targets format: \"%target%, %target%, %target%, and others\"\r\n            # set to -1 to disable bystanders overhearing, or 0 for all players on the server to hear\r\n            Range for bystanders: 5.0\r\n        Formats:\r\n            No target: \"[<[talker].name>]: <[message]>\"\r\n            To target: \"[<[talker].name>] -> You: <[message]>\"\r\n            With target to bystanders: \"[<[talker].name>] -> <[target].name>: <[message]>\"\r\n            With targets to bystanders: \"[<[talker].name>] -> [<[targets]>]: <[message]>\"\r\n    CreateWorld:\r\n        # Whether non-alphanumeric symbols are allowed in world names. Letters, numbers, unicode text, '-', '_', and ' ' are allowed always.\r\n        # If this is changed to 'true', then other symbols like '/' can be used in a world name (effectively putting the world inside a subfolder).\r\n        Allow symbols in names: false\r\n        # Whether 'weird' paths are allowed. These are paths containing '..', inside the \"plugins/\" folder, or other almost-certainly-invalid paths like that.\r\n        Allow weird paths: false\r\n    Delete:\r\n        # Whether scripts are allowed to delete files from your server\r\n        # This is mostly to clean up saves, but could potentially be abused.\r\n        # Set to 'false' if you're worried about security.\r\n        Allow file deletion: true\r\n    Remove:\r\n        # If set to true, the 'remove' command will always show a warning if it's removing all entities of a type from the world.\r\n        # This is usually an intentional action, but can happen by mistake.\r\n        # If entities are disappearing in groups unexpectedly, enable this config option to verify whether the 'remove' command is the source.\r\n        Always warn on mass delete: false\r\n    Restart:\r\n        # Whether scripts are allowed to stop or restart your server.\r\n        # This is usually only done intentionally, and quite identifiable if done on accident,\r\n        # Though you may wish to disable this if your server is stopping and you don't know why,\r\n        # or are just concerned about security in general.\r\n        # Generally only restart should be on. Exception examples include a server manager system, which would restart\r\n        # a server that stops more 'properly' than the restart command might.\r\n        #\r\n        # Note that it is still possible at this time to 'execute' a stop or restart instruction, which can be\r\n        # blocked by blocking the commands themselves with the command event, like so: 'on stop|restart command'\r\n        Allow server stop: false\r\n        Allow server restart: true\r\n    Filecopy:\r\n        # Whether scripts are allowed to copy files across your server\r\n        # Note that this could be abused by overwriting existing files.\r\n        # Set to 'false' if you're worried about security.\r\n        Allow copying files: true\r\n    Log:\r\n        # The log command writes to file, which is potentially dangerous\r\n        # Set to 'false' if you're worried about security.\r\n        Allow logging: true\r\n    Yaml:\r\n        # Whether the YAML command (or any other file-alteration command) is allowed to save outside the minecraft server folder.\r\n        # Set to 'false' if you want good security.\r\n        Allow saving outside folder: false\r\n        # Optionally: set a limited path, where all edits must be contained to.\r\n        # A good path to use for extra security is: plugins/Denizen/data/\r\n        # Leave at \"none\" to disable this optional protection.\r\n        Limit path: none\r\n    Redis:\r\n        # Whether to allow the redis command.\r\n        Allow: true\r\n    SQL:\r\n        # Whether to allow the SQL command.\r\n        Allow: true\r\n    Mongo:\r\n        # Whether to allow the Mongo command.\r\n        Allow: true\r\n    Webget:\r\n        # Whether to allow scripts to read arbitrary data from the web, which can also potentially enable tracking of your private server data.\r\n        # Generally this is safe, but set to 'false' if you want very strict security.\r\n        Allow: true\r\n    WebServer:\r\n        # Whether to allow the 'webserver' command. This command is potentially abusable to expose your server's data.\r\n        # In legitimate usage, this command can still accidentally leak information or induce server lag.\r\n        Allow: false\r\n        # The file path root under plugins/Denizen/ that any usages of the \"FILE:\" or \"CACHED_FILE:\" determinations must refer to files under (in addition to other file read security restrictions).\r\n        Webroot: webroot/\r\n    File:\r\n        # Set 'true' to enable the 'fileread' generic command.\r\n        # This setting does not affect YAML or other specific-file-type commands.\r\n        Allow read: false\r\n        # Set 'true' to enable the 'filewrite' generic command.\r\n        # This setting does not affect YAML or other specific-file-type commands.\r\n        Allow write: false\r\n        # The restricted file path, under plugins/Denizen/, for \"fileread\" and \"filewrite\" to be limited to. Set to 'none' to remove the path restriction.\r\n        Restrict path: data/\r\n    # GENERAL SECURITY NOTE: Always run your server on a user with access to nothing but the minecraft folder\r\n    # Otherwise, a bad script or plugin could potentially damage things (though being careful with these settings should reduce the risk of a script doing so)\r\n    # ALSO: Always read scripts you download for anything suspicious looking, EG the log command, the file_delete mechanism, ...\r\n    Security:\r\n        # Some actions on a server are technically possible and supported by Denizen, but considered 'restricted'\r\n        # That is, misuse of them could potentially lead to you or your server getting in trouble.\r\n        # For example, generating fake tab list entries that look like real players is considered forbidden by Mojang, and may result in your server being blacklisted.\r\n        # Scripters caught abusing these tools may also be refused support by the Denizen team or on other support platforms.\r\n        # However, the code needed to do that can also perform useful behaviors, such as better organizing the tab list, or allowing custom tab completions.\r\n        # As such, you are able to change the 'Allow restricted actions' setting to 'true' if you wish to achieve the non-restricted results that require restricted code access.\r\n        # Note that you have been warned of the potential consequences of abusing these tools,\r\n        # and that none of Denizen, the Denizen team, Spigot, Mojang, etc. in any way support or endorse abusing these tools.\r\n        # Enable at your own risk.\r\n        Allow restricted actions: false\r\n\r\nTags:\r\n    # How long a tag can parse before force-closing the tag parser engine. Set to 0 to disable tag parse timing entirely.\r\n    Timeout: 10\r\n    # Whether the tag system should still timeout when debug is off.\r\n    Timeout when silent: false\r\n    # Settings for tags like cuboid.blocks, location.find.blocks, ...\r\n    Block tags:\r\n        # How many blocks can be read, max, before stopping the tag in place\r\n        Max blocks: 1000000\r\n    Chat history:\r\n        # How many player messages will be stored for each player (<player.chat_history>, etc.)\r\n        Max messages: 10\r\n    List flags:\r\n        # The list_flags and flag_map tools are not meant for normal usage in real scripts.\r\n        # If you have a very specific use-case that requires this, it is recommended you ask on the Denizen Discord and verify it actually makes sense.\r\n        # If you've verified the idea makes sense, you can swap this option to 'true' to disable the default warning.\r\n        I know what im doing and need this: false\r\n    # If the 'nullify skull skin ids' option is changed to 'true', any skull_skin that has a valid texture but is missing a UUID or name, will automatically supply null values for the UUID and Name.\r\n    # Skulls that are missing *both* name and id will be nullified regardless of this setting. Skulls that contain *both* an ID and name will retain their set values regardless of this setting.\r\n    # The benefit of enabling this is it prevents locking the server thread to download the data from Mojang servers.\r\n    # The risk is that it may interfere with other plugins or systems that expect correct data to be set.\r\n    Nullify skull skin ids: false\r\n    # If true, polygons default to WorldEdit style block-inclusive logic. If false, use precise 'exclusive' logic.\r\n    Polygon default inclusive: false\r\n\r\n# Java Reflection is the toolkit for accessing raw underlying Java data.\r\n# Denizen partially exposes this, for example in 'JavaReflectedObjectTag', as some scripts may have use for this.\r\n# This however often isn't needed, and sometimes can be abused to access things a script shouldn't have access to.\r\n# Accordingly, security-minded server owners might prefer to lock all reflection-allow options to 'false' to be safe.\r\n# However, the default settings of allowing reads but not writes or executions is safe for most servers.\r\n# Only servers that really need it should enable write/execute options.\r\nReflection:\r\n    # Whether to allow tags to read raw field data in scripts. This is generally safe.\r\n    Allow reading fields: true\r\n    # Whether to allow scripts to reflect into core object methods like \"toString\" or \"equals\". This is usually safe, but might be abusable in certain edge edges, if for example some object somewhere improperly handles the \"equals\" method to execute logic or similar.\r\n    Allow core methods: true\r\n    #\r\n    # ==== WARNING ====\r\n    # If a scripter or script's documentation has told you to enable access to reflection methods in your config, that script should be scrutinized very carefully.\r\n    # Make sure either you know exactly what it's going to do, or you very much trust whoever gave you it.\r\n    # These tools are very powerful and dangerous.\r\n    # =================\r\n    #\r\n    # Whether to allow the 'reflectionset' command. This command is highly abusable and should be left disabled.\r\n    Allow set command: false\r\n    # Whether the 'reflectionset' command may set to 'private' fields. This is very likely to be dangerous if enabled.\r\n    Allow set private fields: false\r\n    # Whether the 'reflectionset' command may set to 'final' fields. This is very likely to be dangerous if enabled.\r\n    Allow set final fields: false\r\n\r\nSaves:\r\n    # How long (in seconds) before the offline player flag cache times out.\r\n    # Set to -1 to keep offline player flags loaded perpetually, or to 0 to never cache.\r\n    Offline player cache timeout: 300\r\n    # When set to 'true', player data will be loaded offthread during the login sequence, to avoid adding load to the server while players join.\r\n    Load async on login: true\r\n    # When set to 'true', all automatic flag cleanups will be skipped.\r\n    # This might save some processing time on servers that rarely use flag expirations, but otherwise should be left as 'false'.\r\n    Skip flag cleaning: false\r\n    # When set to 'true', skips flag cleaning for ChunkTag and LocationTag flags.\r\n    Skip chunk flag cleaning: false\r\n    # When set to 'true', player flag autosaving will be delayed whenever world saves are disabled (eg via '/save-off').\r\n    # Player flags will still save as normal during shutdown, or when world saving is enabled.\r\n    # This checks based on your default world (server.worlds.first).\r\n    Only save if world save is on: false\r\n    # Delay (DurationTag) after modifying a PlayerTag's offline world-data (gamemode, inventory, etc) before it is saved to file.\r\n    # Larger delays are better for performance when this is used often. Shorter delays are better for interop.\r\n    # Set to '0' for instant save on modification. Set to '999h' to only save at shutdown.\r\n    Save world player file delay: 10s\r\n    # Duration to retain offline player data being purging.\r\n    # Higher values are better for perf but worse for RAM. Lower values are better for interop. Set to '0' to never cache.\r\n    World player data max cache: 1h\r\n\r\nPackets:\r\n    # Whether to allow Denizen to intercept packets from and to player clients.\r\n    # This enables access to certain ScriptEvents, such as PlayerSteers and PlayerReceivesMessage.\r\n    # It also enables hiding item script IDs and most likely has no real reason to be disabled.\r\n    # Note that changing this setting requires a full server restart.\r\n    Interception: true\r\n    # Whether to automatically initialize the packet interceptor at startup.\r\n    # If false, it will only initialize once at least one packet-interception command or event is used.\r\n    # This defaults to false to avoid any possible interference on servers that don't need packet interception.\r\n    # If this is false, some packet-interception related tags may not work.\r\n    Auto init: false\r\n\r\n# This is a special map of custom colors by name, for the '&' tag base to use. 'default' is used when the name is unrecognized. You can add extra keys here.\r\n# Tags work here and will be pre-parsed at load time. RGB color codes work here.\r\nColors:\r\n    # The base text color. 'narrate' command usages should usually start with <&[base]>\r\n    base: <&2>\r\n    # Special/stand-out/focused/emphasized text within a message. 'narrate' commands that need to emphasize a value should use this on those values.\r\n    emphasis: <&b>\r\n    # Error messages. 'narrate' commands displaying an error message should use this.\r\n    error: <red>\r\n    # Warning messages. 'narrate' commands displaying a warning message should use this.\r\n    warning: <yellow>\r\n    # Your preferred color for item names. Item script \"display name\" keys should usually use this.\r\n    item: <gold>\r\n    # Your preferred item lore base color. Item script \"lore\" lines should start with this.\r\n    lore: <&7>\r\n    # Your preferred color for NPC names. Scripts that use the \"create\" command should usually use this.\r\n    npc: <&a>\r\n    # Default: the backup for when a color label is unrecognized. Generally don't touch this.\r\n    default: <white>\r\n"
  },
  {
    "path": "plugin/src/main/resources/plugin.yml",
    "content": "name: Denizen\r\nauthors: ['The DenizenScript Team']\r\nversion: ${project.version} (build ${BUILD_NUMBER}-${BUILD_CLASS})\r\nmain: com.denizenscript.denizen.Denizen\r\nsoftdepend: [Citizens, Vault]\r\n\r\napi-version: '1.17'\r\n\r\nlibraries:\r\n  - org.mongodb:mongodb-driver-sync:4.8.1\r\n  - redis.clients:jedis:4.3.1\r\n\r\ncommands:\r\n  denizen:\r\n    description: Lists denizen commands.\r\n    usage: /denizen help\r\n    permission: denizen.basic\r\n  denizenclickable:\r\n    description: Executes a Denizen clickable command.\r\n    usage: /denizenclickable\r\n    permission: denizen.clickable\r\n  ex:\r\n    description: Executes a Denizen script command.\r\n    usage: /ex (-q) <Denizen script command> (arguments)\r\n    permission: denizen.ex\r\n  exs:\r\n    description: Executes a Denizen script command in a sustained queue.\r\n    usage: /exs (-q) <Denizen script command> (arguments)\r\n    permission: denizen.ex\r\n\r\npermissions:\r\n  denizen.*:\r\n    description: Gives access to Denizen commands\r\n    default: op\r\n    children:\r\n      denizen.basic: true\r\n      denizen.dscript: true\r\n      denizen.ex: true\r\n      denizen.debug: true\r\n      denizen.submit: true\r\n      denizen.npc.health: true\r\n      denizen.npc.sneak: true\r\n      denizen.npc.effect: true\r\n      denizen.npc.fish: true\r\n      denizen.npc.sleep: true\r\n      denizen.npc.stand: true\r\n      denizen.npc.sit: true\r\n      denizen.npc.nameplate: true\r\n      denizen.npc.nickname: true\r\n      denizen.npc.trigger: true\r\n      denizen.npc.assign: true\r\n      denizen.npc.constants: true\r\n      denizen.npc.pushable: true\r\n      denizen.npc.mirror: true\r\n  denizen.npc.*:\r\n    description: Gives access to the /npc commands added by Denizen\r\n    default: op\r\n    children:\r\n      denizen.npc.health: true\r\n      denizen.npc.sneak: true\r\n      denizen.npc.effect: true\r\n      denizen.npc.fish: true\r\n      denizen.npc.sleep: true\r\n      denizen.npc.stand: true\r\n      denizen.npc.sit: true\r\n      denizen.npc.nameplate: true\r\n      denizen.npc.nickname: true\r\n      denizen.npc.trigger: true\r\n      denizen.npc.assign: true\r\n      denizen.npc.constants: true\r\n      denizen.npc.pushable: true\r\n      denizen.npc.mirror: true\r\n"
  },
  {
    "path": "pom.xml",
    "content": "<!-- Denizen build file -->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.denizenscript</groupId>\n    <artifactId>denizen-parent</artifactId>\n    <packaging>pom</packaging>\n    <version>1.3.2-SNAPSHOT</version>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <BUILD_NUMBER>Unknown</BUILD_NUMBER>\n        <BUILD_CLASS>CUSTOM</BUILD_CLASS>\n        <DENIZEN_VERSION>1.3.2</DENIZEN_VERSION>\n    </properties>\n\n    <modules>\n        <module>plugin</module>\n        <module>paper</module>\n        <module>v26_1</module>\n        <module>v1_21</module>\n        <module>v1_20</module>\n        <module>v1_19</module>\n        <module>v1_18</module>\n        <module>v1_17</module>\n        <module>dist</module>\n    </modules>\n\n    <!-- Repositories -->\n    <repositories>\n        <repository>\n            <id>everything</id>\n            <url>https://maven.citizensnpcs.co/repo</url>\n        </repository>\n        <!--<repository>\n            <id>spigot-repo</id>\n            <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>\n        </repository>-->\n    </repositories>\n\n    <ciManagement>\n        <system>jenkins</system>\n        <url>https://ci.citizensnpcs.co</url>\n    </ciManagement>\n    <scm>\n        <connection>scm:git:git://github.com/DenizenScript/Denizen.git</connection>\n        <developerConnection>scm:git:git:@github.com:DenizenScript/Denizen.git</developerConnection>\n        <url>https://github.com/DenizenScript/Denizen/tree/dev/</url>\n    </scm>\n    <distributionManagement>\n        <repository>\n            <id>citizens-repo</id>\n            <url>https://maven.citizensnpcs.co/repo</url>\n        </repository>\n    </distributionManagement>\n\n    <build>\n        <defaultGoal>clean package install</defaultGoal>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-deploy-plugin</artifactId>\n                <version>3.0.0</version>\n                <executions>\n                    <execution>\n                        <id>default-deploy</id>\n                        <phase>none</phase>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.0</version>\n                <configuration>\n                    <source>16</source>\n                    <target>16</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n    <url>https://denizenscript.com/</url>\n</project>\n"
  },
  {
    "path": "v1_17/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.denizenscript</groupId>\n    <artifactId>denizen-v1_17</artifactId>\n    <version>1.3.2-SNAPSHOT</version>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen</artifactId>\n            <version>${project.version}</version>\n            <scope>compile</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>1.17.1-R0.1-SNAPSHOT</version>\n            <scope>provided</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>*</groupId>\n                    <artifactId>*</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot</artifactId>\n            <version>1.17.1-R0.1-SNAPSHOT</version>\n            <classifier>remapped-mojang</classifier>\n            <scope>provided</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>*</groupId>\n                    <artifactId>*</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>net.md-5</groupId>\n                <artifactId>specialsource-maven-plugin</artifactId>\n                <version>1.2.5</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>remap</goal>\n                        </goals>\n                        <id>remap-obf</id>\n                        <configuration>\n                            <srgIn>org.spigotmc:minecraft-server:1.17.1-R0.1-SNAPSHOT:txt:maps-mojang</srgIn>\n                            <reverse>true</reverse>\n                            <remappedDependencies>org.spigotmc:spigot:1.17.1-R0.1-SNAPSHOT:jar:remapped-mojang</remappedDependencies>\n                            <remappedArtifactAttached>true</remappedArtifactAttached>\n                            <remappedClassifierName>remapped-obf</remappedClassifierName>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>remap</goal>\n                        </goals>\n                        <id>remap-spigot</id>\n                        <configuration>\n                            <inputFile>${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar</inputFile>\n                            <srgIn>org.spigotmc:minecraft-server:1.17.1-R0.1-SNAPSHOT:csrg:maps-spigot</srgIn>\n                            <remappedDependencies>org.spigotmc:spigot:1.17.1-R0.1-SNAPSHOT:jar:remapped-obf</remappedDependencies>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.10.1</version>\n                <configuration>\n                    <source>16</source>\n                    <target>16</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/Handler.java",
    "content": "package com.denizenscript.denizen.nms.v1_17;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_17.helpers.*;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.blocks.BlockLightImpl;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.nbt.ByteArrayTag;\r\nimport net.minecraft.nbt.StringTag;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.chat.MutableComponent;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.Container;\r\nimport net.minecraft.world.Nameable;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.World;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryCustom;\r\nimport org.bukkit.craftbukkit.v1_17_R1.persistence.CraftPersistentDataContainer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.util.CraftChatMessage;\r\nimport org.bukkit.craftbukkit.v1_17_R1.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_17_R1.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.persistence.PersistentDataContainer;\r\nimport org.spigotmc.AsyncCatcher;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class Handler extends NMSHandler {\r\n\r\n    public Handler() {\r\n        advancementHelper = new AdvancementHelperImpl();\r\n        animationHelper = new AnimationHelperImpl();\r\n        blockHelper = new BlockHelperImpl();\r\n        chunkHelper = new ChunkHelperImpl();\r\n        customEntityHelper = new CustomEntityHelperImpl();\r\n        entityHelper = new EntityHelperImpl();\r\n        fishingHelper = new FishingHelperImpl();\r\n        itemHelper = new ItemHelperImpl();\r\n        packetHelper = new PacketHelperImpl();\r\n        playerHelper = new PlayerHelperImpl();\r\n        worldHelper = new WorldHelperImpl();\r\n        enchantmentHelper = new EnchantmentHelperImpl();\r\n    }\r\n\r\n    private final ProfileEditor profileEditor = new ProfileEditorImpl();\r\n\r\n    private boolean wasAsyncCatcherEnabled;\r\n\r\n    @Override\r\n    public void disableAsyncCatcher() {\r\n        wasAsyncCatcherEnabled = AsyncCatcher.enabled;\r\n        AsyncCatcher.enabled = false;\r\n    }\r\n\r\n    @Override\r\n    public void undisableAsyncCatcher() {\r\n        AsyncCatcher.enabled = wasAsyncCatcherEnabled;\r\n    }\r\n\r\n    @Override\r\n    public boolean isExactServerVersionMatch() {\r\n        return ((CraftMagicNumbers) CraftMagicNumbers.INSTANCE).getMappingsVersion().equals(\"f0e3dfc7390de285a4693518dd5bd126\");\r\n    }\r\n\r\n    @Override\r\n    public double[] getRecentTps() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().recentTps;\r\n    }\r\n\r\n    @Override\r\n    public Sidebar createSidebar(Player player) {\r\n        return new SidebarImpl(player);\r\n    }\r\n\r\n    @Override\r\n    public BlockLight createBlockLight(Location location, int lightLevel, long ticks) {\r\n        return BlockLightImpl.createLight(location, lightLevel, ticks);\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile fillPlayerProfile(PlayerProfile playerProfile) {\r\n        try {\r\n            if (playerProfile != null) {\r\n                GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n                gameProfile.getProperties().get(\"textures\").clear();\r\n                if (playerProfile.getTextureSignature() != null) {\r\n                    gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n                }\r\n                else {\r\n                    gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture()));\r\n                }\r\n                MinecraftServer minecraftServer = ((CraftServer) Bukkit.getServer()).getServer();\r\n                GameProfile gameProfile1 = null;\r\n                if (gameProfile.getId() != null) {\r\n                    gameProfile1 = minecraftServer.getProfileCache().get(gameProfile.getId()).orElse(null);\r\n                }\r\n                if (gameProfile1 == null && gameProfile.getName() != null) {\r\n                    gameProfile1 = minecraftServer.getProfileCache().get(gameProfile.getName()).orElse(null);\r\n                }\r\n                if (gameProfile1 == null) {\r\n                    gameProfile1 = gameProfile;\r\n                }\r\n                if (playerProfile.hasTexture()) {\r\n                    gameProfile1.getProperties().get(\"textures\").clear();\r\n                    if (playerProfile.getTextureSignature() != null) {\r\n                        gameProfile1.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n                    }\r\n                    else {\r\n                        gameProfile1.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture()));\r\n                    }\r\n                }\r\n                if (Iterables.getFirst(gameProfile1.getProperties().get(\"textures\"), null) == null) {\r\n                    gameProfile1 = minecraftServer.getSessionService().fillProfileProperties(gameProfile1, true);\r\n                }\r\n                Property property = Iterables.getFirst(gameProfile1.getProperties().get(\"textures\"), null);\r\n                return new PlayerProfile(gameProfile1.getName(), gameProfile1.getId(),\r\n                        property != null ? property.getValue() : null,\r\n                        property != null ? property.getSignature() : null);\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public String getTitle(Inventory inventory) {\r\n        Container nms = ((CraftInventory) inventory).getInventory();\r\n        if (nms instanceof Nameable) {\r\n            return CraftChatMessage.fromComponent(((Nameable) nms).getDisplayName());\r\n        }\r\n        else if (MINECRAFT_INVENTORY.isInstance(nms)) {\r\n            try {\r\n                return (String) INVENTORY_TITLE.get(nms);\r\n            }\r\n            catch (IllegalAccessException e) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return \"Chest\";\r\n    }\r\n\r\n    public static final Class MINECRAFT_INVENTORY;\r\n    public static final Field INVENTORY_TITLE;\r\n    public static final Field ENTITY_BUKKITYENTITY = ReflectionHelper.getFields(Entity.class).get(\"bukkitEntity\");\r\n\r\n    static {\r\n        Class minecraftInv = null;\r\n        Field title = null;\r\n        try {\r\n            for (Class clzz : CraftInventoryCustom.class.getDeclaredClasses()) {\r\n                if (CoreUtilities.toLowerCase(clzz.getName()).contains(\"minecraftinventory\")) { // MinecraftInventory.\r\n                    minecraftInv = clzz;\r\n                    title = clzz.getDeclaredField(\"title\");\r\n                    title.setAccessible(true);\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            e.printStackTrace();\r\n        }\r\n        MINECRAFT_INVENTORY = minecraftInv;\r\n        INVENTORY_TITLE = title;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Player player) {\r\n        GameProfile gameProfile = ((CraftPlayer) player).getProfile();\r\n        Property property = Iterables.getFirst(gameProfile.getProperties().get(\"textures\"), null);\r\n        return new PlayerProfile(gameProfile.getName(), gameProfile.getId(),\r\n                property != null ? property.getValue() : null,\r\n                property != null ? property.getSignature() : null);\r\n    }\r\n\r\n    @Override\r\n    public ProfileEditor getProfileEditor() {\r\n        return profileEditor;\r\n    }\r\n\r\n    @Override\r\n    public List<BiomeNMS> getBiomes(World world) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        ArrayList<BiomeNMS> output = new ArrayList<>();\r\n        for (Map.Entry<ResourceKey<Biome>, Biome> pair : level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).entrySet()) {\r\n            output.add(new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(pair.getKey().location())));\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeNMS(World world, NamespacedKey key) {\r\n        BiomeNMSImpl impl = new BiomeNMSImpl(((CraftWorld) world).getHandle(), key);\r\n        if (impl.biomeBase == null) {\r\n            return null;\r\n        }\r\n        return impl;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeAt(Block block) {\r\n        // Based on CraftWorld source\r\n        ServerLevel level = ((CraftWorld) block.getWorld()).getHandle();\r\n        Biome biome = level.getNoiseBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2);\r\n        ResourceLocation key = level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getKey(biome);\r\n        return new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key));\r\n    }\r\n\r\n    @Override\r\n    public ArrayList<String> containerListFlags(PersistentDataContainer container, String prefix) {\r\n        prefix = \"denizen:\" + prefix;\r\n        ArrayList<String> output = new ArrayList<>();\r\n        for (String key : ((CraftPersistentDataContainer) container).getRaw().keySet()) {\r\n            if (key.startsWith(prefix)) {\r\n                output.add(key.substring(prefix.length()));\r\n            }\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public boolean containerHas(PersistentDataContainer container, String key) {\r\n        return ((CraftPersistentDataContainer) container).getRaw().containsKey(key);\r\n    }\r\n\r\n    @Override\r\n    public String containerGetString(PersistentDataContainer container, String key) {\r\n        net.minecraft.nbt.Tag base = ((CraftPersistentDataContainer) container).getRaw().get(key);\r\n        if (base instanceof StringTag) {\r\n            return base.getAsString();\r\n        }\r\n        else if (base instanceof ByteArrayTag) {\r\n            return new String(((ByteArrayTag) base).getAsByteArray(), StandardCharsets.UTF_8);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static BaseComponent[] componentToSpigot(Component nms) {\r\n        String json = Component.Serializer.toJson(nms);\r\n        return ComponentSerializer.parse(json);\r\n    }\r\n\r\n    public static MutableComponent componentToNMS(BaseComponent[] spigot) {\r\n        String json = ComponentSerializer.toString(spigot);\r\n        return Component.Serializer.fromJson(json);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/ReflectionMappingsInfo.java",
    "content": "package com.denizenscript.denizen.nms.v1_17;\r\n\r\npublic class ReflectionMappingsInfo {\r\n\r\n    // Contents gathered via https://minidigger.github.io/MiniMappingViewer/#/mojang/server/1.17.1\r\n\r\n    public static String AdvancementList_roots = \"c\";\r\n    public static String AdvancementList_tasks = \"d\";\r\n\r\n    public static String BlockBehaviour_explosionResistance = \"aI\";\r\n\r\n    public static String RecipeBook_known = \"a\";\r\n\r\n    public static String Entity_onGround = \"z\";\r\n    public static String Entity_DATA_SHARED_FLAGS_ID = \"Z\";\r\n    public static String Entity_DATA_CUSTOM_NAME = \"aJ\";\r\n    public static String Entity_DATA_CUSTOM_NAME_VISIBLE = \"aK\";\r\n\r\n    public static String LivingEntity_attackStrengthTicker = \"aQ\";\r\n\r\n    public static String Player_DATA_PLAYER_ABSORPTION_ID = \"d\";\r\n    public static String Player_DATA_PLAYER_MODE_CUSTOMISATION = \"bP\";\r\n\r\n    public static String ServerPlayer_respawnForced = \"cS\";\r\n\r\n    public static String EnderMan_DATA_CREEPY = \"bV\";\r\n\r\n    public static String Zombie_inWaterTime = \"cc\";\r\n\r\n    public static String Item_maxStackSize = \"c\";\r\n\r\n    public static String Level_isClientSide = \"y\";\r\n\r\n    public static String ThreadedLevelLightEngine_addTask = \"a\";\r\n\r\n    public static String ItemEntity_DATA_ITEM = \"c\";\r\n\r\n    public static String Biome_climateSettings = \"k\";\r\n    public static String Biome_ClimateSettings_temperature = \"c\";\r\n    public static String Biome_ClimateSettings_downfall = \"e\";\r\n    public static String Biome_ClimateSettings_precipitation = \"b\";\r\n    public static String BiomeSpecialEffects_foliageColorOverride = \"f\";\r\n\r\n    public static String ThreadedLevelLightEngine_Update_PRE_UPDATE = \"a\";\r\n\r\n    public static String Connection_receiving = \"i\";\r\n\r\n    public static String ServerGamePacketListenerImpl_aboveGroundTickCount = \"C\";\r\n    public static String ServerGamePacketListenerImpl_aboveGroundVehicleTickCount = \"E\";\r\n    public static String ServerGamePacketListenerImpl_connection = \"a\";\r\n\r\n    public static String ClientboundPlayerAbilitiesPacket_walkingSpeed = \"j\";\r\n\r\n    public static String ClientboundSetEntityDataPacket_packedItems = \"b\";\r\n\r\n    public static String ClientboundBlockBreakAckPacket_state = \"c\";\r\n\r\n    public static String ClientboundSectionBlocksUpdatePacket_sectionPos = \"b\";\r\n    public static String ClientboundSectionBlocksUpdatePacket_states = \"d\";\r\n    public static String ClientboundSectionBlocksUpdatePacket_positions = \"c\";\r\n\r\n    public static String ClientboundMoveEntityPacket_xa = \"b\";\r\n    public static String ClientboundMoveEntityPacket_ya = \"c\";\r\n    public static String ClientboundMoveEntityPacket_za = \"d\";\r\n    public static String ClientboundMoveEntityPacket_yRot = \"e\";\r\n    public static String ClientboundMoveEntityPacket_xRot = \"f\";\r\n\r\n    public static String ClientboundSetEntityMotionPacket_id = \"a\";\r\n\r\n    public static String ClientboundTeleportEntityPacket_id = \"a\";\r\n    public static String ClientboundTeleportEntityPacket_x = \"b\";\r\n    public static String ClientboundTeleportEntityPacket_y = \"c\";\r\n    public static String ClientboundTeleportEntityPacket_z = \"d\";\r\n    public static String ClientboundTeleportEntityPacket_yRot = \"e\";\r\n    public static String ClientboundTeleportEntityPacket_xRot = \"f\";\r\n\r\n    public static String ClientboundLevelChunkPacket_buffer = \"g\";\r\n    public static String ClientboundLevelChunkPacket_blockEntitiesTags = \"h\";\r\n\r\n    public static String ClientboundMerchantOffersPacket_containerId = \"a\";\r\n\r\n    public static String FishingHook_nibble = \"aq\";\r\n    public static String FishingHook_timeUntilLured = \"ar\";\r\n    public static String FishingHook_timeUntilHooked = \"as\";\r\n\r\n    public static String ServerLevel_sleepStatus = \"H\";\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/AdvancementHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.AdvancementHelper;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizen.nms.v1_17.Handler;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.advancements.*;\r\nimport net.minecraft.advancements.critereon.ImpossibleTrigger;\r\nimport net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.PlayerAdvancements;\r\nimport net.minecraft.server.ServerAdvancementManager;\r\nimport net.minecraft.server.dedicated.DedicatedServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.Collections;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.Set;\r\n\r\npublic class AdvancementHelperImpl extends AdvancementHelper {\r\n\r\n    private static final String IMPOSSIBLE_KEY = \"impossible\";\r\n    private static final Map<String, Criterion> IMPOSSIBLE_CRITERIA = Collections.singletonMap(IMPOSSIBLE_KEY, new Criterion(new ImpossibleTrigger.TriggerInstance()));\r\n    private static final String[][] IMPOSSIBLE_REQUIREMENTS = new String[][]{{IMPOSSIBLE_KEY}};\r\n\r\n    public static ServerAdvancementManager getAdvancementDataWorld() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getAdvancements();\r\n    }\r\n\r\n    public static Field FIELD_ADVANCEMENTLIST_LISTENER = ReflectionHelper.getFields(AdvancementList.class).getFirstOfType(AdvancementList.Listener.class);\r\n\r\n    @Override\r\n    public void register(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || advancement.registered) {\r\n            return;\r\n        }\r\n        Advancement nms = asNMSCopy(advancement);\r\n        if (advancement.parent == null) {\r\n            Set<Advancement> roots = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_roots, getAdvancementDataWorld().advancements);\r\n            roots.add(nms);\r\n            AdvancementList.Listener something = ReflectionHelper.getFieldValue(AdvancementList.class, FIELD_ADVANCEMENTLIST_LISTENER.getName(), getAdvancementDataWorld().advancements);\r\n            if (something != null) {\r\n                something.onAddAdvancementRoot(nms);\r\n            }\r\n        }\r\n        else {\r\n            Set<Advancement> branches = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_tasks, getAdvancementDataWorld().advancements);\r\n            branches.add(nms);\r\n            AdvancementList.Listener something = ReflectionHelper.getFieldValue(AdvancementList.class, FIELD_ADVANCEMENTLIST_LISTENER.getName(), getAdvancementDataWorld().advancements);\r\n            if (something != null) {\r\n                something.onAddAdvancementTask(nms);\r\n            }\r\n        }\r\n        getAdvancementDataWorld().advancements.advancements.put(nms.getId(), nms);\r\n        advancement.registered = true;\r\n        if (!advancement.hidden && advancement.parent != null) {\r\n            ((CraftServer) Bukkit.getServer()).getHandle().sendAll(new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nms), Collections.emptySet(), Collections.emptyMap()), (net.minecraft.world.entity.player.Player) null);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void unregister(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || !advancement.registered) {\r\n            return;\r\n        }\r\n        Map<ResourceLocation, Advancement> advancements = getAdvancementDataWorld().advancements.advancements;\r\n        ResourceLocation key = asResourceLocation(advancement.key);\r\n        Advancement nms = advancements.get(key);\r\n        if (advancement.parent == null) {\r\n            Set<Advancement> roots = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_roots, getAdvancementDataWorld().advancements);\r\n            roots.remove(nms);\r\n        }\r\n        else {\r\n            Set<Advancement> branches = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_tasks, getAdvancementDataWorld().advancements);\r\n            branches.remove(nms);\r\n        }\r\n        advancements.remove(key);\r\n        advancement.registered = false;\r\n        ((CraftServer) Bukkit.getServer()).getHandle().sendAll(new ClientboundUpdateAdvancementsPacket(false,\r\n                Collections.emptySet(), Collections.singleton(key), Collections.emptyMap()), (net.minecraft.world.entity.player.Player) null);\r\n    }\r\n\r\n    @Override\r\n    public void grantPartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) {\r\n        if (advancement.length <= 1) {\r\n            grant(advancement, player);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            Advancement nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            Map<String, Criterion> criteria = new HashMap<>();\r\n            String[][] requirements = new String[advancement.length][];\r\n            for (int i = 0; i < advancement.length; i++) {\r\n                criteria.put(IMPOSSIBLE_KEY + i, new Criterion(new ImpossibleTrigger.TriggerInstance()));\r\n                requirements[i] = new String[] { IMPOSSIBLE_KEY + i };\r\n            }\r\n            progress.update(IMPOSSIBLE_CRITERIA, IMPOSSIBLE_REQUIREMENTS);\r\n            for (int i = 0; i < len; i++) {\r\n                progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria\r\n            }\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nmsAdvancement),\r\n                    Collections.emptySet(),\r\n                    Collections.singletonMap(nmsAdvancement.getId(), progress)));\r\n        }\r\n        else {\r\n            Advancement nmsAdvancement = getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.key));\r\n            for (int i = 0; i < len; i++) {\r\n                ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY + i);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void grant(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.length > 1) {\r\n            grantPartial(advancement, player, advancement.length);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            Advancement nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(IMPOSSIBLE_CRITERIA, IMPOSSIBLE_REQUIREMENTS);\r\n            progress.grantProgress(IMPOSSIBLE_KEY); // complete impossible criteria\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nmsAdvancement),\r\n                    Collections.emptySet(),\r\n                    Collections.singletonMap(nmsAdvancement.getId(), progress)));\r\n        }\r\n        else {\r\n            Advancement nmsAdvancement = getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.key));\r\n            ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void revoke(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.temporary) {\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.emptySet(),\r\n                    Collections.singleton(asResourceLocation(advancement.key)),\r\n                    Collections.emptyMap()));\r\n        }\r\n        else {\r\n            Advancement nmsAdvancement = getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.key));\r\n            ((CraftPlayer) player).getHandle().getAdvancements().revoke(nmsAdvancement, IMPOSSIBLE_KEY);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        nmsPlayer.connection.send(new ClientboundUpdateAdvancementsPacket(true,\r\n                Collections.emptySet(),\r\n                Collections.emptySet(),\r\n                Collections.emptyMap()));\r\n        PlayerAdvancements data = nmsPlayer.getAdvancements();\r\n        data.save(); // save progress\r\n        data.reload(DedicatedServer.getServer().getAdvancements()); // clear progress\r\n        data.flushDirty(nmsPlayer); // load progress and update client\r\n    }\r\n\r\n    private static Advancement asNMSCopy(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        ResourceLocation key = asResourceLocation(advancement.key);\r\n        Advancement parent = advancement.parent != null\r\n                ? getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.parent))\r\n                : null;\r\n        DisplayInfo display = new DisplayInfo(CraftItemStack.asNMSCopy(advancement.icon),\r\n                Handler.componentToNMS(FormattedTextHelper.parse(advancement.title, ChatColor.WHITE)), Handler.componentToNMS(FormattedTextHelper.parse(advancement.description, ChatColor.WHITE)),\r\n                asResourceLocation(advancement.background), FrameType.valueOf(advancement.frame.name()),\r\n                advancement.toast, advancement.announceToChat, advancement.hidden);\r\n        display.setLocation(advancement.xOffset, advancement.yOffset);\r\n        Map<String, Criterion> criteria = IMPOSSIBLE_CRITERIA;\r\n        String[][] requirements = IMPOSSIBLE_REQUIREMENTS;\r\n        if (advancement.length > 1) {\r\n            criteria = new HashMap<>();\r\n            requirements = new String[advancement.length][];\r\n            for (int i = 0; i < advancement.length; i++) {\r\n                criteria.put(IMPOSSIBLE_KEY + i, new Criterion(new ImpossibleTrigger.TriggerInstance()));\r\n                requirements[i] = new String[] { IMPOSSIBLE_KEY + i };\r\n            }\r\n        }\r\n        return new Advancement(key, parent, display, AdvancementRewards.EMPTY, criteria, requirements);\r\n    }\r\n\r\n    private static ResourceLocation asResourceLocation(NamespacedKey key) {\r\n        return key != null ? new ResourceLocation(key.getNamespace(), key.getKey()) : null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/AnimationHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.AnimationHelper;\r\nimport net.minecraft.world.entity.Entity;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftHorse;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftPolarBear;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Horse;\r\nimport org.bukkit.entity.IronGolem;\r\n\r\npublic class AnimationHelperImpl extends AnimationHelper {\r\n\r\n    public AnimationHelperImpl() {\r\n        register(\"POLAR_BEAR_START_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(true);\r\n            }\r\n        });\r\n        register(\"POLAR_BEAR_STOP_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(false);\r\n            }\r\n        });\r\n        register(\"HORSE_START_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().setStanding(true);\r\n            }\r\n        });\r\n        register(\"HORSE_STOP_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().setStanding(false);\r\n            }\r\n        });\r\n        register(\"HORSE_BUCK\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().makeMad();\r\n            }\r\n        });\r\n        register(\"IRON_GOLEM_ATTACK\", entity -> {\r\n            if (entity instanceof IronGolem) {\r\n                Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n                nmsEntity.level.broadcastEntityEvent(nmsEntity, (byte) 4);\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/BlockHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.BlockHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Direction;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.BellBlock;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.entity.BlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockBehaviour;\r\nimport net.minecraft.world.level.block.state.properties.NoteBlockInstrument;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.material.PushReaction;\r\nimport org.bukkit.Color;\r\nimport org.bukkit.Instrument;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Bell;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.BlockState;\r\nimport org.bukkit.block.Skull;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_17_R1.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.v1_17_R1.block.CraftBlockEntityState;\r\nimport org.bukkit.craftbukkit.v1_17_R1.block.CraftSkull;\r\nimport org.bukkit.craftbukkit.v1_17_R1.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_17_R1.util.CraftMagicNumbers;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.Random;\r\nimport java.util.UUID;\r\n\r\npublic class BlockHelperImpl implements BlockHelper {\r\n\r\n    public static final Field craftBlockEntityState_tileEntity = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"tileEntity\");\r\n    public static final Field craftBlockEntityState_snapshot = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"snapshot\");\r\n    public static final Field craftSkull_profile = ReflectionHelper.getFields(CraftSkull.class).get(\"profile\");\r\n\r\n    @Override\r\n    public void makeBlockStateRaw(BlockState state) {\r\n        try {\r\n            craftBlockEntityState_snapshot.set(state, craftBlockEntityState_tileEntity.get(state));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void applyPhysics(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        ((CraftWorld) location.getWorld()).getHandle().updateNeighborsAt(pos, CraftMagicNumbers.getBlock(location.getBlock().getType()));\r\n    }\r\n\r\n    public static <T extends BlockEntity> T getTE(CraftBlockEntityState<T> cbs) {\r\n        try {\r\n            return (T) craftBlockEntityState_tileEntity.get(cbs);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Skull skull) {\r\n        GameProfile profile = getTE(((CraftSkull) skull)).owner;\r\n        if (profile == null) {\r\n            return null;\r\n        }\r\n        String name = profile.getName();\r\n        UUID id = profile.getId();\r\n        com.mojang.authlib.properties.Property property = Iterables.getFirst(profile.getProperties().get(\"textures\"), null);\r\n        return new PlayerProfile(name, id, property != null ? property.getValue() : null);\r\n    }\r\n\r\n    @Override\r\n    public void setPlayerProfile(Skull skull, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        if (playerProfile.hasTexture()) {\r\n            gameProfile.getProperties().put(\"textures\",\r\n                    new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n        }\r\n        try {\r\n            craftSkull_profile.set(skull, gameProfile);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        skull.update();\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Block block) {\r\n        BlockEntity te = ((CraftWorld) block.getWorld()).getHandle().getTileEntity(new BlockPos(block.getX(), block.getY(), block.getZ()), true);\r\n        if (te != null) {\r\n            CompoundTag nmsData = new CompoundTag();\r\n            te.save(nmsData);\r\n            return NBTAdapter.toAPI(nmsData);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Block block, CompoundBinaryTag ctag) {\r\n        CompoundTag nmsData = NBTAdapter.toNMS(ctag);\r\n        nmsData.putInt(\"x\", block.getX());\r\n        nmsData.putInt(\"y\", block.getY());\r\n        nmsData.putInt(\"z\", block.getZ());\r\n        BlockPos blockPos = new BlockPos(block.getX(), block.getY(), block.getZ());\r\n        BlockEntity te = ((CraftWorld) block.getWorld()).getHandle().getTileEntity(blockPos, true);\r\n        te.load(nmsData);\r\n    }\r\n\r\n    @Override\r\n    public boolean setBlockResistance(Material material, float resistance) {\r\n        net.minecraft.world.level.block.Block block = getMaterialBlock(material);\r\n        if (block == null) {\r\n            return false;\r\n        }\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, ReflectionMappingsInfo.BlockBehaviour_explosionResistance, block, resistance);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public float getBlockResistance(Material material) {\r\n        net.minecraft.world.level.block.Block block = getMaterialBlock(material);\r\n        if (block == null) {\r\n            return 0;\r\n        }\r\n        return ReflectionHelper.getFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, ReflectionMappingsInfo.BlockBehaviour_explosionResistance, block);\r\n    }\r\n\r\n    public static final Field BLOCK_MATERIAL = ReflectionHelper.getFields(net.minecraft.world.level.block.state.BlockBehaviour.class).getFirstOfType(net.minecraft.world.level.material.Material.class);\r\n\r\n    public static final MethodHandle MATERIAL_PUSH_REACTION_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.material.Material.class, PushReaction.class);\r\n\r\n    public static final MethodHandle BLOCK_STRENGTH_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase.class, float.class); // destroySpeed\r\n\r\n    public net.minecraft.world.level.block.Block getMaterialBlock(Material bukkitMaterial) {\r\n        if (!bukkitMaterial.isBlock()) {\r\n            return null;\r\n        }\r\n        return ((CraftBlockData) bukkitMaterial.createBlockData()).getState().getBlock();\r\n    }\r\n\r\n    public net.minecraft.world.level.material.Material getInternalMaterial(Material bukkitMaterial) {\r\n        try {\r\n            return (net.minecraft.world.level.material.Material) BLOCK_MATERIAL.get(getMaterialBlock(bukkitMaterial));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public PistonPushReaction getPushReaction(Material mat) {\r\n        return PistonPushReaction.VALUES[getInternalMaterial(mat).getPushReaction().ordinal()];\r\n    }\r\n\r\n    @Override\r\n    public void setPushReaction(Material mat, PistonPushReaction reaction) {\r\n        try {\r\n            MATERIAL_PUSH_REACTION_SETTER.invoke(getInternalMaterial(mat), PushReaction.values()[reaction.ordinal()]);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBlockStrength(Material mat) {\r\n        return getMaterialBlock(mat).defaultBlockState().destroySpeed;\r\n    }\r\n\r\n    @Override\r\n    public void setBlockStrength(Material mat, float strength) {\r\n        try {\r\n            BLOCK_STRENGTH_SETTER.invoke(getMaterialBlock(mat).defaultBlockState(), strength);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    // This is to debork Spigot's class remapper mishandling 'getFluidState' which remaps 'FluidState' to 'material.FluidType' (incorrectly) in the call and thus errors out.\r\n    // TODO: 1.18: This might be fixed by Spigot and can be switched to raw method calls\r\n    // Relevant issue: https://hub.spigotmc.org/jira/browse/SPIGOT-6696\r\n    public static MethodHandle BLOCKSTATEBASE_GETFLUIDSTATE = ReflectionHelper.getMethodHandle(BlockBehaviour.BlockStateBase.class, \"getFluid\");\r\n    public static MethodHandle FLUIDSTATE_ISRANDOMLYTICKING = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), \"f\");\r\n    public static MethodHandle FLUIDSTATE_ISEMPTY = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), \"isEmpty\");\r\n    public static MethodHandle FLUIDSTATE_CREATELEGACYBLOCK = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), \"getBlockData\");\r\n    public static MethodHandle FLUIDSTATE_ANIMATETICK = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), \"a\", Level.class, BlockPos.class, Random.class);\r\n\r\n    @Override\r\n    public void doRandomTick(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        LevelChunk nmsChunk = ((CraftChunk) location.getChunk()).getHandle();\r\n        net.minecraft.world.level.block.state.BlockState nmsBlock = nmsChunk.getBlockState(pos);\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        if (nmsBlock.isRandomlyTicking()) {\r\n            nmsBlock.randomTick(nmsWorld, pos, nmsWorld.random);\r\n        }\r\n        try {\r\n            // FluidState fluid = nmsBlock.getFluidState();\r\n            // if (fluid.isRandomlyTicking()) {\r\n            //     fluid.animateTick(nmsWorld, pos, nmsWorld.random);\r\n            // }\r\n            Object fluid = BLOCKSTATEBASE_GETFLUIDSTATE.invoke(nmsBlock);\r\n            if ((boolean) FLUIDSTATE_ISRANDOMLYTICKING.invoke(fluid)) {\r\n                FLUIDSTATE_ANIMATETICK.invoke(fluid, nmsWorld, pos, nmsWorld.random);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Instrument getInstrumentFor(Material mat) {\r\n        net.minecraft.world.level.block.Block blockType = getMaterialBlock(mat);\r\n        NoteBlockInstrument nmsInstrument = NoteBlockInstrument.byState(blockType.defaultBlockState());\r\n        return Instrument.values()[(nmsInstrument.ordinal())];\r\n    }\r\n\r\n    @Override\r\n    public void ringBell(Bell bell) {\r\n        org.bukkit.block.data.type.Bell bellData = (org.bukkit.block.data.type.Bell) bell.getBlockData();\r\n        Direction face = CraftBlock.blockFaceToNotch(bellData.getFacing());\r\n        Direction dir = Direction.NORTH;\r\n        switch (bellData.getAttachment()) {\r\n            case DOUBLE_WALL:\r\n            case SINGLE_WALL:\r\n                switch (face) {\r\n                    case NORTH:\r\n                    case SOUTH:\r\n                        dir = Direction.EAST;\r\n                        break;\r\n                }\r\n                break;\r\n            case FLOOR:\r\n                dir = face;\r\n                break;\r\n        }\r\n        CraftBlock craftBlock = (CraftBlock) bell.getBlock();\r\n        ((BellBlock) Blocks.BELL).attemptToRing(craftBlock.getCraftWorld().getHandle(), craftBlock.getPosition(), dir);\r\n    }\r\n\r\n    @Override\r\n    public int getExpDrop(Block block, org.bukkit.inventory.ItemStack item) {\r\n        net.minecraft.world.level.block.Block blockType = getMaterialBlock(block.getType());\r\n        if (blockType == null) {\r\n            return 0;\r\n        }\r\n        return blockType.getExpDrop(((CraftBlock) block).getNMS(), ((CraftBlock) block).getCraftWorld().getHandle(), ((CraftBlock) block).getPosition(),\r\n                item == null ? null : CraftItemStack.asNMSCopy(item));\r\n    }\r\n\r\n    @Override\r\n    public Color getMapColor(Block block) {\r\n        CraftBlock craftBlock = (CraftBlock) block;\r\n        return Color.fromRGB(craftBlock.getNMS().getMapColor(craftBlock.getHandle(), craftBlock.getPosition()).col);\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/ChunkHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.nms.interfaces.ChunkHelper;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkPacket;\r\nimport net.minecraft.server.level.ChunkHolder;\r\nimport net.minecraft.server.level.ServerChunkCache;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.chunk.ChunkBiomeContainer;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport org.bukkit.World;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\n\r\npublic class ChunkHelperImpl implements ChunkHelper {\r\n\r\n    public final static Field chunkProviderServerThreadField;\r\n    public final static MethodHandle chunkProviderServerThreadFieldSetter;\r\n    public final static Field worldThreadField;\r\n    public final static MethodHandle worldThreadFieldSetter;\r\n\r\n    static {\r\n        chunkProviderServerThreadField = ReflectionHelper.getFields(ServerChunkCache.class).getFirstOfType(Thread.class);\r\n        chunkProviderServerThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(ServerChunkCache.class, Thread.class);\r\n        worldThreadField = ReflectionHelper.getFields(net.minecraft.world.level.Level.class).getFirstOfType(Thread.class);\r\n        worldThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.Level.class, Thread.class);\r\n    }\r\n\r\n    public Thread resetServerThread;\r\n\r\n    @Override\r\n    public void changeChunkServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread != null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkProvider();\r\n        try {\r\n            resetServerThread = (Thread) chunkProviderServerThreadField.get(provider);\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, Thread.currentThread());\r\n            worldThreadFieldSetter.invoke(nmsWorld, Thread.currentThread());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void restoreServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread == null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkProvider();\r\n        try {\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, resetServerThread);\r\n            worldThreadFieldSetter.invoke(nmsWorld, resetServerThread);\r\n            resetServerThread = null;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void refreshChunkSections(Chunk chunk) {\r\n        ClientboundLevelChunkPacket packet = new ClientboundLevelChunkPacket(((CraftChunk) chunk).getHandle());\r\n        ChunkPos pos = new ChunkPos(chunk.getX(), chunk.getZ());\r\n        ChunkHolder playerChunk = ((CraftWorld) chunk.getWorld()).getHandle().getChunkProvider().chunkMap.l.get(pos.toLong());\r\n        if (playerChunk == null) {\r\n            return;\r\n        }\r\n        playerChunk.playerProvider.getPlayers(pos, false).forEach(player -> {\r\n            player.connection.send(packet);\r\n        });\r\n    }\r\n\r\n    @Override\r\n    public int[] getHeightMap(Chunk chunk) {\r\n        Heightmap map = ((CraftChunk) chunk).getHandle().heightmaps.get(Heightmap.Types.MOTION_BLOCKING);\r\n        int[] outputMap = new int[256];\r\n        for (int x = 0; x < 16; x++) {\r\n            for (int y = 0; y < 16; y++) {\r\n                outputMap[x * 16 + y] = map.getFirstAvailable(x, y);\r\n            }\r\n        }\r\n        return outputMap;\r\n    }\r\n\r\n    @Override\r\n    public void setAllBiomes(Chunk chunk, BiomeNMS biome) {\r\n        Biome nmsBiome = ((BiomeNMSImpl) biome).biomeBase;\r\n        LevelChunk nmsChunk = ((CraftChunk) chunk).getHandle();\r\n        ChunkBiomeContainer biomeContainer = nmsChunk.getBiomes();\r\n        for(int x = 0; x < 4; x++) {\r\n            for (int y = 0; y < 64; y++) {\r\n                for (int z = 0; z < 4; z++) {\r\n                    biomeContainer.setBiome(x, y, z, nmsBiome);\r\n                }\r\n            }\r\n        }\r\n        nmsChunk.markUnsaved();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/CustomEntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.v1_17.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.entities.EntityFakeArrowImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.entities.EntityFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.CustomEntityHelper;\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scoreboard.Scoreboard;\r\nimport org.bukkit.scoreboard.Team;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.UUID;\r\n\r\npublic class CustomEntityHelperImpl implements CustomEntityHelper {\r\n\r\n    @Override\r\n    public FakeArrow spawnFakeArrow(Location location) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityFakeArrowImpl arrow = new EntityFakeArrowImpl(world, location);\r\n        return arrow.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public ItemProjectile spawnItemProjectile(Location location, ItemStack itemStack) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityItemProjectileImpl entity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n        world.getHandle().addEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return entity.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public FakePlayer spawnFakePlayer(Location location, String name, String skin, String blob, boolean doAdd) throws IllegalArgumentException {\r\n        return spawnFakePlayer(location, name, skin, doAdd);\r\n    }\r\n\r\n    public static FakePlayer spawnFakePlayer(Location location, String name, String skin, boolean doAdd) throws IllegalArgumentException {\r\n        String fullName = name;\r\n        String prefix = null;\r\n        String suffix = null;\r\n        if (name == null) {\r\n            return null;\r\n        }\r\n        else if (fullName.length() > 16) {\r\n            prefix = fullName.substring(0, 16);\r\n            if (fullName.length() > 30) {\r\n                int len = 30;\r\n                name = fullName.substring(16, 30);\r\n                if (name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    if (fullName.length() >= 32) {\r\n                        len = 32;\r\n                        name = fullName.substring(16, 32);\r\n                    }\r\n                    else if (fullName.length() == 31) {\r\n                        len = 31;\r\n                        name = fullName.substring(16, 31);\r\n                    }\r\n                }\r\n                else if (name.length() > 46) {\r\n                    throw new IllegalArgumentException(\"You must specify a name with no more than 46 characters for FAKE_PLAYER entities!\");\r\n                }\r\n                else {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                suffix = fullName.substring(len);\r\n            }\r\n            else {\r\n                name = fullName.substring(16);\r\n                if (!name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                if (name.length() > 16) {\r\n                    suffix = name.substring(16);\r\n                    name = name.substring(0, 16);\r\n                }\r\n            }\r\n        }\r\n        if (skin != null && skin.length() > 16) {\r\n            throw new IllegalArgumentException(\"You must specify a name with no more than 16 characters for FAKE_PLAYER entity skins!\");\r\n        }\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        ServerLevel worldServer = world.getHandle();\r\n        PlayerProfile playerProfile = new PlayerProfile(name, null);\r\n        if (skin == null && !name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n            playerProfile = NMSHandler.instance.fillPlayerProfile(playerProfile);\r\n        }\r\n        if (skin != null) {\r\n            PlayerProfile skinProfile = new PlayerProfile(skin, null);\r\n            skinProfile = NMSHandler.instance.fillPlayerProfile(skinProfile);\r\n            playerProfile.setTexture(skinProfile.getTexture());\r\n            playerProfile.setTextureSignature(skinProfile.getTextureSignature());\r\n        }\r\n        UUID uuid = UUID.randomUUID();\r\n        playerProfile.setUniqueId(uuid);\r\n\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        gameProfile.getProperties().put(\"textures\",\r\n                new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n\r\n        final EntityFakePlayerImpl fakePlayer = new EntityFakePlayerImpl(worldServer.getServer(), worldServer, gameProfile, doAdd);\r\n\r\n        fakePlayer.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(),\r\n                location.getYaw(), location.getPitch());\r\n        CraftFakePlayerImpl craftFakePlayer = fakePlayer.getBukkitEntity();\r\n        craftFakePlayer.fullName = fullName;\r\n        if (prefix != null) {\r\n            Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();\r\n            String teamName = \"FAKE_PLAYER_TEAM_\" + fullName;\r\n            String hash = null;\r\n            try {\r\n                hash = CoreUtilities.hash_md5(teamName.getBytes(StandardCharsets.UTF_8)).substring(0, 16);\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(e);\r\n            }\r\n            if (hash != null) {\r\n                Team team = scoreboard.getTeam(hash);\r\n                if (team == null) {\r\n                    team = scoreboard.registerNewTeam(hash);\r\n                    team.setPrefix(prefix);\r\n                    if (suffix != null) {\r\n                        team.setSuffix(suffix);\r\n                    }\r\n                }\r\n                team.addPlayer(craftFakePlayer);\r\n            }\r\n        }\r\n        return craftFakePlayer;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/EnchantmentHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.EnchantmentHelper;\r\nimport com.denizenscript.denizen.nms.v1_17.Handler;\r\nimport com.denizenscript.denizen.scripts.containers.core.EnchantmentScriptContainer;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.world.damagesource.DamageSource;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.entity.EquipmentSlot;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.entity.MobType;\r\nimport net.minecraft.world.item.enchantment.EnchantmentCategory;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.craftbukkit.v1_17_R1.enchantments.CraftEnchantment;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_17_R1.util.CraftNamespacedKey;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\n\r\nimport java.util.Map;\r\n\r\npublic class EnchantmentHelperImpl extends EnchantmentHelper {\r\n\r\n    public static Map<NamespacedKey, Enchantment> ENCHANTMENTS_BY_KEY = ReflectionHelper.getFieldValue(org.bukkit.enchantments.Enchantment.class, \"byKey\", null);\r\n    public static Map<String, org.bukkit.enchantments.Enchantment> ENCHANTMENTS_BY_NAME = ReflectionHelper.getFieldValue(org.bukkit.enchantments.Enchantment.class, \"byName\", null);\r\n\r\n    @Override\r\n    public org.bukkit.enchantments.Enchantment registerFakeEnchantment(EnchantmentScriptContainer.EnchantmentReference script) {\r\n        try {\r\n            EquipmentSlot[] slots = new EquipmentSlot[script.script.slots.size()];\r\n            for (int i = 0; i < slots.length; i++) {\r\n                slots[i] = EquipmentSlot.valueOf(script.script.slots.get(i).toUpperCase());\r\n            }\r\n            net.minecraft.world.item.enchantment.Enchantment nmsEnchant = new net.minecraft.world.item.enchantment.Enchantment(net.minecraft.world.item.enchantment.Enchantment.Rarity.valueOf(script.script.rarity), EnchantmentCategory.valueOf(script.script.category), slots) {\r\n                @Override\r\n                public int getMinLevel() {\r\n                    return script.script.minLevel;\r\n                }\r\n                @Override\r\n                public int getMaxLevel() {\r\n                    return script.script.maxLevel;\r\n                }\r\n                @Override\r\n                public int getMinCost(int level) {\r\n                    return script.script.getMinCost(level);\r\n                }\r\n                @Override\r\n                public int getMaxCost(int level) {\r\n                    return script.script.getMaxCost(level);\r\n                }\r\n                @Override\r\n                public int getDamageProtection(int level, DamageSource src) {\r\n                    return script.script.getDamageProtection(level, src.msgId, src.getEntity() == null ? null : src.getEntity().getBukkitEntity());\r\n                }\r\n                @Override\r\n                public float getDamageBonus(int level, MobType type) {\r\n                    String typeName = \"UNDEFINED\";\r\n                    if (type == MobType.ARTHROPOD) {\r\n                        typeName = \"ARTHROPOD\";\r\n                    }\r\n                    else if (type == MobType.ILLAGER) {\r\n                        typeName = \"ILLAGER\";\r\n                    }\r\n                    else if (type == MobType.UNDEAD) {\r\n                        typeName = \"UNDEAD\";\r\n                    }\r\n                    else if (type == MobType.WATER) {\r\n                        typeName = \"WATER\";\r\n                    }\r\n                    return script.script.getDamageBonus(level, typeName);\r\n                }\r\n                @Override\r\n                protected boolean checkCompatibility(net.minecraft.world.item.enchantment.Enchantment nmsEnchantment) {\r\n                    ResourceLocation nmsKey = Registry.ENCHANTMENT.getKey(nmsEnchantment);\r\n                    NamespacedKey bukkitKey = CraftNamespacedKey.fromMinecraft(nmsKey);\r\n                    org.bukkit.enchantments.Enchantment bukkitEnchant = CraftEnchantment.getByKey(bukkitKey);\r\n                    return script.script.isCompatible(bukkitEnchant);\r\n                }\r\n                @Override\r\n                protected String getOrCreateDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public String getDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public Component getFullname(int level) {\r\n                    return Handler.componentToNMS(script.script.getFullName(level));\r\n                }\r\n                @Override\r\n                public boolean canEnchant(net.minecraft.world.item.ItemStack var0) {\r\n                    return super.canEnchant(var0) && script.script.canEnchant(CraftItemStack.asBukkitCopy(var0));\r\n                }\r\n                @Override\r\n                public void doPostAttack(LivingEntity attacker, Entity victim, int level) {\r\n                    script.script.doPostAttack(attacker.getBukkitEntity(), victim.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public void doPostHurt(LivingEntity victim, Entity attacker, int level) {\r\n                    script.script.doPostHurt(victim.getBukkitEntity(), attacker.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public boolean isTreasureOnly() {\r\n                    return script.script.isTreasureOnly;\r\n                }\r\n                @Override\r\n                public boolean isCurse() {\r\n                    return script.script.isCurse;\r\n                }\r\n                @Override\r\n                public boolean isTradeable() {\r\n                    return script.script.isTradable;\r\n                }\r\n                @Override\r\n                public boolean isDiscoverable() {\r\n                    return script.script.isDiscoverable;\r\n                }\r\n            };\r\n            String enchName = script.script.id.toUpperCase();\r\n            Registry.register(Registry.ENCHANTMENT, \"denizen:\" + script.script.id, nmsEnchant);\r\n            CraftEnchantment ench = new CraftEnchantment(nmsEnchant) {\r\n                @Override\r\n                public String getName() {\r\n                    return enchName;\r\n                }\r\n            };\r\n            ENCHANTMENTS_BY_KEY.put(ench.getKey(), ench);\r\n            ENCHANTMENTS_BY_NAME.put(enchName, ench);\r\n            return ench;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(\"Failed to register enchantment \" + script.script.id);\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getRarity(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getRarity().name();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDiscoverable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isDiscoverable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isTradable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isTradeable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isCurse(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isCurse();\r\n    }\r\n\r\n    @Override\r\n    public int getMinCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMinCost(level);\r\n    }\r\n\r\n    @Override\r\n    public int getMaxCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMaxCost(level);\r\n    }\r\n\r\n    @Override\r\n    public String getFullName(Enchantment enchantment, int level) {\r\n        return FormattedTextHelper.stringify(Handler.componentToSpigot(((CraftEnchantment) enchantment).getHandle().getFullname(level)));\r\n    }\r\n\r\n    @Override\r\n    public float getDamageBonus(Enchantment enchantment, int level, String type) {\r\n        MobType mobType = MobType.UNDEFINED;\r\n        switch (type) {\r\n            case \"illager\":\r\n                mobType = MobType.ILLAGER;\r\n                break;\r\n            case \"undead\":\r\n                mobType = MobType.UNDEAD;\r\n                break;\r\n            case \"water\":\r\n                mobType = MobType.WATER;\r\n                break;\r\n            case \"arthropod\":\r\n                mobType = MobType.ARTHROPOD;\r\n                break;\r\n        }\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageBonus(level, mobType);\r\n    }\r\n\r\n    @Override\r\n    public int getDamageProtection(Enchantment enchantment, int level, EntityDamageEvent.DamageCause type, org.bukkit.entity.Entity attacker) {\r\n        DamageSource src = EntityHelperImpl.getSourceFor(attacker == null ? null : ((CraftEntity) attacker).getHandle(), type);\r\n        if (src instanceof EntityHelperImpl.FakeDamageSrc) {\r\n            src = ((EntityHelperImpl.FakeDamageSrc) src).real;\r\n        }\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageProtection(level, src);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/EntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.entity.EntityEntersVehicleScriptEvent;\r\nimport com.denizenscript.denizen.events.entity.EntityExitsVehicleScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerPlayerConnection;\r\nimport net.minecraft.world.InteractionHand;\r\nimport net.minecraft.world.damagesource.CombatRules;\r\nimport net.minecraft.world.damagesource.DamageSource;\r\nimport net.minecraft.world.entity.Mob;\r\nimport net.minecraft.world.entity.MobType;\r\nimport net.minecraft.world.entity.MoverType;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.goal.Goal;\r\nimport net.minecraft.world.entity.ai.navigation.PathNavigation;\r\nimport net.minecraft.world.entity.item.FallingBlockEntity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.level.ClipContext;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.pathfinder.Path;\r\nimport net.minecraft.world.phys.AABB;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport net.minecraft.world.phys.HitResult;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Art;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.attribute.Attribute;\r\nimport org.bukkit.attribute.AttributeInstance;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_17_R1.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.v1_17_R1.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.v1_17_R1.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.*;\r\nimport org.bukkit.craftbukkit.v1_17_R1.event.CraftEventFactory;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\nimport org.bukkit.util.BoundingBox;\r\nimport org.bukkit.util.Vector;\r\nimport org.spigotmc.event.entity.EntityDismountEvent;\r\nimport org.spigotmc.event.entity.EntityMountEvent;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class EntityHelperImpl extends EntityHelper {\r\n\r\n    public static final MethodHandle ENTITY_ONGROUND_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_onGround, boolean.class);\r\n\r\n    public static final EntityDataAccessor<Boolean> ENTITY_ENDERMAN_DATAWATCHER_SCREAMING = ReflectionHelper.getFieldValue(EnderMan.class, ReflectionMappingsInfo.EnderMan_DATA_CREEPY, null);\r\n\r\n    @Override\r\n    public int getBlockHeight(Art art) {\r\n        return art.getBlockHeight();\r\n    }\r\n\r\n    @Override\r\n    public int getBlockWidth(Art art) {\r\n        return art.getBlockWidth();\r\n    }\r\n\r\n    @Override\r\n    public void setInvisible(Entity entity, boolean invisible) {\r\n        ((CraftEntity) entity).getHandle().setInvisible(invisible);\r\n    }\r\n\r\n    @Override\r\n    public boolean isInvisible(Entity entity) {\r\n        return ((CraftEntity) entity).getHandle().isInvisible();\r\n    }\r\n\r\n    @Override\r\n    public void setPose(Entity entity, Pose pose) {\r\n        ((CraftEntity) entity).getHandle().setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]);\r\n    }\r\n\r\n    @Override\r\n    public double getDamageTo(LivingEntity attacker, Entity target) {\r\n        MobType monsterType;\r\n        if (target instanceof LivingEntity) {\r\n            monsterType = ((CraftLivingEntity) target).getHandle().getMobType();\r\n        }\r\n        else {\r\n            monsterType = MobType.UNDEFINED;\r\n        }\r\n        double damage = 0;\r\n        AttributeInstance attrib = attacker.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE);\r\n        if (attrib != null) {\r\n            damage = attrib.getValue();\r\n        }\r\n        if (attacker.getEquipment() != null && attacker.getEquipment().getItemInMainHand() != null) {\r\n            damage += EnchantmentHelper.getDamageBonus(CraftItemStack.asNMSCopy(attacker.getEquipment().getItemInMainHand()), monsterType);\r\n        }\r\n        if (damage <= 0) {\r\n            return 0;\r\n        }\r\n        if (target != null) {\r\n            DamageSource source;\r\n            if (attacker instanceof Player) {\r\n                source = DamageSource.playerAttack(((CraftPlayer) attacker).getHandle());\r\n            }\r\n            else {\r\n                source = DamageSource.mobAttack(((CraftLivingEntity) attacker).getHandle());\r\n            }\r\n            net.minecraft.world.entity.Entity nmsTarget = ((CraftEntity) target).getHandle();\r\n            if (nmsTarget.isInvulnerableTo(source)) {\r\n                return 0;\r\n            }\r\n            if (!(nmsTarget instanceof net.minecraft.world.entity.LivingEntity)) {\r\n                return damage;\r\n            }\r\n            net.minecraft.world.entity.LivingEntity livingTarget = (net.minecraft.world.entity.LivingEntity) nmsTarget;\r\n            damage = CombatRules.getDamageAfterAbsorb((float) damage, (float) livingTarget.getArmorValue(), (float) livingTarget.getAttributeValue(Attributes.ARMOR_TOUGHNESS));\r\n            int enchantDamageModifier = EnchantmentHelper.getDamageProtection(livingTarget.getArmorSlots(), source);\r\n            if (enchantDamageModifier > 0) {\r\n                damage = CombatRules.getDamageAfterMagicAbsorb((float) damage, (float) enchantDamageModifier);\r\n            }\r\n        }\r\n        return damage;\r\n    }\r\n\r\n    @Override\r\n    public void setRiptide(Entity entity, boolean state) {\r\n        ((CraftLivingEntity) entity).getHandle().startAutoSpinAttack(state ? 0 : 1);\r\n    }\r\n\r\n    @Override\r\n    public void forceInteraction(Player player, Location location) {\r\n        CraftPlayer craftPlayer = (CraftPlayer) player;\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        ((CraftBlock) location.getBlock()).getNMS().use(((CraftWorld) location.getWorld()).getHandle(),\r\n                craftPlayer != null ? craftPlayer.getHandle() : null, InteractionHand.MAIN_HAND,\r\n                new BlockHitResult(new Vec3(0, 0, 0), null, pos, false));\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Entity entity) {\r\n        CompoundTag compound = new CompoundTag();\r\n        ((CraftEntity) entity).getHandle().saveAsPassenger(compound);\r\n        return NBTAdapter.toAPI(compound);\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Entity entity, CompoundBinaryTag compoundTag) {\r\n        ((CraftEntity) entity).getHandle().load(NBTAdapter.toNMS(compoundTag));\r\n    }\r\n\r\n    /*\r\n        Entity Movement\r\n     */\r\n\r\n    private final static Map<UUID, BukkitTask> followTasks = new HashMap<>();\r\n\r\n    @Override\r\n    public void stopFollowing(Entity follower) {\r\n        if (follower == null) {\r\n            return;\r\n        }\r\n        UUID uuid = follower.getUniqueId();\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void stopWalking(Entity entity) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        if (!(nmsEntity instanceof Mob)) {\r\n            return;\r\n        }\r\n        ((Mob) nmsEntity).getNavigation().stop();\r\n    }\r\n\r\n    @Override\r\n    public void follow(final Entity target, final Entity follower, final double speed, final double lead,\r\n                       final double maxRange, final boolean allowWander, final boolean teleport) {\r\n        if (target == null || follower == null) {\r\n            return;\r\n        }\r\n\r\n        final net.minecraft.world.entity.Entity nmsEntityFollower = ((CraftEntity) follower).getHandle();\r\n        if (!(nmsEntityFollower instanceof Mob)) {\r\n            return;\r\n        }\r\n        final Mob nmsFollower = (Mob) nmsEntityFollower;\r\n        final PathNavigation followerNavigation = nmsFollower.getNavigation();\r\n\r\n        UUID uuid = follower.getUniqueId();\r\n\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n\r\n        final int locationNearInt = (int) Math.floor(lead);\r\n        final boolean hasMax = maxRange > lead;\r\n\r\n        followTasks.put(follower.getUniqueId(), new BukkitRunnable() {\r\n\r\n            private boolean inRadius = false;\r\n\r\n            public void run() {\r\n                if (!target.isValid() || !follower.isValid()) {\r\n                    this.cancel();\r\n                }\r\n                followerNavigation.setSpeedModifier(2D);\r\n                Location targetLocation = target.getLocation();\r\n                Path path;\r\n\r\n                if (hasMax && !Utilities.checkLocation(targetLocation, follower.getLocation(), maxRange)\r\n                        && !target.isDead() && target.isOnGround()) {\r\n                    if (!inRadius) {\r\n                        if (teleport) {\r\n                            follower.teleport(Utilities.getWalkableLocationNear(targetLocation, locationNearInt));\r\n                        }\r\n                        else {\r\n                            cancel();\r\n                        }\r\n                    }\r\n                    else {\r\n                        inRadius = false;\r\n                        path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                        if (path != null) {\r\n                            followerNavigation.moveTo(path, 1D);\r\n                            followerNavigation.setSpeedModifier(2D);\r\n                        }\r\n                    }\r\n                }\r\n                else if (!inRadius && !Utilities.checkLocation(targetLocation, follower.getLocation(), lead)) {\r\n                    path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                    if (path != null) {\r\n                        followerNavigation.moveTo(path, 1D);\r\n                        followerNavigation.setSpeedModifier(2D);\r\n                    }\r\n                }\r\n                else {\r\n                    inRadius = true;\r\n                }\r\n                if (inRadius && !allowWander) {\r\n                    followerNavigation.stop();\r\n                }\r\n                nmsFollower.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n        }.runTaskTimer(NMSHandler.getJavaPlugin(), 0, 10));\r\n    }\r\n\r\n    @Override\r\n    public void walkTo(final LivingEntity entity, Location location, Double speed, final Runnable callback) {\r\n        if (entity == null || location == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.Entity nmsEntityEntity = ((CraftEntity) entity).getHandle();\r\n        if (!(nmsEntityEntity instanceof Mob)) {\r\n            return;\r\n        }\r\n        final Mob nmsEntity = (Mob) nmsEntityEntity;\r\n        final PathNavigation entityNavigation = nmsEntity.getNavigation();\r\n        final Path path;\r\n        final boolean aiDisabled = !entity.hasAI();\r\n        if (aiDisabled) {\r\n            entity.setAI(true);\r\n            try {\r\n                ENTITY_ONGROUND_SETTER.invoke(nmsEntity, true);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        path = entityNavigation.createPath(location.getX(), location.getY(), location.getZ(), 1);\r\n        if (path != null) {\r\n            nmsEntity.goalSelector.enableControlFlag(Goal.Flag.MOVE);\r\n            entityNavigation.moveTo(path, 1D);\r\n            entityNavigation.setSpeedModifier(2D);\r\n            final double oldSpeed = nmsEntity.getAttribute(Attributes.MOVEMENT_SPEED).getBaseValue();\r\n            if (speed != null) {\r\n                nmsEntity.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    if (!entity.isValid()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        cancel();\r\n                        return;\r\n                    }\r\n                    if (aiDisabled && entity instanceof Wolf) {\r\n                        ((Wolf) entity).setAngry(false);\r\n                    }\r\n                    if (entityNavigation.isDone() || path.isDone()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        if (speed != null) {\r\n                            nmsEntity.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(oldSpeed);\r\n                        }\r\n                        if (aiDisabled) {\r\n                            entity.setAI(false);\r\n                        }\r\n                        cancel();\r\n                    }\r\n                }\r\n            }.runTaskTimer(NMSHandler.getJavaPlugin(), 1, 1);\r\n        }\r\n        //if (!Utilities.checkLocation(location, entity.getLocation(), 20)) {\r\n        // TODO: generate waypoints to the target location?\r\n        else {\r\n            entity.teleport(location);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public List<Player> getPlayersThatSee(Entity entity) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level).getChunkProvider().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.G.get(entity.getEntityId());\r\n        ArrayList<Player> output = new ArrayList<>();\r\n        if (entityTracker == null) {\r\n            return output;\r\n        }\r\n        for (ServerPlayerConnection player : entityTracker.seenBy) {\r\n            output.add(player.getPlayer().getBukkitEntity());\r\n        }\r\n        return output;\r\n    }\r\n\r\n    /*\r\n        Hide Entity\r\n     */\r\n\r\n    @Override\r\n    public void sendHidePacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player) {\r\n            pl.hidePlayer(Denizen.getInstance(), (Player) entity);\r\n            return;\r\n        }\r\n        CraftPlayer craftPlayer = (CraftPlayer) pl;\r\n        ServerPlayer entityPlayer = craftPlayer.getHandle();\r\n        if (entityPlayer.connection != null && !craftPlayer.equals(entity)) {\r\n            ChunkMap tracker = ((ServerLevel) craftPlayer.getHandle().level).getChunkProvider().chunkMap;\r\n            net.minecraft.world.entity.Entity other = ((CraftEntity) entity).getHandle();\r\n            ChunkMap.TrackedEntity entry = tracker.G.get(other.getId());\r\n            if (entry != null) {\r\n                entry.removePlayer(entityPlayer);\r\n            }\r\n            if (Denizen.supportsPaper) { // Workaround for Paper issue\r\n                entityPlayer.connection.send(new ClientboundRemoveEntitiesPacket(other.getId()));\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendShowPacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player) {\r\n            pl.showPlayer(Denizen.getInstance(), (Player) entity);\r\n            return;\r\n        }\r\n        CraftPlayer craftPlayer = (CraftPlayer) pl;\r\n        ServerPlayer entityPlayer = craftPlayer.getHandle();\r\n        if (entityPlayer.connection != null && !craftPlayer.equals(entity)) {\r\n            ChunkMap tracker = ((ServerLevel) craftPlayer.getHandle().level).getChunkProvider().chunkMap;\r\n            net.minecraft.world.entity.Entity other = ((CraftEntity) entity).getHandle();\r\n            ChunkMap.TrackedEntity entry = tracker.G.get(other.getId());\r\n            if (entry != null) {\r\n                entry.removePlayer(entityPlayer);\r\n                entry.updatePlayer(entityPlayer);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void rotate(Entity entity, float yaw, float pitch) {\r\n        // If this entity is a real player instead of a player type NPC,\r\n        // it will appear to be online\r\n        if (entity instanceof Player && ((Player) entity).isOnline()) {\r\n            Location location = entity.getLocation();\r\n            location.setYaw(yaw);\r\n            location.setPitch(pitch);\r\n            teleport(entity, location);\r\n        }\r\n        else if (entity instanceof LivingEntity) {\r\n            if (entity instanceof EnderDragon) {\r\n                yaw = normalizeYaw(yaw - 180);\r\n            }\r\n            look(entity, yaw, pitch);\r\n        }\r\n        else {\r\n            net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n            handle.setYRot(yaw - 360);\r\n            handle.setXRot(pitch);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBaseYaw(LivingEntity entity) {\r\n        return ((CraftLivingEntity) entity).getHandle().yBodyRot;\r\n    }\r\n\r\n    @Override\r\n    public void look(Entity entity, float yaw, float pitch) {\r\n        net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n        if (handle != null) {\r\n            handle.setYRot(yaw);\r\n            if (handle instanceof net.minecraft.world.entity.LivingEntity) {\r\n                net.minecraft.world.entity.LivingEntity livingHandle = (net.minecraft.world.entity.LivingEntity) handle;\r\n                while (yaw < -180.0F) {\r\n                    yaw += 360.0F;\r\n                }\r\n                while (yaw >= 180.0F) {\r\n                    yaw -= 360.0F;\r\n                }\r\n                livingHandle.yBodyRotO = yaw;\r\n                if (!(handle instanceof net.minecraft.world.entity.player.Player)) {\r\n                    livingHandle.setYBodyRot(yaw);\r\n                }\r\n                livingHandle.setYHeadRot(yaw);\r\n            }\r\n            handle.setXRot(pitch);\r\n        }\r\n        else {\r\n            Debug.echoError(\"Cannot set look direction for unspawned entity \" + entity.getUniqueId());\r\n        }\r\n    }\r\n\r\n    private static HitResult rayTrace(World world, Vector start, Vector end) {\r\n        try {\r\n            NMSHandler.chunkHelper.changeChunkServerThread(world);\r\n            return ((CraftWorld) world).getHandle().clip(new ClipContext(new Vec3(start.getX(), start.getY(), start.getZ()),\r\n                    new Vec3(end.getX(), end.getY(), end.getZ()),\r\n                    ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, null));\r\n        }\r\n        finally {\r\n            NMSHandler.chunkHelper.restoreServerThread(world);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean canTrace(World world, Vector start, Vector end) {\r\n        HitResult pos = rayTrace(world, start, end);\r\n        if (pos == null) {\r\n            return true;\r\n        }\r\n        return pos.getType() == HitResult.Type.MISS;\r\n    }\r\n\r\n    @Override\r\n    public void snapPositionTo(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().setPosRaw(vector.getX(), vector.getY(), vector.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void move(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().move(MoverType.SELF, new Vec3(vector.getX(), vector.getY(), vector.getZ()));\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Entity entity, Location loc) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        nmsEntity.setYRot(loc.getYaw());\r\n        nmsEntity.setXRot(loc.getPitch());\r\n        if (nmsEntity instanceof ServerPlayer) {\r\n            nmsEntity.teleportTo(loc.getX(), loc.getY(), loc.getZ());\r\n        }\r\n        nmsEntity.setPos(loc.getX(), loc.getY(), loc.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void setBoundingBox(Entity entity, BoundingBox box) {\r\n        ((CraftEntity) entity).getHandle().setBoundingBox(new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()));\r\n    }\r\n\r\n    @Override\r\n    public void setTicksLived(Entity entity, int ticks) {\r\n        // Bypass Spigot's must-be-at-least-1-tick requirement, as negative tick counts are useful\r\n        ((CraftEntity) entity).getHandle().tickCount = ticks;\r\n        if (entity instanceof CraftFallingBlock) {\r\n            ((CraftFallingBlock) entity).getHandle().time = ticks;\r\n        }\r\n        else if (entity instanceof CraftItem) {\r\n            ((ItemEntity) ((CraftItem) entity).getHandle()).age = ticks;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHeadAngle(LivingEntity entity, float angle) {\r\n        net.minecraft.world.entity.LivingEntity handle = ((CraftLivingEntity) entity).getHandle();\r\n        handle.yHeadRot = angle;\r\n        handle.setYHeadRot(angle);\r\n    }\r\n\r\n    @Override\r\n    public void setGhastAttacking(Ghast ghast, boolean attacking) {\r\n        ((CraftGhast) ghast).getHandle().setCharging(attacking);\r\n    }\r\n\r\n    @Override\r\n    public void setEndermanAngry(Enderman enderman, boolean angry) {\r\n        ((CraftEnderman) enderman).getHandle().getEntityData().set(ENTITY_ENDERMAN_DATAWATCHER_SCREAMING, angry);\r\n    }\r\n\r\n    public static class FakeDamageSrc extends DamageSource { public DamageSource real; public FakeDamageSrc(DamageSource src) { super(\"fake\"); real = src; } }\r\n\r\n    public static DamageSource getSourceFor(net.minecraft.world.entity.Entity nmsSource, EntityDamageEvent.DamageCause cause) {\r\n        DamageSource src = DamageSource.GENERIC;\r\n        if (nmsSource != null) {\r\n            if (nmsSource instanceof net.minecraft.world.entity.player.Player) {\r\n                src = DamageSource.playerAttack((net.minecraft.world.entity.player.Player) nmsSource);\r\n            }\r\n            else if (nmsSource instanceof net.minecraft.world.entity.LivingEntity) {\r\n                src = DamageSource.mobAttack((net.minecraft.world.entity.LivingEntity) nmsSource);\r\n            }\r\n        }\r\n        if (cause == null) {\r\n            return src;\r\n        }\r\n        switch (cause) {\r\n            case CONTACT:\r\n                return DamageSource.CACTUS;\r\n            case ENTITY_ATTACK:\r\n                return DamageSource.mobAttack(nmsSource instanceof net.minecraft.world.entity.LivingEntity ? (net.minecraft.world.entity.LivingEntity) nmsSource : null);\r\n            case ENTITY_SWEEP_ATTACK:\r\n                if (src != DamageSource.GENERIC) {\r\n                    src.sweep();\r\n                }\r\n                return src;\r\n            case PROJECTILE:\r\n                return DamageSource.thrown(nmsSource, nmsSource != null && nmsSource.getBukkitEntity() instanceof Projectile\r\n                        && ((Projectile) nmsSource.getBukkitEntity()).getShooter() instanceof Entity ? ((CraftEntity) ((Projectile) nmsSource.getBukkitEntity()).getShooter()).getHandle() : null);\r\n            case SUFFOCATION:\r\n                return DamageSource.IN_WALL;\r\n            case FALL:\r\n                return DamageSource.FALL;\r\n            case FIRE:\r\n                return DamageSource.IN_FIRE;\r\n            case FIRE_TICK:\r\n                return DamageSource.ON_FIRE;\r\n            case MELTING:\r\n                return CraftEventFactory.MELTING;\r\n            case LAVA:\r\n                return DamageSource.LAVA;\r\n            case DROWNING:\r\n                return DamageSource.DROWN;\r\n            case BLOCK_EXPLOSION:\r\n                return DamageSource.explosion(nmsSource instanceof TNTPrimed && ((TNTPrimed) nmsSource).getSource() instanceof net.minecraft.world.entity.LivingEntity ? (net.minecraft.world.entity.LivingEntity) ((TNTPrimed) nmsSource).getSource() : null);\r\n            case ENTITY_EXPLOSION:\r\n                return DamageSource.explosion(nmsSource instanceof net.minecraft.world.entity.LivingEntity ? (net.minecraft.world.entity.LivingEntity) nmsSource : null);\r\n            case VOID:\r\n                return DamageSource.OUT_OF_WORLD;\r\n            case LIGHTNING:\r\n                return DamageSource.LIGHTNING_BOLT;\r\n            case STARVATION:\r\n                return DamageSource.STARVE;\r\n            case POISON:\r\n                return CraftEventFactory.POISON;\r\n            case MAGIC:\r\n                return DamageSource.MAGIC;\r\n            case WITHER:\r\n                return DamageSource.WITHER;\r\n            case FALLING_BLOCK:\r\n                return DamageSource.FALLING_BLOCK;\r\n            case THORNS:\r\n                return DamageSource.thorns(nmsSource);\r\n            case DRAGON_BREATH:\r\n                return DamageSource.DRAGON_BREATH;\r\n            case CUSTOM:\r\n                return DamageSource.GENERIC;\r\n            case FLY_INTO_WALL:\r\n                return DamageSource.FLY_INTO_WALL;\r\n            case HOT_FLOOR:\r\n                return DamageSource.HOT_FLOOR;\r\n            case CRAMMING:\r\n                return DamageSource.CRAMMING;\r\n            case DRYOUT:\r\n                return DamageSource.DRY_OUT;\r\n            //case SUICIDE:\r\n            default:\r\n                return new FakeDamageSrc(src);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void damage(LivingEntity target, float amount, EntityTag source, Location sourceLoc, EntityDamageEvent.DamageCause cause) {\r\n        if (target == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.LivingEntity nmsTarget = ((CraftLivingEntity) target).getHandle();\r\n        net.minecraft.world.entity.Entity nmsSource = source == null ? null : ((CraftEntity) source.getBukkitEntity()).getHandle();\r\n        CraftEventFactory.entityDamage = nmsSource;\r\n        CraftEventFactory.blockDamage = sourceLoc == null ? null : sourceLoc.getBlock();\r\n        try {\r\n            DamageSource src = getSourceFor(nmsSource, cause);\r\n            if (src instanceof FakeDamageSrc) {\r\n                src = ((FakeDamageSrc) src).real;\r\n                EntityDamageEvent ede = fireFakeDamageEvent(target, source, sourceLoc, cause, amount);\r\n                if (ede.isCancelled()) {\r\n                    return;\r\n                }\r\n            }\r\n            nmsTarget.hurt(src, amount);\r\n        }\r\n        finally {\r\n            CraftEventFactory.entityDamage = null;\r\n            CraftEventFactory.blockDamage = null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setLastHurtBy(LivingEntity mob, LivingEntity damager) {\r\n        ((CraftLivingEntity) mob).getHandle().setLastHurtByMob(((CraftLivingEntity) damager).getHandle());\r\n    }\r\n\r\n    public static final MethodHandle FALLINGBLOCK_TYPE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.entity.item.FallingBlockEntity.class, BlockState.class);\r\n\r\n    @Override\r\n    public void setFallingBlockType(FallingBlock fallingBlock, BlockData block) {\r\n        BlockState state = ((CraftBlockData) block).getState();\r\n        FallingBlockEntity nmsEntity = ((CraftFallingBlock) fallingBlock).getHandle();\r\n        try {\r\n            FALLINGBLOCK_TYPE_SETTER.invoke(nmsEntity, state);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public EntityTag getMobSpawnerDisplayEntity(CreatureSpawner spawner) {\r\n        SpawnerBlockEntity nmsSpawner = BlockHelperImpl.getTE((CraftCreatureSpawner) spawner);\r\n        net.minecraft.world.entity.Entity nmsEntity = nmsSpawner.getSpawner().getOrCreateDisplayEntity(((CraftWorld) spawner.getWorld()).getHandle());\r\n        return new EntityTag(nmsEntity.getBukkitEntity());\r\n    }\r\n\r\n    @Override\r\n    public void setFireworkLifetime(Firework firework, int ticks) {\r\n        ((CraftFirework) firework).getHandle().lifetime = ticks;\r\n    }\r\n\r\n    @Override\r\n    public int getFireworkLifetime(Firework firework) {\r\n        return ((CraftFirework) firework).getHandle().lifetime;\r\n    }\r\n\r\n    public static final Field ZOMBIE_INWATERTIME = ReflectionHelper.getFields(net.minecraft.world.entity.monster.Zombie.class).get(ReflectionMappingsInfo.Zombie_inWaterTime, int.class);\r\n\r\n    @Override\r\n    public int getInWaterTime(Zombie zombie) {\r\n        try {\r\n            return ZOMBIE_INWATERTIME.getInt(((CraftZombie) zombie).getHandle());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return 0;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setInWaterTime(Zombie zombie, int ticks) {\r\n        try {\r\n            ZOMBIE_INWATERTIME.setInt(((CraftZombie) zombie).getHandle(), ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final MethodHandle TRACKING_RANGE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ChunkMap.TrackedEntity.class, int.class);\r\n\r\n    @Override\r\n    public void setTrackingRange(Entity entity, int range) {\r\n        try {\r\n            ChunkMap map = ((CraftWorld) entity.getWorld()).getHandle().getChunkProvider().chunkMap;\r\n            ChunkMap.TrackedEntity entry = map.G.get(entity.getEntityId());\r\n            TRACKING_RANGE_SETTER.invoke(entry, range);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean isAggressive(org.bukkit.entity.Mob mob) {\r\n        return ((CraftMob) mob).getHandle().isAggressive();\r\n    }\r\n\r\n    @Override\r\n    public void setAggressive(org.bukkit.entity.Mob mob, boolean aggressive) {\r\n        ((CraftMob) mob).getHandle().setAggressive(aggressive);\r\n    }\r\n\r\n    @Override\r\n    public void openHorseInventory(Player player, AbstractHorse horse) {\r\n        net.minecraft.world.entity.animal.horse.AbstractHorse nmsHorse = ((CraftAbstractHorse) horse).getHandle();\r\n        ((CraftPlayer) player).getHandle().openHorseInventory(nmsHorse, nmsHorse.inventory);\r\n    }\r\n\r\n    public static class EntityEntersVehicleScriptEventImpl extends EntityEntersVehicleScriptEvent {\r\n        @EventHandler\r\n        public void onEntityMount(EntityMountEvent event) {\r\n            fire(event, event.getMount());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Class<? extends EntityEntersVehicleScriptEvent> getEntersVehicleEventImpl() {\r\n        return EntityEntersVehicleScriptEventImpl.class;\r\n    }\r\n\r\n    public static class EntityExitsVehicleScriptEventImpl extends EntityExitsVehicleScriptEvent {\r\n        @EventHandler\r\n        public void onEntityMount(EntityDismountEvent event) {\r\n            fire(event, event.getDismounted());\r\n        }\r\n    }\r\n\r\n    public Class<? extends EntityExitsVehicleScriptEvent> getExitsVehicleEventImpl() {\r\n        return EntityExitsVehicleScriptEventImpl.class;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/FishingHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FishingHelper;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.entity.projectile.FishingHook;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.item.enchantment.Enchantments;\r\nimport net.minecraft.world.level.storage.loot.BuiltInLootTables;\r\nimport net.minecraft.world.level.storage.loot.LootContext;\r\nimport net.minecraft.world.level.storage.loot.LootTables;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParams;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftFishHook;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.entity.FishHook;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.List;\r\n\r\npublic class FishingHelperImpl implements FishingHelper {\r\n\r\n    @Override\r\n    public org.bukkit.inventory.ItemStack getResult(FishHook fishHook, CatchType catchType) {\r\n        ItemStack result = null;\r\n        FishingHook nmsHook = ((CraftFishHook) fishHook).getHandle();\r\n        if (catchType == CatchType.DEFAULT) {\r\n            float f = ((CraftWorld) fishHook.getWorld()).getHandle().random.nextFloat();\r\n            int i = EnchantmentHelper.getMobLooting(nmsHook.getPlayerOwner());\r\n            int j = EnchantmentHelper.getEnchantmentLevel(Enchantments.FISHING_LUCK, nmsHook.getPlayerOwner());\r\n            float f1 = 0.1F - (float) i * 0.025F - (float) j * 0.01F;\r\n            float f2 = 0.05F + (float) i * 0.01F - (float) j * 0.01F;\r\n\r\n            f1 = Mth.clamp(f1, 0.0F, 1.0F);\r\n            f2 = Mth.clamp(f2, 0.0F, 1.0F);\r\n            if (f < f1) {\r\n                result = catchRandomJunk(nmsHook);\r\n            }\r\n            else {\r\n                f -= f1;\r\n                if (f < f2) {\r\n                    result = catchRandomTreasure(nmsHook);\r\n                }\r\n                else {\r\n                    result = catchRandomFish(nmsHook);\r\n                }\r\n            }\r\n        }\r\n        else if (catchType == CatchType.JUNK) {\r\n            result = catchRandomJunk(nmsHook);\r\n        }\r\n        else if (catchType == CatchType.TREASURE) {\r\n            result = catchRandomTreasure(nmsHook);\r\n        }\r\n        else if (catchType == CatchType.FISH) {\r\n            result = catchRandomFish(nmsHook);\r\n        }\r\n        if (result != null) {\r\n            return CraftItemStack.asBukkitCopy(result);\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public ItemStack getRandomReward(FishingHook hook, ResourceLocation key) {\r\n        ServerLevel worldServer = (ServerLevel) hook.level;\r\n        LootContext.Builder playerFishEvent2 = new LootContext.Builder(worldServer);\r\n        LootTables registry = ((ServerLevel) hook.level).getServer().getLootTables();\r\n        // registry.getLootTable(key).getLootContextParameterSet()\r\n        LootContext info = playerFishEvent2.withOptionalParameter(LootContextParams.ORIGIN, new Vec3(hook.getX(), hook.getY(), hook.getZ()))\r\n                .withOptionalParameter(LootContextParams.TOOL, new ItemStack(Items.FISHING_ROD)).create(LootContextParamSets.FISHING);\r\n        List<ItemStack> itemStacks = registry.get(key).getRandomItems(info);\r\n        return itemStacks.get(worldServer.random.nextInt(itemStacks.size()));\r\n    }\r\n\r\n    @Override\r\n    public FishHook spawnHook(Location location, Player player) {\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        FishingHook hook = new FishingHook(((CraftPlayer) player).getHandle(), nmsWorld, 0, 0);\r\n        nmsWorld.addEntity(hook, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return (FishHook) hook.getBukkitEntity();\r\n    }\r\n\r\n    private ItemStack catchRandomJunk(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_JUNK);\r\n    }\r\n\r\n    private ItemStack catchRandomTreasure(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_TREASURE);\r\n    }\r\n\r\n    private ItemStack catchRandomFish(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_FISH);\r\n    }\r\n\r\n    public static Field FISHING_HOOK_NIBBLE_SETTER = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_nibble, int.class);\r\n    public static Field FISHING_HOOK_LURE_TIME_SETTER = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_timeUntilLured, int.class);\r\n    public static Field FISHING_HOOK_HOOK_TIME_SETTER = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_timeUntilHooked, int.class);\r\n\r\n    @Override\r\n    public FishHook getHookFrom(Player player) {\r\n        FishingHook hook = ((CraftPlayer) player).getHandle().fishing;\r\n        if (hook == null) {\r\n            return null;\r\n        }\r\n        return (FishHook) hook.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public void setNibble(FishHook hook, int ticks) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_NIBBLE_SETTER.setInt(nmsEntity, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHookTime(FishHook hook, int ticks) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_HOOK_TIME_SETTER.setInt(nmsEntity, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int getLureTime(FishHook hook) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            return FISHING_HOOK_LURE_TIME_SETTER.getInt(nmsEntity);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public void setLureTime(FishHook hook, int ticks) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_LURE_TIME_SETTER.setInt(nmsEntity, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/ItemHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.ItemHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Iterables;\r\nimport com.google.common.collect.LinkedHashMultiset;\r\nimport com.google.common.collect.Multiset;\r\nimport com.google.common.collect.Multisets;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport net.kyori.adventure.nbt.BinaryTag;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Direction;\r\nimport net.minecraft.core.NonNullList;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.nbt.ListTag;\r\nimport net.minecraft.nbt.NbtUtils;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.world.item.Item;\r\nimport net.minecraft.world.item.crafting.*;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport net.minecraft.world.level.material.MaterialColor;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryPlayer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_17_R1.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_17_R1.util.CraftNamespacedKey;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.RecipeChoice;\r\nimport org.bukkit.inventory.ShapedRecipe;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\n\r\npublic class ItemHelperImpl extends ItemHelper {\r\n\r\n    public static net.minecraft.world.item.crafting.Recipe<?> getNMSRecipe(NamespacedKey key) {\r\n        ResourceLocation nmsKey = CraftNamespacedKey.toMinecraft(key);\r\n        for (Object2ObjectLinkedOpenHashMap<ResourceLocation, net.minecraft.world.item.crafting.Recipe<?>> recipeMap : ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().recipes.values()) {\r\n            net.minecraft.world.item.crafting.Recipe<?> recipe = recipeMap.get(nmsKey);\r\n            if (recipe != null) {\r\n                return recipe;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public void setMaxStackSize(Material material, int size) {\r\n        try {\r\n            ReflectionHelper.getFinalSetter(Material.class, \"maxStack\").invoke(material, size);\r\n            ReflectionHelper.getFinalSetter(Item.class, ReflectionMappingsInfo.Item_maxStackSize).invoke(Registry.ITEM.get(CraftNamespacedKey.toMinecraft(material.getKey())), size);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Integer burnTime(Material material) {\r\n        return AbstractFurnaceBlockEntity.getFuel().get(CraftMagicNumbers.getItem(material));\r\n    }\r\n\r\n    @Override\r\n    public void setShapedRecipeIngredient(ShapedRecipe recipe, char c, ItemStack[] item, boolean exact) {\r\n        if (item.length == 1 && item[0].getType() == Material.AIR) {\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(Material.AIR));\r\n        }\r\n        else if (exact) {\r\n            recipe.setIngredient(c, new RecipeChoice.ExactChoice(item));\r\n        }\r\n        else {\r\n            Material[] mats = new Material[item.length];\r\n            for (int i = 0; i < item.length; i++) {\r\n                mats[i] = item[i].getType();\r\n            }\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(mats));\r\n        }\r\n    }\r\n\r\n    public static Ingredient itemArrayToRecipe(ItemStack[] items, boolean exact) {\r\n        Ingredient.ItemValue[] stacks = new Ingredient.ItemValue[items.length];\r\n        for (int i = 0; i < items.length; i++) {\r\n            stacks[i] = new Ingredient.ItemValue(CraftItemStack.asNMSCopy(items[i]));\r\n        }\r\n        Ingredient itemRecipe = new Ingredient(Arrays.stream(stacks));\r\n        itemRecipe.exact = exact;\r\n        return itemRecipe;\r\n    }\r\n\r\n    @Override\r\n    public void registerFurnaceRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, float exp, int time, String type, boolean exact, String category) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        AbstractCookingRecipe recipe;\r\n        if (type.equalsIgnoreCase(\"smoker\")) {\r\n            recipe = new SmokingRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"blast\")) {\r\n            recipe = new BlastingRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"campfire\")) {\r\n            recipe = new CampfireCookingRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else {\r\n            recipe = new SmeltingRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public void registerStonecuttingRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, boolean exact) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        StonecutterRecipe recipe = new StonecutterRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result));\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public void registerSmithingRecipe(String keyName, ItemStack result, ItemStack[] baseItem, boolean baseExact, ItemStack[] upgradeItem, boolean upgradeExact, ItemStack[] templateItem, boolean templateExact) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient baseItemRecipe = itemArrayToRecipe(baseItem, baseExact);\r\n        Ingredient upgradeItemRecipe = itemArrayToRecipe(upgradeItem, upgradeExact);\r\n        UpgradeRecipe recipe = new UpgradeRecipe(key, baseItemRecipe, upgradeItemRecipe, CraftItemStack.asNMSCopy(result));\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public void registerShapelessRecipe(String keyName, String group, ItemStack result, List<ItemStack[]> ingredients, boolean[] exact, String category) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        ArrayList<Ingredient> ingredientList = new ArrayList<>();\r\n        for (int i = 0; i < ingredients.size(); i++) {\r\n            ingredientList.add(itemArrayToRecipe(ingredients.get(i), exact[i]));\r\n        }\r\n        ShapelessRecipe recipe = new ShapelessRecipe(key, group, CraftItemStack.asNMSCopy(result), NonNullList.of(null, ingredientList.toArray(new Ingredient[0])));\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public String getJsonString(ItemStack itemStack) {\r\n        String json = CraftItemStack.asNMSCopy(itemStack).getDisplayName().getStyle().toString().replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\");\r\n        return json.substring(176, json.length() - 185);\r\n    }\r\n\r\n    @Override\r\n    public String getLegacyHoverNbt(ItemTag item) {\r\n        net.minecraft.nbt.CompoundTag tag = CraftItemStack.asNMSCopy(item.getItemStack()).getTag();\r\n        if (tag == null) {\r\n            return null;\r\n        }\r\n        return tag.toString();\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getSkullSkin(ItemStack is) {\r\n        net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(is);\r\n        if (itemStack.hasTag()) {\r\n            net.minecraft.nbt.CompoundTag tag = itemStack.getTag();\r\n            if (tag.contains(\"SkullOwner\", 10)) {\r\n                GameProfile profile = NbtUtils.readGameProfile(tag.getCompound(\"SkullOwner\"));\r\n                if (profile != null) {\r\n                    Property property = Iterables.getFirst(profile.getProperties().get(\"textures\"), null);\r\n                    return new PlayerProfile(profile.getName(), profile.getId(),\r\n                            property != null ? property.getValue() : null,\r\n                            property != null ? property.getSignature() : null);\r\n                }\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setSkullSkin(ItemStack itemStack, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        if (playerProfile.hasTexture()) {\r\n            gameProfile.getProperties().get(\"textures\").clear();\r\n            if (playerProfile.getTextureSignature() != null) {\r\n                gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n            }\r\n            else {\r\n                gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture()));\r\n            }\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        net.minecraft.nbt.CompoundTag tag = nmsItemStack.hasTag() ? nmsItemStack.getTag() : new net.minecraft.nbt.CompoundTag();\r\n        tag.put(\"SkullOwner\", NbtUtils.writeGameProfile(new net.minecraft.nbt.CompoundTag(), gameProfile));\r\n        nmsItemStack.setTag(tag);\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack addNbtData(ItemStack itemStack, String key, BinaryTag value) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.getOrCreateTag().put(key, NBTAdapter.toNMS(value));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(ItemStack itemStack) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        if (nmsItemStack != null && nmsItemStack.hasTag()) {\r\n            return NBTAdapter.toAPI(nmsItemStack.getTag());\r\n        }\r\n        return CompoundBinaryTag.empty();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setNbtData(ItemStack itemStack, CompoundBinaryTag compoundTag) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.setTag(NBTAdapter.toNMS(compoundTag));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public void setInventoryItem(Inventory inventory, ItemStack item, int slot) {\r\n        if (inventory instanceof CraftInventoryPlayer && ((CraftInventoryPlayer) inventory).getInventory().player == null) {\r\n            ((CraftInventoryPlayer) inventory).getInventory().setItem(slot, CraftItemStack.asNMSCopy(item));\r\n        }\r\n        else {\r\n            inventory.setItem(slot, item);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getDisplayName(ItemTag item) {\r\n        if (!item.getItemMeta().hasDisplayName()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        String jsonText = ((net.minecraft.nbt.CompoundTag) nmsItemStack.getTag().get(\"display\")).getString(\"Name\");\r\n        BaseComponent[] nameComponent = ComponentSerializer.parse(jsonText);\r\n        return FormattedTextHelper.stringify(nameComponent);\r\n    }\r\n\r\n    @Override\r\n    public List<String> getLore(ItemTag item) {\r\n        if (!item.getItemMeta().hasLore()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        ListTag list = ((net.minecraft.nbt.CompoundTag) nmsItemStack.getTag().get(\"display\")).getList(\"Lore\", 8);\r\n        List<String> outList = new ArrayList<>();\r\n        for (int i = 0; i < list.size(); i++) {\r\n            BaseComponent[] lineComponent = ComponentSerializer.parse(list.getString(i));\r\n            outList.add(FormattedTextHelper.stringify(lineComponent));\r\n        }\r\n        return outList;\r\n    }\r\n\r\n    @Override\r\n    public void setDisplayName(ItemTag item, String name) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        net.minecraft.nbt.CompoundTag tag = nmsItemStack.getOrCreateTag();\r\n        net.minecraft.nbt.CompoundTag display = tag.getCompound(\"display\");\r\n        if (!tag.contains(\"display\")) {\r\n            tag.put(\"display\", display);\r\n        }\r\n        if (name == null || name.isEmpty()) {\r\n            display.put(\"Name\", null);\r\n            return;\r\n        }\r\n        BaseComponent[] components = FormattedTextHelper.parse(name, ChatColor.WHITE);\r\n        display.put(\"Name\", net.minecraft.nbt.StringTag.valueOf(ComponentSerializer.toString(components)));\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    @Override\r\n    public void setLore(ItemTag item, List<String> lore) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        net.minecraft.nbt.CompoundTag tag = nmsItemStack.getOrCreateTag();\r\n        net.minecraft.nbt.CompoundTag display = tag.getCompound(\"display\");\r\n        if (!tag.contains(\"display\")) {\r\n            tag.put(\"display\", display);\r\n        }\r\n        if (lore == null || lore.isEmpty()) {\r\n            display.put(\"Lore\", null);\r\n        }\r\n        else {\r\n            ListTag tagList = new ListTag();\r\n            for (String line : lore) {\r\n                tagList.add(net.minecraft.nbt.StringTag.valueOf(ComponentSerializer.toString(FormattedTextHelper.parse(line, ChatColor.WHITE))));\r\n            }\r\n            display.put(\"Lore\", tagList);\r\n        }\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.getCorrectStateForFluidBlock, and rewritten as reflection due to Spigot bug - refer to bottom of BlockHelperImpl for detail.\r\n     */\r\n    public static BlockState getCorrectStateForFluidBlock(Level world, BlockState iblockdata, BlockPos blockposition) {\r\n        try {\r\n            // FluidState fluid = iblockdata.getFluidState();\r\n            Object fluid = BlockHelperImpl.BLOCKSTATEBASE_GETFLUIDSTATE.invoke(iblockdata);\r\n            //return !fluid.isEmpty() && !iblockdata.isFaceSturdy(world, blockposition, Direction.UP) ? fluid.createLegacyBlock() : iblockdata;\r\n            boolean isEmpty = (boolean) BlockHelperImpl.FLUIDSTATE_ISEMPTY.invoke(fluid);\r\n            if (!isEmpty && !iblockdata.isFaceSturdy(world, blockposition, Direction.UP)) {\r\n                return (BlockState) BlockHelperImpl.FLUIDSTATE_CREATELEGACYBLOCK.invoke(fluid);\r\n            }\r\n            else {\r\n                return iblockdata;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return iblockdata;\r\n        }\r\n    }\r\n\r\n    public static boolean blockStateFluidIsEmpty(BlockState iblockdata) {\r\n        try {\r\n            Object fluid = BlockHelperImpl.BLOCKSTATEBASE_GETFLUIDSTATE.invoke(iblockdata);\r\n            return (boolean) BlockHelperImpl.FLUIDSTATE_ISEMPTY.invoke(fluid);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return false;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.update, redesigned slightly to render totally rather than just relative to a player.\r\n     * Some variables manually renamed for readability.\r\n     * Also contains reflection fixes for Spigot's FluidState bug.\r\n     */\r\n    public static void renderFullMap(MapItemSavedData worldmap, int xMin, int zMin, int xMax, int zMax) {\r\n        Level world = ((CraftWorld) worldmap.mapView.getWorld()).getHandle();\r\n        int scale = 1 << worldmap.scale;\r\n        int mapX = worldmap.x;\r\n        int mapZ = worldmap.z;\r\n        for (int x = xMin; x < xMax; x++) {\r\n            double d0 = 0.0D;\r\n            for (int z = zMin; z < zMax; z++) {\r\n                int k2 = (mapX / scale + x - 64) * scale;\r\n                int l2 = (mapZ / scale + z - 64) * scale;\r\n                Multiset<MaterialColor> multiset = LinkedHashMultiset.create();\r\n                LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2));\r\n                if (!chunk.isEmpty()) {\r\n                    ChunkPos chunkcoordintpair = chunk.getPos();\r\n                    int i3 = k2 & 15;\r\n                    int j3 = l2 & 15;\r\n                    int k3 = 0;\r\n                    double d1 = 0.0D;\r\n                    if (world.dimensionType().hasCeiling()) {\r\n                        int l3 = k2 + l2 * 231871;\r\n                        l3 = l3 * l3 * 31287121 + l3 * 11;\r\n                        if ((l3 >> 20 & 1) == 0) {\r\n                            multiset.add(Blocks.DIRT.defaultBlockState().getMapColor(world, BlockPos.ZERO), 10);\r\n                        }\r\n                        else {\r\n                            multiset.add(Blocks.STONE.defaultBlockState().getMapColor(world, BlockPos.ZERO), 100);\r\n                        }\r\n\r\n                        d1 = 100.0D;\r\n                    }\r\n                    else {\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition1 = new BlockPos.MutableBlockPos();\r\n                        for (int i4 = 0; i4 < scale; ++i4) {\r\n                            for (int j4 = 0; j4 < scale; ++j4) {\r\n                                int k4 = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i4 + i3, j4 + j3) + 1;\r\n                                BlockState iblockdata;\r\n                                if (k4 <= world.getMinBuildHeight() + 1) {\r\n                                    iblockdata = Blocks.BEDROCK.defaultBlockState();\r\n                                }\r\n                                else {\r\n                                    do {\r\n                                        --k4;\r\n                                        blockposition_mutableblockposition.set(chunkcoordintpair.getMinBlockX() + i4 + i3, k4, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                        iblockdata = chunk.getBlockState(blockposition_mutableblockposition);\r\n                                    } while (iblockdata.getMapColor(world, blockposition_mutableblockposition) == MaterialColor.NONE && k4 > world.getMinBuildHeight());\r\n                                    if (k4 > world.getMinBuildHeight() && !blockStateFluidIsEmpty(iblockdata)) {\r\n                                        int l4 = k4 - 1;\r\n                                        blockposition_mutableblockposition1.set(blockposition_mutableblockposition);\r\n\r\n                                        BlockState iblockdata1;\r\n                                        do {\r\n                                            blockposition_mutableblockposition1.t(l4--);\r\n                                            iblockdata1 = chunk.getBlockState(blockposition_mutableblockposition1);\r\n                                            k3++;\r\n                                        } while (l4 > world.getMinBuildHeight() && !blockStateFluidIsEmpty(iblockdata1));\r\n                                        iblockdata = getCorrectStateForFluidBlock(world, iblockdata, blockposition_mutableblockposition);\r\n                                    }\r\n                                }\r\n                                worldmap.checkBanners(world, chunkcoordintpair.getMinBlockX() + i4 + i3, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                d1 += (double) k4 / (double) (scale * scale);\r\n                                multiset.add(iblockdata.getMapColor(world, blockposition_mutableblockposition));\r\n                            }\r\n                        }\r\n                    }\r\n                    k3 /= scale * scale;\r\n                    double d2 = (d1 - d0) * 4.0D / (double) (scale + 4) + ((double) (x + z & 1) - 0.5D) * 0.4D;\r\n                    byte b0 = 1;\r\n                    if (d2 > 0.6D) {\r\n                        b0 = 2;\r\n                    }\r\n                    if (d2 < -0.6D) {\r\n                        b0 = 0;\r\n                    }\r\n                    MaterialColor materialmapcolor = Iterables.getFirst(Multisets.copyHighestCountFirst(multiset), MaterialColor.NONE);\r\n                    if (materialmapcolor == MaterialColor.WATER) {\r\n                        d2 = (double) k3 * 0.1D + (double) (x + z & 1) * 0.2D;\r\n                        b0 = 1;\r\n                        if (d2 < 0.5D) {\r\n                            b0 = 2;\r\n                        }\r\n                        if (d2 > 0.9D) {\r\n                            b0 = 0;\r\n                        }\r\n                    }\r\n                    d0 = d1;\r\n                    worldmap.updateColor(x, z, (byte) (materialmapcolor.id * 4 + b0));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean renderEntireMap(int mapId, int xMin, int zMin, int xMax, int zMax) {\r\n        MapItemSavedData worldmap = ((CraftServer) Bukkit.getServer()).getServer().getLevel(net.minecraft.world.level.Level.OVERWORLD).getMapData(\"map_\" + mapId);\r\n        if (worldmap == null) {\r\n            return false;\r\n        }\r\n        renderFullMap(worldmap, xMin, zMin, xMax, zMax);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean isValidMix(ItemStack input, ItemStack ingredient) {\r\n        net.minecraft.world.item.ItemStack nmsInput = CraftItemStack.asNMSCopy(input);\r\n        net.minecraft.world.item.ItemStack nmsIngredient = CraftItemStack.asNMSCopy(ingredient);\r\n        return net.minecraft.world.item.alchemy.PotionBrewing.hasMix(nmsInput, nmsIngredient);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/NBTAdapter.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\n\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport net.kyori.adventure.nbt.*;\nimport net.minecraft.nbt.*;\n\nimport java.lang.invoke.MethodHandle;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class NBTAdapter {\n\n    public static final MethodHandle COMPOUND_TAG_MAP_CONSTRUCTOR = ReflectionHelper.getConstructor(CompoundTag.class, Map.class);\n\n    public static Tag toNMS(BinaryTag tag) {\n        if (tag instanceof ByteBinaryTag byteTag) {\n            return switch (byteTag.value()) {\n                case 0 -> ByteTag.ZERO;\n                case 1 -> ByteTag.ONE;\n                default -> ByteTag.valueOf(byteTag.value());\n            };\n        }\n        else if (tag instanceof ShortBinaryTag shortTag) {\n            return ShortTag.valueOf(shortTag.value());\n        }\n        else if (tag instanceof IntBinaryTag intTag) {\n            return IntTag.valueOf(intTag.value());\n        }\n        else if (tag instanceof LongBinaryTag longTag) {\n            return LongTag.valueOf(longTag.value());\n        }\n        else if (tag instanceof FloatBinaryTag floatTag) {\n            return FloatTag.valueOf(floatTag.value());\n        }\n        else if (tag instanceof DoubleBinaryTag doubleTag) {\n            return DoubleTag.valueOf(doubleTag.value());\n        }\n        else if (tag instanceof ByteArrayBinaryTag byteArrayTag) {\n            return new ByteArrayTag(byteArrayTag.value());\n        }\n        else if (tag instanceof IntArrayBinaryTag intArrayTag) {\n            return new IntArrayTag(intArrayTag.value());\n        }\n        else if (tag instanceof LongArrayBinaryTag longArrayTag) {\n            return new LongArrayTag(longArrayTag.value());\n        }\n        else if (tag instanceof StringBinaryTag stringTag) {\n            return StringTag.valueOf(stringTag.value());\n        }\n        else if (tag instanceof ListBinaryTag listTag) {\n            return toNMS(listTag);\n        }\n        else if (tag instanceof CompoundBinaryTag compoundTag) {\n            return toNMS(compoundTag);\n        }\n        else if (tag instanceof EndBinaryTag) {\n            return EndTag.INSTANCE;\n        }\n        throw new IllegalStateException(\"Unrecognized API tag of type '\" + tag.type() + \"': \" + tag);\n    }\n\n    public static BinaryTag toAPI(Tag nmsTag) {\n        if (nmsTag instanceof ByteTag nmsByteTag) {\n            return ByteBinaryTag.byteBinaryTag(nmsByteTag.getAsByte());\n        }\n        else if (nmsTag instanceof ShortTag nmsShortTag) {\n            return ShortBinaryTag.shortBinaryTag(nmsShortTag.getAsShort());\n        }\n        else if (nmsTag instanceof IntTag nmsIntTag) {\n            return IntBinaryTag.intBinaryTag(nmsIntTag.getAsInt());\n        }\n        else if (nmsTag instanceof LongTag nmsLongTag) {\n            return LongBinaryTag.longBinaryTag(nmsLongTag.getAsLong());\n        }\n        else if (nmsTag instanceof FloatTag nmsFloatTag) {\n            return FloatBinaryTag.floatBinaryTag(nmsFloatTag.getAsFloat());\n        }\n        else if (nmsTag instanceof DoubleTag nmsDoubleTag) {\n            return DoubleBinaryTag.doubleBinaryTag(nmsDoubleTag.getAsDouble());\n        }\n        else if (nmsTag instanceof ByteArrayTag nmsByteArrayTag) {\n            return ByteArrayBinaryTag.byteArrayBinaryTag(nmsByteArrayTag.getAsByteArray());\n        }\n        else if (nmsTag instanceof IntArrayTag nmsIntArrayTag) {\n            return IntArrayBinaryTag.intArrayBinaryTag(nmsIntArrayTag.getAsIntArray());\n        }\n        else if (nmsTag instanceof LongArrayTag nmsLongArrayTag) {\n            return LongArrayBinaryTag.longArrayBinaryTag(nmsLongArrayTag.getAsLongArray());\n        }\n        else if (nmsTag instanceof StringTag nmsStringTag) {\n            return StringBinaryTag.stringBinaryTag(nmsStringTag.getAsString());\n        }\n        else if (nmsTag instanceof ListTag nmsListTag) {\n            return toAPI(nmsListTag);\n        }\n        else if (nmsTag instanceof CompoundTag nmsCompoundTag) {\n            return toAPI(nmsCompoundTag);\n        }\n        else if (nmsTag instanceof EndTag) {\n            return EndBinaryTag.endBinaryTag();\n        }\n        throw new IllegalStateException(\"Unrecognized NMS tag of type '\" + nmsTag.getClass().getName() + '/' + nmsTag.getType().getName() + \"': \" + nmsTag);\n    }\n\n    public static ListBinaryTag toAPI(ListTag nmsListTag) {\n        ListBinaryTag.Builder<BinaryTag> builder = ListBinaryTag.builder(nmsListTag.size());\n        for (Tag nmsValue : nmsListTag) {\n            builder.add(toAPI(nmsValue));\n        }\n        return builder.build();\n    }\n\n    public static ListTag toNMS(ListBinaryTag listTag) {\n        ListTag nmsListTag = new ListTag();\n        for (BinaryTag value : listTag) {\n            nmsListTag.add(toNMS(value));\n        }\n        return nmsListTag;\n    }\n\n    public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) {\n        CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size());\n        for (String key : nmsCompoundTag.getAllKeys()) {\n            builder.put(key, toAPI(nmsCompoundTag.get(key)));\n        }\n        return builder.build();\n    }\n\n    public static CompoundTag toNMS(CompoundBinaryTag compoundTag) {\n        Map<String, Tag> nmsTags = new HashMap<>(compoundTag.size());\n        for (Map.Entry<String, ? extends BinaryTag> entry : compoundTag) {\n            nmsTags.put(entry.getKey(), toNMS(entry.getValue()));\n        }\n        try {\n            return (CompoundTag) COMPOUND_TAG_MAP_CONSTRUCTOR.invokeExact(nmsTags);\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/PacketHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.PacketHelper;\r\nimport com.denizenscript.denizen.nms.v1_17.Handler;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizen.utilities.maps.MapImage;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.datafixers.util.Pair;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.kyori.adventure.nbt.BinaryTagTypes;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.kyori.adventure.nbt.ListBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.monster.CaveSpider;\r\nimport net.minecraft.world.entity.monster.Creeper;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.entity.monster.Spider;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.entity.BlockEntity;\r\nimport net.minecraft.world.level.block.entity.SignBlockEntity;\r\nimport net.minecraft.world.level.border.WorldBorder;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Team;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.EntityEffect;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.block.banner.Pattern;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftEquipmentSlot;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_17_R1.map.CraftMapCanvas;\r\nimport org.bukkit.craftbukkit.v1_17_R1.map.CraftMapView;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.EntityEquipment;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapPalette;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.*;\r\n\r\npublic class PacketHelperImpl implements PacketHelper {\r\n\r\n    public static final EntityDataAccessor<Float> ENTITY_HUMAN_DATA_WATCHER_ABSORPTION = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, ReflectionMappingsInfo.Player_DATA_PLAYER_ABSORPTION_ID, null);\r\n\r\n    public static final EntityDataAccessor<Byte> ENTITY_DATA_WATCHER_FLAGS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_SHARED_FLAGS_ID, null);\r\n\r\n    public static final MethodHandle ABILITIES_PACKET_FOV_SETTER = ReflectionHelper.getFinalSetter(ClientboundPlayerAbilitiesPacket.class, ReflectionMappingsInfo.ClientboundPlayerAbilitiesPacket_walkingSpeed);\r\n\r\n    public static MethodHandle ENTITY_METADATA_LIST_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundSetEntityDataPacket.class, List.class); // packedItems\r\n\r\n    public static Field ENTITY_TRACKER_ENTRY_GETTER = ReflectionHelper.getFields(ChunkMap.TrackedEntity.class).getFirstOfType(ServerEntity.class);\r\n\r\n    public static MethodHandle CANVAS_GET_BUFFER = ReflectionHelper.getMethodHandle(CraftMapCanvas.class, \"getBuffer\");\r\n    public static Field MAPVIEW_WORLDMAP = ReflectionHelper.getFields(CraftMapView.class).get(\"worldMap\");\r\n\r\n    public static EntityDataAccessor<Optional<Component>> ENTITY_CUSTOM_NAME_METADATA;\r\n    public static EntityDataAccessor<Boolean> ENTITY_CUSTOM_NAME_VISIBLE_METADATA;\r\n\r\n    static {\r\n        try {\r\n            ENTITY_CUSTOM_NAME_METADATA = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_CUSTOM_NAME, null);\r\n            ENTITY_CUSTOM_NAME_VISIBLE_METADATA = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_CUSTOM_NAME_VISIBLE, null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setFakeAbsorption(Player player, float value) {\r\n        SynchedEntityData dw = new SynchedEntityData(null);\r\n        dw.define(ENTITY_HUMAN_DATA_WATCHER_ABSORPTION, value);\r\n        send(player, new ClientboundSetEntityDataPacket(player.getEntityId(), dw, true));\r\n    }\r\n\r\n    @Override\r\n    public void resetWorldBorder(Player player) {\r\n        WorldBorder wb = ((CraftWorld) player.getWorld()).getHandle().getWorldBorder();\r\n        send(player, new ClientboundInitializeBorderPacket(wb));\r\n    }\r\n\r\n    @Override\r\n    public void setWorldBorder(Player player, Location center, double size, double currSize, long time, int warningDistance, int warningTime) {\r\n        WorldBorder wb = new WorldBorder();\r\n        wb.world = ((CraftWorld) player.getWorld()).getHandle();\r\n        wb.setCenter(center.getX(), center.getZ());\r\n        wb.setWarningBlocks(warningDistance);\r\n        wb.setWarningTime(warningTime);\r\n        if (time > 0) {\r\n            wb.lerpSizeBetween(currSize, size, time);\r\n        }\r\n        else {\r\n            wb.setSize(size);\r\n        }\r\n        send(player, new ClientboundInitializeBorderPacket(wb));\r\n    }\r\n\r\n    @Override\r\n    public void setSlot(Player player, int slot, ItemStack itemStack, boolean playerOnly) {\r\n        AbstractContainerMenu menu = ((CraftPlayer) player).getHandle().containerMenu;\r\n        int windowId = playerOnly ? 0 : menu.containerId;\r\n        send(player, new ClientboundContainerSetSlotPacket(windowId, menu.incrementStateId(), slot, CraftItemStack.asNMSCopy(itemStack)));\r\n    }\r\n\r\n    @Override\r\n    public void setFieldOfView(Player player, float fov) {\r\n        ClientboundPlayerAbilitiesPacket packet = new ClientboundPlayerAbilitiesPacket(((CraftPlayer) player).getHandle().getAbilities());\r\n        if (!Float.isNaN(fov)) {\r\n            try {\r\n                ABILITIES_PACKET_FOV_SETTER.invoke(packet, fov);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void respawn(Player player) {\r\n        ((CraftPlayer) player).getHandle().connection.handleClientCommand(new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN));\r\n    }\r\n\r\n    @Override\r\n    public void setVision(Player player, EntityType entityType) {\r\n        final net.minecraft.world.entity.LivingEntity entity;\r\n        if (entityType == EntityType.CREEPER) {\r\n            entity = new Creeper(net.minecraft.world.entity.EntityType.CREEPER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.SPIDER) {\r\n            entity = new Spider(net.minecraft.world.entity.EntityType.SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.CAVE_SPIDER) {\r\n            entity = new CaveSpider(net.minecraft.world.entity.EntityType.CAVE_SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.ENDERMAN) {\r\n            entity = new EnderMan(net.minecraft.world.entity.EntityType.ENDERMAN, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n\r\n        // Spectating an entity then immediately respawning the player prevents a client shader update,\r\n        // allowing the player to retain whatever vision the mob they spectated had.\r\n        send(player, new ClientboundAddMobPacket(entity));\r\n        send(player, new ClientboundSetCameraPacket(entity));\r\n        ((CraftServer) Bukkit.getServer()).getHandle().moveToWorld(((CraftPlayer) player).getHandle(),\r\n                ((CraftWorld) player.getWorld()).getHandle(), true, player.getLocation(), false);\r\n    }\r\n\r\n    @Override\r\n    public void showDemoScreen(Player player) {\r\n        send(player, new ClientboundGameEventPacket(ClientboundGameEventPacket.DEMO_EVENT, 0f));\r\n    }\r\n\r\n    @Override\r\n    public void showBlockAction(Player player, Location location, int action, int state) {\r\n        BlockPos position = new BlockPos(location.getX(), location.getY(), location.getZ());\r\n        Block block = ((CraftWorld) location.getWorld()).getHandle().getBlockState(position).getBlock();\r\n        send(player, new ClientboundBlockEventPacket(position, block, action, state));\r\n    }\r\n\r\n    @Override\r\n    public void showBlockCrack(Player player, int id, Location location, int progress) {\r\n        BlockPos position = new BlockPos(location.getX(), location.getY(), location.getZ());\r\n        send(player, new ClientboundBlockDestructionPacket(id, position, progress));\r\n    }\r\n\r\n    @Override\r\n    public void showTileEntityData(Player player, Location location, int action, CompoundBinaryTag compoundTag) {\r\n        BlockPos position = new BlockPos(location.getX(), location.getY(), location.getZ());\r\n        send(player, new ClientboundBlockEntityDataPacket(position, action, NBTAdapter.toNMS(compoundTag)));\r\n    }\r\n\r\n    @Override\r\n    public void showBannerUpdate(Player player, Location location, List<Pattern> patterns) {\r\n        ListBinaryTag.Builder<CompoundBinaryTag> nbtPatterns = ListBinaryTag.builder(BinaryTagTypes.COMPOUND);\r\n        for (Pattern pattern : patterns) {\r\n            nbtPatterns.add(CompoundBinaryTag.builder()\r\n                    .putInt(\"Color\", pattern.getColor().getDyeData())\r\n                    .putString(\"Pattern\", pattern.getPattern().getIdentifier())\r\n                    .build());\r\n        }\r\n        CompoundBinaryTag compoundTag = NMSHandler.blockHelper.getNbtData(location.getBlock()).put(\"Patterns\", nbtPatterns.build());\r\n        showTileEntityData(player, location, 3, compoundTag);\r\n    }\r\n\r\n    @Override\r\n    public void showTabListHeaderFooter(Player player, String header, String footer) {\r\n        Component cHeader = Handler.componentToNMS(FormattedTextHelper.parse(header, ChatColor.WHITE));\r\n        Component cFooter = Handler.componentToNMS(FormattedTextHelper.parse(footer, ChatColor.WHITE));\r\n        ClientboundTabListPacket packet = new ClientboundTabListPacket(cHeader, cFooter);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void showTitle(Player player, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) {\r\n        send(player, new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks));\r\n        if (title != null) {\r\n            send(player, new ClientboundSetTitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE))));\r\n        }\r\n        if (subtitle != null) {\r\n            send(player, new ClientboundSetSubtitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(subtitle, ChatColor.WHITE))));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void showEquipment(Player player, LivingEntity entity, EquipmentSlot equipmentSlot, ItemStack itemStack) {\r\n        Pair<net.minecraft.world.entity.EquipmentSlot, net.minecraft.world.item.ItemStack> pair = new Pair<>(CraftEquipmentSlot.getNMS(equipmentSlot), CraftItemStack.asNMSCopy(itemStack));\r\n        ArrayList<Pair<net.minecraft.world.entity.EquipmentSlot, net.minecraft.world.item.ItemStack>> pairList = new ArrayList<>();\r\n        pairList.add(pair);\r\n        send(player, new ClientboundSetEquipmentPacket(entity.getEntityId(), pairList));\r\n    }\r\n\r\n    @Override\r\n    public void resetEquipment(Player player, LivingEntity entity) {\r\n        EntityEquipment equipment = entity.getEquipment();\r\n        ArrayList<Pair<net.minecraft.world.entity.EquipmentSlot, net.minecraft.world.item.ItemStack>> pairList = new ArrayList<>();\r\n        pairList.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.MAINHAND, CraftItemStack.asNMSCopy(equipment.getItemInMainHand())));\r\n        pairList.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.OFFHAND, CraftItemStack.asNMSCopy(equipment.getItemInOffHand())));\r\n        pairList.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.HEAD, CraftItemStack.asNMSCopy(equipment.getHelmet())));\r\n        pairList.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.CHEST, CraftItemStack.asNMSCopy(equipment.getChestplate())));\r\n        pairList.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.LEGS, CraftItemStack.asNMSCopy(equipment.getLeggings())));\r\n        pairList.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.FEET, CraftItemStack.asNMSCopy(equipment.getBoots())));\r\n        send(player, new ClientboundSetEquipmentPacket(entity.getEntityId(), pairList));\r\n    }\r\n\r\n    @Override\r\n    public void showMobHealth(Player player, LivingEntity mob, double health, double maxHealth) {\r\n        AttributeInstance attr = new AttributeInstance(Attributes.MAX_HEALTH, (a) -> { });\r\n        attr.setBaseValue(maxHealth);\r\n        send(player, new ClientboundUpdateAttributesPacket(mob.getEntityId(), Collections.singletonList(attr)));\r\n        FriendlyByteBuf healthData = new FriendlyByteBuf(Unpooled.buffer());\r\n        healthData.writeVarInt(mob.getEntityId());\r\n        healthData.writeByte(9); // health id\r\n        healthData.writeVarInt(2); // type = float\r\n        healthData.writeFloat((float) health);\r\n        healthData.writeByte(255); // Mark end of packet\r\n        send(player, new ClientboundSetEntityDataPacket(healthData));\r\n    }\r\n\r\n    @Override\r\n    public void showHealth(Player player, float health, int food, float saturation) {\r\n        send(player, new ClientboundSetHealthPacket(health, food, saturation));\r\n    }\r\n\r\n    @Override\r\n    public void resetHealth(Player player) {\r\n        showHealth(player, (float) player.getHealth(), player.getFoodLevel(), player.getSaturation());\r\n    }\r\n\r\n    @Override\r\n    public void showSignEditor(Player player, Location location) {\r\n        if (location == null) {\r\n            LocationTag fakeSign = new LocationTag(player.getLocation());\r\n            fakeSign.setY(0);\r\n            FakeBlock.showFakeBlockTo(Collections.singletonList(new PlayerTag(player)), fakeSign, new MaterialTag(org.bukkit.Material.OAK_WALL_SIGN), new DurationTag(1), true);\r\n            BlockPos pos = new BlockPos(fakeSign.getX(), 0, fakeSign.getZ());\r\n            ((DenizenNetworkManagerImpl) ((CraftPlayer) player).getHandle().connection.connection).packetListener.fakeSignExpected = pos;\r\n            send(player, new ClientboundOpenSignEditorPacket(pos));\r\n            return;\r\n        }\r\n        BlockEntity tileEntity = ((CraftWorld) location.getWorld()).getHandle().getTileEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), true);\r\n        if (tileEntity instanceof SignBlockEntity) {\r\n            SignBlockEntity sign = (SignBlockEntity) tileEntity;\r\n            // Prevent client crashing by sending current state of the sign\r\n            send(player, sign.getUpdatePacket());\r\n            sign.isEditable = true;\r\n            sign.setAllowedPlayerEditor(player.getUniqueId());\r\n            send(player, new ClientboundOpenSignEditorPacket(sign.getBlockPos()));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void forceSpectate(Player player, Entity entity) {\r\n        send(player, new ClientboundSetCameraPacket(((CraftEntity) entity).getHandle()));\r\n    }\r\n\r\n    @Override\r\n    public void sendRename(Player player, Entity entity, String name, boolean listMode) {\r\n        try {\r\n            if (entity.getType() == EntityType.PLAYER) {\r\n                if (listMode) {\r\n                    send(player, new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, ((CraftPlayer) player).getHandle()));\r\n                }\r\n                else {\r\n                    // For player entities, force a respawn packet and let the dynamic intercept correct the details\r\n                    ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level).getChunkProvider().chunkMap;\r\n                    ChunkMap.TrackedEntity entityTracker = tracker.G.get(entity.getEntityId());\r\n                    if (entityTracker != null) {\r\n                        try {\r\n                            ServerEntity entry = (ServerEntity) ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n                            if (entry != null) {\r\n                                entry.removePairing(((CraftPlayer) player).getHandle());\r\n                                entry.addPairing(((CraftPlayer) player).getHandle());\r\n                            }\r\n                        }\r\n                        catch (Throwable ex) {\r\n                            Debug.echoError(ex);\r\n                        }\r\n                    }\r\n                }\r\n                return;\r\n            }\r\n            SynchedEntityData fakeData = new SynchedEntityData(((CraftEntity) entity).getHandle());\r\n            ClientboundSetEntityDataPacket packet = new ClientboundSetEntityDataPacket(entity.getEntityId(), fakeData, false);\r\n            List<SynchedEntityData.DataItem<?>> list = new ArrayList<>();\r\n            list.add(new SynchedEntityData.DataItem<>(ENTITY_CUSTOM_NAME_METADATA, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)))));\r\n            list.add(new SynchedEntityData.DataItem<>(ENTITY_CUSTOM_NAME_VISIBLE_METADATA, true));\r\n            ENTITY_METADATA_LIST_SETTER.invoke(packet, list);\r\n            send(player, packet);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static HashMap<UUID, HashMap<UUID, PlayerTeam>> noCollideTeamMap = new HashMap<>();\r\n\r\n    @Override\r\n    public void generateNoCollideTeam(Player player, UUID noCollide) {\r\n        removeNoCollideTeam(player, noCollide);\r\n        PlayerTeam team = new PlayerTeam(SidebarImpl.dummyScoreboard, Utilities.generateRandomColors(8));\r\n        team.getPlayers().add(noCollide.toString());\r\n        team.setCollisionRule(Team.CollisionRule.NEVER);\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>());\r\n        map.put(noCollide, team);\r\n        send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n    }\r\n\r\n    @Override\r\n    public void removeNoCollideTeam(Player player, UUID noCollide) {\r\n        if (noCollide == null || !player.isOnline()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n            return;\r\n        }\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.get(player.getUniqueId());\r\n        if (map == null) {\r\n            return;\r\n        }\r\n        PlayerTeam team = map.remove(noCollide);\r\n        if (team != null) {\r\n            send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        if (map.isEmpty()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityMetadataFlagsUpdate(Player player, Entity entity) {\r\n        SynchedEntityData dw = new SynchedEntityData(null);\r\n        dw.define(ENTITY_DATA_WATCHER_FLAGS, ((CraftEntity) entity).getHandle().getEntityData().get(ENTITY_DATA_WATCHER_FLAGS));\r\n        send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), dw, true));\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityEffect(Player player, Entity entity, EntityEffect effect) {\r\n        send(player, new ClientboundEntityEventPacket(((CraftEntity) entity).getHandle(), effect.getData()));\r\n    }\r\n\r\n    @Override\r\n    public int getPacketStats(Player player, boolean sent) {\r\n        DenizenNetworkManagerImpl netMan = (DenizenNetworkManagerImpl) ((CraftPlayer) player).getHandle().connection.connection;\r\n        return sent ? netMan.packetsSent : netMan.packetsReceived;\r\n    }\r\n\r\n    @Override\r\n    public void setMapData(MapCanvas canvas, byte[] bytes, int x, int y, MapImage image) {\r\n        if (x > 127 || y > 127) {\r\n            return;\r\n        }\r\n        int width = Math.min(image.width, 128 - x),\r\n                height = Math.min(image.height, 128 - y);\r\n        if (x + width <= 0 || y + height <= 0) {\r\n            return;\r\n        }\r\n        try {\r\n            boolean anyChanged = false;\r\n            byte[] buffer = (byte[]) CANVAS_GET_BUFFER.invoke(canvas);\r\n            for (int x2 = x < 0 ? -x : 0; x2 < width; ++x2) {\r\n                for (int y2 = y < 0 ? -y : 0; y2 < height; ++y2) {\r\n                    byte p = bytes[y2 * image.width + x2];\r\n                    if (p != MapPalette.TRANSPARENT) {\r\n                        int index = (y2 + y) * 128 + (x2 + x);\r\n                        if (buffer[index] != p) {\r\n                            buffer[index] = p;\r\n                            anyChanged = true;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            if (anyChanged) {\r\n                // Flag the whole image as dirty\r\n                MapItemSavedData map = (MapItemSavedData) MAPVIEW_WORLDMAP.get(canvas.getMapView());\r\n                map.setColorsDirty(Math.max(x, 0), Math.max(y, 0));\r\n                map.setColorsDirty(width + x - 1, height + y - 1);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setNetworkManagerFor(Player player) {\r\n        DenizenNetworkManagerImpl.setNetworkManager(player);\r\n    }\r\n\r\n    @Override\r\n    public void showDebugTestMarker(Player player, Location location, ColorTag color, String name, int time) {\r\n        ResourceLocation packetKey = new ResourceLocation(\"minecraft\", \"debug/game_test_add_marker\");\r\n        FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());\r\n        buf.writeBlockPos(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()));\r\n        int colorInt = color.blue | (color.green << 8) | (color.red << 16) | (color.alpha << 24);\r\n        buf.writeInt(colorInt);\r\n        buf.writeByteArray(name.getBytes(StandardCharsets.UTF_8));\r\n        buf.writeInt(time);\r\n        ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(packetKey, buf);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void clearDebugTestMarker(Player player) {\r\n        ResourceLocation packetKey = new ResourceLocation(\"minecraft\", \"debug/game_test_clear\");\r\n        FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());\r\n        ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(packetKey, buf);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendBrand(Player player, String brand) {\r\n        ResourceLocation packetKey = new ResourceLocation(\"minecraft\", \"brand\");\r\n        FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());\r\n        buf.writeUtf(brand);\r\n        ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(packetKey, buf);\r\n        send(player, packet);\r\n    }\r\n\r\n    public static void send(Player player, Packet packet) {\r\n        ((CraftPlayer) player).getHandle().connection.send(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/PlayerHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.enums.CustomEntityType;\r\nimport com.denizenscript.denizen.nms.interfaces.PlayerHelper;\r\nimport com.denizenscript.denizen.nms.v1_17.Handler;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.ImprovedOfflinePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.network.handlers.AbstractListenerPlayInImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.entity.DenizenEntityType;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.players.ServerOpList;\r\nimport net.minecraft.server.players.ServerOpListEntry;\r\nimport net.minecraft.stats.ServerRecipeBook;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.item.crafting.Recipe;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport org.bukkit.*;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_17_R1.boss.CraftBossBar;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_17_R1.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class PlayerHelperImpl extends PlayerHelper {\r\n\r\n    public static final Field ATTACK_COOLDOWN_TICKS = ReflectionHelper.getFields(LivingEntity.class).get(ReflectionMappingsInfo.LivingEntity_attackStrengthTicker, int.class);\r\n\r\n    public static final Field FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundTickCount, int.class);\r\n    public static final Field VEHICLE_FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundVehicleTickCount, int.class);\r\n    public static final MethodHandle PLAYER_RESPAWNFORCED_SETTER = ReflectionHelper.getFinalSetter(ServerPlayer.class, ReflectionMappingsInfo.ServerPlayer_respawnForced, boolean.class);\r\n\r\n    public static final EntityDataAccessor<Byte> ENTITY_HUMAN_SKINLAYERS_DATAWATCHER;\r\n\r\n    static {\r\n        EntityDataAccessor<Byte> skinlayers = null;\r\n        try {\r\n            skinlayers = (EntityDataAccessor<Byte>) ReflectionHelper.getFields(net.minecraft.world.entity.player.Player.class).get(ReflectionMappingsInfo.Player_DATA_PLAYER_MODE_CUSTOMISATION).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n        ENTITY_HUMAN_SKINLAYERS_DATAWATCHER = skinlayers;\r\n    }\r\n\r\n    @Override\r\n    public void stopSound(Player player, NamespacedKey sound, SoundCategory category) {\r\n        ResourceLocation soundKey = sound == null ? null : CraftNamespacedKey.toMinecraft(sound);\r\n        net.minecraft.sounds.SoundSource nmsCategory = category == null ? null : net.minecraft.sounds.SoundSource.valueOf(category.name());\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundStopSoundPacket(soundKey, nmsCategory));\r\n    }\r\n\r\n    @Override\r\n    public void deTrackEntity(Player player, Entity entity) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerLevel world = (ServerLevel) nmsPlayer.level;\r\n        ChunkMap.TrackedEntity tracker = world.getChunkProvider().chunkMap.G.get(entity.getEntityId());\r\n        if (tracker == null) {\r\n            return;\r\n        }\r\n        sendEntityDestroy(player, entity);\r\n        tracker.removePlayer(nmsPlayer);\r\n    }\r\n\r\n    public static class TrackerData {\r\n        public PlayerTag player;\r\n        public ServerEntity tracker;\r\n    }\r\n\r\n    @Override\r\n    public FakeEntity sendEntitySpawn(List<PlayerTag> players, DenizenEntityType entityType, LocationTag location, ArrayList<Mechanism> mechanisms, int customId, UUID customUUID, boolean autoTrack) {\r\n        CraftWorld world = ((CraftWorld) location.getWorld());\r\n        net.minecraft.world.entity.Entity nmsEntity;\r\n        if (entityType.isCustom()) {\r\n            if (entityType.customEntityType == CustomEntityType.ITEM_PROJECTILE) {\r\n                org.bukkit.inventory.ItemStack itemStack = new ItemStack(Material.STONE);\r\n                for (Mechanism mechanism : mechanisms) {\r\n                    if (mechanism.matches(\"item\") && mechanism.requireObject(ItemTag.class)) {\r\n                        itemStack = mechanism.valueAsType(ItemTag.class).getItemStack();\r\n                    }\r\n                }\r\n                nmsEntity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n            }\r\n            else if (entityType.customEntityType == CustomEntityType.FAKE_PLAYER) {\r\n                String name = null;\r\n                String skin = null;\r\n                for (Mechanism mechanism : new ArrayList<>(mechanisms)) {\r\n                    if (mechanism.matches(\"name\")) {\r\n                        name = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin\")) {\r\n                        skin = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    if (name != null && skin != null) {\r\n                        break;\r\n                    }\r\n                }\r\n                nmsEntity = ((CraftFakePlayerImpl) CustomEntityHelperImpl.spawnFakePlayer(location, name, skin, false)).getHandle();\r\n            }\r\n            else {\r\n                throw new IllegalArgumentException(\"entityType\");\r\n            }\r\n        }\r\n        else {\r\n            nmsEntity = world.createEntity(location, entityType.getBukkitEntityType().getEntityClass());\r\n        }\r\n        if (customUUID != null) {\r\n            nmsEntity.setId(customId);\r\n            nmsEntity.setUUID(customUUID);\r\n        }\r\n        EntityTag entity = new EntityTag(nmsEntity.getBukkitEntity());\r\n        entity.isFake = true;\r\n        entity.isFakeValid = true;\r\n        for (Mechanism mechanism : mechanisms) {\r\n            entity.safeAdjustDuplicate(mechanism);\r\n        }\r\n        nmsEntity.unsetRemoved();\r\n        FakeEntity fake = new FakeEntity(players, location, entity.getBukkitEntity().getEntityId());\r\n        fake.entity = new EntityTag(entity.getBukkitEntity());\r\n        fake.entity.isFake = true;\r\n        fake.entity.isFakeValid = true;\r\n        List<TrackerData> trackers = new ArrayList<>();\r\n        fake.triggerSpawnPacket = (player) -> {\r\n            ServerPlayer nmsPlayer = ((CraftPlayer) player.getPlayerEntity()).getHandle();\r\n            ServerGamePacketListenerImpl conn = nmsPlayer.connection;\r\n            final ServerEntity tracker = new ServerEntity(world.getHandle(), nmsEntity, 1, true, conn::send, Collections.singleton(nmsPlayer.connection));\r\n            tracker.addPairing(nmsPlayer);\r\n            final TrackerData data = new TrackerData();\r\n            data.player = player;\r\n            data.tracker = tracker;\r\n            trackers.add(data);\r\n            if (autoTrack) {\r\n                new BukkitRunnable() {\r\n                    boolean wasOnline = true;\r\n                    @Override\r\n                    public void run() {\r\n                        if (!fake.entity.isFakeValid) {\r\n                            cancel();\r\n                            return;\r\n                        }\r\n                        if (player.isOnline()) {\r\n                            if (!wasOnline) {\r\n                                tracker.addPairing(((CraftPlayer) player.getPlayerEntity()).getHandle());\r\n                                wasOnline = true;\r\n                            }\r\n                            tracker.sendChanges();\r\n                        }\r\n                        else if (wasOnline) {\r\n                            wasOnline = false;\r\n                        }\r\n                    }\r\n                }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n            }\r\n        };\r\n        for (PlayerTag player : players) {\r\n            fake.triggerSpawnPacket.accept(player);\r\n        }\r\n        fake.triggerUpdatePacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.sendChanges();\r\n                }\r\n            }\r\n        };\r\n        fake.triggerDestroyPacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.removePairing(((CraftPlayer) tracker.player.getPlayerEntity()).getHandle());\r\n                }\r\n            }\r\n            trackers.clear();\r\n        };\r\n        return fake;\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityDestroy(Player player, Entity entity) {\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId()));\r\n    }\r\n\r\n    @Override\r\n    public int getFlyKickCooldown(Player player) {\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl) {\r\n            conn = ((AbstractListenerPlayInImpl) conn).oldListener;\r\n        }\r\n        try {\r\n            return Math.max(80 - Math.max(FLY_TICKS.getInt(conn), VEHICLE_FLY_TICKS.getInt(conn)), 0);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return 80;\r\n    }\r\n\r\n    @Override\r\n    public void setFlyKickCooldown(Player player, int ticks) {\r\n        ticks = 80 - ticks;\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl) {\r\n            conn = ((AbstractListenerPlayInImpl) conn).oldListener;\r\n        }\r\n        try {\r\n            FLY_TICKS.setInt(conn, ticks);\r\n            VEHICLE_FLY_TICKS.setInt(conn, ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int ticksPassedDuringCooldown(Player player) {\r\n        try {\r\n            return ATTACK_COOLDOWN_TICKS.getInt(((CraftPlayer) player).getHandle());\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public float getMaxAttackCooldownTicks(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getCurrentItemAttackStrengthDelay() + 3;\r\n    }\r\n\r\n    @Override\r\n    public void setAttackCooldown(Player player, int ticks) {\r\n        try {\r\n            ATTACK_COOLDOWN_TICKS.setInt(((CraftPlayer) player).getHandle(), ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean hasChunkLoaded(Player player, Chunk chunk) {\r\n        return ((CraftWorld) chunk.getWorld()).getHandle().getChunkProvider().chunkMap\r\n                .getPlayers(new ChunkPos(chunk.getX(), chunk.getZ()), false)\r\n                .anyMatch(entityPlayer -> entityPlayer.getUUID().equals(player.getUniqueId()));\r\n    }\r\n\r\n    @Override\r\n    public void setTemporaryOp(Player player, boolean op) {\r\n        MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();\r\n        GameProfile profile = ((CraftPlayer) player).getProfile();\r\n        ServerOpList opList = server.getPlayerList().getOps();\r\n        if (op) {\r\n            int permLevel = server.getOperatorUserPermissionLevel();\r\n            opList.add(new ServerOpListEntry(profile, permLevel, opList.canBypassPlayerLimit(profile)));\r\n        }\r\n        else {\r\n            opList.remove(profile);\r\n        }\r\n        player.recalculatePermissions();\r\n    }\r\n\r\n    @Override\r\n    public void showEndCredits(Player player) {\r\n        ((CraftPlayer) player).getHandle().wonGame = true;\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1f));\r\n    }\r\n\r\n    @Override\r\n    public ImprovedOfflinePlayer getOfflineData(UUID uuid) {\r\n        return new ImprovedOfflinePlayerImpl(uuid);\r\n    }\r\n\r\n    @Override\r\n    public void resendRecipeDetails(Player player) {\r\n        Collection<Recipe<?>> recipes = ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().getRecipes();\r\n        ClientboundUpdateRecipesPacket updatePacket = new ClientboundUpdateRecipesPacket(recipes);\r\n        ((CraftPlayer) player).getHandle().connection.send(updatePacket);\r\n    }\r\n\r\n    @Override\r\n    public void resendDiscoveredRecipes(Player player) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        recipeBook.sendInitialRecipeBook(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    @Override\r\n    public void quietlyAddRecipe(Player player, NamespacedKey key) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        Recipe<?> recipe = ItemHelperImpl.getNMSRecipe(key);\r\n        if (recipe == null) {\r\n            Debug.echoError(\"Cannot add recipe '\" + key + \"': it does not exist.\");\r\n            return;\r\n        }\r\n        recipeBook.add(recipe);\r\n        recipeBook.addHighlight(recipe);\r\n    }\r\n\r\n    @Override\r\n    public String getClientBrand(Player player) {\r\n        return ((DenizenNetworkManagerImpl) ((CraftPlayer) player).getHandle().connection.connection).packetListener.brand;\r\n    }\r\n\r\n    @Override\r\n    public byte getSkinLayers(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getEntityData().get(ENTITY_HUMAN_SKINLAYERS_DATAWATCHER);\r\n    }\r\n\r\n    @Override\r\n    public void setSkinLayers(Player player, byte flags) {\r\n        ((CraftPlayer) player).getHandle().getEntityData().set(ENTITY_HUMAN_SKINLAYERS_DATAWATCHER, flags);\r\n    }\r\n\r\n    @Override\r\n    public void setBossBarTitle(BossBar bar, String title) {\r\n        ((CraftBossBar) bar).getHandle().name = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        ((CraftBossBar) bar).getHandle().broadcast(ClientboundBossEventPacket::createUpdateNamePacket);\r\n    }\r\n\r\n    @Override\r\n    public boolean getSpawnForced(Player player) {\r\n        return ((CraftPlayer) player).getHandle().isRespawnForced();\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnForced(Player player, boolean forced) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        try {\r\n            PLAYER_RESPAWNFORCED_SETTER.invoke(nmsPlayer, forced);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Location getBedSpawnLocation(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        BlockPos spawnPosition = nmsPlayer.getRespawnPosition();\r\n        if (spawnPosition == null) {\r\n            return null;\r\n        }\r\n        Level nmsWorld = MinecraftServer.getServer().getLevel(nmsPlayer.getRespawnDimension());\r\n        if (nmsWorld == null) {\r\n            return null;\r\n        }\r\n        return new Location(nmsWorld.getWorld(), spawnPosition.getX(), spawnPosition.getY(), spawnPosition.getZ(), nmsPlayer.getRespawnAngle(), 0);\r\n    }\r\n\r\n    @Override\r\n    public long getLastActionTime(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getLastActionTime();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/helpers/WorldHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.WorldHelper;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.objects.BiomeTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.players.SleepStatus;\r\nimport net.minecraft.world.DifficultyInstance;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.level.storage.PrimaryLevelData;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\n\r\npublic class WorldHelperImpl implements WorldHelper {\r\n\r\n    @Override\r\n    public boolean isStatic(World world) {\r\n        return ((CraftWorld) world).getHandle().isClientSide;\r\n    }\r\n\r\n    @Override\r\n    public void setStatic(World world, boolean isStatic) {\r\n        ServerLevel worldServer = ((CraftWorld) world).getHandle();\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.Level.class, ReflectionMappingsInfo.Level_isClientSide, worldServer, isStatic);\r\n    }\r\n\r\n    @Override\r\n    public float getLocalDifficulty(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        DifficultyInstance scaler = ((CraftWorld) location.getWorld()).getHandle().getCurrentDifficultyAt(pos);\r\n        return scaler.getEffectiveDifficulty();\r\n    }\r\n\r\n    @Override\r\n    public Location getNearestBiomeLocation(Location start, BiomeTag biome) {\r\n        BlockPos result = ((CraftWorld) start.getWorld()).getHandle().findNearestBiome(((BiomeNMSImpl) biome.getBiome()).biomeBase, new BlockPos(start.getBlockX(), start.getBlockY(), start.getBlockZ()), 6400, 8);\r\n        if (result == null) {\r\n            return null;\r\n        }\r\n        return new Location(start.getWorld(), result.getX(), result.getY(), result.getZ());\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughSleeping(World world, int percentage) {\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, ((CraftWorld) world).getHandle());\r\n        return status.areEnoughSleeping(percentage);\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughDeepSleeping(World world, int percentage) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, level);\r\n        return status.areEnoughDeepSleeping(percentage, level.players());\r\n    }\r\n\r\n    @Override\r\n    public int getSkyDarken(World world) {\r\n        return ((CraftWorld) world).getHandle().getSkyDarken();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDay(World world) {\r\n        return ((CraftWorld) world).getHandle().isDay();\r\n    }\r\n\r\n    @Override\r\n    public boolean isNight(World world) {\r\n        return ((CraftWorld) world).getHandle().isNight();\r\n    }\r\n\r\n    @Override\r\n    public void setDayTime(World world, long time) {\r\n        ((CraftWorld) world).getHandle().setDayTime(time);\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#wakeUpAllPlayers()\r\n    @Override\r\n    public void wakeUpAllPlayers(World world) {\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, nmsWorld);\r\n        status.removeAllSleepers();\r\n        nmsWorld.getPlayers(LivingEntity::isSleeping).forEach((player) -> player.stopSleepInBed(false, false));\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#stopWeather()\r\n    @Override\r\n    public void clearWeather(World world) {\r\n        PrimaryLevelData data = ((CraftWorld) world).getHandle().E;\r\n        data.setRaining(false);\r\n        if (!data.isRaining()) {\r\n            data.setRainTime(0);\r\n        }\r\n        data.setThundering(false);\r\n        if (!data.isThundering()) {\r\n            data.setThunderTime(0);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/BiomeNMSImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.random.WeightedRandomList;\r\nimport net.minecraft.world.entity.MobCategory;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.BiomeSpecialEffects;\r\nimport net.minecraft.world.level.biome.MobSpawnSettings;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_17_R1.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Locale;\r\nimport java.util.Optional;\r\n\r\npublic class BiomeNMSImpl extends BiomeNMS {\r\n\r\n    public net.minecraft.world.level.biome.Biome biomeBase;\r\n\r\n    public ServerLevel world;\r\n\r\n    public BiomeNMSImpl(ServerLevel world, NamespacedKey key) {\r\n        super(world.getWorld(), key);\r\n        this.world = world;\r\n        biomeBase = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).get(CraftNamespacedKey.toMinecraft(key));\r\n    }\r\n\r\n    @Override\r\n    public DownfallType getDownfallType() {\r\n        Biome.Precipitation nmsType = biomeBase.getPrecipitation();\r\n        switch (nmsType) {\r\n            case RAIN:\r\n                return DownfallType.RAIN;\r\n            case SNOW:\r\n                return DownfallType.SNOW;\r\n            case NONE:\r\n                return DownfallType.NONE;\r\n            default:\r\n                throw new UnsupportedOperationException();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getHumidity() {\r\n        return biomeBase.getDownfall();\r\n    }\r\n\r\n    @Override\r\n    public float getBaseTemperature() {\r\n        return biomeBase.getBaseTemperature();\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getAmbientEntities() {\r\n        return getSpawnableEntities(MobCategory.AMBIENT);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getCreatureEntities() {\r\n        return getSpawnableEntities(MobCategory.CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getMonsterEntities() {\r\n        return getSpawnableEntities(MobCategory.MONSTER);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getWaterEntities() {\r\n        return getSpawnableEntities(MobCategory.WATER_CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public int getFoliageColor() {\r\n        // Check if the biome already has a default color\r\n        if (biomeBase.getFoliageColor() != 0) {\r\n            return biomeBase.getFoliageColor();\r\n        }\r\n        // Based on net.minecraft.world.level.biome.Biome#getFoliageColorFromTexture()\r\n        float temperature = clampColor(getBaseTemperature());\r\n        float humidity = clampColor(getHumidity());\r\n        // Based on net.minecraft.world.level.FoliageColor#get()\r\n        humidity *= temperature;\r\n        int humidityValue = (int)((1.0f - humidity) * 255.0f);\r\n        int temperatureValue = (int)((1.0f - temperature) * 255.0f);\r\n        int index = temperatureValue << 8 | humidityValue;\r\n        return index >= 65536 ? 4764952 : getColor(index / 256, index % 256).asRGB();\r\n    }\r\n\r\n    public Object getClimate() {\r\n        return ReflectionHelper.getFieldValue(net.minecraft.world.level.biome.Biome.class, ReflectionMappingsInfo.Biome_climateSettings, biomeBase);\r\n    }\r\n\r\n    @Override\r\n    public void setHumidity(float humidity) {\r\n        Object climate = getClimate();\r\n        ReflectionHelper.setFieldValue(climate.getClass(), ReflectionMappingsInfo.Biome_ClimateSettings_downfall, climate, humidity);\r\n    }\r\n\r\n    @Override\r\n    public void setBaseTemperature(float temperature) {\r\n        Object climate = getClimate();\r\n        ReflectionHelper.setFieldValue(climate.getClass(), ReflectionMappingsInfo.Biome_ClimateSettings_temperature, climate, temperature);\r\n    }\r\n\r\n    @Override\r\n    public void setPrecipitation(DownfallType type) {\r\n        Biome.Precipitation nmsType;\r\n        switch (type) {\r\n            case NONE:\r\n                nmsType = Biome.Precipitation.NONE;\r\n                break;\r\n            case RAIN:\r\n                nmsType = Biome.Precipitation.RAIN;\r\n                break;\r\n            case SNOW:\r\n                nmsType = Biome.Precipitation.SNOW;\r\n                break;\r\n            default:\r\n                throw new UnsupportedOperationException();\r\n        }\r\n        Object climate = getClimate();\r\n        ReflectionHelper.setFieldValue(climate.getClass(), ReflectionMappingsInfo.Biome_ClimateSettings_precipitation, climate, nmsType);\r\n    }\r\n\r\n    @Override\r\n    public void setFoliageColor(int color) {\r\n        try {\r\n            ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_foliageColorOverride, biomeBase.getSpecialEffects(), Optional.of(color));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    private List<EntityType> getSpawnableEntities(MobCategory creatureType) {\r\n        MobSpawnSettings mobs = biomeBase.getMobSettings();\r\n        WeightedRandomList<MobSpawnSettings.SpawnerData> typeSettingList = mobs.getMobs(creatureType);\r\n        List<EntityType> entityTypes = new ArrayList<>();\r\n        if (typeSettingList == null) {\r\n            return entityTypes;\r\n        }\r\n        for (MobSpawnSettings.SpawnerData meta : typeSettingList.unwrap()) {\r\n            try {\r\n                String n = net.minecraft.world.entity.EntityType.getKey(meta.type).getPath();\r\n                EntityType et = EntityType.fromName(n);\r\n                if (et == null) {\r\n                    et = EntityType.valueOf(n.toUpperCase(Locale.ENGLISH));\r\n                }\r\n                entityTypes.add(et);\r\n            }\r\n            catch (Throwable e) {\r\n                // Ignore the error. Likely from invalid entity type name output.\r\n            }\r\n        }\r\n        return entityTypes;\r\n    }\r\n\r\n    @Override\r\n    public void setTo(Block block) {\r\n        if (((CraftWorld) block.getWorld()).getHandle() != this.world) {\r\n            NMSHandler.instance.getBiomeNMS(block.getWorld(), getKey()).setTo(block);\r\n            return;\r\n        }\r\n        // Based on CraftWorld source\r\n        BlockPos pos = new BlockPos(block.getX(), 0, block.getZ());\r\n        if (world.hasChunkAt(pos)) {\r\n            LevelChunk chunk = world.getChunkAt(pos);\r\n            if (chunk != null) {\r\n                chunk.getBiomes().setBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2, biomeBase);\r\n                chunk.markUnsaved();\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/ImprovedOfflinePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.v1_17.helpers.NBTAdapter;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.kyori.adventure.nbt.BinaryTagTypes;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.nbt.ListTag;\r\nimport net.minecraft.nbt.NbtIo;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeMap;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.attributes.DefaultAttributes;\r\nimport net.minecraft.world.inventory.PlayerEnderChestContainer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryPlayer;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryHolder;\r\n\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.FileOutputStream;\r\nimport java.util.UUID;\r\n\r\npublic class ImprovedOfflinePlayerImpl extends ImprovedOfflinePlayer {\r\n\r\n    public ImprovedOfflinePlayerImpl(UUID playeruuid) {\r\n        super(playeruuid);\r\n    }\r\n\r\n    public static class OfflinePlayerInventory extends net.minecraft.world.entity.player.Inventory {\r\n\r\n        public OfflinePlayerInventory(net.minecraft.world.entity.player.Player entityhuman) {\r\n            super(entityhuman);\r\n        }\r\n\r\n        @Override\r\n        public InventoryHolder getOwner() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static class OfflineCraftInventoryPlayer extends CraftInventoryPlayer {\r\n\r\n        public OfflineCraftInventoryPlayer(net.minecraft.world.entity.player.Inventory inventory) {\r\n            super(inventory);\r\n        }\r\n\r\n        @Override\r\n        public HumanEntity getHolder() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public org.bukkit.inventory.PlayerInventory getInventory() {\r\n        if (inventory == null) {\r\n            net.minecraft.world.entity.player.Inventory newInv = new OfflinePlayerInventory(null);\r\n            newInv.load(NBTAdapter.toNMS(this.compound.getList(\"Inventory\", BinaryTagTypes.COMPOUND)));\r\n            inventory = new OfflineCraftInventoryPlayer(newInv);\r\n        }\r\n        return inventory;\r\n    }\r\n\r\n    @Override\r\n    public void setInventory(org.bukkit.inventory.PlayerInventory inventory) {\r\n        CraftInventoryPlayer inv = (CraftInventoryPlayer) inventory;\r\n        this.compound = compound.put(\"Inventory\", NBTAdapter.toAPI(inv.getInventory().save(new ListTag())));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    public Inventory getEnderChest() {\r\n        if (enderchest == null) {\r\n            PlayerEnderChestContainer endchest = new PlayerEnderChestContainer(null);\r\n            endchest.fromTag(NBTAdapter.toNMS(this.compound.getList(\"EnderItems\", BinaryTagTypes.COMPOUND)));\r\n            enderchest = new CraftInventory(endchest);\r\n        }\r\n        return enderchest;\r\n    }\r\n\r\n    @Override\r\n    public void setEnderChest(Inventory inventory) {\r\n        this.compound = compound.put(\"EnderItems\", NBTAdapter.toAPI(((PlayerEnderChestContainer) ((CraftInventory) inventory).getInventory()).createTag()));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    public double getMaxHealth() {\r\n        AttributeInstance maxHealth = getAttributes().getInstance(Attributes.MAX_HEALTH);\r\n        return maxHealth == null ? Attributes.MAX_HEALTH.getDefaultValue() : maxHealth.getValue();\r\n    }\r\n\r\n    @Override\r\n    public void setMaxHealth(double input) {\r\n        AttributeMap attributes = getAttributes();\r\n        AttributeInstance maxHealth = attributes.getInstance(Attributes.MAX_HEALTH);\r\n        maxHealth.setBaseValue(input);\r\n        setAttributes(attributes);\r\n    }\r\n\r\n    private AttributeMap getAttributes() {\r\n        AttributeMap amb = new AttributeMap(DefaultAttributes.getSupplier(net.minecraft.world.entity.EntityType.PLAYER));\r\n        amb.load(NBTAdapter.toNMS(this.compound.getList(\"Attributes\", BinaryTagTypes.COMPOUND)));\r\n        return amb;\r\n    }\r\n\r\n    public void setAttributes(AttributeMap attributes) {\r\n        this.compound = compound.put(\"Attributes\", NBTAdapter.toAPI(attributes.save()));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    protected boolean loadPlayerData(UUID uuid) {\r\n        try {\r\n            this.player = uuid;\r\n            for (org.bukkit.World w : Bukkit.getWorlds()) {\r\n                this.file = new File(w.getWorldFolder(), \"playerdata\" + File.separator + this.player + \".dat\");\r\n                if (this.file.exists()) {\r\n                    this.compound = NBTAdapter.toAPI(NbtIo.readCompressed(new FileInputStream(this.file)));\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void saveInternal(CompoundBinaryTag compound) {\r\n        try {\r\n            NbtIo.writeCompressed(NBTAdapter.toNMS(compound), new FileOutputStream(this.file));\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/ProfileEditorImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.v1_17.Handler;\r\nimport com.denizenscript.denizen.nms.v1_17.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.game.ClientboundAddPlayerPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic class ProfileEditorImpl extends ProfileEditor {\r\n\r\n    @Override\r\n    protected void updatePlayer(Player player, final boolean isSkinChanging) {\r\n        final ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle();\r\n        final UUID uuid = player.getUniqueId();\r\n        ClientboundRemoveEntitiesPacket destroyPacket = new ClientboundRemoveEntitiesPacket(entityPlayer.getId());\r\n        for (Player p : Bukkit.getServer().getOnlinePlayers()) {\r\n            if (!p.getUniqueId().equals(uuid)) {\r\n                PacketHelperImpl.send(p, destroyPacket);\r\n            }\r\n        }\r\n        new BukkitRunnable() {\r\n            @Override\r\n            public void run() {\r\n                ClientboundPlayerInfoPacket playerInfo = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, entityPlayer);\r\n                ClientboundAddPlayerPacket spawnPacket = new ClientboundAddPlayerPacket(entityPlayer);\r\n                for (Player player : Bukkit.getServer().getOnlinePlayers()) {\r\n                    PacketHelperImpl.send(player, playerInfo);\r\n                    if (!player.getUniqueId().equals(uuid)) {\r\n                        PacketHelperImpl.send(player, spawnPacket);\r\n                    }\r\n                    else {\r\n                        if (isSkinChanging) {\r\n                            ((CraftServer) Bukkit.getServer()).getHandle().moveToWorld(entityPlayer, (ServerLevel) entityPlayer.level, true, player.getLocation(), false);\r\n                        }\r\n                        player.updateInventory();\r\n                    }\r\n                }\r\n            }\r\n        }.runTaskLater(NMSHandler.getJavaPlugin(), 5);\r\n    }\r\n\r\n    public static boolean handleAlteredProfiles(ClientboundPlayerInfoPacket packet, DenizenNetworkManagerImpl manager) {\r\n        if (ProfileEditor.mirrorUUIDs.isEmpty() && !RenameCommand.hasAnyDynamicRenames()) {\r\n            return true;\r\n        }\r\n        ClientboundPlayerInfoPacket.Action action = packet.getAction();\r\n        if (action != ClientboundPlayerInfoPacket.Action.ADD_PLAYER && action != ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {\r\n            return true;\r\n        }\r\n        List<ClientboundPlayerInfoPacket.PlayerUpdate> dataList = packet.getEntries();\r\n        if (dataList == null) {\r\n            return true;\r\n        }\r\n        try {\r\n            boolean any = false;\r\n            for (ClientboundPlayerInfoPacket.PlayerUpdate data : dataList) {\r\n                if (ProfileEditor.mirrorUUIDs.contains(data.getProfile().getId()) || RenameCommand.customNames.containsKey(data.getProfile().getId())) {\r\n                    any = true;\r\n                }\r\n            }\r\n            if (!any) {\r\n                return true;\r\n            }\r\n            GameProfile ownProfile = manager.player.getGameProfile();\r\n            for (ClientboundPlayerInfoPacket.PlayerUpdate data : dataList) {\r\n                if (!ProfileEditor.mirrorUUIDs.contains(data.getProfile().getId()) && !RenameCommand.customNames.containsKey(data.getProfile().getId())) {\r\n                    ClientboundPlayerInfoPacket newPacket = new ClientboundPlayerInfoPacket(action);\r\n                    List<ClientboundPlayerInfoPacket.PlayerUpdate> newPacketDataList = newPacket.getEntries();\r\n                    newPacketDataList.add(data);\r\n                    manager.oldManager.send(newPacket);\r\n                }\r\n                else {\r\n                    String rename = RenameCommand.getCustomNameFor(data.getProfile().getId(), manager.player.getBukkitEntity(), false);\r\n                    ClientboundPlayerInfoPacket newPacket = new ClientboundPlayerInfoPacket(action);\r\n                    List<ClientboundPlayerInfoPacket.PlayerUpdate> newPacketDataList = newPacket.getEntries();\r\n                    GameProfile patchedProfile = new GameProfile(data.getProfile().getId(), rename != null ? (rename.length() > 16 ? rename.substring(0, 16) : rename) : data.getProfile().getName());\r\n                    if (ProfileEditor.mirrorUUIDs.contains(data.getProfile().getId())) {\r\n                        patchedProfile.getProperties().putAll(ownProfile.getProperties());\r\n                    }\r\n                    else {\r\n                        patchedProfile.getProperties().putAll(data.getProfile().getProperties());\r\n                    }\r\n                    String listRename = RenameCommand.getCustomNameFor(data.getProfile().getId(), manager.player.getBukkitEntity(), true);\r\n                    Component displayName = listRename != null ? Handler.componentToNMS(FormattedTextHelper.parse(listRename, ChatColor.WHITE)) : data.getDisplayName();\r\n                    ClientboundPlayerInfoPacket.PlayerUpdate newData = new ClientboundPlayerInfoPacket.PlayerUpdate(patchedProfile, data.getLatency(), data.getGameMode(), displayName);\r\n                    newPacketDataList.add(newData);\r\n                    manager.oldManager.send(newPacket);\r\n                }\r\n            }\r\n            return false;\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n            return true;\r\n        }\r\n    }\r\n\r\n    public static void updatePlayerProfiles(ClientboundPlayerInfoPacket packet) {\r\n        ClientboundPlayerInfoPacket.Action action = packet.getAction();\r\n        if (action != ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {\r\n            return;\r\n        }\r\n        List<ClientboundPlayerInfoPacket.PlayerUpdate> dataList = packet.getEntries();\r\n        if (dataList != null) {\r\n            try {\r\n                for (ClientboundPlayerInfoPacket.PlayerUpdate data : dataList) {\r\n                    GameProfile gameProfile = data.getProfile();\r\n                    if (fakeProfiles.containsKey(gameProfile.getId())) {\r\n                        playerInfoData_gameProfile_Setter.invoke(data, getGameProfile(fakeProfiles.get(gameProfile.getId())));\r\n                    }\r\n                }\r\n            }\r\n            catch (Throwable e) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n    }\r\n\r\n    private static GameProfile getGameProfile(PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n        return gameProfile;\r\n    }\r\n\r\n    public static final MethodHandle playerInfoData_gameProfile_Setter = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundPlayerInfoPacket.PlayerUpdate.class, GameProfile.class);\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/SidebarImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl;\r\n\r\nimport com.denizenscript.denizen.nms.v1_17.Handler;\r\nimport com.denizenscript.denizen.nms.v1_17.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.MutableComponent;\r\nimport net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetScorePacket;\r\nimport net.minecraft.server.ServerScoreboard;\r\nimport net.minecraft.world.scores.Objective;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Scoreboard;\r\nimport net.minecraft.world.scores.criteria.ObjectiveCriteria;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.reflect.Constructor;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class SidebarImpl extends Sidebar {\r\n\r\n    public static Scoreboard dummyScoreboard = new Scoreboard();\r\n    public static ObjectiveCriteria dummyCriteria;\r\n\r\n    static {\r\n        try {\r\n            Constructor<ObjectiveCriteria> constructor = ObjectiveCriteria.class.getDeclaredConstructor(String.class);\r\n            constructor.setAccessible(true);\r\n            dummyCriteria = constructor.newInstance(\"dummy\");\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public Objective obj1;\r\n    public Objective obj2;\r\n\r\n    public SidebarImpl(Player player) {\r\n        super(player);\r\n        MutableComponent chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        this.obj1 = new Objective(dummyScoreboard, \"dummy_1\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER);\r\n        this.obj2 = new Objective(dummyScoreboard, \"dummy_2\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER);\r\n    }\r\n\r\n    @Override\r\n    protected void setDisplayName(String title) {\r\n        if (this.obj1 != null) {\r\n            MutableComponent chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n            this.obj1.setDisplayName(chatComponentTitle);\r\n            this.obj2.setDisplayName(chatComponentTitle);\r\n        }\r\n    }\r\n\r\n    public List<PlayerTeam> generatedTeams = new ArrayList<>();\r\n\r\n    @Override\r\n    public void sendUpdate() {\r\n        List<PlayerTeam> oldTeams = generatedTeams;\r\n        generatedTeams = new ArrayList<>();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj1, 0));\r\n        for (int i = 0; i < this.lines.length; i++) {\r\n            String line = this.lines[i];\r\n            if (line == null) {\r\n                break;\r\n            }\r\n            String lineId = Utilities.generateRandomColors(8);\r\n            PlayerTeam team = new PlayerTeam(dummyScoreboard, lineId);\r\n            team.getPlayers().add(lineId);\r\n            team.setPlayerPrefix(Handler.componentToNMS(FormattedTextHelper.parse(line, ChatColor.WHITE)));\r\n            generatedTeams.add(team);\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n            PacketHelperImpl.send(player, new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, obj1.getName(), lineId, this.scores[i]));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundSetDisplayObjectivePacket(1, this.obj1));\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n        Objective temp = this.obj2;\r\n        this.obj2 = this.obj1;\r\n        this.obj1 = temp;\r\n        for (PlayerTeam team : oldTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        for (PlayerTeam team : generatedTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        generatedTeams.clear();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/blocks/BlockLightImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.blocks;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;\r\nimport net.minecraft.server.level.ServerChunkCache;\r\nimport net.minecraft.server.level.ThreadedLevelLightEngine;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.LightLayer;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.ChunkStatus;\r\nimport net.minecraft.world.level.chunk.DataLayer;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.lighting.BlockLightEngine;\r\nimport net.minecraft.world.level.lighting.LevelLightEngine;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_17_R1.block.CraftBlock;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.ArrayList;\r\nimport java.util.BitSet;\r\nimport java.util.List;\r\n\r\npublic class BlockLightImpl extends BlockLight {\r\n\r\n    public static final Class LIGHTENGINETHREADED_UPDATE = ThreadedLevelLightEngine.class.getDeclaredClasses()[0]; // TaskType\r\n    public static final Object LIGHTENGINETHREADED_UPDATE_PRE;\r\n\r\n    static {\r\n        Object preObj = null;\r\n        try {\r\n            preObj = ReflectionHelper.getFields(LIGHTENGINETHREADED_UPDATE).get(ReflectionMappingsInfo.ThreadedLevelLightEngine_Update_PRE_UPDATE).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n        LIGHTENGINETHREADED_UPDATE_PRE = preObj;\r\n    }\r\n\r\n    public static final MethodHandle LIGHTENGINETHREADED_QUEUERUNNABLE = ReflectionHelper.getMethodHandle(ThreadedLevelLightEngine.class, ReflectionMappingsInfo.ThreadedLevelLightEngine_addTask,\r\n            int.class, int.class,  LIGHTENGINETHREADED_UPDATE, Runnable.class);\r\n\r\n    public static void enqueueRunnable(LevelChunk chunk, Runnable runnable) {\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        if (lightEngine instanceof ThreadedLevelLightEngine) {\r\n            ChunkPos coord = chunk.getPos();\r\n            try {\r\n                LIGHTENGINETHREADED_QUEUERUNNABLE.invoke(lightEngine, coord.x, coord.z, LIGHTENGINETHREADED_UPDATE_PRE, runnable);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        else {\r\n            runnable.run();\r\n        }\r\n    }\r\n\r\n    private BlockLightImpl(Location location, long ticks) {\r\n        super(location, ticks);\r\n    }\r\n\r\n    public static BlockLight createLight(Location location, int lightLevel, long ticks) {\r\n        location = location.getBlock().getLocation();\r\n        BlockLight blockLight;\r\n        if (lightsByLocation.containsKey(location)) {\r\n            blockLight = lightsByLocation.get(location);\r\n            if (blockLight.removeTask != null) {\r\n                blockLight.removeTask.cancel();\r\n                blockLight.removeTask = null;\r\n            }\r\n            if (blockLight.updateTask != null) {\r\n                blockLight.updateTask.cancel();\r\n                blockLight.updateTask = null;\r\n            }\r\n            blockLight.removeLater(ticks);\r\n        }\r\n        else {\r\n            blockLight = new BlockLightImpl(location, ticks);\r\n            lightsByLocation.put(location, blockLight);\r\n            if (!lightsByChunk.containsKey(blockLight.chunkCoord)) {\r\n                lightsByChunk.put(blockLight.chunkCoord, new ArrayList<>());\r\n            }\r\n            lightsByChunk.get(blockLight.chunkCoord).add(blockLight);\r\n        }\r\n        blockLight.intendedLevel = lightLevel;\r\n        blockLight.update(lightLevel, true);\r\n        return blockLight;\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundBlockUpdatePacket packet, Level world) {\r\n        try {\r\n            BlockPos pos = packet.getPos();\r\n            int chunkX = pos.getX() >> 4;\r\n            int chunkZ = pos.getZ() >> 4;\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                LevelChunk chunk = world.getChunkAt(chunkX, chunkZ);\r\n                boolean any = false;\r\n                for (Vector vec : RELATIVE_CHUNKS) {\r\n                    ChunkAccess other = world.getChunk(chunkX + vec.getBlockX(), chunkZ + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n                    if (other instanceof LevelChunk) {\r\n                        List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(((LevelChunk) other).bukkitChunk));\r\n                        if (lights != null) {\r\n                            any = true;\r\n                            for (BlockLight light : lights) {\r\n                                Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates(chunk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundLightUpdatePacket packet, Level world) {\r\n        if (doNotCheck) {\r\n            return;\r\n        }\r\n        try {\r\n            int cX = packet.getX();\r\n            int cZ = packet.getZ();\r\n            BitSet bitMask = packet.getBlockYMask();\r\n            List<byte[]> blockData = packet.getBlockUpdates();\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                ChunkAccess chk = world.getChunk(cX, cZ, ChunkStatus.FULL, false);\r\n                if (!(chk instanceof LevelChunk)) {\r\n                    return;\r\n                }\r\n                List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(((LevelChunk) chk).bukkitChunk));\r\n                if (lights == null) {\r\n                    return;\r\n                }\r\n                boolean any = false;\r\n                for (BlockLight light : lights) {\r\n                    if (((BlockLightImpl) light).checkIfChangedBy(bitMask, blockData)) {\r\n                        Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                        any = true;\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates((LevelChunk) chk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static boolean doNotCheck = false;\r\n\r\n    public boolean checkIfChangedBy(BitSet bitmask, List<byte[]> data) {\r\n        Location blockLoc = block.getLocation();\r\n        int layer = (blockLoc.getBlockY() >> 4) + 1;\r\n        if (!bitmask.get(layer)) {\r\n            return false;\r\n        }\r\n        int found = 0;\r\n        for (int i = 0; i < 16; i++) {\r\n            if (bitmask.get(i)) {\r\n                if (i == layer) {\r\n                    byte[] blocks = data.get(found);\r\n                    DataLayer arr = new DataLayer(blocks);\r\n                    int x = blockLoc.getBlockX() - (chunkCoord.x << 4);\r\n                    int y = blockLoc.getBlockY() % 16;\r\n                    int z = blockLoc.getBlockZ() - (chunkCoord.z << 4);\r\n                    int level = arr.get(x, y, z);\r\n                    return intendedLevel != level;\r\n                }\r\n                found++;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static void runResetFor(final LevelChunk chunk, final BlockPos pos) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            BlockLightEngine engineBlock = (BlockLightEngine) lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            engineBlock.checkBlock(pos);\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    public static void runSetFor(final LevelChunk chunk, final BlockPos pos, final int level) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            BlockLightEngine engineBlock = (BlockLightEngine) lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            engineBlock.onBlockEmissionIncrease(pos, level);\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    @Override\r\n    public void reset(boolean updateChunk) {\r\n        runResetFor(((CraftChunk) getChunk()).getHandle(), ((CraftBlock) block).getPosition());\r\n        if (updateChunk) {\r\n            // This runnable cast is necessary despite what your IDE may claim\r\n            updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(int lightLevel, boolean updateChunk) {\r\n        runResetFor(((CraftChunk) getChunk()).getHandle(), ((CraftBlock) block).getPosition());\r\n        updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), () -> {\r\n            updateTask = null;\r\n            runSetFor(((CraftChunk) chunk).getHandle(), ((CraftBlock) block).getPosition(), lightLevel);\r\n            if (updateChunk) {\r\n                // This runnable cast is necessary despite what your IDE may claim\r\n                updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n            }\r\n        }, 1);\r\n    }\r\n\r\n    public static final Vector[] RELATIVE_CHUNKS = new Vector[] {\r\n            new Vector(0, 0, 0),\r\n            new Vector(-1, 0, 0), new Vector(1, 0, 0), new Vector(0, 0, -1), new Vector(0, 0, 1),\r\n            new Vector(-1, 0, -1), new Vector(-1, 0, 1), new Vector(1, 0, -1), new Vector(1, 0, 1)\r\n    };\r\n\r\n    public void sendNearbyChunkUpdates() {\r\n        sendNearbyChunkUpdates(((CraftChunk) getChunk()).getHandle());\r\n    }\r\n\r\n    public static void sendNearbyChunkUpdates(LevelChunk chunk) {\r\n        ChunkPos pos = chunk.getPos();\r\n        for (Vector vec : RELATIVE_CHUNKS) {\r\n            ChunkAccess other = chunk.getLevel().getChunk(pos.x + vec.getBlockX(), pos.z + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n            if (other instanceof LevelChunk) {\r\n                sendSingleChunkUpdate((LevelChunk) other);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void sendSingleChunkUpdate(LevelChunk chunk) {\r\n        doNotCheck = true;\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        ChunkPos pos = chunk.getPos();\r\n        ClientboundLightUpdatePacket packet = new ClientboundLightUpdatePacket(pos, lightEngine, null, null, true); // TODO: 1.16: should 'trust edges' be true here?\r\n        ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(pos, false).forEach((player) -> {\r\n            player.connection.send(packet);\r\n        });\r\n        doNotCheck = false;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/entities/CraftFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport net.minecraft.world.entity.projectile.AbstractArrow;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftArrow;\r\n\r\npublic class CraftFakeArrowImpl extends CraftArrow implements FakeArrow {\r\n\r\n    public CraftFakeArrowImpl(CraftServer craftServer, AbstractArrow entityArrow) {\r\n        super(craftServer, entityArrow);\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        if (getPassenger() != null) {\r\n            return;\r\n        }\r\n        super.remove();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_ARROW\";\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/entities/CraftFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;\r\nimport org.bukkit.metadata.FixedMetadataValue;\r\nimport org.bukkit.metadata.MetadataValue;\r\nimport org.bukkit.plugin.Plugin;\r\n\r\nimport java.util.List;\r\nimport java.util.Set;\r\n\r\npublic class CraftFakePlayerImpl extends CraftPlayer implements FakePlayer {\r\n\r\n    private final CraftServer server;\r\n    public String fullName;\r\n\r\n    public CraftFakePlayerImpl(CraftServer server, EntityFakePlayerImpl entity) {\r\n        super(server, entity);\r\n        this.server = server;\r\n        setMetadata(\"NPC\", new FixedMetadataValue(NMSHandler.getJavaPlugin(), true));\r\n    }\r\n\r\n    @Override\r\n    public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {\r\n        this.server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue);\r\n    }\r\n\r\n    @Override\r\n    public List<MetadataValue> getMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().getMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().hasMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public void removeMetadata(String metadataKey, Plugin owningPlugin) {\r\n        this.server.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin);\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_PLAYER\";\r\n    }\r\n\r\n    @Override\r\n    public String getFullName() {\r\n        return fullName;\r\n    }\r\n\r\n    @Override\r\n    public Block getTargetBlock(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public List<Block> getLastTwoTargetBlocks(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/entities/CraftItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\n\r\nimport java.util.UUID;\r\n\r\npublic class CraftItemProjectileImpl extends CraftEntity implements ItemProjectile {\r\n\r\n    private boolean doesBounce;\r\n\r\n    public CraftItemProjectileImpl(CraftServer server, EntityItemProjectileImpl entity) {\r\n        super(server, entity);\r\n    }\r\n\r\n    @Override\r\n    public EntityItemProjectileImpl getHandle() {\r\n        return (EntityItemProjectileImpl) super.getHandle();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return getType().name();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack getItemStack() {\r\n        return CraftItemStack.asBukkitCopy(getHandle().getItemStack());\r\n    }\r\n\r\n    @Override\r\n    public void setItemStack(ItemStack itemStack) {\r\n        getHandle().setItemStack(CraftItemStack.asNMSCopy(itemStack));\r\n    }\r\n\r\n    @Override\r\n    public int getPickupDelay() {\r\n        return 0;\r\n    }\r\n\r\n    @Override\r\n    public void setPickupDelay(int i) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public void setOwner(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getOwner() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setThrower(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getThrower() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ProjectileSource getShooter() {\r\n        return getHandle().projectileSource;\r\n    }\r\n\r\n    @Override\r\n    public void setShooter(ProjectileSource projectileSource) {\r\n        if (projectileSource instanceof CraftEntity) {\r\n            getHandle().setOwner(((CraftEntity) projectileSource).getHandle());\r\n        }\r\n        else {\r\n            getHandle().projectileSource = projectileSource;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean doesBounce() {\r\n        return doesBounce;\r\n    }\r\n\r\n    @Override\r\n    public void setBounce(boolean doesBounce) {\r\n        this.doesBounce = doesBounce;\r\n    }\r\n\r\n    @Override\r\n    public EntityType getType() {\r\n        return EntityType.DROPPED_ITEM;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/entities/EntityFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_17.Handler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.world.entity.projectile.SpectralArrow;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftWorld;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakeArrowImpl extends SpectralArrow {\r\n\r\n    public EntityFakeArrowImpl(CraftWorld craftWorld, Location location) {\r\n        super(net.minecraft.world.entity.EntityType.SPECTRAL_ARROW, craftWorld.getHandle());\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakeArrowImpl((CraftServer) Bukkit.getServer(), this));\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        level.addEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    protected ItemStack getPickupItem() {\r\n        return new ItemStack(Items.ARROW);\r\n    }\r\n\r\n    @Override\r\n    public CraftFakeArrowImpl getBukkitEntity() {\r\n        return (CraftFakeArrowImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/entities/EntityFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_17.Handler;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.network.fakes.FakeNetworkManagerImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.network.fakes.FakePlayerConnectionImpl;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.player.Player;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftServer;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakePlayerImpl extends ServerPlayer {\r\n\r\n    public EntityFakePlayerImpl(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile, boolean doAdd) {\r\n        super(minecraftserver, worldserver, gameprofile);\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakePlayerImpl((CraftServer) Bukkit.getServer(), this));\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        Connection networkManager = new FakeNetworkManagerImpl(PacketFlow.CLIENTBOUND);\r\n        connection = new FakePlayerConnectionImpl(minecraftserver, networkManager, this);\r\n        networkManager.setListener(connection);\r\n        getEntityData().set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) 127);\r\n        if (doAdd) {\r\n            worldserver.addEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public CraftFakePlayerImpl getBukkitEntity() {\r\n        return (CraftFakePlayerImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/entities/EntityItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.base.Preconditions;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.projectile.ThrowableProjectile;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport org.bukkit.Location;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\n\r\npublic class EntityItemProjectileImpl extends ThrowableProjectile {\r\n\r\n    public static MethodHandle setBukkitEntityMethod = ReflectionHelper.getFinalSetter(Entity.class, \"bukkitEntity\");\r\n\r\n    public static final EntityDataAccessor<ItemStack> ITEM;\r\n\r\n    static {\r\n        EntityDataAccessor<ItemStack> watcher = null;\r\n        try {\r\n            watcher = (EntityDataAccessor<ItemStack>) ReflectionHelper.getFields(ItemEntity.class).get(ReflectionMappingsInfo.ItemEntity_DATA_ITEM).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        ITEM = watcher;\r\n    }\r\n\r\n    public EntityItemProjectileImpl(Level world, Location location, ItemStack item) {\r\n        super((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.ITEM, world);\r\n        try {\r\n            setBukkitEntityMethod.invoke(this, new CraftItemProjectileImpl(((ServerLevel) world).getServer().server, this));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        setItemStack(item);\r\n    }\r\n\r\n    @Override\r\n    protected void defineSynchedData() {\r\n        this.getEntityData().define(ITEM, ItemStack.EMPTY);\r\n    }\r\n\r\n    public ItemStack getItemStack() {\r\n        return this.getEntityData().get(ITEM);\r\n    }\r\n\r\n    public void setItemStack(ItemStack itemstack) {\r\n        Preconditions.checkArgument(!itemstack.isEmpty(), \"Cannot drop air\");\r\n        this.getEntityData().set(ITEM, itemstack);\r\n        this.getEntityData().markDirty(ITEM);\r\n    }\r\n\r\n    @Override\r\n    protected void onHitBlock(BlockHitResult movingobjectpositionblock) {\r\n        super.onHitBlock(movingobjectpositionblock);\r\n        remove(RemovalReason.KILLED);\r\n    }\r\n\r\n    @Override\r\n    public void onSyncedDataUpdated(EntityDataAccessor<?> datawatcherobject) {\r\n        super.onSyncedDataUpdated(datawatcherobject);\r\n        if (ITEM.equals(datawatcherobject)) {\r\n            this.getItemStack().setEntityRepresentation(this);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean save(net.minecraft.nbt.CompoundTag nbttagcompound) {\r\n        if (!this.getItemStack().isEmpty()) {\r\n            nbttagcompound.put(\"Item\", this.getItemStack().save(new net.minecraft.nbt.CompoundTag()));\r\n        }\r\n        super.save(nbttagcompound);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public void load(net.minecraft.nbt.CompoundTag nbttagcompound) {\r\n        net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttagcompound.getCompound(\"Item\");\r\n        this.setItemStack(ItemStack.of(nbttagcompound1));\r\n        if (this.getItemStack().isEmpty()) {\r\n            this.remove(RemovalReason.KILLED);\r\n        }\r\n        super.load(nbttagcompound);\r\n    }\r\n\r\n    @Override\r\n    public CraftItemProjectileImpl getBukkitEntity() {\r\n        return (CraftItemProjectileImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/fakes/FakeChannelImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.fakes;\r\n\r\nimport io.netty.channel.*;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeChannelImpl extends AbstractChannel {\r\n\r\n    private final ChannelConfig config = new DefaultChannelConfig(this);\r\n\r\n    protected FakeChannelImpl(Channel parent) {\r\n        super(parent);\r\n    }\r\n\r\n    @Override\r\n    public ChannelConfig config() {\r\n        config.setAutoRead(true);\r\n        return config;\r\n    }\r\n\r\n    @Override\r\n    protected AbstractUnsafe newUnsafe() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected boolean isCompatible(EventLoop eventLoop) {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress localAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress remoteAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected void doBind(SocketAddress socketAddress) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doDisconnect() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doClose() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doBeginRead() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doWrite(ChannelOutboundBuffer channelOutboundBuffer) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean isOpen() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActive() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public ChannelMetadata metadata() {\r\n        return new ChannelMetadata(true);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/fakes/FakeNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeNetworkManagerImpl extends Connection {\r\n\r\n    public FakeNetworkManagerImpl(PacketFlow enumprotocoldirection) {\r\n        super(enumprotocoldirection);\r\n        channel = new FakeChannelImpl(null);\r\n        address = new SocketAddress() {\r\n        };\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/fakes/FakePlayerConnectionImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\n\r\npublic class FakePlayerConnectionImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public FakePlayerConnectionImpl(MinecraftServer minecraftserver, Connection networkmanager, ServerPlayer entityplayer) {\r\n        super(minecraftserver, networkmanager, entityplayer);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet packet) {\r\n        // Do nothing\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/handlers/AbstractListenerPlayInImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport io.netty.util.concurrent.Future;\r\nimport io.netty.util.concurrent.GenericFutureListener;\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\n\r\nimport java.util.Set;\r\n\r\npublic class AbstractListenerPlayInImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public final ServerGamePacketListenerImpl oldListener;\r\n    public final DenizenNetworkManagerImpl denizenNetworkManager;\r\n\r\n    public AbstractListenerPlayInImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer, ServerGamePacketListenerImpl oldListener) {\r\n        super(MinecraftServer.getServer(), networkManager, entityPlayer);\r\n        this.oldListener = oldListener;\r\n        this.denizenNetworkManager = networkManager;\r\n    }\r\n\r\n    /*\r\n    @Override\r\n    public CraftPlayer getPlayer() {\r\n        return oldListener.getPlayer();\r\n    }*/\r\n\r\n    @Override\r\n    public Connection getConnection() {\r\n        return this.connection;\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        oldListener.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(String s) {\r\n        oldListener.disconnect(s);\r\n    }\r\n\r\n    @Override\r\n    public void dismount(double d0, double d1, double d2, float f, float f1) {\r\n        oldListener.dismount(d0, d1, d2, f, f1);\r\n    }\r\n\r\n    @Override\r\n    public void a(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) {\r\n        oldListener.a(d0, d1, d2, f, f1, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1) {\r\n        oldListener.teleport(d0, d1, d2, f, f1);\r\n    }\r\n\r\n    @Override\r\n    public void b(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) {\r\n        oldListener.b(d0, d1, d2, f, f1, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1, Set<ClientboundPlayerPositionPacket.RelativeArgument> set) {\r\n        oldListener.teleport(d0, d1, d2, f, f1, set);\r\n    }\r\n\r\n    @Override\r\n    public void a(double d0, double d1, double d2, float f, float f1, Set<ClientboundPlayerPositionPacket.RelativeArgument> set, PlayerTeleportEvent.TeleportCause cause) {\r\n        oldListener.a(d0, d1, d2, f, f1, set, cause);\r\n    }\r\n\r\n    @Override\r\n    public boolean a(double d0, double d1, double d2, float f, float f1, Set<ClientboundPlayerPositionPacket.RelativeArgument> set, boolean flag, PlayerTeleportEvent.TeleportCause cause) {\r\n        return oldListener.a(d0, d1, d2, f, f1, set, flag, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Location dest) {\r\n        oldListener.teleport(dest);\r\n    }\r\n\r\n    @Override\r\n    public void chat(String s, boolean async) {\r\n        oldListener.chat(s, async);\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldListener.tick();\r\n    }\r\n\r\n    @Override\r\n    public void resetPosition() {\r\n        oldListener.resetPosition();\r\n    }\r\n\r\n    @Override\r\n    public void onDisconnect(Component ichatbasecomponent) {\r\n        oldListener.onDisconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        oldListener.send(packet);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        oldListener.send(packet, genericfuturelistener);\r\n    }\r\n\r\n    public void handlePacketIn(Packet<ServerGamePacketListener> packet) {\r\n        denizenNetworkManager.packetsReceived++;\r\n        if (NMSHandler.debugPackets) {\r\n            Debug.log(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent from \" + player.getScoreboardName());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(ServerboundPlayerInputPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePlayerInput(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleMoveVehicle(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleAcceptTeleportPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookSeenRecipePacket(ServerboundRecipeBookSeenRecipePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleRecipeBookSeenRecipePacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleRecipeBookChangeSettingsPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSeenAdvancements(ServerboundSeenAdvancementsPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSeenAdvancements(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleCustomCommandSuggestions(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandBlock(ServerboundSetCommandBlockPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetCommandBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandMinecart(ServerboundSetCommandMinecartPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetCommandMinecart(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePickItem(ServerboundPickItemPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePickItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRenameItem(ServerboundRenameItemPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleRenameItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetBeaconPacket(ServerboundSetBeaconPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetBeaconPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetStructureBlock(ServerboundSetStructureBlockPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetStructureBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetJigsawBlock(ServerboundSetJigsawBlockPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetJigsawBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleJigsawGenerate(ServerboundJigsawGeneratePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleJigsawGenerate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSelectTrade(ServerboundSelectTradePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSelectTrade(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEditBook(ServerboundEditBookPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleEditBook(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEntityTagQuery(ServerboundEntityTagQuery packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleEntityTagQuery(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleBlockEntityTagQuery(ServerboundBlockEntityTagQuery packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleBlockEntityTagQuery(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleMovePlayer(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItemOn(ServerboundUseItemOnPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleUseItemOn(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleTeleportToEntityPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePaddleBoat(ServerboundPaddleBoatPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePaddleBoat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePong(ServerboundPongPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePong(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChat(ServerboundChatPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleChat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePlayerCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleInteract(ServerboundInteractPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleInteract(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientCommand(ServerboundClientCommandPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleClientCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClose(ServerboundContainerClosePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleContainerClose(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePlaceRecipe(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleContainerButtonClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCreativeModeSlot(ServerboundSetCreativeModeSlotPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetCreativeModeSlot(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleKeepAlive(ServerboundKeepAlivePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleKeepAlive(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePlayerAbilities(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientInformation(ServerboundClientInformationPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleClientInformation(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleChangeDifficulty(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleLockDifficulty(ServerboundLockDifficultyPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleLockDifficulty(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/handlers/DenizenNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerHearsSoundScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerReceivesActionbarScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerReceivesMessageScriptEvent;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.v1_17.Handler;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.network.packets.*;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.blocks.BlockLightImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.entities.EntityFakePlayerImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\r\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\r\nimport com.denizenscript.denizen.scripts.commands.entity.SneakCommand;\r\nimport com.denizenscript.denizen.scripts.commands.player.DisguiseCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\r\nimport com.denizenscript.denizen.utilities.entity.HideEntitiesHelper;\r\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\r\nimport com.denizenscript.denizen.utilities.packets.HideParticles;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.mojang.datafixers.util.Pair;\r\nimport io.netty.buffer.Unpooled;\r\nimport io.netty.channel.ChannelHandlerContext;\r\nimport io.netty.util.concurrent.Future;\r\nimport io.netty.util.concurrent.GenericFutureListener;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.NonNullList;\r\nimport net.minecraft.core.SectionPos;\r\nimport net.minecraft.core.particles.ParticleOptions;\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.ConnectionProtocol;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.PacketListener;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Particle;\r\nimport org.bukkit.craftbukkit.v1_17_R1.CraftParticle;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport javax.crypto.Cipher;\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.net.SocketAddress;\r\nimport java.util.*;\r\n\r\npublic class DenizenNetworkManagerImpl extends Connection {\r\n\r\n    public static FriendlyByteBuf copyPacket(Packet<?> original) {\r\n        try {\r\n            FriendlyByteBuf copier = new FriendlyByteBuf(Unpooled.buffer());\r\n            original.write(copier);\r\n            return copier;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public final Connection oldManager;\r\n    public final DenizenPacketListenerImpl packetListener;\r\n    public final ServerPlayer player;\r\n    public int packetsSent, packetsReceived;\r\n\r\n    public DenizenNetworkManagerImpl(ServerPlayer entityPlayer, Connection oldManager) {\r\n        super(getProtocolDirection(oldManager));\r\n        this.oldManager = oldManager;\r\n        this.channel = oldManager.channel;\r\n        this.packetListener = new DenizenPacketListenerImpl(this, entityPlayer);\r\n        oldManager.setListener(packetListener);\r\n        this.player = this.packetListener.player;\r\n    }\r\n\r\n    public static void setNetworkManager(Player player) {\r\n        ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerGamePacketListenerImpl playerConnection = entityPlayer.connection;\r\n        setNetworkManager(playerConnection, new DenizenNetworkManagerImpl(entityPlayer, playerConnection.connection));\r\n    }\r\n\r\n    @Override\r\n    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelRegistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelUnregistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception {\r\n        oldManager.channelActive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public void setProtocol(ConnectionProtocol enumprotocol) {\r\n        oldManager.setProtocol(enumprotocol);\r\n    }\r\n\r\n    @Override\r\n    public void channelInactive(ChannelHandlerContext channelhandlercontext) {\r\n        oldManager.channelInactive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public boolean isSharable() {\r\n        return oldManager.isSharable();\r\n    }\r\n\r\n    @Override\r\n    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerAdded(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerRemoved(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) {\r\n        oldManager.exceptionCaught(channelhandlercontext, throwable);\r\n    }\r\n\r\n    @Override\r\n    protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) {\r\n        if (oldManager.channel.isOpen()) {\r\n            try {\r\n                packet.handle(this.packetListener);\r\n            }\r\n            catch (Exception e) {\r\n                // Do nothing\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setListener(PacketListener packetlistener) {\r\n        oldManager.setListener(packetlistener);\r\n    }\r\n\r\n    public static Field ENTITY_ID_PACKVELENT = ReflectionHelper.getFields(ClientboundSetEntityMotionPacket.class).get(ReflectionMappingsInfo.ClientboundSetEntityMotionPacket_id);\r\n    public static Field ENTITY_ID_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_id);\r\n    public static Field POS_X_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_x);\r\n    public static Field POS_Y_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_y);\r\n    public static Field POS_Z_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_z);\r\n    public static Field YAW_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_yRot);\r\n    public static Field PITCH_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_xRot);\r\n    public static Field POS_X_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_xa);\r\n    public static Field POS_Y_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_ya);\r\n    public static Field POS_Z_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_za);\r\n    public static Field YAW_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_yRot);\r\n    public static Field PITCH_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_xRot);\r\n    public static Field SECTIONPOS_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_sectionPos);\r\n    public static Field OFFSETARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_positions);\r\n    public static Field BLOCKARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_states);\r\n    public static Field BLOCKDATA_BLOCKBREAK = ReflectionHelper.getFields(ClientboundBlockBreakAckPacket.class).get(ReflectionMappingsInfo.ClientboundBlockBreakAckPacket_state);\r\n    public static Field ENTITY_METADATA_LIST = ReflectionHelper.getFields(ClientboundSetEntityDataPacket.class).get(ReflectionMappingsInfo.ClientboundSetEntityDataPacket_packedItems);\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        send(packet, null);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            if (Settings.cache_warnOnAsyncPackets\r\n                    && !(packet instanceof ClientboundChatPacket) // Vanilla supports an async chat system, though it's normally disabled, some plugins use this as justification for sending messages async\r\n                    && !(packet instanceof ClientboundCommandSuggestionsPacket)) { // Async tab complete is wholly unsupported in Spigot (and will cause an exception), however Paper explicitly adds async support (for unclear reasons), so let it through too\r\n                Debug.echoError(\"Warning: packet sent off main thread! This is completely unsupported behavior! Denizen network interceptor ignoring packet to avoid crash. Packet class: \"\r\n                        + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName() + \" identify the sender of the packet from the stack trace:\");\r\n                try {\r\n                    throw new RuntimeException(\"Trace\");\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(ex);\r\n                }\r\n            }\r\n            oldManager.send(packet, genericfuturelistener);\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPackets) {\r\n            if (packet instanceof ClientboundSetEntityDataPacket) {\r\n                StringBuilder output = new StringBuilder(128);\r\n                output.append(\"Packet: ClientboundSetEntityDataPacket sent to \").append(player.getScoreboardName()).append(\" for entity ID: \").append(((ClientboundSetEntityDataPacket) packet).getId()).append(\": \");\r\n                List<SynchedEntityData.DataItem<?>> list = ((ClientboundSetEntityDataPacket) packet).getUnpackedData();\r\n                if (list == null) {\r\n                    output.append(\"None\");\r\n                }\r\n                else {\r\n                    for (SynchedEntityData.DataItem<?> data : list) {\r\n                        output.append('[').append(data.getAccessor().getId()).append(\": \").append(data.getValue()).append(\"], \");\r\n                    }\r\n                }\r\n                Debug.log(output.toString());\r\n            }\r\n            else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n                ClientboundSetEntityMotionPacket velPacket = (ClientboundSetEntityMotionPacket) packet;\r\n                Debug.log(\"Packet: ClientboundSetEntityMotionPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + velPacket.getId() + \": \" + velPacket.getXa() + \",\" + velPacket.getYa() + \",\" + velPacket.getZa());\r\n            }\r\n            else if (packet instanceof ClientboundAddEntityPacket) {\r\n                ClientboundAddEntityPacket addEntityPacket = (ClientboundAddEntityPacket) packet;\r\n                Debug.log(\"Packet: ClientboundAddEntityPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + addEntityPacket.getId() + \": \" + \"uuid: \" + addEntityPacket.getUUID()\r\n                        + \", type: \" + addEntityPacket.getType() + \", at: \" + addEntityPacket.getX() + \",\" + addEntityPacket.getY() + \",\" + addEntityPacket.getZ() + \", data: \" + addEntityPacket.getData());\r\n            }\r\n            else if (packet instanceof ClientboundMapItemDataPacket) {\r\n                ClientboundMapItemDataPacket mapPacket = (ClientboundMapItemDataPacket) packet;\r\n                Debug.log(\"Packet: ClientboundMapItemDataPacket sent to \" + player.getScoreboardName() + \" for map ID: \" + mapPacket.getMapId() + \", scale: \" + mapPacket.getScale() + \", locked: \" + mapPacket.isLocked());\r\n            }\r\n            else if (packet instanceof ClientboundLevelChunkPacket) {\r\n                ClientboundLevelChunkPacket chunkPacket = (ClientboundLevelChunkPacket) packet;\r\n                Debug.log(\"Packet: ClientboundLevelChunkPacket sent to \" + player.getScoreboardName() + \" for chunk: \" + chunkPacket.getX() + \", \" + chunkPacket.getZ()\r\n                        + \", blockEnts: \" + chunkPacket.getBlockEntitiesTags().size() + \", bufferLen: \" + chunkPacket.getReadBuffer().array().length);\r\n            }\r\n            else {\r\n                Debug.log(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName());\r\n            }\r\n        }\r\n        packetsSent++;\r\n        if (processAttachToForPacket(packet)\r\n            || processHiddenEntitiesForPacket(packet)\r\n            || processPacketHandlerForPacket(packet)\r\n            || processMirrorForPacket(packet)\r\n            || processParticlesForPacket(packet)\r\n            || processSoundPacket(packet)\r\n            || processActionbarPacket(packet, genericfuturelistener)\r\n            || processDisguiseForPacket(packet, genericfuturelistener)\r\n            || processMetadataChangesForPacket(packet, genericfuturelistener)\r\n            || processEquipmentForPacket(packet, genericfuturelistener)\r\n            || processShowFakeForPacket(packet, genericfuturelistener)) {\r\n            return;\r\n        }\r\n        processBlockLightForPacket(packet);\r\n        oldManager.send(packet, genericfuturelistener);\r\n    }\r\n\r\n    public boolean processActionbarPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (!PlayerReceivesActionbarScriptEvent.instance.loaded) {\r\n            return false;\r\n        }\r\n        if (packet instanceof ClientboundSetActionBarTextPacket) {\r\n            ClientboundSetActionBarTextPacket actionbarPacket = (ClientboundSetActionBarTextPacket) packet;\r\n            PlayerReceivesActionbarScriptEvent event = PlayerReceivesActionbarScriptEvent.instance;\r\n            Component baseComponent = actionbarPacket.getText();\r\n            event.reset();\r\n            event.message = new ElementTag(FormattedTextHelper.stringify(Handler.componentToSpigot(baseComponent)));\r\n            event.rawJson = new ElementTag(Component.Serializer.toJson(baseComponent));\r\n            event.system = new ElementTag(false);\r\n            event.player = PlayerTag.mirrorBukkitPlayer(player.getBukkitEntity());\r\n            event = (PlayerReceivesActionbarScriptEvent) event.triggerNow();\r\n            if (event.cancelled) {\r\n                return true;\r\n            }\r\n            if (event.modified) {\r\n                Component component = Handler.componentToNMS(event.altMessageDetermination);\r\n                ClientboundSetActionBarTextPacket newPacket = new ClientboundSetActionBarTextPacket(component);\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processSoundPacket(Packet<?> packet) {\r\n        if (!PlayerHearsSoundScriptEvent.instance.eventData.isEnabled) {\r\n            return false;\r\n        }\r\n        // (Player player, String name, String category, boolean isCustom, Entity entity, Location location, float volume, float pitch)\r\n        if (packet instanceof ClientboundSoundPacket) {\r\n            ClientboundSoundPacket spacket = (ClientboundSoundPacket) packet;\r\n            return PlayerHearsSoundScriptEvent.instance.run(player.getBukkitEntity(), spacket.getSound().getLocation().getPath(), spacket.getSource().name(),\r\n                    false, null, new Location(player.getBukkitEntity().getWorld(), spacket.getX(), spacket.getY(), spacket.getZ()), spacket.getVolume(), spacket.getPitch());\r\n        }\r\n        else if (packet instanceof ClientboundSoundEntityPacket) {\r\n            ClientboundSoundEntityPacket spacket = (ClientboundSoundEntityPacket) packet;\r\n            Entity entity = player.getLevel().getEntity(spacket.getId());\r\n            if (entity == null) {\r\n                return false;\r\n            }\r\n            return PlayerHearsSoundScriptEvent.instance.run(player.getBukkitEntity(), spacket.getSound().getLocation().getPath(), spacket.getSource().name(),\r\n                    false, entity.getBukkitEntity(), null, spacket.getVolume(), spacket.getPitch());\r\n        }\r\n        else if (packet instanceof ClientboundCustomSoundPacket) {\r\n            ClientboundCustomSoundPacket spacket = (ClientboundCustomSoundPacket) packet;\r\n            return PlayerHearsSoundScriptEvent.instance.run(player.getBukkitEntity(), spacket.getName().toString(), spacket.getSource().name(),\r\n                    true, null, new Location(player.getBukkitEntity().getWorld(), spacket.getX(), spacket.getY(), spacket.getZ()), spacket.getVolume(), spacket.getPitch());\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processEquipmentForPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (FakeEquipCommand.overrides.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundSetEquipmentPacket) {\r\n                int eid = ((ClientboundSetEquipmentPacket) packet).getEntity();\r\n                Entity ent = player.level.getEntity(eid);\r\n                if (ent == null) {\r\n                    return false;\r\n                }\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(ent.getUUID(), player.getBukkitEntity());\r\n                if (override == null) {\r\n                    return false;\r\n                }\r\n                List<Pair<net.minecraft.world.entity.EquipmentSlot, ItemStack>> equipment = new ArrayList<>(((ClientboundSetEquipmentPacket) packet).getSlots());\r\n                ClientboundSetEquipmentPacket newPacket = new ClientboundSetEquipmentPacket(eid, equipment);\r\n                for (int i = 0; i < equipment.size(); i++) {\r\n                    Pair<net.minecraft.world.entity.EquipmentSlot, ItemStack> pair =  equipment.get(i);\r\n                    ItemStack use = pair.getSecond();\r\n                    switch (pair.getFirst()) {\r\n                        case MAINHAND:\r\n                            use = override.hand == null ? use : CraftItemStack.asNMSCopy(override.hand.getItemStack());\r\n                            break;\r\n                        case OFFHAND:\r\n                            use = override.offhand == null ? use : CraftItemStack.asNMSCopy(override.offhand.getItemStack());\r\n                            break;\r\n                        case CHEST:\r\n                            use = override.chest == null ? use : CraftItemStack.asNMSCopy(override.chest.getItemStack());\r\n                            break;\r\n                        case HEAD:\r\n                            use = override.head == null ? use : CraftItemStack.asNMSCopy(override.head.getItemStack());\r\n                            break;\r\n                        case LEGS:\r\n                            use = override.legs == null ? use : CraftItemStack.asNMSCopy(override.legs.getItemStack());\r\n                            break;\r\n                        case FEET:\r\n                            use = override.boots == null ? use : CraftItemStack.asNMSCopy(override.boots.getItemStack());\r\n                            break;\r\n                    }\r\n                    equipment.set(i, new Pair<>(pair.getFirst(), use));\r\n                }\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundEntityEventPacket) {\r\n                Entity ent = ((ClientboundEntityEventPacket) packet).getEntity(player.level);\r\n                if (!(ent instanceof net.minecraft.world.entity.LivingEntity)) {\r\n                    return false;\r\n                }\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(ent.getUUID(), player.getBukkitEntity());\r\n                if (override == null || (override.hand == null && override.offhand == null)) {\r\n                    return false;\r\n                }\r\n                if (((ClientboundEntityEventPacket) packet).getEventId() != (byte) 55) {\r\n                    return false;\r\n                }\r\n                List<Pair<net.minecraft.world.entity.EquipmentSlot, ItemStack>> equipment = new ArrayList<>();\r\n                ItemStack hand = override.hand != null ? CraftItemStack.asNMSCopy(override.hand.getItemStack()) : ((net.minecraft.world.entity.LivingEntity) ent).getMainHandItem();\r\n                ItemStack offhand = override.offhand != null ? CraftItemStack.asNMSCopy(override.offhand.getItemStack()) : ((net.minecraft.world.entity.LivingEntity) ent).getOffhandItem();\r\n                equipment.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.MAINHAND, hand));\r\n                equipment.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.OFFHAND, offhand));\r\n                ClientboundSetEquipmentPacket newPacket = new ClientboundSetEquipmentPacket(ent.getId(), equipment);\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundContainerSetContentPacket) {\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), player.getBukkitEntity());\r\n                if (override == null) {\r\n                    return false;\r\n                }\r\n                int window = ((ClientboundContainerSetContentPacket) packet).getContainerId();\r\n                if (window != 0) {\r\n                    return false;\r\n                }\r\n                NonNullList<ItemStack> items = (NonNullList<ItemStack>) ((ClientboundContainerSetContentPacket) packet).getItems();\r\n                if (override.head != null) {\r\n                    items.set(5, CraftItemStack.asNMSCopy(override.head.getItemStack()));\r\n                }\r\n                if (override.chest != null) {\r\n                    items.set(6, CraftItemStack.asNMSCopy(override.chest.getItemStack()));\r\n                }\r\n                if (override.legs != null) {\r\n                    items.set(7, CraftItemStack.asNMSCopy(override.legs.getItemStack()));\r\n                }\r\n                if (override.boots != null) {\r\n                    items.set(8, CraftItemStack.asNMSCopy(override.boots.getItemStack()));\r\n                }\r\n                if (override.offhand != null) {\r\n                    items.set(45, CraftItemStack.asNMSCopy(override.offhand.getItemStack()));\r\n                }\r\n                if (override.hand != null) {\r\n                    items.set(player.getInventory().selected + 36, CraftItemStack.asNMSCopy(override.hand.getItemStack()));\r\n                }\r\n                ClientboundContainerSetContentPacket newPacket = new ClientboundContainerSetContentPacket(window, ((ClientboundContainerSetContentPacket) packet).getStateId(), items, ((ClientboundContainerSetContentPacket) packet).getCarriedItem());\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundContainerSetSlotPacket) {\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), player.getBukkitEntity());\r\n                if (override == null) {\r\n                    return false;\r\n                }\r\n                int window = ((ClientboundContainerSetSlotPacket) packet).getContainerId();\r\n                if (window != 0) {\r\n                    return false;\r\n                }\r\n                int slot = ((ClientboundContainerSetSlotPacket) packet).getSlot();\r\n                org.bukkit.inventory.ItemStack item = null;\r\n                if (slot == 5 && override.head != null) {\r\n                    item = override.head.getItemStack();\r\n                }\r\n                else if (slot == 6 && override.chest != null) {\r\n                    item = override.chest.getItemStack();\r\n                }\r\n                else if (slot == 7 && override.legs != null) {\r\n                    item = override.legs.getItemStack();\r\n                }\r\n                else if (slot == 8 && override.boots != null) {\r\n                    item = override.boots.getItemStack();\r\n                }\r\n                else if (slot == 45 && override.offhand != null) {\r\n                    item = override.offhand.getItemStack();\r\n                }\r\n                else if (slot == player.getInventory().selected + 36 && override.hand != null) {\r\n                    item = override.hand.getItemStack();\r\n                }\r\n                if (item == null) {\r\n                    return false;\r\n                }\r\n                ClientboundContainerSetSlotPacket newPacket = new ClientboundContainerSetSlotPacket(window, ((ClientboundContainerSetSlotPacket) packet).getStateId(), slot, CraftItemStack.asNMSCopy(item));\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processParticlesForPacket(Packet<?> packet) {\r\n        if (HideParticles.hidden.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundLevelParticlesPacket) {\r\n                HashSet<Particle> hidden = HideParticles.hidden.get(player.getUUID());\r\n                if (hidden == null) {\r\n                    return false;\r\n                }\r\n                ParticleOptions particle = ((ClientboundLevelParticlesPacket) packet).getParticle();\r\n                Particle bukkitParticle = CraftParticle.toBukkit(particle);\r\n                if (hidden.contains(bukkitParticle)) {\r\n                    return true;\r\n                }\r\n                return false;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    private boolean antiDuplicate = false;\r\n\r\n    public boolean processDisguiseForPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (DisguiseCommand.disguises.isEmpty() || antiDuplicate) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundSetEntityDataPacket) {\r\n                ClientboundSetEntityDataPacket metadataPacket = (ClientboundSetEntityDataPacket) packet;\r\n                int eid = metadataPacket.getId();\r\n                Entity ent = player.level.getEntity(eid);\r\n                if (ent == null) {\r\n                    return false;\r\n                }\r\n                HashMap<UUID, DisguiseCommand.TrackedDisguise> playerMap = DisguiseCommand.disguises.get(ent.getUUID());\r\n                if (playerMap == null) {\r\n                    return false;\r\n                }\r\n                DisguiseCommand.TrackedDisguise disguise = playerMap.get(player.getUUID());\r\n                if (disguise == null) {\r\n                    disguise = playerMap.get(null);\r\n                    if (disguise == null) {\r\n                        return false;\r\n                    }\r\n                }\r\n                if (ent.getId() == player.getId()) {\r\n                    if (!disguise.shouldFake) {\r\n                        return false;\r\n                    }\r\n                    List<SynchedEntityData.DataItem<?>> data = metadataPacket.getUnpackedData();\r\n                    for (SynchedEntityData.DataItem item : data) {\r\n                        EntityDataAccessor<?> watcherObject = item.getAccessor();\r\n                        int watcherId = watcherObject.getId();\r\n                        if (watcherId == 0) { // Entity flags\r\n                            ClientboundSetEntityDataPacket altPacket = new ClientboundSetEntityDataPacket(copyPacket(metadataPacket));\r\n                            data = new ArrayList<>(data);\r\n                            ENTITY_METADATA_LIST.set(altPacket, data);\r\n                            data.remove(item);\r\n                            byte flags = (byte) item.getValue();\r\n                            flags |= 0x20; // Invisible flag\r\n                            data.add(new SynchedEntityData.DataItem(watcherObject, flags));\r\n                            ClientboundSetEntityDataPacket updatedPacket = getModifiedMetadataFor(altPacket);\r\n                            oldManager.send(updatedPacket == null ? altPacket : updatedPacket, genericfuturelistener);\r\n                            return true;\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    ClientboundSetEntityDataPacket altPacket = new ClientboundSetEntityDataPacket(ent.getId(), ((CraftEntity) disguise.toOthers.entity.entity).getHandle().getEntityData(), true);\r\n                    oldManager.send(altPacket, genericfuturelistener);\r\n                    return true;\r\n                }\r\n                return false;\r\n            }\r\n            int ider = -1;\r\n            if (packet instanceof ClientboundAddPlayerPacket) {\r\n                ider = ((ClientboundAddPlayerPacket) packet).getEntityId();\r\n            }\r\n            else if (packet instanceof ClientboundAddEntityPacket) {\r\n                ider = ((ClientboundAddEntityPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundAddMobPacket) {\r\n                ider = ((ClientboundAddMobPacket) packet).getId();\r\n            }\r\n            if (ider != -1) {\r\n                Entity e = player.getLevel().getEntity(ider);\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                HashMap<UUID, DisguiseCommand.TrackedDisguise> playerMap = DisguiseCommand.disguises.get(e.getUUID());\r\n                if (playerMap == null) {\r\n                    return false;\r\n                }\r\n                DisguiseCommand.TrackedDisguise disguise = playerMap.get(player.getUUID());\r\n                if (disguise == null) {\r\n                    disguise = playerMap.get(null);\r\n                    if (disguise == null) {\r\n                        return false;\r\n                    }\r\n                }\r\n                antiDuplicate = true;\r\n                disguise.sendTo(Collections.singletonList(new PlayerTag(player.getBukkitEntity())));\r\n                antiDuplicate = false;\r\n                return true;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            antiDuplicate = false;\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public ClientboundSetEntityDataPacket getModifiedMetadataFor(ClientboundSetEntityDataPacket metadataPacket) {\r\n        if (!RenameCommand.hasAnyDynamicRenames() && SneakCommand.forceSetSneak.isEmpty()) {\r\n            return null;\r\n        }\r\n        try {\r\n            int eid = metadataPacket.getId();\r\n            Entity ent = player.level.getEntity(eid);\r\n            if (ent == null) {\r\n                return null; // If it doesn't exist on-server, it's definitely not relevant, so move on\r\n            }\r\n            String nameToApply = RenameCommand.getCustomNameFor(ent.getUUID(), player.getBukkitEntity(), false);\r\n            Boolean forceSneak = SneakCommand.shouldSneak(ent.getUUID(), player.getUUID());\r\n            if (nameToApply == null && forceSneak == null) {\r\n                return null;\r\n            }\r\n            List<SynchedEntityData.DataItem<?>> data = new ArrayList<>(metadataPacket.getUnpackedData());\r\n            boolean any = false;\r\n            for (int i = 0; i < data.size(); i++) {\r\n                SynchedEntityData.DataItem<?> item = data.get(i);\r\n                EntityDataAccessor<?> watcherObject = item.getAccessor();\r\n                int watcherId = watcherObject.getId();\r\n                if (watcherId == 0 && forceSneak != null) { // 0: Entity flags\r\n                    byte val = (Byte) item.getValue();\r\n                    if (forceSneak) {\r\n                        val |= 0x02; // 8: Crouching\r\n                    }\r\n                    else {\r\n                        val &= ~0x02;\r\n                    }\r\n                    data.set(i, new SynchedEntityData.DataItem(watcherObject, val));\r\n                    any = true;\r\n                }\r\n                else if (watcherId == 2 && nameToApply != null) { // 2: Custom name metadata\r\n                    Optional<Component> name = Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(nameToApply, ChatColor.WHITE)));\r\n                    data.set(i, new SynchedEntityData.DataItem(watcherObject, name));\r\n                    any = true;\r\n                }\r\n                else if (watcherId == 3 && nameToApply != null) { // 3: custom name visible metadata\r\n                    data.set(i, new SynchedEntityData.DataItem(watcherObject, true));\r\n                    any = true;\r\n                }\r\n            }\r\n            if (!any) {\r\n                return null;\r\n            }\r\n            ClientboundSetEntityDataPacket altPacket = new ClientboundSetEntityDataPacket(copyPacket(metadataPacket));\r\n            ENTITY_METADATA_LIST.set(altPacket, data);\r\n            return altPacket;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public boolean processMetadataChangesForPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (!(packet instanceof ClientboundSetEntityDataPacket)) {\r\n            return false;\r\n        }\r\n        ClientboundSetEntityDataPacket altPacket = getModifiedMetadataFor((ClientboundSetEntityDataPacket) packet);\r\n        if (altPacket == null) {\r\n            return false;\r\n        }\r\n        oldManager.send(altPacket, genericfuturelistener);\r\n        return true;\r\n    }\r\n\r\n    public void tryProcessMovePacketForAttach(ClientboundMoveEntityPacket packet, Entity e) throws IllegalAccessException {\r\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n        if (attList != null) {\r\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                if (attMap.attached.isValid() && att != null) {\r\n                    ClientboundMoveEntityPacket pNew;\r\n                    int newId = att.attached.getBukkitEntity().getEntityId();\r\n                    if (packet instanceof ClientboundMoveEntityPacket.Pos) {\r\n                        pNew = new ClientboundMoveEntityPacket.Pos(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.isOnGround());\r\n                    }\r\n                    else if (packet instanceof ClientboundMoveEntityPacket.Rot) {\r\n                        pNew = new ClientboundMoveEntityPacket.Rot(newId, packet.getyRot(), packet.getxRot(), packet.isOnGround());\r\n                    }\r\n                    else if (packet instanceof ClientboundMoveEntityPacket.PosRot) {\r\n                        pNew = new ClientboundMoveEntityPacket.PosRot(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.getyRot(), packet.getxRot(), packet.isOnGround());\r\n                    }\r\n                    else {\r\n                        if (CoreConfiguration.debugVerbose) {\r\n                            Debug.echoError(\"Impossible move-entity packet class: \" + packet.getClass().getCanonicalName());\r\n                        }\r\n                        return;\r\n                    }\r\n                    if (att.positionalOffset != null && (packet instanceof ClientboundMoveEntityPacket.Pos || packet instanceof ClientboundMoveEntityPacket.PosRot)) {\r\n                        boolean isRotate = packet instanceof ClientboundMoveEntityPacket.PosRot;\r\n                        byte yaw, pitch;\r\n                        if (att.noRotate) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            yaw = EntityAttachmentHelper.compressAngle(attachedEntity.getYRot());\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        else if (isRotate) {\r\n                            yaw = packet.getyRot();\r\n                            pitch = packet.getxRot();\r\n                        }\r\n                        else {\r\n                            yaw = EntityAttachmentHelper.compressAngle(e.getYRot());\r\n                            pitch = EntityAttachmentHelper.compressAngle(e.getXRot());\r\n                        }\r\n                        if (att.noPitch) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        byte newYaw = yaw;\r\n                        if (isRotate) {\r\n                            newYaw = EntityAttachmentHelper.adaptedCompressedAngle(newYaw, att.positionalOffset.getYaw());\r\n                            pitch = EntityAttachmentHelper.adaptedCompressedAngle(pitch, att.positionalOffset.getPitch());\r\n                        }\r\n                        Vector goalPosition = att.fixedForOffset(new Vector(e.getX(), e.getY(), e.getZ()), e.getYRot(), e.getXRot());\r\n                        Vector oldPos = att.visiblePositions.get(player.getUUID());\r\n                        boolean forceTele = false;\r\n                        if (oldPos == null) {\r\n                            oldPos = att.attached.getLocation().toVector();\r\n                            forceTele = true;\r\n                        }\r\n                        Vector moveNeeded = goalPosition.clone().subtract(oldPos);\r\n                        att.visiblePositions.put(player.getUUID(), goalPosition.clone());\r\n                        int offX = (int) (moveNeeded.getX() * (32 * 128));\r\n                        int offY = (int) (moveNeeded.getY() * (32 * 128));\r\n                        int offZ = (int) (moveNeeded.getZ() * (32 * 128));\r\n                        if (forceTele || offX < Short.MIN_VALUE || offX > Short.MAX_VALUE\r\n                                || offY < Short.MIN_VALUE || offY > Short.MAX_VALUE\r\n                                || offZ < Short.MIN_VALUE || offZ > Short.MAX_VALUE) {\r\n                            ClientboundTeleportEntityPacket newTeleportPacket = new ClientboundTeleportEntityPacket(e);\r\n                            ENTITY_ID_PACKTELENT.setInt(newTeleportPacket, att.attached.getBukkitEntity().getEntityId());\r\n                            POS_X_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getX());\r\n                            POS_Y_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getY());\r\n                            POS_Z_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getZ());\r\n                            YAW_PACKTELENT.setByte(newTeleportPacket, newYaw);\r\n                            PITCH_PACKTELENT.setByte(newTeleportPacket, pitch);\r\n                            if (NMSHandler.debugPackets) {\r\n                                Debug.log(\"Attach Move-Tele Packet: \" + newTeleportPacket.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\r\n                            }\r\n                            oldManager.send(newTeleportPacket);\r\n                        }\r\n                        else {\r\n                            POS_X_PACKENT.setShort(pNew, (short) Mth.clamp(offX, Short.MIN_VALUE, Short.MAX_VALUE));\r\n                            POS_Y_PACKENT.setShort(pNew, (short) Mth.clamp(offY, Short.MIN_VALUE, Short.MAX_VALUE));\r\n                            POS_Z_PACKENT.setShort(pNew, (short) Mth.clamp(offZ, Short.MIN_VALUE, Short.MAX_VALUE));\r\n                            if (isRotate) {\r\n                                YAW_PACKENT.setByte(pNew, yaw);\r\n                                PITCH_PACKENT.setByte(pNew, pitch);\r\n                            }\r\n                            if (NMSHandler.debugPackets) {\r\n                                Debug.log(\"Attach Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\r\n                            }\r\n                            oldManager.send(pNew);\r\n                        }\r\n                    }\r\n                    else {\r\n                        if (NMSHandler.debugPackets) {\r\n                            Debug.log(\"Attach Replica-Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getName());\r\n                        }\r\n                        oldManager.send(pNew);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (e.passengers != null && !e.passengers.isEmpty()) {\r\n            for (Entity ent : e.passengers) {\r\n                tryProcessMovePacketForAttach(packet, ent);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void tryProcessVelocityPacketForAttach(ClientboundSetEntityMotionPacket packet, Entity e) throws IllegalAccessException {\r\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n        if (attList != null) {\r\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                if (attMap.attached.isValid() && att != null) {\r\n                    ClientboundSetEntityMotionPacket pNew = new ClientboundSetEntityMotionPacket(copyPacket(packet));\r\n                    ENTITY_ID_PACKVELENT.setInt(pNew, att.attached.getBukkitEntity().getEntityId());\r\n                    if (NMSHandler.debugPackets) {\r\n                        Debug.log(\"Attach Velocity Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getName());\r\n                    }\r\n                    oldManager.send(pNew);\r\n                }\r\n            }\r\n        }\r\n        if (e.passengers != null && !e.passengers.isEmpty()) {\r\n            for (Entity ent : e.passengers) {\r\n                tryProcessVelocityPacketForAttach(packet, ent);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void tryProcessTeleportPacketForAttach(ClientboundTeleportEntityPacket packet, Entity e, Vector relative) throws IllegalAccessException {\r\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n        if (attList != null) {\r\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                if (attMap.attached.isValid() && att != null) {\r\n                    ClientboundTeleportEntityPacket pNew = new ClientboundTeleportEntityPacket(copyPacket(packet));\r\n                    ENTITY_ID_PACKTELENT.setInt(pNew, att.attached.getBukkitEntity().getEntityId());\r\n                    Vector resultPos = new Vector(POS_X_PACKTELENT.getDouble(pNew), POS_Y_PACKTELENT.getDouble(pNew), POS_Z_PACKTELENT.getDouble(pNew)).add(relative);\r\n                    if (att.positionalOffset != null) {\r\n                        resultPos = att.fixedForOffset(resultPos, e.getYRot(), e.getXRot());\r\n                        byte yaw, pitch;\r\n                        if (att.noRotate) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            yaw = EntityAttachmentHelper.compressAngle(attachedEntity.getYRot());\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        else {\r\n                            yaw = packet.getyRot();\r\n                            pitch = packet.getxRot();\r\n                        }\r\n                        if (att.noPitch) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        byte newYaw = EntityAttachmentHelper.adaptedCompressedAngle(yaw, att.positionalOffset.getYaw());\r\n                        pitch = EntityAttachmentHelper.adaptedCompressedAngle(pitch, att.positionalOffset.getPitch());\r\n                        POS_X_PACKTELENT.setDouble(pNew, resultPos.getX());\r\n                        POS_Y_PACKTELENT.setDouble(pNew, resultPos.getY());\r\n                        POS_Z_PACKTELENT.setDouble(pNew, resultPos.getZ());\r\n                        YAW_PACKTELENT.setByte(pNew, newYaw);\r\n                        PITCH_PACKTELENT.setByte(pNew, pitch);\r\n                        if (NMSHandler.debugPackets) {\r\n                            Debug.log(\"Attach Teleport Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getName() + \" with raw yaw \" + yaw + \" adapted to \" + newYaw);\r\n                        }\r\n                    }\r\n                    att.visiblePositions.put(player.getUUID(), resultPos.clone());\r\n                    oldManager.send(pNew);\r\n                }\r\n            }\r\n        }\r\n        if (e.passengers != null && !e.passengers.isEmpty()) {\r\n            for (Entity ent : e.passengers) {\r\n                tryProcessTeleportPacketForAttach(packet, ent, new Vector(ent.getX() - e.getX(), ent.getY() - e.getY(), ent.getZ() - e.getZ()));\r\n            }\r\n        }\r\n    }\r\n\r\n    public static Vector VECTOR_ZERO = new Vector(0, 0, 0);\r\n\r\n    public boolean processAttachToForPacket(Packet<?> packet) {\r\n        if (EntityAttachmentHelper.toEntityToData.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundMoveEntityPacket) {\r\n                Entity e = ((ClientboundMoveEntityPacket) packet).getEntity(player.getLevel());\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                if (!e.isPassenger()) {\r\n                    tryProcessMovePacketForAttach((ClientboundMoveEntityPacket) packet, e);\r\n                }\r\n                return EntityAttachmentHelper.denyOriginalPacketSend(player.getUUID(), e.getUUID());\r\n            }\r\n            else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n                int ider = ((ClientboundSetEntityMotionPacket) packet).getId();\r\n                Entity e = player.getLevel().getEntity(ider);\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                tryProcessVelocityPacketForAttach((ClientboundSetEntityMotionPacket) packet, e);\r\n                return EntityAttachmentHelper.denyOriginalPacketSend(player.getUUID(), e.getUUID());\r\n            }\r\n            else if (packet instanceof ClientboundTeleportEntityPacket) {\r\n                int ider = ((ClientboundTeleportEntityPacket) packet).getId();\r\n                Entity e = player.getLevel().getEntity(ider);\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                tryProcessTeleportPacketForAttach((ClientboundTeleportEntityPacket) packet, e, VECTOR_ZERO);\r\n                return EntityAttachmentHelper.denyOriginalPacketSend(player.getUUID(), e.getUUID());\r\n            }\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean isHidden(Entity entity) {\r\n        return entity != null && HideEntitiesHelper.playerShouldHide(player.getBukkitEntity().getUniqueId(), entity.getBukkitEntity());\r\n    }\r\n\r\n    public boolean processHiddenEntitiesForPacket(Packet<?> packet) {\r\n        if (!HideEntitiesHelper.hasAnyHides()) {\r\n            return false;\r\n        }\r\n        try {\r\n            int ider = -1;\r\n            Entity e = null;\r\n            if (packet instanceof ClientboundAddPlayerPacket) {\r\n                ider = ((ClientboundAddPlayerPacket) packet).getEntityId();\r\n            }\r\n            else if (packet instanceof ClientboundAddEntityPacket) {\r\n                ider = ((ClientboundAddEntityPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundAddMobPacket) {\r\n                ider = ((ClientboundAddMobPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundAddPaintingPacket) {\r\n                ider = ((ClientboundAddPaintingPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundAddExperienceOrbPacket) {\r\n                ider = ((ClientboundAddExperienceOrbPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundMoveEntityPacket) {\r\n                e = ((ClientboundMoveEntityPacket) packet).getEntity(player.getLevel());\r\n            }\r\n            else if (packet instanceof ClientboundSetEntityDataPacket) {\r\n                ider = ((ClientboundSetEntityDataPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n                ider = ((ClientboundSetEntityMotionPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundTeleportEntityPacket) {\r\n                ider = ((ClientboundTeleportEntityPacket) packet).getId();\r\n            }\r\n            if (e == null && ider != -1) {\r\n                e = player.getLevel().getEntity(ider);\r\n            }\r\n            if (e != null) {\r\n                if (isHidden(e)) {\r\n                    return true;\r\n                }\r\n                if (packet instanceof ClientboundAddPlayerPacket\r\n                        || packet instanceof ClientboundAddEntityPacket\r\n                        || packet instanceof ClientboundAddMobPacket\r\n                        || packet instanceof ClientboundAddPaintingPacket\r\n                        || packet instanceof ClientboundAddExperienceOrbPacket) {\r\n                    processFakePlayerSpawn(e);\r\n                }\r\n            }\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void processFakePlayerSpawn(Entity entity) {\r\n        if (entity instanceof EntityFakePlayerImpl) {\r\n            final EntityFakePlayerImpl fakePlayer = (EntityFakePlayerImpl) entity;\r\n            send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, fakePlayer));\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(),\r\n                    () -> send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.REMOVE_PLAYER, fakePlayer)), 5);\r\n        }\r\n    }\r\n\r\n    public boolean processMirrorForPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundPlayerInfoPacket) {\r\n            ClientboundPlayerInfoPacket playerInfo = (ClientboundPlayerInfoPacket) packet;\r\n            ProfileEditorImpl.updatePlayerProfiles(playerInfo);\r\n            if (!ProfileEditorImpl.handleAlteredProfiles(playerInfo, this)) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processPacketHandlerForPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundChatPacket && DenizenPacketHandler.instance.shouldInterceptChatPacket()) {\r\n            PacketOutChatImpl packetHelper = new PacketOutChatImpl((ClientboundChatPacket) packet);\r\n            PlayerReceivesMessageScriptEvent result = DenizenPacketHandler.instance.sendPacket(player.getBukkitEntity(), packetHelper);\r\n            if (result != null) {\r\n                if (result.cancelled) {\r\n                    return true;\r\n                }\r\n                if (result.modified) {\r\n                    packetHelper.setRawJson(ComponentSerializer.toString(result.altMessageDetermination));\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processShowFakeForPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (FakeBlock.blocks.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundLevelChunkPacket) {\r\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(player.getUUID());\r\n                if (map == null) {\r\n                    return false;\r\n                }\r\n                int chunkX = ((ClientboundLevelChunkPacket) packet).getX();\r\n                int chunkZ = ((ClientboundLevelChunkPacket) packet).getZ();\r\n                ChunkCoordinate chunkCoord = new ChunkCoordinate(chunkX, chunkZ, player.getLevel().getWorld().getName());\r\n                List<FakeBlock> blocks = FakeBlock.getFakeBlocksFor(player.getUUID(), chunkCoord);\r\n                if (blocks == null || blocks.isEmpty()) {\r\n                    return false;\r\n                }\r\n                ClientboundLevelChunkPacket newPacket = FakeBlockHelper.handleMapChunkPacket((ClientboundLevelChunkPacket) packet, blocks);\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundSectionBlocksUpdatePacket) {\r\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(player.getUUID());\r\n                if (map == null) {\r\n                    return false;\r\n                }\r\n                SectionPos coord = (SectionPos) SECTIONPOS_MULTIBLOCKCHANGE.get(packet);\r\n                ChunkCoordinate coordinateDenizen = new ChunkCoordinate(coord.getX(), coord.getZ(), player.getLevel().getWorld().getName());\r\n                if (!map.byChunk.containsKey(coordinateDenizen)) {\r\n                    return false;\r\n                }\r\n                ClientboundSectionBlocksUpdatePacket newPacket = new ClientboundSectionBlocksUpdatePacket(copyPacket(packet));\r\n                LocationTag location = new LocationTag(player.getLevel().getWorld(), 0, 0, 0);\r\n                short[] originalOffsetArray = (short[])OFFSETARRAY_MULTIBLOCKCHANGE.get(newPacket);\r\n                BlockState[] originalDataArray = (BlockState[])BLOCKARRAY_MULTIBLOCKCHANGE.get(newPacket);\r\n                short[] offsetArray = Arrays.copyOf(originalOffsetArray, originalOffsetArray.length);\r\n                BlockState[] dataArray = Arrays.copyOf(originalDataArray, originalDataArray.length);\r\n                OFFSETARRAY_MULTIBLOCKCHANGE.set(newPacket, offsetArray);\r\n                BLOCKARRAY_MULTIBLOCKCHANGE.set(newPacket, dataArray);\r\n                for (int i = 0; i < offsetArray.length; i++) {\r\n                    short offset = offsetArray[i];\r\n                    BlockPos pos = coord.relativeToBlockPos(offset);\r\n                    location.setX(pos.getX());\r\n                    location.setY(pos.getY());\r\n                    location.setZ(pos.getZ());\r\n                    FakeBlock block = map.byLocation.get(location);\r\n                    if (block != null) {\r\n                        dataArray[i] = FakeBlockHelper.getNMSState(block);\r\n                    }\r\n                }\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundBlockUpdatePacket) {\r\n                BlockPos pos = ((ClientboundBlockUpdatePacket) packet).getPos();\r\n                LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ());\r\n                FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc);\r\n                if (block != null) {\r\n                    ClientboundBlockUpdatePacket newPacket = new ClientboundBlockUpdatePacket(((ClientboundBlockUpdatePacket) packet).getPos(), FakeBlockHelper.getNMSState(block));\r\n                    oldManager.send(newPacket, genericfuturelistener);\r\n                    return true;\r\n                }\r\n            }\r\n            else if (packet instanceof ClientboundBlockBreakAckPacket) {\r\n                BlockPos pos = ((ClientboundBlockBreakAckPacket) packet).getPos();\r\n                LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ());\r\n                FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc);\r\n                if (block != null) {\r\n                    ClientboundBlockBreakAckPacket newPacket = new ClientboundBlockBreakAckPacket(copyPacket(packet));\r\n                    BLOCKDATA_BLOCKBREAK.set(newPacket, FakeBlockHelper.getNMSState(block));\r\n                    oldManager.send(newPacket, genericfuturelistener);\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void processBlockLightForPacket(Packet<?> packet) {\r\n        if (BlockLight.lightsByChunk.isEmpty()) {\r\n            return;\r\n        }\r\n        if (packet instanceof ClientboundLightUpdatePacket) {\r\n            BlockLightImpl.checkIfLightsBrokenByPacket((ClientboundLightUpdatePacket) packet, player.level);\r\n        }\r\n        else if (packet instanceof ClientboundBlockUpdatePacket) {\r\n            BlockLightImpl.checkIfLightsBrokenByPacket((ClientboundBlockUpdatePacket) packet, player.level);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldManager.tick();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRemoteAddress() {\r\n        return oldManager.getRemoteAddress();\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        oldManager.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public boolean isMemoryConnection() {\r\n        return oldManager.isMemoryConnection();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getReceiving() {\r\n        return oldManager.getReceiving();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getSending() {\r\n        return oldManager.getSending();\r\n    }\r\n\r\n    @Override\r\n    public void setEncryptionKey(Cipher cipher, Cipher cipher1) {\r\n        oldManager.setEncryptionKey(cipher, cipher1);\r\n    }\r\n\r\n    @Override\r\n    public boolean isEncrypted() {\r\n        return oldManager.isEncrypted();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnected() {\r\n        return oldManager.isConnected();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnecting() {\r\n        return oldManager.isConnecting();\r\n    }\r\n\r\n    @Override\r\n    public PacketListener getPacketListener() {\r\n        return oldManager.getPacketListener();\r\n    }\r\n\r\n    @Override\r\n    public Component getDisconnectedReason() {\r\n        return oldManager.getDisconnectedReason();\r\n    }\r\n\r\n    @Override\r\n    public void setReadOnly() {\r\n        oldManager.setReadOnly();\r\n    }\r\n\r\n    @Override\r\n    public void setupCompression(int i, boolean b) {\r\n        oldManager.setupCompression(i, b);\r\n    }\r\n\r\n    @Override\r\n    public void handleDisconnection() {\r\n        oldManager.handleDisconnection();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageReceivedPackets() {\r\n        return oldManager.getAverageReceivedPackets();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageSentPackets() {\r\n        return oldManager.getAverageSentPackets();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRawAddress() {\r\n        return oldManager.getRawAddress();\r\n    }\r\n\r\n    //////////////////////////////////\r\n    //// Reflection Methods/Fields\r\n    ///////////\r\n\r\n    private static final Field protocolDirectionField;\r\n    private static final MethodHandle networkManagerField;\r\n\r\n    static {\r\n        Field directionField = null;\r\n        MethodHandle managerField = null;\r\n        try {\r\n            directionField = ReflectionHelper.getFields(Connection.class).get(ReflectionMappingsInfo.Connection_receiving);\r\n            directionField.setAccessible(true);\r\n            managerField = ReflectionHelper.getFinalSetter(ServerGamePacketListenerImpl.class, ReflectionMappingsInfo.ServerGamePacketListenerImpl_connection);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        protocolDirectionField = directionField;\r\n        networkManagerField = managerField;\r\n    }\r\n\r\n    private static PacketFlow getProtocolDirection(Connection networkManager) {\r\n        PacketFlow direction = null;\r\n        try {\r\n            direction = (PacketFlow) protocolDirectionField.get(networkManager);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return direction;\r\n    }\r\n\r\n    private static void setNetworkManager(ServerGamePacketListenerImpl playerConnection, Connection networkManager) {\r\n        try {\r\n            networkManagerField.invoke(playerConnection, networkManager);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean acceptInboundMessage(Object msg) throws Exception {\r\n        return oldManager.acceptInboundMessage(msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\r\n        oldManager.channelRead(ctx, msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelReadComplete(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\r\n        oldManager.userEventTriggered(ctx, evt);\r\n    }\r\n\r\n    @Override\r\n    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelWritabilityChanged(ctx);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/handlers/DenizenPacketListenerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerChangesSignScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerSteersEntityScriptEvent;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.network.packets.PacketInResourcePackStatusImpl;\r\nimport com.denizenscript.denizen.nms.v1_17.impl.network.packets.PacketInSteerVehicleImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\r\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.event.block.SignChangeEvent;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\n\r\npublic class DenizenPacketListenerImpl extends AbstractListenerPlayInImpl {\r\n\r\n    public String brand = \"unknown\";\r\n\r\n    public BlockPos fakeSignExpected;\r\n\r\n    public DenizenPacketListenerImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer) {\r\n        super(networkManager, entityPlayer, entityPlayer.connection);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(final ServerboundPlayerInputPacket packet) {\r\n        if (!PlayerSteersEntityScriptEvent.instance.eventData.isEnabled) {\r\n            super.handlePlayerInput(packet);\r\n            return;\r\n        }\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInSteerVehicleImpl(packet), () -> super.handlePlayerInput(packet));\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInResourcePackStatusImpl(packet));\r\n        super.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        DenizenPacketHandler.instance.receivePlacePacket(player.getBukkitEntity());\r\n        super.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        DenizenPacketHandler.instance.receiveDigPacket(player.getBukkitEntity());\r\n        super.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), player.getBukkitEntity());\r\n        if (override != null && (override.hand != null || override.offhand != null)) {\r\n            player.getBukkitEntity().updateInventory();\r\n        }\r\n        super.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), player.getBukkitEntity());\r\n        if (override != null && override.hand != null) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 2);\r\n        }\r\n        super.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), player.getBukkitEntity());\r\n        if (override != null && packet.getContainerId() == 0) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 1);\r\n        }\r\n        super.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        if (NMSHandler.debugPackets) {\r\n            Debug.log(\"Custom packet payload: \" + packet.identifier.toString() + \" sent from \" + player.getScoreboardName());\r\n        }\r\n        if (packet.identifier.getNamespace().equals(\"minecraft\") && packet.identifier.getPath().equals(\"brand\")) {\r\n            FriendlyByteBuf newData = new FriendlyByteBuf(packet.data.copy());\r\n            int i = newData.readVarInt(); // read off the varInt of length to get rid of it\r\n            brand = StandardCharsets.UTF_8.decode(newData.nioBuffer()).toString();\r\n        }\r\n        super.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        if (fakeSignExpected != null && packet.getPos().equals(fakeSignExpected)) {\r\n            fakeSignExpected = null;\r\n            PlayerChangesSignScriptEvent evt = (PlayerChangesSignScriptEvent) PlayerChangesSignScriptEvent.instance.clone();\r\n            evt.material = new MaterialTag(org.bukkit.Material.OAK_WALL_SIGN);\r\n            evt.location = new LocationTag(player.getBukkitEntity().getLocation());\r\n            LocationTag loc = evt.location.clone();\r\n            loc.setY(0);\r\n            evt.event = new SignChangeEvent(loc.getBlock(), player.getBukkitEntity(), packet.getLines());\r\n            evt.fire(evt.event);\r\n        }\r\n        super.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        if (DenizenPacketHandler.forceNoclip.contains(player.getUUID())) {\r\n            player.noPhysics = true;\r\n        }\r\n        super.handleMovePlayer(packet);\r\n    }\r\n\r\n    // For compatibility with other plugins using Reflection weirdly...\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        super.send(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/handlers/FakeBlockHelper.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkPacket;\r\nimport net.minecraft.util.BitStorage;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.LevelChunkSection;\r\nimport org.bukkit.craftbukkit.v1_17_R1.block.data.CraftBlockData;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.ArrayList;\r\nimport java.util.BitSet;\r\nimport java.util.List;\r\nimport java.util.ListIterator;\r\n\r\npublic class FakeBlockHelper {\r\n\r\n    public static Field DATA_MAPCHUNK = ReflectionHelper.getFields(ClientboundLevelChunkPacket.class).get(ReflectionMappingsInfo.ClientboundLevelChunkPacket_buffer, byte[].class);\r\n    public static Field BLOCKENTITIES_MAPCHUNK = ReflectionHelper.getFields(ClientboundLevelChunkPacket.class).get(ReflectionMappingsInfo.ClientboundLevelChunkPacket_blockEntitiesTags, List.class);\r\n\r\n    public static BlockState getNMSState(FakeBlock block) {\r\n        return ((CraftBlockData) block.material.getModernData()).getState();\r\n    }\r\n\r\n    public static boolean anyBlocksInSection(List<FakeBlock> blocks, int y) {\r\n        int minY = y << 4;\r\n        int maxY = (y << 4) + 16;\r\n        for (FakeBlock block : blocks) {\r\n            int blockY = block.location.getBlockY();\r\n            if (blockY >= minY && blockY < maxY && block.material != null) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static int indexInPalette(BlockState data) {\r\n        return LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(data);\r\n    }\r\n\r\n    public static int blockArrayIndex(int x, int y, int z) {\r\n        return y * (16 * 16) + z * 16 + x;\r\n    }\r\n\r\n    public static int getPaletteSubId(int[] palette, int id) {\r\n        for (int i = 0; i < palette.length; i++) {\r\n            if (palette[i] == id) {\r\n                return i;\r\n            }\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    public static Field PAPER_CHUNK_EXTRAPACKETS;\r\n    public static Field PAPER_CHUNK_READY;\r\n    public static boolean tryPaperPatch = true;\r\n\r\n    public static void copyPacketPaperPatch(ClientboundLevelChunkPacket newPacket, ClientboundLevelChunkPacket oldPacket) {\r\n        if (!Denizen.supportsPaper || !tryPaperPatch) {\r\n            return;\r\n        }\r\n        try {\r\n            if (PAPER_CHUNK_EXTRAPACKETS == null) {\r\n                PAPER_CHUNK_EXTRAPACKETS = ReflectionHelper.getFields(ClientboundLevelChunkPacket.class).get(\"extraPackets\");\r\n                PAPER_CHUNK_READY = ReflectionHelper.getFields(ClientboundLevelChunkPacket.class).get(\"ready\");\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            tryPaperPatch = false;\r\n            Debug.echoError(\"Paper packet patch failed:\");\r\n            Debug.echoError(ex);\r\n            return;\r\n        }\r\n        try {\r\n            PAPER_CHUNK_EXTRAPACKETS.set(newPacket, PAPER_CHUNK_EXTRAPACKETS.get(oldPacket));\r\n            PAPER_CHUNK_READY.setBoolean(newPacket, PAPER_CHUNK_READY.getBoolean(oldPacket));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static ClientboundLevelChunkPacket handleMapChunkPacket(ClientboundLevelChunkPacket originalPacket, List<FakeBlock> blocks) {\r\n        try {\r\n            ClientboundLevelChunkPacket packet = new ClientboundLevelChunkPacket(DenizenNetworkManagerImpl.copyPacket(originalPacket));\r\n            copyPacketPaperPatch(packet, originalPacket);\r\n            // TODO: properly update HeightMap?\r\n            BitSet bitmask = packet.getAvailableSections();\r\n            FriendlyByteBuf serial = originalPacket.getReadBuffer();\r\n            FriendlyByteBuf outputSerial = new FriendlyByteBuf(Unpooled.buffer(serial.readableBytes()));\r\n            List<net.minecraft.nbt.CompoundTag> blockEntities = new ArrayList<>(packet.getBlockEntitiesTags());\r\n            BLOCKENTITIES_MAPCHUNK.set(packet, blockEntities);\r\n            ListIterator<CompoundTag> iterator = blockEntities.listIterator();\r\n            while (iterator.hasNext()) {\r\n                net.minecraft.nbt.CompoundTag blockEnt = iterator.next();\r\n                int x = blockEnt.getInt(\"x\");\r\n                int y = blockEnt.getInt(\"y\");\r\n                int z = blockEnt.getInt(\"z\");\r\n                for (FakeBlock block : blocks) {\r\n                    LocationTag loc = block.location;\r\n                    if (loc.getBlockX() == x && loc.getBlockY() == y && loc.getBlockZ() == z && block.material != null) {\r\n                        iterator.remove();\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            for (FakeBlock block : blocks) {\r\n                if (block.material != null) {\r\n                    LocationTag loc = block.location;\r\n                    net.minecraft.nbt.CompoundTag newCompound = new net.minecraft.nbt.CompoundTag();\r\n                    newCompound.putInt(\"x\", loc.getBlockX());\r\n                    newCompound.putInt(\"y\", loc.getBlockY());\r\n                    newCompound.putInt(\"z\", loc.getBlockZ());\r\n                    newCompound.putString(\"id\", block.material.getMaterial().getKey().toString());\r\n                    blockEntities.add(newCompound);\r\n                }\r\n            }\r\n            for (int y = 0; y < 16; y++) {\r\n                if (bitmask.get(y)) {\r\n                    int blockCount = serial.readShort();\r\n                    int width = serial.readUnsignedByte();\r\n                    int paletteLen = serial.readVarInt();\r\n                    int[] palette = new int[paletteLen];\r\n                    for (int p = 0; p < paletteLen; p++) {\r\n                        palette[p] = serial.readVarInt();\r\n                    }\r\n                    int dataLen = serial.readVarInt();\r\n                    long[] blockListHelper = new long[dataLen];\r\n                    for (int i = 0; i < blockListHelper.length; i++) {\r\n                        blockListHelper[i] = serial.readLong();\r\n                    }\r\n                    outputSerial.writeShort(blockCount);\r\n                    if (!anyBlocksInSection(blocks, y)) {\r\n                        outputSerial.writeByte(width);\r\n                        outputSerial.writeVarInt(paletteLen);\r\n                        for (int p = 0; p < paletteLen; p++) {\r\n                            outputSerial.writeVarInt(palette[p]);\r\n                        }\r\n                        outputSerial.writeLongArray(blockListHelper);\r\n                        continue;\r\n                    }\r\n                    char dataBitsF = (char)(64 / width);\r\n                    int expectedLength = (4096 + dataBitsF - 1) / dataBitsF;\r\n                    if (blockListHelper.length != expectedLength) {\r\n                        return originalPacket; // This chunk is too-complex and is using non-standard chunk format. For now, just ignore it.\r\n                        // TODO: Add support for processing very-complex chunks (DataPaletteHash might be responsible for the unique format?)\r\n                    }\r\n                    BitStorage bits = new BitStorage(width, 4096, blockListHelper);\r\n                    int minY = y << 4;\r\n                    int maxY = (y << 4) + 16;\r\n                    for (FakeBlock block : blocks) {\r\n                        if (block.material != null) {\r\n                            int blockY = block.location.getBlockY();\r\n                            if (blockY >= minY && blockY < maxY) {\r\n                                int blockX = block.location.getBlockX();\r\n                                int blockZ = block.location.getBlockZ();\r\n                                blockX -= (blockX >> 4) * 16;\r\n                                blockY -= (blockY >> 4) * 16;\r\n                                blockZ -= (blockZ >> 4) * 16;\r\n                                int blockIndex = blockArrayIndex(blockX, blockY, blockZ);\r\n                                BlockState replacementData = getNMSState(block);\r\n                                int globalPaletteIndex = indexInPalette(replacementData);\r\n                                int subPaletteId = getPaletteSubId(palette, globalPaletteIndex);\r\n                                if (subPaletteId == -1) {\r\n                                    int[] newPalette = new int[paletteLen + 1];\r\n                                    if (paletteLen >= 0) System.arraycopy(palette, 0, newPalette, 0, paletteLen);\r\n                                    newPalette[paletteLen] = globalPaletteIndex;\r\n                                    subPaletteId = paletteLen;\r\n                                    paletteLen++;\r\n                                    palette = newPalette;\r\n                                    int newWidth = Mth.ceillog2(paletteLen);\r\n                                    if (newWidth > width) {\r\n                                        BitStorage newBits = new BitStorage(newWidth, 4096);\r\n                                        for (int i = 0; i < bits.getSize(); i++) {\r\n                                            newBits.getAndSet(i, bits.get(i));\r\n                                        }\r\n                                        bits = newBits;\r\n                                        width = newWidth;\r\n                                    }\r\n                                }\r\n                                bits.getAndSet(blockIndex, subPaletteId);\r\n                            }\r\n                        }\r\n                    }\r\n                    outputSerial.writeByte(width);\r\n                    outputSerial.writeVarInt(paletteLen);\r\n                    for (int p = 0; p < palette.length; p++) {\r\n                        outputSerial.writeVarInt(palette[p]);\r\n                    }\r\n                    outputSerial.writeLongArray(bits.getRaw());\r\n                }\r\n            }\r\n            int[] biomes =  packet.getBiomes();\r\n            if (biomes != null) {\r\n                outputSerial.writeVarIntArray(biomes);\r\n            }\r\n            byte[] outputBytes = outputSerial.array();\r\n            DATA_MAPCHUNK.set(packet, outputBytes);\r\n            return packet;\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/packets/PacketInResourcePackStatusImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInResourcePackStatus;\r\nimport net.minecraft.network.protocol.game.ServerboundResourcePackPacket;\r\n\r\npublic class PacketInResourcePackStatusImpl implements PacketInResourcePackStatus {\r\n\r\n    private ServerboundResourcePackPacket internal;\r\n\r\n    public PacketInResourcePackStatusImpl(ServerboundResourcePackPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public String getStatus() {\r\n        return internal.action.name();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/packets/PacketInSteerVehicleImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInSteerVehicle;\r\nimport net.minecraft.network.protocol.game.ServerboundPlayerInputPacket;\r\n\r\npublic class PacketInSteerVehicleImpl implements PacketInSteerVehicle {\r\n\r\n    private ServerboundPlayerInputPacket internal;\r\n\r\n    public PacketInSteerVehicleImpl(ServerboundPlayerInputPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public float getLeftwardInput() {\r\n        return internal.getXxa();\r\n    }\r\n\r\n    @Override\r\n    public float getForwardInput() {\r\n        return internal.getZza();\r\n    }\r\n\r\n    @Override\r\n    public boolean getJumpInput() {\r\n        return internal.isJumping();\r\n    }\r\n\r\n    @Override\r\n    public boolean getDismountInput() {\r\n        return internal.isShiftKeyDown();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/packets/PacketOutChatImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizen.nms.v1_17.Handler;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.network.chat.ChatType;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.game.ClientboundChatPacket;\r\n\r\nimport java.lang.reflect.Field;\r\n\r\npublic class PacketOutChatImpl extends PacketOutChat {\r\n\r\n    private ClientboundChatPacket internal;\r\n    private String message;\r\n    private String rawJson;\r\n    private boolean bungee;\r\n    private ChatType position;\r\n\r\n    public PacketOutChatImpl(ClientboundChatPacket internal) {\r\n        this.internal = internal;\r\n        try {\r\n            Component baseComponent = (Component) MESSAGE.get(internal);\r\n            if (baseComponent != null) {\r\n                message = FormattedTextHelper.stringify(Handler.componentToSpigot(baseComponent));\r\n                rawJson = Component.Serializer.toJson(baseComponent);\r\n            }\r\n            else {\r\n                if (internal.components != null) {\r\n                    message = FormattedTextHelper.stringify(internal.components);\r\n                    rawJson = ComponentSerializer.toString(internal.components);\r\n                }\r\n                bungee = true;\r\n            }\r\n            position = (ChatType) POSITION.get(internal);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean isSystem() {\r\n        return position == ChatType.SYSTEM;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActionbar() {\r\n        return position == ChatType.GAME_INFO;\r\n    }\r\n\r\n    @Override\r\n    public String getMessage() {\r\n        return message;\r\n    }\r\n\r\n    @Override\r\n    public String getRawJson() {\r\n        return rawJson;\r\n    }\r\n\r\n    public void setRawJson(String rawJson) {\r\n        try {\r\n            if (!bungee) {\r\n                MESSAGE.set(internal, Component.Serializer.fromJson(rawJson));\r\n            }\r\n            else {\r\n                internal.components = ComponentSerializer.parse(rawJson);\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    private static final Field MESSAGE, POSITION;\r\n\r\n    static {\r\n        MESSAGE = ReflectionHelper.getFields(ClientboundChatPacket.class).getFirstOfType(Component.class);\r\n        POSITION = ReflectionHelper.getFields(ClientboundChatPacket.class).getFirstOfType(ChatType.class);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/packets/PacketOutSetSlotImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutSetSlot;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\n\r\nimport java.lang.reflect.Field;\r\n\r\npublic class PacketOutSetSlotImpl implements PacketOutSetSlot {\r\n\r\n    private ClientboundContainerSetSlotPacket internal;\r\n    private org.bukkit.inventory.ItemStack itemStack;\r\n\r\n    public PacketOutSetSlotImpl(ClientboundContainerSetSlotPacket internal) {\r\n        this.internal = internal;\r\n        try {\r\n            ItemStack nms = (ItemStack) ITEM_STACK.get(internal);\r\n            itemStack = CraftItemStack.asBukkitCopy(nms);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public org.bukkit.inventory.ItemStack getItemStack() {\r\n        return itemStack;\r\n    }\r\n\r\n    @Override\r\n    public void setItemStack(org.bukkit.inventory.ItemStack itemStack) {\r\n        try {\r\n            ITEM_STACK.set(internal, CraftItemStack.asNMSCopy(itemStack));\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    private static final Field ITEM_STACK;\r\n\r\n    static {\r\n        ITEM_STACK = ReflectionHelper.getFields(ClientboundContainerSetSlotPacket.class).get(\"c\");\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/packets/PacketOutSpawnEntityImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutSpawnEntity;\r\n\r\npublic class PacketOutSpawnEntityImpl implements PacketOutSpawnEntity {\r\n\r\n    private int entityId;\r\n\r\n    public PacketOutSpawnEntityImpl(int eid) {\r\n        this.entityId = eid;\r\n    }\r\n\r\n    @Override\r\n    public int getEntityId() {\r\n        return entityId;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/packets/PacketOutTradeListImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutTradeList;\r\nimport com.denizenscript.denizen.nms.v1_17.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizen.nms.util.TradeOffer;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.network.protocol.game.ClientboundMerchantOffersPacket;\r\nimport net.minecraft.world.item.trading.MerchantOffer;\r\nimport net.minecraft.world.item.trading.MerchantOffers;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class PacketOutTradeListImpl implements PacketOutTradeList {\r\n\r\n    private ClientboundMerchantOffersPacket internal;\r\n    private int container;\r\n    private List<TradeOffer> tradeOffers;\r\n\r\n    public PacketOutTradeListImpl(ClientboundMerchantOffersPacket internal) {\r\n        this.internal = internal;\r\n        try {\r\n            container = (int) CONTAINER.get(internal);\r\n            MerchantOffers list = (MerchantOffers) RECIPE_LIST.get(internal);\r\n            tradeOffers = new ArrayList<>();\r\n            for (MerchantOffer recipe : list) {\r\n                tradeOffers.add(new TradeOffer(CraftItemStack.asBukkitCopy(recipe.result),\r\n                        CraftItemStack.asBukkitCopy(recipe.baseCostA),\r\n                        CraftItemStack.asBukkitCopy(recipe.costB),\r\n                        recipe.isOutOfStock(), recipe.uses, recipe.maxUses,\r\n                        recipe.rewardExp, recipe.xp, recipe.priceMultiplier));\r\n            }\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public List<TradeOffer> getTradeOffers() {\r\n        return tradeOffers;\r\n    }\r\n\r\n    @Override\r\n    public void setTradeOffers(List<TradeOffer> tradeOffers) {\r\n        MerchantOffers list = new MerchantOffers();\r\n        for (TradeOffer offer : tradeOffers) {\r\n            MerchantOffer recipe = new MerchantOffer(CraftItemStack.asNMSCopy(offer.getFirstCost()),\r\n                    CraftItemStack.asNMSCopy(offer.getSecondCost()),\r\n                    CraftItemStack.asNMSCopy(offer.getProduct()),\r\n                    offer.getCurrentUses(), offer.getMaxUses(), offer.xp, offer.priceMultiplier);\r\n            recipe.rewardExp = offer.rewardExp;\r\n            list.add(recipe);\r\n        }\r\n        try {\r\n            RECIPE_LIST.set(internal, list);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    private static final Field CONTAINER = ReflectionHelper.getFields(ClientboundMerchantOffersPacket.class).get(ReflectionMappingsInfo.ClientboundMerchantOffersPacket_containerId);\r\n    private static final Field RECIPE_LIST = ReflectionHelper.getFields(ClientboundMerchantOffersPacket.class).getFirstOfType(MerchantOffers.class);\r\n}\r\n"
  },
  {
    "path": "v1_17/src/main/java/com/denizenscript/denizen/nms/v1_17/impl/network/packets/PacketOutWindowItemsImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_17.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutWindowItems;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.NonNullList;\r\nimport net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.List;\r\n\r\npublic class PacketOutWindowItemsImpl implements PacketOutWindowItems {\r\n\r\n    private ClientboundContainerSetContentPacket internal;\r\n    private List<org.bukkit.inventory.ItemStack> contents;\r\n\r\n    public PacketOutWindowItemsImpl(ClientboundContainerSetContentPacket internal) {\r\n        this.internal = internal;\r\n        try {\r\n            List<ItemStack> nms = (List<ItemStack>) CONTENTS.get(internal);\r\n            contents = NonNullList.create();\r\n            for (ItemStack itemStack : nms) {\r\n                contents.add(CraftItemStack.asBukkitCopy(itemStack));\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public org.bukkit.inventory.ItemStack[] getContents() {\r\n        return contents.toArray(new org.bukkit.inventory.ItemStack[0]);\r\n    }\r\n\r\n    @Override\r\n    public void setContents(org.bukkit.inventory.ItemStack[] contents) {\r\n        List<ItemStack> nms = NonNullList.create();\r\n        for (org.bukkit.inventory.ItemStack content : contents) {\r\n            nms.add(CraftItemStack.asNMSCopy(content));\r\n        }\r\n        try {\r\n            CONTENTS.set(internal, nms);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    private static final Field CONTENTS = ReflectionHelper.getFields(ClientboundContainerSetContentPacket.class).getFirstOfType(List.class);\r\n}\r\n"
  },
  {
    "path": "v1_18/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.denizenscript</groupId>\n    <artifactId>denizen-v1_18</artifactId>\n    <version>1.3.2-SNAPSHOT</version>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen</artifactId>\n            <version>${project.version}</version>\n            <scope>compile</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>1.18.2-R0.1-SNAPSHOT</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot</artifactId>\n            <version>1.18.2-R0.1-SNAPSHOT</version>\n            <classifier>remapped-mojang</classifier>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>net.md-5</groupId>\n                <artifactId>specialsource-maven-plugin</artifactId>\n                <version>1.2.5</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>remap</goal>\n                        </goals>\n                        <id>remap-obf</id>\n                        <configuration>\n                            <srgIn>org.spigotmc:minecraft-server:1.18.2-R0.1-SNAPSHOT:txt:maps-mojang</srgIn>\n                            <reverse>true</reverse>\n                            <remappedDependencies>org.spigotmc:spigot:1.18.2-R0.1-SNAPSHOT:jar:remapped-mojang</remappedDependencies>\n                            <remappedArtifactAttached>true</remappedArtifactAttached>\n                            <remappedClassifierName>remapped-obf</remappedClassifierName>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>remap</goal>\n                        </goals>\n                        <id>remap-spigot</id>\n                        <configuration>\n                            <inputFile>${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar</inputFile>\n                            <srgIn>org.spigotmc:minecraft-server:1.18.2-R0.1-SNAPSHOT:csrg:maps-spigot</srgIn>\n                            <remappedDependencies>org.spigotmc:spigot:1.18.2-R0.1-SNAPSHOT:jar:remapped-obf</remappedDependencies>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.10.1</version>\n                <configuration>\n                    <source>16</source>\n                    <target>16</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/Handler.java",
    "content": "package com.denizenscript.denizen.nms.v1_18;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_18.helpers.*;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.blocks.BlockLightImpl;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.nbt.ByteArrayTag;\r\nimport net.minecraft.nbt.StringTag;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.chat.MutableComponent;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.Container;\r\nimport net.minecraft.world.Nameable;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.World;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryCustom;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView;\r\nimport org.bukkit.craftbukkit.v1_18_R2.persistence.CraftPersistentDataContainer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftChatMessage;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryView;\r\nimport org.bukkit.persistence.PersistentDataContainer;\r\nimport org.spigotmc.AsyncCatcher;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class Handler extends NMSHandler {\r\n\r\n    public Handler() {\r\n        advancementHelper = new AdvancementHelperImpl();\r\n        animationHelper = new AnimationHelperImpl();\r\n        blockHelper = new BlockHelperImpl();\r\n        chunkHelper = new ChunkHelperImpl();\r\n        customEntityHelper = new CustomEntityHelperImpl();\r\n        entityHelper = new EntityHelperImpl();\r\n        fishingHelper = new FishingHelperImpl();\r\n        itemHelper = new ItemHelperImpl();\r\n        packetHelper = new PacketHelperImpl();\r\n        playerHelper = new PlayerHelperImpl();\r\n        worldHelper = new WorldHelperImpl();\r\n        enchantmentHelper = new EnchantmentHelperImpl();\r\n    }\r\n\r\n    private final ProfileEditor profileEditor = new ProfileEditorImpl();\r\n\r\n    private boolean wasAsyncCatcherEnabled;\r\n\r\n    @Override\r\n    public void disableAsyncCatcher() {\r\n        wasAsyncCatcherEnabled = AsyncCatcher.enabled;\r\n        AsyncCatcher.enabled = false;\r\n    }\r\n\r\n    @Override\r\n    public void undisableAsyncCatcher() {\r\n        AsyncCatcher.enabled = wasAsyncCatcherEnabled;\r\n    }\r\n\r\n    @Override\r\n    public boolean isExactServerVersionMatch() {\r\n        return ((CraftMagicNumbers) CraftMagicNumbers.INSTANCE).getMappingsVersion().equals(\"eaeedbff51b16ead3170906872fda334\");\r\n    }\r\n\r\n    @Override\r\n    public double[] getRecentTps() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().recentTps;\r\n    }\r\n\r\n    @Override\r\n    public Sidebar createSidebar(Player player) {\r\n        return new SidebarImpl(player);\r\n    }\r\n\r\n    @Override\r\n    public BlockLight createBlockLight(Location location, int lightLevel, long ticks) {\r\n        return BlockLightImpl.createLight(location, lightLevel, ticks);\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile fillPlayerProfile(PlayerProfile playerProfile) {\r\n        if (playerProfile == null) {\r\n            return null;\r\n        }\r\n        if (playerProfile.getName() == null && playerProfile.getUniqueId() == null) {\r\n            return playerProfile; // Cannot fill without lookup data\r\n        }\r\n        if (playerProfile.hasTexture() && playerProfile.hasTextureSignature() && playerProfile.getName() != null && playerProfile.getUniqueId() != null) {\r\n            return playerProfile; // Already filled\r\n        }\r\n        try {\r\n            GameProfile profile = null;\r\n            MinecraftServer minecraftServer = ((CraftServer) Bukkit.getServer()).getServer();\r\n            if (playerProfile.getUniqueId() != null) {\r\n                profile = minecraftServer.getProfileCache().get(playerProfile.getUniqueId()).orElse(null);\r\n            }\r\n            if (profile == null && playerProfile.getName() != null) {\r\n                profile = minecraftServer.getProfileCache().get(playerProfile.getName()).orElse(null);\r\n            }\r\n            if (profile == null) {\r\n                profile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n            }\r\n            Property textures = profile.getProperties().containsKey(\"textures\") ? Iterables.getFirst(profile.getProperties().get(\"textures\"), null) : null;\r\n            if (textures == null || !textures.hasSignature() || profile.getName() == null || profile.getId() == null) {\r\n                profile = minecraftServer.getSessionService().fillProfileProperties(profile, true);\r\n                textures = profile.getProperties().containsKey(\"textures\") ? Iterables.getFirst(profile.getProperties().get(\"textures\"), null) : null;\r\n            }\r\n            return new PlayerProfile(profile.getName(), profile.getId(), textures == null ? null : textures.getValue(), textures == null ? null : textures.getSignature());\r\n        }\r\n        catch (Exception e) {\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static MethodHandle PAPER_INVENTORY_TITLE_GETTER;\r\n\r\n    @Override\r\n    public String getTitle(Inventory inventory) {\r\n        Container nms = ((CraftInventory) inventory).getInventory();\r\n        if (inventory instanceof CraftInventoryCustom && Denizen.supportsPaper) {\r\n            try {\r\n                if (PAPER_INVENTORY_TITLE_GETTER == null) {\r\n                    PAPER_INVENTORY_TITLE_GETTER = ReflectionHelper.getMethodHandle(nms.getClass(), \"title\");\r\n                }\r\n                return PaperAPITools.instance.parseComponent(PAPER_INVENTORY_TITLE_GETTER.invoke(nms));\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        if (nms instanceof Nameable) {\r\n            return CraftChatMessage.fromComponent(((Nameable) nms).getDisplayName());\r\n        }\r\n        else if (MINECRAFT_INVENTORY.isInstance(nms)) {\r\n            try {\r\n                return (String) INVENTORY_TITLE.get(nms);\r\n            }\r\n            catch (IllegalAccessException e) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return \"Chest\";\r\n    }\r\n\r\n    public static MethodHandle AbstractContainerMenu_title_SETTER = ReflectionHelper.getFinalSetter(AbstractContainerMenu.class, \"title\");\r\n\r\n    @Override\r\n    public void setInventoryTitle(InventoryView view, String title) {\r\n        AbstractContainerMenu menu = ((CraftInventoryView) view).getHandle();\r\n        try {\r\n            AbstractContainerMenu_title_SETTER.invoke(menu, componentToNMS(FormattedTextHelper.parse(title, ChatColor.DARK_GRAY)));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final Class MINECRAFT_INVENTORY;\r\n    public static final Field INVENTORY_TITLE;\r\n    public static final Field ENTITY_BUKKITYENTITY = ReflectionHelper.getFields(Entity.class).get(\"bukkitEntity\");\r\n\r\n    static {\r\n        Class minecraftInv = null;\r\n        Field title = null;\r\n        try {\r\n            for (Class clzz : CraftInventoryCustom.class.getDeclaredClasses()) {\r\n                if (CoreUtilities.toLowerCase(clzz.getName()).contains(\"minecraftinventory\")) { // MinecraftInventory.\r\n                    minecraftInv = clzz;\r\n                    title = clzz.getDeclaredField(\"title\");\r\n                    title.setAccessible(true);\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            e.printStackTrace();\r\n        }\r\n        MINECRAFT_INVENTORY = minecraftInv;\r\n        INVENTORY_TITLE = title;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Player player) {\r\n        GameProfile gameProfile = ((CraftPlayer) player).getProfile();\r\n        Property property = Iterables.getFirst(gameProfile.getProperties().get(\"textures\"), null);\r\n        return new PlayerProfile(gameProfile.getName(), gameProfile.getId(),\r\n                property != null ? property.getValue() : null,\r\n                property != null ? property.getSignature() : null);\r\n    }\r\n\r\n    @Override\r\n    public ProfileEditor getProfileEditor() {\r\n        return profileEditor;\r\n    }\r\n\r\n    @Override\r\n    public List<BiomeNMS> getBiomes(World world) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        ArrayList<BiomeNMS> output = new ArrayList<>();\r\n        for (Map.Entry<ResourceKey<Biome>, Biome> pair : level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).entrySet()) {\r\n            output.add(new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(pair.getKey().location())));\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeNMS(World world, NamespacedKey key) {\r\n        BiomeNMSImpl impl = new BiomeNMSImpl(((CraftWorld) world).getHandle(), key);\r\n        if (impl.biomeBase == null) {\r\n            return null;\r\n        }\r\n        return impl;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeAt(Block block) {\r\n        // Based on CraftWorld source\r\n        ServerLevel level = ((CraftWorld) block.getWorld()).getHandle();\r\n        Holder<Biome> biome = level.getNoiseBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2);\r\n        ResourceLocation key = level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getKey(biome.value());\r\n        return new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key));\r\n    }\r\n\r\n    @Override\r\n    public ArrayList<String> containerListFlags(PersistentDataContainer container, String prefix) {\r\n        prefix = \"denizen:\" + prefix;\r\n        ArrayList<String> output = new ArrayList<>();\r\n        for (String key : ((CraftPersistentDataContainer) container).getRaw().keySet()) {\r\n            if (key.startsWith(prefix)) {\r\n                output.add(key.substring(prefix.length()));\r\n            }\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public boolean containerHas(PersistentDataContainer container, String key) {\r\n        return ((CraftPersistentDataContainer) container).getRaw().containsKey(key);\r\n    }\r\n\r\n    @Override\r\n    public String containerGetString(PersistentDataContainer container, String key) {\r\n        net.minecraft.nbt.Tag base = ((CraftPersistentDataContainer) container).getRaw().get(key);\r\n        if (base instanceof StringTag) {\r\n            return base.getAsString();\r\n        }\r\n        else if (base instanceof ByteArrayTag) {\r\n            return new String(((ByteArrayTag) base).getAsByteArray(), StandardCharsets.UTF_8);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static BaseComponent[] componentToSpigot(Component nms) {\r\n        if (nms == null) {\r\n            return null;\r\n        }\r\n        String json = Component.Serializer.toJson(nms);\r\n        return ComponentSerializer.parse(json);\r\n    }\r\n\r\n    public static MutableComponent componentToNMS(BaseComponent[] spigot) {\r\n        if (spigot == null) {\r\n            return null;\r\n        }\r\n        String json = ComponentSerializer.toString(spigot);\r\n        return Component.Serializer.fromJson(json);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/ReflectionMappingsInfo.java",
    "content": "package com.denizenscript.denizen.nms.v1_18;\r\n\r\npublic class ReflectionMappingsInfo {\r\n\r\n    // Contents gathered via https://minidigger.github.io/MiniMappingViewer/#/mojang/server/1.18.2\r\n\r\n    // net.minecraft.advancements.AdvancementList\r\n    public static String AdvancementList_roots = \"c\";\r\n    public static String AdvancementList_tasks = \"d\";\r\n\r\n    // net.minecraft.world.level.block.state.BlockBehaviour\r\n    public static String BlockBehaviour_explosionResistance = \"aH\";\r\n\r\n    // net.minecraft.stats.RecipeBook\r\n    public static String RecipeBook_known = \"a\";\r\n\r\n    // net.minecraft.core.MappedRegistry\r\n    public static String MappedRegistry_frozen = \"bL\";\r\n\r\n    // net.minecraft.world.item.crafting.RecipeManager\r\n    public static String RecipeManager_byName = \"d\";\r\n\r\n    // net.minecraft.world.entity.Entity\r\n    public static String Entity_onGround = \"y\";\r\n    public static String Entity_DATA_SHARED_FLAGS_ID = \"Z\";\r\n    public static String Entity_DATA_CUSTOM_NAME = \"aM\";\r\n    public static String Entity_DATA_CUSTOM_NAME_VISIBLE = \"aN\";\r\n\r\n    // net.minecraft.world.entity.LivingEntity\r\n    public static String LivingEntity_attackStrengthTicker = \"aQ\";\r\n    public static String LivingEntity_autoSpinAttackTicks = \"bC\";\r\n    public static String LivingEntity_setLivingEntityFlag = \"c\";\r\n\r\n    // net.minecraft.world.entity.player.Player\r\n    public static String Player_DATA_PLAYER_ABSORPTION_ID = \"d\";\r\n    public static String Player_DATA_PLAYER_MODE_CUSTOMISATION = \"bP\";\r\n\r\n    // net.minecraft.server.level.ServerPlayer\r\n    public static String ServerPlayer_respawnForced = \"cU\";\r\n\r\n    // net.minecraft.world.entity.monster.EnderMan\r\n    public static String EnderMan_DATA_CREEPY = \"bX\";\r\n\r\n    // net.minecraft.world.entity.monster.Zombie\r\n    public static String Zombie_inWaterTime = \"ce\";\r\n\r\n    // net.minecraft.world.item.Item\r\n    public static String Item_maxStackSize = \"d\";\r\n\r\n    // net.minecraft.world.level.Level\r\n    public static String Level_isClientSide = \"x\";\r\n\r\n    // net.minecraft.server.level.ThreadedLevelLightEngine\r\n    public static String ThreadedLevelLightEngine_addTask = \"a\";\r\n    // net.minecraft.server.level.ThreadedLevelLightEngine$TaskType\r\n    public static String ThreadedLevelLightEngine_TaskType_PRE_UPDATE = \"a\";\r\n\r\n    // net.minecraft.world.entity.item.ItemEntity\r\n    public static String ItemEntity_DATA_ITEM = \"c\";\r\n\r\n    // net.minecraft.world.level.biome.Biome\r\n    public static String Biome_climateSettings = \"i\";\r\n\r\n    // net.minecraft.world.level.biome.Biome$ClimateSettings\r\n    public static String Biome_ClimateSettings_temperature = \"c\";\r\n    public static String Biome_ClimateSettings_downfall = \"e\";\r\n    public static String Biome_ClimateSettings_precipitation = \"b\";\r\n\r\n    // net.minecraft.world.level.biome.BiomeSpecialEffects\r\n    public static String BiomeSpecialEffects_foliageColorOverride = \"f\";\r\n\r\n    // net.minecraft.network.Connection\r\n    public static String Connection_receiving = \"k\";\r\n\r\n    // net.minecraft.server.network.ServerGamePacketListenerImpl\r\n    public static String ServerGamePacketListenerImpl_aboveGroundTickCount = \"C\";\r\n    public static String ServerGamePacketListenerImpl_aboveGroundVehicleTickCount = \"E\";\r\n    public static String ServerGamePacketListenerImpl_connection = \"a\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket\r\n    public static String ClientboundPlayerAbilitiesPacket_walkingSpeed = \"j\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket\r\n    public static String ClientboundSetEntityDataPacket_packedItems = \"b\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket\r\n    public static String ClientboundSectionBlocksUpdatePacket_sectionPos = \"b\";\r\n    public static String ClientboundSectionBlocksUpdatePacket_states = \"d\";\r\n    public static String ClientboundSectionBlocksUpdatePacket_positions = \"c\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundMoveEntityPacket\r\n    public static String ClientboundMoveEntityPacket_xa = \"b\";\r\n    public static String ClientboundMoveEntityPacket_ya = \"c\";\r\n    public static String ClientboundMoveEntityPacket_za = \"d\";\r\n    public static String ClientboundMoveEntityPacket_yRot = \"e\";\r\n    public static String ClientboundMoveEntityPacket_xRot = \"f\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket\r\n    public static String ClientboundSetEntityMotionPacket_id = \"a\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket\r\n    public static String ClientboundTeleportEntityPacket_id = \"a\";\r\n    public static String ClientboundTeleportEntityPacket_x = \"b\";\r\n    public static String ClientboundTeleportEntityPacket_y = \"c\";\r\n    public static String ClientboundTeleportEntityPacket_z = \"d\";\r\n    public static String ClientboundTeleportEntityPacket_yRot = \"e\";\r\n    public static String ClientboundTeleportEntityPacket_xRot = \"f\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo\r\n    public static String ClientboundLevelChunkPacketData_BlockEntityInfo_packedXZ = \"a\";\r\n    public static String ClientboundLevelChunkPacketData_BlockEntityInfo_y = \"b\";\r\n\r\n    // net.minecraft.world.entity.projectile.FishingHook\r\n    public static String FishingHook_nibble = \"ar\";\r\n    public static String FishingHook_timeUntilLured = \"as\";\r\n    public static String FishingHook_timeUntilHooked = \"at\";\r\n\r\n    // net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase\r\n    public static String BlockBehaviour_BlockStateBase_getFluidState = \"o\";\r\n\r\n    // net.minecraft.world.level.material.FluidState\r\n    public static String FluidState_isRandomlyTicking = \"f\";\r\n    public static String FluidState_isEmpty = \"c\";\r\n    public static String FluidState_createLegacyBlock = \"g\";\r\n    public static String FluidState_animateTick = \"a\";\r\n\r\n    // net.minecraft.tags.TagNetworkSerialization$NetworkPayload\r\n    public static String TagNetworkSerialization_NetworkPayload_tags = \"a\";\r\n\r\n    // net.minecraft.core.HolderSet$Named\r\n    public static String HolderSet_Named_bind = \"b\";\r\n\r\n    // net.minecraft.core.Holder$Reference\r\n    public static String Holder_Reference_bindTags = \"a\";\r\n\r\n    // net.minecraft.server.level.ServerLevel\r\n    public static String ServerLevel_sleepStatus = \"P\";\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/AdvancementHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.AdvancementHelper;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizen.nms.v1_18.Handler;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.advancements.*;\r\nimport net.minecraft.advancements.critereon.ImpossibleTrigger;\r\nimport net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.PlayerAdvancements;\r\nimport net.minecraft.server.ServerAdvancementManager;\r\nimport net.minecraft.server.dedicated.DedicatedServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.Collections;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.Set;\r\n\r\npublic class AdvancementHelperImpl extends AdvancementHelper {\r\n\r\n    private static final String IMPOSSIBLE_KEY = \"impossible\";\r\n    private static final Map<String, Criterion> IMPOSSIBLE_CRITERIA = Collections.singletonMap(IMPOSSIBLE_KEY, new Criterion(new ImpossibleTrigger.TriggerInstance()));\r\n    private static final String[][] IMPOSSIBLE_REQUIREMENTS = new String[][]{{IMPOSSIBLE_KEY}};\r\n\r\n    public static ServerAdvancementManager getAdvancementDataWorld() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getAdvancements();\r\n    }\r\n\r\n    public static Field FIELD_ADVANCEMENTLIST_LISTENER = ReflectionHelper.getFields(AdvancementList.class).getFirstOfType(AdvancementList.Listener.class);\r\n\r\n    @Override\r\n    public void register(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || advancement.registered) {\r\n            return;\r\n        }\r\n        Advancement nms = asNMSCopy(advancement);\r\n        if (advancement.parent == null) {\r\n            Set<Advancement> roots = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_roots, getAdvancementDataWorld().advancements);\r\n            roots.add(nms);\r\n            AdvancementList.Listener something = ReflectionHelper.getFieldValue(AdvancementList.class, FIELD_ADVANCEMENTLIST_LISTENER.getName(), getAdvancementDataWorld().advancements);\r\n            if (something != null) {\r\n                something.onAddAdvancementRoot(nms);\r\n            }\r\n        }\r\n        else {\r\n            Set<Advancement> branches = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_tasks, getAdvancementDataWorld().advancements);\r\n            branches.add(nms);\r\n            AdvancementList.Listener something = ReflectionHelper.getFieldValue(AdvancementList.class, FIELD_ADVANCEMENTLIST_LISTENER.getName(), getAdvancementDataWorld().advancements);\r\n            if (something != null) {\r\n                something.onAddAdvancementTask(nms);\r\n            }\r\n        }\r\n        getAdvancementDataWorld().advancements.advancements.put(nms.getId(), nms);\r\n        advancement.registered = true;\r\n        if (!advancement.hidden && advancement.parent != null) {\r\n            ((CraftServer) Bukkit.getServer()).getHandle().broadcastAll(new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nms), Collections.emptySet(), Collections.emptyMap()), (net.minecraft.world.entity.player.Player) null);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void unregister(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || !advancement.registered) {\r\n            return;\r\n        }\r\n        Map<ResourceLocation, Advancement> advancements = getAdvancementDataWorld().advancements.advancements;\r\n        ResourceLocation key = asResourceLocation(advancement.key);\r\n        Advancement nms = advancements.get(key);\r\n        if (advancement.parent == null) {\r\n            Set<Advancement> roots = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_roots, getAdvancementDataWorld().advancements);\r\n            roots.remove(nms);\r\n        }\r\n        else {\r\n            Set<Advancement> branches = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_tasks, getAdvancementDataWorld().advancements);\r\n            branches.remove(nms);\r\n        }\r\n        advancements.remove(key);\r\n        advancement.registered = false;\r\n        ((CraftServer) Bukkit.getServer()).getHandle().broadcastAll(new ClientboundUpdateAdvancementsPacket(false,\r\n                Collections.emptySet(), Collections.singleton(key), Collections.emptyMap()), (net.minecraft.world.entity.player.Player) null);\r\n    }\r\n\r\n    @Override\r\n    public void grantPartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) {\r\n        if (advancement.length <= 1) {\r\n            grant(advancement, player);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            Advancement nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            Map<String, Criterion> criteria = new HashMap<>();\r\n            String[][] requirements = new String[advancement.length][];\r\n            for (int i = 0; i < advancement.length; i++) {\r\n                criteria.put(IMPOSSIBLE_KEY + i, new Criterion(new ImpossibleTrigger.TriggerInstance()));\r\n                requirements[i] = new String[] { IMPOSSIBLE_KEY + i };\r\n            }\r\n            progress.update(IMPOSSIBLE_CRITERIA, IMPOSSIBLE_REQUIREMENTS);\r\n            for (int i = 0; i < len; i++) {\r\n                progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria\r\n            }\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nmsAdvancement),\r\n                    Collections.emptySet(),\r\n                    Collections.singletonMap(nmsAdvancement.getId(), progress)));\r\n        }\r\n        else {\r\n            Advancement nmsAdvancement = getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.key));\r\n            for (int i = 0; i < len; i++) {\r\n                ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY + i);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void grant(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.length > 1) {\r\n            grantPartial(advancement, player, advancement.length);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            Advancement nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(IMPOSSIBLE_CRITERIA, IMPOSSIBLE_REQUIREMENTS);\r\n            progress.grantProgress(IMPOSSIBLE_KEY); // complete impossible criteria\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nmsAdvancement),\r\n                    Collections.emptySet(),\r\n                    Collections.singletonMap(nmsAdvancement.getId(), progress)));\r\n        }\r\n        else {\r\n            Advancement nmsAdvancement = getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.key));\r\n            ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void revoke(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.temporary) {\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.emptySet(),\r\n                    Collections.singleton(asResourceLocation(advancement.key)),\r\n                    Collections.emptyMap()));\r\n        }\r\n        else {\r\n            Advancement nmsAdvancement = getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.key));\r\n            ((CraftPlayer) player).getHandle().getAdvancements().revoke(nmsAdvancement, IMPOSSIBLE_KEY);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        nmsPlayer.connection.send(new ClientboundUpdateAdvancementsPacket(true,\r\n                Collections.emptySet(),\r\n                Collections.emptySet(),\r\n                Collections.emptyMap()));\r\n        PlayerAdvancements data = nmsPlayer.getAdvancements();\r\n        data.save(); // save progress\r\n        data.reload(DedicatedServer.getServer().getAdvancements()); // clear progress\r\n        data.flushDirty(nmsPlayer); // load progress and update client\r\n    }\r\n\r\n    private static Advancement asNMSCopy(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        ResourceLocation key = asResourceLocation(advancement.key);\r\n        Advancement parent = advancement.parent != null\r\n                ? getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.parent))\r\n                : null;\r\n        DisplayInfo display = new DisplayInfo(CraftItemStack.asNMSCopy(advancement.icon),\r\n                Handler.componentToNMS(FormattedTextHelper.parse(advancement.title, ChatColor.WHITE)), Handler.componentToNMS(FormattedTextHelper.parse(advancement.description, ChatColor.WHITE)),\r\n                asResourceLocation(advancement.background), FrameType.valueOf(advancement.frame.name()),\r\n                advancement.toast, advancement.announceToChat, advancement.hidden);\r\n        display.setLocation(advancement.xOffset, advancement.yOffset);\r\n        Map<String, Criterion> criteria = IMPOSSIBLE_CRITERIA;\r\n        String[][] requirements = IMPOSSIBLE_REQUIREMENTS;\r\n        if (advancement.length > 1) {\r\n            criteria = new HashMap<>();\r\n            requirements = new String[advancement.length][];\r\n            for (int i = 0; i < advancement.length; i++) {\r\n                criteria.put(IMPOSSIBLE_KEY + i, new Criterion(new ImpossibleTrigger.TriggerInstance()));\r\n                requirements[i] = new String[] { IMPOSSIBLE_KEY + i };\r\n            }\r\n        }\r\n        return new Advancement(key, parent, display, AdvancementRewards.EMPTY, criteria, requirements);\r\n    }\r\n\r\n    private static ResourceLocation asResourceLocation(NamespacedKey key) {\r\n        return key != null ? new ResourceLocation(key.getNamespace(), key.getKey()) : null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/AnimationHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.AnimationHelper;\r\nimport net.minecraft.world.entity.Entity;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftHorse;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftPolarBear;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Horse;\r\nimport org.bukkit.entity.IronGolem;\r\n\r\npublic class AnimationHelperImpl extends AnimationHelper {\r\n\r\n    public AnimationHelperImpl() {\r\n        register(\"POLAR_BEAR_START_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(true);\r\n            }\r\n        });\r\n        register(\"POLAR_BEAR_STOP_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(false);\r\n            }\r\n        });\r\n        register(\"HORSE_START_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().setStanding(true);\r\n            }\r\n        });\r\n        register(\"HORSE_STOP_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().setStanding(false);\r\n            }\r\n        });\r\n        register(\"HORSE_BUCK\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().makeMad();\r\n            }\r\n        });\r\n        register(\"IRON_GOLEM_ATTACK\", entity -> {\r\n            if (entity instanceof IronGolem) {\r\n                Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n                nmsEntity.level.broadcastEntityEvent(nmsEntity, (byte) 4);\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/BlockHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.BlockHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.VanillaTagHelper;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.core.*;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.protocol.game.ClientboundUpdateTagsPacket;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.tags.TagKey;\r\nimport net.minecraft.tags.TagNetworkSerialization;\r\nimport net.minecraft.util.InclusiveRange;\r\nimport net.minecraft.util.random.SimpleWeightedRandomList;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.level.BaseSpawner;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.SpawnData;\r\nimport net.minecraft.world.level.block.BellBlock;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.entity.BlockEntity;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockBehaviour;\r\nimport net.minecraft.world.level.block.state.properties.NoteBlockInstrument;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.material.PushReaction;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.*;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.CraftBlockEntityState;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.CraftSkull;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_18_R2.tag.CraftBlockTag;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.stream.Collectors;\r\n\r\npublic class BlockHelperImpl implements BlockHelper {\r\n\r\n    public static final Field craftBlockEntityState_tileEntity = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"tileEntity\");\r\n    public static final Field craftBlockEntityState_snapshot = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"snapshot\");\r\n    public static final Field craftSkull_profile = ReflectionHelper.getFields(CraftSkull.class).get(\"profile\");\r\n\r\n    @Override\r\n    public void makeBlockStateRaw(BlockState state) {\r\n        try {\r\n            craftBlockEntityState_snapshot.set(state, craftBlockEntityState_tileEntity.get(state));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void applyPhysics(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        ((CraftWorld) location.getWorld()).getHandle().updateNeighborsAt(pos, CraftMagicNumbers.getBlock(location.getBlock().getType()));\r\n    }\r\n\r\n    public static <T extends BlockEntity> T getTE(CraftBlockEntityState<T> cbs) {\r\n        try {\r\n            return (T) craftBlockEntityState_tileEntity.get(cbs);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Skull skull) {\r\n        GameProfile profile = getTE(((CraftSkull) skull)).owner;\r\n        if (profile == null) {\r\n            return null;\r\n        }\r\n        String name = profile.getName();\r\n        UUID id = profile.getId();\r\n        com.mojang.authlib.properties.Property property = Iterables.getFirst(profile.getProperties().get(\"textures\"), null);\r\n        return new PlayerProfile(name, id, property != null ? property.getValue() : null);\r\n    }\r\n\r\n    @Override\r\n    public void setPlayerProfile(Skull skull, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        if (playerProfile.hasTexture()) {\r\n            gameProfile.getProperties().put(\"textures\",\r\n                    new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n        }\r\n        try {\r\n            craftSkull_profile.set(skull, gameProfile);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        skull.update();\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Block block) {\r\n        BlockEntity te = ((CraftWorld) block.getWorld()).getHandle().getBlockEntity(new BlockPos(block.getX(), block.getY(), block.getZ()), true);\r\n        if (te != null) {\r\n            CompoundTag nmsData = te.saveWithFullMetadata();\r\n            return NBTAdapter.toAPI(nmsData);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Block block, CompoundBinaryTag ctag) {\r\n        CompoundTag nmsData = NBTAdapter.toNMS(ctag);\r\n        nmsData.putInt(\"x\", block.getX());\r\n        nmsData.putInt(\"y\", block.getY());\r\n        nmsData.putInt(\"z\", block.getZ());\r\n        BlockPos blockPos = new BlockPos(block.getX(), block.getY(), block.getZ());\r\n        BlockEntity te = ((CraftWorld) block.getWorld()).getHandle().getBlockEntity(blockPos, true);\r\n        te.load(nmsData);\r\n    }\r\n\r\n    @Override\r\n    public boolean setBlockResistance(Material material, float resistance) {\r\n        net.minecraft.world.level.block.Block block = getMaterialBlock(material);\r\n        if (block == null) {\r\n            return false;\r\n        }\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, ReflectionMappingsInfo.BlockBehaviour_explosionResistance, block, resistance);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public float getBlockResistance(Material material) {\r\n        net.minecraft.world.level.block.Block block = getMaterialBlock(material);\r\n        if (block == null) {\r\n            return 0;\r\n        }\r\n        return ReflectionHelper.getFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, ReflectionMappingsInfo.BlockBehaviour_explosionResistance, block);\r\n    }\r\n\r\n    public static final Field BLOCK_MATERIAL = ReflectionHelper.getFields(net.minecraft.world.level.block.state.BlockBehaviour.class).getFirstOfType(net.minecraft.world.level.material.Material.class);\r\n\r\n    public static final MethodHandle MATERIAL_PUSH_REACTION_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.material.Material.class, PushReaction.class);\r\n\r\n    public static final MethodHandle BLOCK_STRENGTH_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase.class, float.class); // destroySpeed\r\n\r\n    public net.minecraft.world.level.block.Block getMaterialBlock(Material bukkitMaterial) {\r\n        if (!bukkitMaterial.isBlock()) {\r\n            return null;\r\n        }\r\n        return ((CraftBlockData) bukkitMaterial.createBlockData()).getState().getBlock();\r\n    }\r\n\r\n    public net.minecraft.world.level.material.Material getInternalMaterial(Material bukkitMaterial) {\r\n        try {\r\n            return (net.minecraft.world.level.material.Material) BLOCK_MATERIAL.get(getMaterialBlock(bukkitMaterial));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public PistonPushReaction getPushReaction(Material mat) {\r\n        return PistonPushReaction.VALUES[getInternalMaterial(mat).getPushReaction().ordinal()];\r\n    }\r\n\r\n    @Override\r\n    public void setPushReaction(Material mat, PistonPushReaction reaction) {\r\n        try {\r\n            MATERIAL_PUSH_REACTION_SETTER.invoke(getInternalMaterial(mat), PushReaction.values()[reaction.ordinal()]);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBlockStrength(Material mat) {\r\n        return getMaterialBlock(mat).defaultBlockState().destroySpeed;\r\n    }\r\n\r\n    @Override\r\n    public void setBlockStrength(Material mat, float strength) {\r\n        try {\r\n            BLOCK_STRENGTH_SETTER.invoke(getMaterialBlock(mat).defaultBlockState(), strength);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    // This is to debork Spigot's class remapper mishandling 'getFluidState' which remaps 'FluidState' to 'material.FluidType' (incorrectly) in the call and thus errors out.\r\n    // TODO: 1.18: This might be fixed by Spigot and can be switched to raw method calls\r\n    // Relevant issue: https://hub.spigotmc.org/jira/browse/SPIGOT-6696\r\n    public static MethodHandle BLOCKSTATEBASE_GETFLUIDSTATE = ReflectionHelper.getMethodHandle(BlockBehaviour.BlockStateBase.class, ReflectionMappingsInfo.BlockBehaviour_BlockStateBase_getFluidState);\r\n    public static MethodHandle FLUIDSTATE_ISRANDOMLYTICKING = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), ReflectionMappingsInfo.FluidState_isRandomlyTicking);\r\n    public static MethodHandle FLUIDSTATE_ISEMPTY = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), ReflectionMappingsInfo.FluidState_isEmpty);\r\n    public static MethodHandle FLUIDSTATE_CREATELEGACYBLOCK = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), ReflectionMappingsInfo.FluidState_createLegacyBlock);\r\n    public static MethodHandle FLUIDSTATE_ANIMATETICK = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), ReflectionMappingsInfo.FluidState_animateTick, Level.class, BlockPos.class, Random.class);\r\n\r\n    @Override\r\n    public void doRandomTick(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        LevelChunk nmsChunk = ((CraftChunk) location.getChunk()).getHandle();\r\n        net.minecraft.world.level.block.state.BlockState nmsBlock = nmsChunk.getBlockState(pos);\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        if (nmsBlock.isRandomlyTicking()) {\r\n            nmsBlock.randomTick(nmsWorld, pos, nmsWorld.random);\r\n        }\r\n        try {\r\n            // FluidState fluid = nmsBlock.getFluidState();\r\n            // if (fluid.isRandomlyTicking()) {\r\n            //     fluid.animateTick(nmsWorld, pos, nmsWorld.random);\r\n            // }\r\n            Object fluid = BLOCKSTATEBASE_GETFLUIDSTATE.invoke(nmsBlock);\r\n            if ((boolean) FLUIDSTATE_ISRANDOMLYTICKING.invoke(fluid)) {\r\n                FLUIDSTATE_ANIMATETICK.invoke(fluid, nmsWorld, pos, nmsWorld.random);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Instrument getInstrumentFor(Material mat) {\r\n        net.minecraft.world.level.block.Block blockType = getMaterialBlock(mat);\r\n        NoteBlockInstrument nmsInstrument = NoteBlockInstrument.byState(blockType.defaultBlockState());\r\n        return Instrument.values()[(nmsInstrument.ordinal())];\r\n    }\r\n\r\n    @Override\r\n    public void ringBell(Bell bell) {\r\n        org.bukkit.block.data.type.Bell bellData = (org.bukkit.block.data.type.Bell) bell.getBlockData();\r\n        Direction face = CraftBlock.blockFaceToNotch(bellData.getFacing());\r\n        Direction dir = Direction.NORTH;\r\n        switch (bellData.getAttachment()) {\r\n            case DOUBLE_WALL:\r\n            case SINGLE_WALL:\r\n                switch (face) {\r\n                    case NORTH:\r\n                    case SOUTH:\r\n                        dir = Direction.EAST;\r\n                        break;\r\n                }\r\n                break;\r\n            case FLOOR:\r\n                dir = face;\r\n                break;\r\n        }\r\n        CraftBlock craftBlock = (CraftBlock) bell.getBlock();\r\n        ((BellBlock) Blocks.BELL).attemptToRing(craftBlock.getCraftWorld().getHandle(), craftBlock.getPosition(), dir);\r\n    }\r\n\r\n    @Override\r\n    public int getExpDrop(Block block, org.bukkit.inventory.ItemStack item) {\r\n        net.minecraft.world.level.block.Block blockType = getMaterialBlock(block.getType());\r\n        if (blockType == null) {\r\n            return 0;\r\n        }\r\n        return blockType.getExpDrop(((CraftBlock) block).getNMS(), ((CraftBlock) block).getCraftWorld().getHandle(), ((CraftBlock) block).getPosition(),\r\n                item == null ? null : CraftItemStack.asNMSCopy(item));\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnerSpawnedType(CreatureSpawner spawner, EntityTag entity) {\r\n        spawner.setSpawnedType(entity.getBukkitEntityType());\r\n        if (entity.getWaitingMechanisms() == null || entity.getWaitingMechanisms().size() == 0) {\r\n            return;\r\n        }\r\n        try {\r\n            // Wrangle a fake entity\r\n            Entity nmsEntity = ((CraftWorld) spawner.getWorld()).createEntity(spawner.getLocation(), entity.getBukkitEntityType().getEntityClass());\r\n            EntityTag entityTag = new EntityTag(nmsEntity.getBukkitEntity());\r\n            entityTag.isFake = true;\r\n            entityTag.isFakeValid = true;\r\n            for (Mechanism mechanism : entity.getWaitingMechanisms()) {\r\n                entityTag.safeAdjustDuplicate(mechanism);\r\n            }\r\n            nmsEntity.unsetRemoved();\r\n            // Store it into the spawner\r\n            CraftCreatureSpawner bukkitSpawner = (CraftCreatureSpawner) spawner;\r\n            SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(bukkitSpawner);\r\n            BaseSpawner nmsSpawner = nmsSnapshot.getSpawner();\r\n            SpawnData toSpawn = nmsSpawner.nextSpawnData;\r\n            CompoundTag tag = toSpawn.getEntityToSpawn();\r\n            nmsEntity.saveWithoutId(tag);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnerCustomRules(CreatureSpawner spawner, int skyMin, int skyMax, int blockMin, int blockMax) {\r\n        try {\r\n            CraftCreatureSpawner bukkitSpawner = (CraftCreatureSpawner) spawner;\r\n            SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(bukkitSpawner);\r\n            BaseSpawner nmsSpawner = nmsSnapshot.getSpawner();\r\n            SpawnData toSpawn = nmsSpawner.nextSpawnData;\r\n            SpawnData.CustomSpawnRules rules = skyMin == -1 ? null : new SpawnData.CustomSpawnRules(new InclusiveRange<>(skyMin, skyMax), new InclusiveRange<>(blockMin, blockMax));\r\n            nmsSpawner.nextSpawnData = new SpawnData(toSpawn.entityToSpawn(), Optional.ofNullable(rules));\r\n            nmsSpawner.spawnPotentials = SimpleWeightedRandomList.empty();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Color getMapColor(Block block) {\r\n        CraftBlock craftBlock = (CraftBlock) block;\r\n        return Color.fromRGB(craftBlock.getNMS().getMapColor(craftBlock.getHandle(), craftBlock.getPosition()).col);\r\n    }\r\n\r\n    public static MethodHandle HolderSet_Named_bind = ReflectionHelper.getMethodHandle(HolderSet.Named.class, ReflectionMappingsInfo.HolderSet_Named_bind, List.class);\r\n    public static MethodHandle Holder_Reference_bindTags = ReflectionHelper.getMethodHandle(Holder.Reference.class, ReflectionMappingsInfo.Holder_Reference_bindTags, Collection.class);\r\n\r\n    @Override\r\n    public void setVanillaTags(Material material, Set<NamespacedKey> tags) {\r\n        Holder<net.minecraft.world.level.block.Block> nmsHolder = getMaterialBlock(material).builtInRegistryHolder();\r\n        nmsHolder.tags().forEach(nmsTag -> {\r\n            HolderSet.Named<net.minecraft.world.level.block.Block> nmsHolderSet = Registry.BLOCK.getTag(nmsTag).orElse(null);\r\n            if (nmsHolderSet == null) {\r\n                return;\r\n            }\r\n            List<Holder<net.minecraft.world.level.block.Block>> nmsHolders = nmsHolderSet.stream().collect(Collectors.toCollection(ArrayList::new));\r\n            nmsHolders.remove(nmsHolder);\r\n            try {\r\n                HolderSet_Named_bind.invoke(nmsHolderSet, nmsHolders);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            VanillaTagHelper.updateMaterialTag(new CraftBlockTag(Registry.BLOCK, nmsTag));\r\n        });\r\n        List<TagKey<net.minecraft.world.level.block.Block>> newNmsTags = new ArrayList<>();\r\n        for (NamespacedKey tag : tags) {\r\n            TagKey<net.minecraft.world.level.block.Block> newNmsTag = TagKey.create(Registry.BLOCK_REGISTRY, CraftNamespacedKey.toMinecraft(tag));\r\n            HolderSet.Named<net.minecraft.world.level.block.Block> nmsHolderSet = Registry.BLOCK.getOrCreateTag(newNmsTag);\r\n            List<Holder<net.minecraft.world.level.block.Block>> nmsHolders = nmsHolderSet.stream().collect(Collectors.toCollection(ArrayList::new));\r\n            nmsHolders.add(nmsHolder);\r\n            try {\r\n                HolderSet_Named_bind.invoke(nmsHolderSet, nmsHolders);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            newNmsTags.add(newNmsTag);\r\n            VanillaTagHelper.addOrUpdateMaterialTag(new CraftBlockTag(Registry.BLOCK, newNmsTag));\r\n        }\r\n        try {\r\n            Holder_Reference_bindTags.invoke(nmsHolder, newNmsTags);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        ClientboundUpdateTagsPacket tagsPacket = new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registryAccess()));\r\n        for (Player player : Bukkit.getOnlinePlayers()) {\r\n            PacketHelperImpl.send(player, tagsPacket);\r\n        }\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/ChunkHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.nms.interfaces.ChunkHelper;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.QuartPos;\r\nimport net.minecraft.server.level.ServerChunkCache;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.LevelHeightAccessor;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.chunk.LevelChunkSection;\r\nimport net.minecraft.world.level.chunk.PalettedContainer;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport org.bukkit.World;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\n\r\npublic class ChunkHelperImpl implements ChunkHelper {\r\n\r\n    public final static Field chunkProviderServerThreadField;\r\n    public final static MethodHandle chunkProviderServerThreadFieldSetter;\r\n    public final static Field worldThreadField;\r\n    public final static MethodHandle worldThreadFieldSetter;\r\n\r\n    static {\r\n        chunkProviderServerThreadField = ReflectionHelper.getFields(ServerChunkCache.class).getFirstOfType(Thread.class);\r\n        chunkProviderServerThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(ServerChunkCache.class, Thread.class);\r\n        worldThreadField = ReflectionHelper.getFields(net.minecraft.world.level.Level.class).getFirstOfType(Thread.class);\r\n        worldThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.Level.class, Thread.class);\r\n    }\r\n\r\n    public Thread resetServerThread;\r\n\r\n    @Override\r\n    public void changeChunkServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread != null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkSource();\r\n        try {\r\n            resetServerThread = (Thread) chunkProviderServerThreadField.get(provider);\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, Thread.currentThread());\r\n            worldThreadFieldSetter.invoke(nmsWorld, Thread.currentThread());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void restoreServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread == null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkSource();\r\n        try {\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, resetServerThread);\r\n            worldThreadFieldSetter.invoke(nmsWorld, resetServerThread);\r\n            resetServerThread = null;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int[] getHeightMap(Chunk chunk) {\r\n        Heightmap map = ((CraftChunk) chunk).getHandle().heightmaps.get(Heightmap.Types.MOTION_BLOCKING);\r\n        int[] outputMap = new int[256];\r\n        for (int x = 0; x < 16; x++) {\r\n            for (int y = 0; y < 16; y++) {\r\n                outputMap[x * 16 + y] = map.getFirstAvailable(x, y);\r\n            }\r\n        }\r\n        return outputMap;\r\n    }\r\n\r\n    @Override\r\n    public void setAllBiomes(Chunk chunk, BiomeNMS biome) {\r\n        Holder<Biome> nmsBiome = ((BiomeNMSImpl) biome).biomeBase;\r\n        LevelChunk nmsChunk = ((CraftChunk) chunk).getHandle();\r\n        ChunkPos chunkcoordintpair = nmsChunk.getPos();\r\n        int i = QuartPos.fromBlock(chunkcoordintpair.getMinBlockX());\r\n        int j = QuartPos.fromBlock(chunkcoordintpair.getMinBlockZ());\r\n        LevelHeightAccessor levelheightaccessor = nmsChunk.getHeightAccessorForGeneration();\r\n        for(int k = levelheightaccessor.getMinSection(); k < levelheightaccessor.getMaxSection(); ++k) {\r\n            LevelChunkSection chunksection = nmsChunk.getSection(nmsChunk.getSectionIndexFromSectionY(k));\r\n            PalettedContainer<Holder<Biome>> datapaletteblock = chunksection.getBiomes();\r\n            datapaletteblock.acquire();\r\n            for(int l = 0; l < 4; ++l) {\r\n                for(int i1 = 0; i1 < 4; ++i1) {\r\n                    for(int j1 = 0; j1 < 4; ++j1) {\r\n                        datapaletteblock.getAndSetUnchecked(l, i1, j1, nmsBiome);\r\n                    }\r\n                }\r\n            }\r\n            datapaletteblock.release();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/CustomEntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.v1_18.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.entities.EntityFakeArrowImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.entities.EntityFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.CustomEntityHelper;\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scoreboard.Scoreboard;\r\nimport org.bukkit.scoreboard.Team;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.UUID;\r\n\r\npublic class CustomEntityHelperImpl implements CustomEntityHelper {\r\n\r\n    @Override\r\n    public FakeArrow spawnFakeArrow(Location location) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityFakeArrowImpl arrow = new EntityFakeArrowImpl(world, location);\r\n        return arrow.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public ItemProjectile spawnItemProjectile(Location location, ItemStack itemStack) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityItemProjectileImpl entity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n        world.getHandle().addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return entity.getBukkitEntity();\r\n    }\r\n\r\n    public FakePlayer spawnFakePlayer(Location location, String name, String skin, String blob, boolean doAdd) throws IllegalArgumentException {\r\n        String fullName = name;\r\n        String prefix = null;\r\n        String suffix = null;\r\n        if (name == null) {\r\n            Debug.echoError(\"FAKE_PLAYER: null name, cannot spawn\");\r\n            return null;\r\n        }\r\n        else if (fullName.length() > 16) {\r\n            prefix = fullName.substring(0, 16);\r\n            if (fullName.length() > 30) {\r\n                int len = 30;\r\n                name = fullName.substring(16, 30);\r\n                if (name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    if (fullName.length() >= 32) {\r\n                        len = 32;\r\n                        name = fullName.substring(16, 32);\r\n                    }\r\n                    else if (fullName.length() == 31) {\r\n                        len = 31;\r\n                        name = fullName.substring(16, 31);\r\n                    }\r\n                }\r\n                else if (name.length() > 46) {\r\n                    throw new IllegalArgumentException(\"You must specify a name with no more than 46 characters for FAKE_PLAYER entities!\");\r\n                }\r\n                else {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                suffix = fullName.substring(len);\r\n            }\r\n            else {\r\n                name = fullName.substring(16);\r\n                if (!name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                if (name.length() > 16) {\r\n                    suffix = name.substring(16);\r\n                    name = name.substring(0, 16);\r\n                }\r\n            }\r\n        }\r\n        if (skin != null && skin.length() > 16) {\r\n            throw new IllegalArgumentException(\"You must specify a name with no more than 16 characters for FAKE_PLAYER entity skins!\");\r\n        }\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        ServerLevel worldServer = world.getHandle();\r\n        PlayerProfile playerProfile = new PlayerProfile(name, null);\r\n        if (blob != null) {\r\n            int sc = blob.indexOf(';');\r\n            if (sc != -1) {\r\n                playerProfile.setTexture(blob.substring(0, sc));\r\n                playerProfile.setTextureSignature(blob.substring(sc + 1));\r\n            }\r\n        }\r\n        else if (skin == null && !name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n            playerProfile = NMSHandler.instance.fillPlayerProfile(playerProfile);\r\n        }\r\n        if (skin != null) {\r\n            PlayerProfile skinProfile = new PlayerProfile(skin, null);\r\n            skinProfile = NMSHandler.instance.fillPlayerProfile(skinProfile);\r\n            playerProfile.setTexture(skinProfile.getTexture());\r\n            playerProfile.setTextureSignature(skinProfile.getTextureSignature());\r\n        }\r\n        UUID uuid = UUID.randomUUID();\r\n        playerProfile.setUniqueId(uuid);\r\n\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        gameProfile.getProperties().put(\"textures\",\r\n                new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n\r\n        final EntityFakePlayerImpl fakePlayer = new EntityFakePlayerImpl(worldServer.getServer(), worldServer, gameProfile, doAdd);\r\n\r\n        fakePlayer.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(),\r\n                location.getYaw(), location.getPitch());\r\n        CraftFakePlayerImpl craftFakePlayer = fakePlayer.getBukkitEntity();\r\n        craftFakePlayer.fullName = fullName;\r\n        if (prefix != null) {\r\n            Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();\r\n            String teamName = \"FAKE_PLAYER_TEAM_\" + fullName;\r\n            String hash = null;\r\n            try {\r\n                hash = CoreUtilities.hash_md5(teamName.getBytes(StandardCharsets.UTF_8)).substring(0, 16);\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(e);\r\n            }\r\n            if (hash != null) {\r\n                Team team = scoreboard.getTeam(hash);\r\n                if (team == null) {\r\n                    team = scoreboard.registerNewTeam(hash);\r\n                    team.setPrefix(prefix);\r\n                    if (suffix != null) {\r\n                        team.setSuffix(suffix);\r\n                    }\r\n                }\r\n                team.addPlayer(craftFakePlayer);\r\n            }\r\n        }\r\n        return craftFakePlayer;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/EnchantmentHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.EnchantmentHelper;\r\nimport com.denizenscript.denizen.nms.v1_18.Handler;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.scripts.containers.core.EnchantmentScriptContainer;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport net.minecraft.core.MappedRegistry;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.world.damagesource.DamageSource;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.entity.EquipmentSlot;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.entity.MobType;\r\nimport net.minecraft.world.item.enchantment.EnchantmentCategory;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.craftbukkit.v1_18_R2.enchantments.CraftEnchantment;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftNamespacedKey;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.Map;\r\n\r\npublic class EnchantmentHelperImpl extends EnchantmentHelper {\r\n\r\n    public static Map<NamespacedKey, Enchantment> ENCHANTMENTS_BY_KEY = ReflectionHelper.getFieldValue(org.bukkit.enchantments.Enchantment.class, \"byKey\", null);\r\n    public static Map<String, org.bukkit.enchantments.Enchantment> ENCHANTMENTS_BY_NAME = ReflectionHelper.getFieldValue(org.bukkit.enchantments.Enchantment.class, \"byName\", null);\r\n    public static Field REGISTRY_FROZEN = ReflectionHelper.getFields(MappedRegistry.class).get(ReflectionMappingsInfo.MappedRegistry_frozen, boolean.class);\r\n\r\n    @Override\r\n    public org.bukkit.enchantments.Enchantment registerFakeEnchantment(EnchantmentScriptContainer.EnchantmentReference script) {\r\n        try {\r\n            EquipmentSlot[] slots = new EquipmentSlot[script.script.slots.size()];\r\n            for (int i = 0; i < slots.length; i++) {\r\n                slots[i] = EquipmentSlot.valueOf(script.script.slots.get(i).toUpperCase());\r\n            }\r\n            net.minecraft.world.item.enchantment.Enchantment nmsEnchant = new net.minecraft.world.item.enchantment.Enchantment(net.minecraft.world.item.enchantment.Enchantment.Rarity.valueOf(script.script.rarity), EnchantmentCategory.valueOf(script.script.category), slots) {\r\n                @Override\r\n                public int getMinLevel() {\r\n                    return script.script.minLevel;\r\n                }\r\n                @Override\r\n                public int getMaxLevel() {\r\n                    return script.script.maxLevel;\r\n                }\r\n                @Override\r\n                public int getMinCost(int level) {\r\n                    return script.script.getMinCost(level);\r\n                }\r\n                @Override\r\n                public int getMaxCost(int level) {\r\n                    return script.script.getMaxCost(level);\r\n                }\r\n                @Override\r\n                public int getDamageProtection(int level, DamageSource src) {\r\n                    return script.script.getDamageProtection(level, src.msgId, src.getEntity() == null ? null : src.getEntity().getBukkitEntity());\r\n                }\r\n                @Override\r\n                public float getDamageBonus(int level, MobType type) {\r\n                    String typeName = \"UNDEFINED\";\r\n                    if (type == MobType.ARTHROPOD) {\r\n                        typeName = \"ARTHROPOD\";\r\n                    }\r\n                    else if (type == MobType.ILLAGER) {\r\n                        typeName = \"ILLAGER\";\r\n                    }\r\n                    else if (type == MobType.UNDEAD) {\r\n                        typeName = \"UNDEAD\";\r\n                    }\r\n                    else if (type == MobType.WATER) {\r\n                        typeName = \"WATER\";\r\n                    }\r\n                    return script.script.getDamageBonus(level, typeName);\r\n                }\r\n                @Override\r\n                protected boolean checkCompatibility(net.minecraft.world.item.enchantment.Enchantment nmsEnchantment) {\r\n                    ResourceLocation nmsKey = Registry.ENCHANTMENT.getKey(nmsEnchantment);\r\n                    NamespacedKey bukkitKey = CraftNamespacedKey.fromMinecraft(nmsKey);\r\n                    org.bukkit.enchantments.Enchantment bukkitEnchant = CraftEnchantment.getByKey(bukkitKey);\r\n                    return script.script.isCompatible(bukkitEnchant);\r\n                }\r\n                @Override\r\n                protected String getOrCreateDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public String getDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public Component getFullname(int level) {\r\n                    return Handler.componentToNMS(script.script.getFullName(level));\r\n                }\r\n                @Override\r\n                public boolean canEnchant(net.minecraft.world.item.ItemStack var0) {\r\n                    return super.canEnchant(var0) && script.script.canEnchant(CraftItemStack.asBukkitCopy(var0));\r\n                }\r\n                @Override\r\n                public void doPostAttack(LivingEntity attacker, Entity victim, int level) {\r\n                    script.script.doPostAttack(attacker.getBukkitEntity(), victim.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public void doPostHurt(LivingEntity victim, Entity attacker, int level) {\r\n                    script.script.doPostHurt(victim.getBukkitEntity(), attacker.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public boolean isTreasureOnly() {\r\n                    return script.script.isTreasureOnly;\r\n                }\r\n                @Override\r\n                public boolean isCurse() {\r\n                    return script.script.isCurse;\r\n                }\r\n                @Override\r\n                public boolean isTradeable() {\r\n                    return script.script.isTradable;\r\n                }\r\n                @Override\r\n                public boolean isDiscoverable() {\r\n                    return script.script.isDiscoverable;\r\n                }\r\n            };\r\n            String enchName = script.script.id.toUpperCase();\r\n            boolean wasFrozen = REGISTRY_FROZEN.getBoolean(Registry.ENCHANTMENT);\r\n            REGISTRY_FROZEN.setBoolean(Registry.ENCHANTMENT, false);\r\n            Registry.register(Registry.ENCHANTMENT, \"denizen:\" + script.script.id, nmsEnchant);\r\n            CraftEnchantment ench = new CraftEnchantment(nmsEnchant) {\r\n                @Override\r\n                public String getName() {\r\n                    return enchName;\r\n                }\r\n            };\r\n            if (wasFrozen) {\r\n                ((MappedRegistry) Registry.ENCHANTMENT).freeze();\r\n            }\r\n            ENCHANTMENTS_BY_KEY.put(ench.getKey(), ench);\r\n            ENCHANTMENTS_BY_NAME.put(enchName, ench);\r\n            return ench;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(\"Failed to register enchantment \" + script.script.id);\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getRarity(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getRarity().name();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDiscoverable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isDiscoverable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isTradable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isTradeable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isCurse(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isCurse();\r\n    }\r\n\r\n    @Override\r\n    public int getMinCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMinCost(level);\r\n    }\r\n\r\n    @Override\r\n    public int getMaxCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMaxCost(level);\r\n    }\r\n\r\n    @Override\r\n    public String getFullName(Enchantment enchantment, int level) {\r\n        return FormattedTextHelper.stringify(Handler.componentToSpigot(((CraftEnchantment) enchantment).getHandle().getFullname(level)));\r\n    }\r\n\r\n    @Override\r\n    public float getDamageBonus(Enchantment enchantment, int level, String type) {\r\n        MobType mobType = MobType.UNDEFINED;\r\n        switch (type) {\r\n            case \"illager\":\r\n                mobType = MobType.ILLAGER;\r\n                break;\r\n            case \"undead\":\r\n                mobType = MobType.UNDEAD;\r\n                break;\r\n            case \"water\":\r\n                mobType = MobType.WATER;\r\n                break;\r\n            case \"arthropod\":\r\n                mobType = MobType.ARTHROPOD;\r\n                break;\r\n        }\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageBonus(level, mobType);\r\n    }\r\n\r\n    @Override\r\n    public int getDamageProtection(Enchantment enchantment, int level, EntityDamageEvent.DamageCause type, org.bukkit.entity.Entity attacker) {\r\n        DamageSource src = EntityHelperImpl.getSourceFor(attacker == null ? null : ((CraftEntity) attacker).getHandle(), type);\r\n        if (src instanceof EntityHelperImpl.FakeDamageSrc) {\r\n            src = ((EntityHelperImpl.FakeDamageSrc) src).real;\r\n        }\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageProtection(level, src);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/EntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.entity.EntityEntersVehicleScriptEvent;\r\nimport com.denizenscript.denizen.events.entity.EntityExitsVehicleScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.commands.arguments.EntityAnchorArgument;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerPlayerConnection;\r\nimport net.minecraft.world.InteractionHand;\r\nimport net.minecraft.world.damagesource.CombatRules;\r\nimport net.minecraft.world.damagesource.DamageSource;\r\nimport net.minecraft.world.entity.Mob;\r\nimport net.minecraft.world.entity.MobType;\r\nimport net.minecraft.world.entity.MoverType;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.goal.Goal;\r\nimport net.minecraft.world.entity.ai.navigation.PathNavigation;\r\nimport net.minecraft.world.entity.item.FallingBlockEntity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.level.ClipContext;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.pathfinder.Path;\r\nimport net.minecraft.world.phys.AABB;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport net.minecraft.world.phys.HitResult;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Art;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.attribute.Attribute;\r\nimport org.bukkit.attribute.AttributeInstance;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.*;\r\nimport org.bukkit.craftbukkit.v1_18_R2.event.CraftEventFactory;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\nimport org.bukkit.util.BoundingBox;\r\nimport org.bukkit.util.Vector;\r\nimport org.spigotmc.event.entity.EntityDismountEvent;\r\nimport org.spigotmc.event.entity.EntityMountEvent;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class EntityHelperImpl extends EntityHelper {\r\n\r\n    public static final MethodHandle ENTITY_ONGROUND_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_onGround, boolean.class);\r\n\r\n    public static final EntityDataAccessor<Boolean> ENTITY_ENDERMAN_DATAWATCHER_SCREAMING = ReflectionHelper.getFieldValue(EnderMan.class, ReflectionMappingsInfo.EnderMan_DATA_CREEPY, null);\r\n\r\n    @Override\r\n    public int getBlockHeight(Art art) {\r\n        return art.getBlockHeight();\r\n    }\r\n\r\n    @Override\r\n    public int getBlockWidth(Art art) {\r\n        return art.getBlockWidth();\r\n    }\r\n\r\n    @Override\r\n    public void setInvisible(Entity entity, boolean invisible) {\r\n        ((CraftEntity) entity).getHandle().setInvisible(invisible);\r\n    }\r\n\r\n    @Override\r\n    public boolean isInvisible(Entity entity) {\r\n        return ((CraftEntity) entity).getHandle().isInvisible();\r\n    }\r\n\r\n    @Override\r\n    public void setPose(Entity entity, Pose pose) {\r\n        ((CraftEntity) entity).getHandle().setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]);\r\n    }\r\n\r\n    @Override\r\n    public double getDamageTo(LivingEntity attacker, Entity target) {\r\n        MobType monsterType;\r\n        if (target instanceof LivingEntity) {\r\n            monsterType = ((CraftLivingEntity) target).getHandle().getMobType();\r\n        }\r\n        else {\r\n            monsterType = MobType.UNDEFINED;\r\n        }\r\n        double damage = 0;\r\n        AttributeInstance attrib = attacker.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE);\r\n        if (attrib != null) {\r\n            damage = attrib.getValue();\r\n        }\r\n        if (attacker.getEquipment() != null && attacker.getEquipment().getItemInMainHand() != null) {\r\n            damage += EnchantmentHelper.getDamageBonus(CraftItemStack.asNMSCopy(attacker.getEquipment().getItemInMainHand()), monsterType);\r\n        }\r\n        if (damage <= 0) {\r\n            return 0;\r\n        }\r\n        if (target != null) {\r\n            DamageSource source;\r\n            if (attacker instanceof Player) {\r\n                source = DamageSource.playerAttack(((CraftPlayer) attacker).getHandle());\r\n            }\r\n            else {\r\n                source = DamageSource.mobAttack(((CraftLivingEntity) attacker).getHandle());\r\n            }\r\n            net.minecraft.world.entity.Entity nmsTarget = ((CraftEntity) target).getHandle();\r\n            if (nmsTarget.isInvulnerableTo(source)) {\r\n                return 0;\r\n            }\r\n            if (!(nmsTarget instanceof net.minecraft.world.entity.LivingEntity)) {\r\n                return damage;\r\n            }\r\n            net.minecraft.world.entity.LivingEntity livingTarget = (net.minecraft.world.entity.LivingEntity) nmsTarget;\r\n            damage = CombatRules.getDamageAfterAbsorb((float) damage, (float) livingTarget.getArmorValue(), (float) livingTarget.getAttributeValue(Attributes.ARMOR_TOUGHNESS));\r\n            int enchantDamageModifier = EnchantmentHelper.getDamageProtection(livingTarget.getArmorSlots(), source);\r\n            if (enchantDamageModifier > 0) {\r\n                damage = CombatRules.getDamageAfterMagicAbsorb((float) damage, (float) enchantDamageModifier);\r\n            }\r\n        }\r\n        return damage;\r\n    }\r\n\r\n    public static final MethodHandle LIVINGENTITY_AUTOSPINATTACK_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.LivingEntity.class, ReflectionMappingsInfo.LivingEntity_autoSpinAttackTicks);\r\n    public static final MethodHandle LIVINGENTITY_SETLIVINGENTITYFLAG = ReflectionHelper.getMethodHandle(net.minecraft.world.entity.LivingEntity.class, ReflectionMappingsInfo.LivingEntity_setLivingEntityFlag, int.class, boolean.class);\r\n\r\n    @Override\r\n    public void setRiptide(Entity entity, boolean state) {\r\n        try {\r\n            net.minecraft.world.entity.LivingEntity nmsEntity = ((CraftLivingEntity) entity).getHandle();\r\n            LIVINGENTITY_AUTOSPINATTACK_SETTER.invoke(nmsEntity, state ? 0 : 1);\r\n            LIVINGENTITY_SETLIVINGENTITYFLAG.invoke(nmsEntity, 4, true);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void forceInteraction(Player player, Location location) {\r\n        CraftPlayer craftPlayer = (CraftPlayer) player;\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        ((CraftBlock) location.getBlock()).getNMS().use(((CraftWorld) location.getWorld()).getHandle(),\r\n                craftPlayer != null ? craftPlayer.getHandle() : null, InteractionHand.MAIN_HAND,\r\n                new BlockHitResult(new Vec3(0, 0, 0), null, pos, false));\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Entity entity) {\r\n        CompoundTag compound = new CompoundTag();\r\n        ((CraftEntity) entity).getHandle().saveAsPassenger(compound);\r\n        return NBTAdapter.toAPI(compound);\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Entity entity, CompoundBinaryTag compoundTag) {\r\n        ((CraftEntity) entity).getHandle().load(NBTAdapter.toNMS(compoundTag));\r\n    }\r\n\r\n    /*\r\n        Entity Movement\r\n     */\r\n\r\n    private final static Map<UUID, BukkitTask> followTasks = new HashMap<>();\r\n\r\n    @Override\r\n    public void stopFollowing(Entity follower) {\r\n        if (follower == null) {\r\n            return;\r\n        }\r\n        UUID uuid = follower.getUniqueId();\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void stopWalking(Entity entity) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        if (!(nmsEntity instanceof Mob)) {\r\n            return;\r\n        }\r\n        ((Mob) nmsEntity).getNavigation().stop();\r\n    }\r\n\r\n    @Override\r\n    public void follow(final Entity target, final Entity follower, final double speed, final double lead,\r\n                       final double maxRange, final boolean allowWander, final boolean teleport) {\r\n        if (target == null || follower == null) {\r\n            return;\r\n        }\r\n\r\n        final net.minecraft.world.entity.Entity nmsEntityFollower = ((CraftEntity) follower).getHandle();\r\n        if (!(nmsEntityFollower instanceof Mob)) {\r\n            return;\r\n        }\r\n        final Mob nmsFollower = (Mob) nmsEntityFollower;\r\n        final PathNavigation followerNavigation = nmsFollower.getNavigation();\r\n\r\n        UUID uuid = follower.getUniqueId();\r\n\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n\r\n        final int locationNearInt = (int) Math.floor(lead);\r\n        final boolean hasMax = maxRange > lead;\r\n\r\n        followTasks.put(follower.getUniqueId(), new BukkitRunnable() {\r\n\r\n            private boolean inRadius = false;\r\n\r\n            public void run() {\r\n                if (!target.isValid() || !follower.isValid()) {\r\n                    this.cancel();\r\n                }\r\n                followerNavigation.setSpeedModifier(2D);\r\n                Location targetLocation = target.getLocation();\r\n                Path path;\r\n\r\n                if (hasMax && !Utilities.checkLocation(targetLocation, follower.getLocation(), maxRange)\r\n                        && !target.isDead() && target.isOnGround()) {\r\n                    if (!inRadius) {\r\n                        if (teleport) {\r\n                            follower.teleport(Utilities.getWalkableLocationNear(targetLocation, locationNearInt));\r\n                        }\r\n                        else {\r\n                            cancel();\r\n                        }\r\n                    }\r\n                    else {\r\n                        inRadius = false;\r\n                        path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                        if (path != null) {\r\n                            followerNavigation.moveTo(path, 1D);\r\n                            followerNavigation.setSpeedModifier(2D);\r\n                        }\r\n                    }\r\n                }\r\n                else if (!inRadius && !Utilities.checkLocation(targetLocation, follower.getLocation(), lead)) {\r\n                    path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                    if (path != null) {\r\n                        followerNavigation.moveTo(path, 1D);\r\n                        followerNavigation.setSpeedModifier(2D);\r\n                    }\r\n                }\r\n                else {\r\n                    inRadius = true;\r\n                }\r\n                if (inRadius && !allowWander) {\r\n                    followerNavigation.stop();\r\n                }\r\n                nmsFollower.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n        }.runTaskTimer(NMSHandler.getJavaPlugin(), 0, 10));\r\n    }\r\n\r\n    @Override\r\n    public void walkTo(final LivingEntity entity, Location location, Double speed, final Runnable callback) {\r\n        if (entity == null || location == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        if (!(nmsEntity instanceof final Mob nmsMob)) {\r\n            return;\r\n        }\r\n        final PathNavigation entityNavigation = nmsMob.getNavigation();\r\n        final Path path;\r\n        final boolean aiDisabled = !entity.hasAI();\r\n        if (aiDisabled) {\r\n            entity.setAI(true);\r\n            try {\r\n                ENTITY_ONGROUND_SETTER.invoke(nmsMob, true);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        path = entityNavigation.createPath(location.getX(), location.getY(), location.getZ(), 1);\r\n        if (path != null) {\r\n            nmsMob.goalSelector.enableControlFlag(Goal.Flag.MOVE);\r\n            entityNavigation.moveTo(path, 1D);\r\n            final double oldSpeed = nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).getBaseValue();\r\n            if (speed != null) {\r\n                nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    if (!entity.isValid()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        cancel();\r\n                        return;\r\n                    }\r\n                    if (aiDisabled && entity instanceof Wolf wolf) {\r\n                        wolf.setAngry(false);\r\n                    }\r\n                    if (entityNavigation.isDone() || path.isDone()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        if (speed != null) {\r\n                            nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(oldSpeed);\r\n                        }\r\n                        if (aiDisabled) {\r\n                            entity.setAI(false);\r\n                        }\r\n                        cancel();\r\n                    }\r\n                }\r\n            }.runTaskTimer(NMSHandler.getJavaPlugin(), 1, 1);\r\n        }\r\n        //if (!Utilities.checkLocation(location, entity.getLocation(), 20)) {\r\n        // TODO: generate waypoints to the target location?\r\n        else {\r\n            entity.teleport(location);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public List<Player> getPlayersThatSee(Entity entity) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        ArrayList<Player> output = new ArrayList<>();\r\n        if (entityTracker == null) {\r\n            return output;\r\n        }\r\n        for (ServerPlayerConnection player : entityTracker.seenBy) {\r\n            output.add(player.getPlayer().getBukkitEntity());\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public void sendAllUpdatePackets(Entity entity) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        ArrayList<Player> output = new ArrayList<>();\r\n        if (entityTracker == null) {\r\n            return;\r\n        }\r\n        try {\r\n            ServerEntity serverEntity = (ServerEntity) PacketHelperImpl.ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n            serverEntity.sendChanges();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    /*\r\n        Hide Entity\r\n     */\r\n\r\n    @Override\r\n    public void sendHidePacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player) {\r\n            pl.hidePlayer(Denizen.getInstance(), (Player) entity);\r\n            return;\r\n        }\r\n        CraftPlayer craftPlayer = (CraftPlayer) pl;\r\n        ServerPlayer entityPlayer = craftPlayer.getHandle();\r\n        if (entityPlayer.connection != null && !craftPlayer.equals(entity)) {\r\n            ChunkMap tracker = ((ServerLevel) craftPlayer.getHandle().level).getChunkSource().chunkMap;\r\n            net.minecraft.world.entity.Entity other = ((CraftEntity) entity).getHandle();\r\n            ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId());\r\n            if (entry != null) {\r\n                entry.removePlayer(entityPlayer);\r\n            }\r\n            if (Denizen.supportsPaper) { // Workaround for Paper issue\r\n                entityPlayer.connection.send(new ClientboundRemoveEntitiesPacket(other.getId()));\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendShowPacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player) {\r\n            pl.showPlayer(Denizen.getInstance(), (Player) entity);\r\n            return;\r\n        }\r\n        CraftPlayer craftPlayer = (CraftPlayer) pl;\r\n        ServerPlayer entityPlayer = craftPlayer.getHandle();\r\n        if (entityPlayer.connection != null && !craftPlayer.equals(entity)) {\r\n            ChunkMap tracker = ((ServerLevel) craftPlayer.getHandle().level).getChunkSource().chunkMap;\r\n            net.minecraft.world.entity.Entity other = ((CraftEntity) entity).getHandle();\r\n            ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId());\r\n            if (entry != null) {\r\n                entry.removePlayer(entityPlayer);\r\n                entry.updatePlayer(entityPlayer);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void rotate(Entity entity, float yaw, float pitch) {\r\n        // If this entity is a real player instead of a player type NPC,\r\n        // it will appear to be online\r\n        if (entity instanceof Player && ((Player) entity).isOnline()) {\r\n            Location location = entity.getLocation();\r\n            location.setYaw(yaw);\r\n            location.setPitch(pitch);\r\n            teleport(entity, location);\r\n        }\r\n        else if (entity instanceof LivingEntity) {\r\n            if (entity instanceof EnderDragon) {\r\n                yaw = normalizeYaw(yaw - 180);\r\n            }\r\n            look(entity, yaw, pitch);\r\n        }\r\n        else {\r\n            net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n            handle.setYRot(yaw - 360);\r\n            handle.setXRot(pitch);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBaseYaw(LivingEntity entity) {\r\n        return ((CraftLivingEntity) entity).getHandle().yBodyRot;\r\n    }\r\n\r\n    @Override\r\n    public void look(Entity entity, float yaw, float pitch) {\r\n        net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n        if (handle != null) {\r\n            handle.setYRot(yaw);\r\n            if (handle instanceof net.minecraft.world.entity.LivingEntity) {\r\n                net.minecraft.world.entity.LivingEntity livingHandle = (net.minecraft.world.entity.LivingEntity) handle;\r\n                while (yaw < -180.0F) {\r\n                    yaw += 360.0F;\r\n                }\r\n                while (yaw >= 180.0F) {\r\n                    yaw -= 360.0F;\r\n                }\r\n                livingHandle.yBodyRotO = yaw;\r\n                if (!(handle instanceof net.minecraft.world.entity.player.Player)) {\r\n                    livingHandle.setYBodyRot(yaw);\r\n                }\r\n                livingHandle.setYHeadRot(yaw);\r\n            }\r\n            handle.setXRot(pitch);\r\n        }\r\n        else {\r\n            Debug.echoError(\"Cannot set look direction for unspawned entity \" + entity.getUniqueId());\r\n        }\r\n    }\r\n\r\n    private static HitResult rayTrace(World world, Vector start, Vector end) {\r\n        try {\r\n            NMSHandler.chunkHelper.changeChunkServerThread(world);\r\n            return ((CraftWorld) world).getHandle().clip(new ClipContext(new Vec3(start.getX(), start.getY(), start.getZ()),\r\n                    new Vec3(end.getX(), end.getY(), end.getZ()),\r\n                    ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, null));\r\n        }\r\n        finally {\r\n            NMSHandler.chunkHelper.restoreServerThread(world);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean canTrace(World world, Vector start, Vector end) {\r\n        HitResult pos = rayTrace(world, start, end);\r\n        if (pos == null) {\r\n            return true;\r\n        }\r\n        return pos.getType() == HitResult.Type.MISS;\r\n    }\r\n\r\n    @Override\r\n    public void snapPositionTo(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().setPosRaw(vector.getX(), vector.getY(), vector.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void move(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().move(MoverType.SELF, new Vec3(vector.getX(), vector.getY(), vector.getZ()));\r\n    }\r\n\r\n    @Override\r\n    public boolean internalLook(Player player, Location at) {\r\n        ClientboundPlayerLookAtPacket packet = new ClientboundPlayerLookAtPacket(EntityAnchorArgument.Anchor.EYES, at.getX(), at.getY(), at.getZ());\r\n        PacketHelperImpl.send(player, packet);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public void fakeMove(Entity entity, Vector vector) {\r\n        long x = ClientboundMoveEntityPacket.entityToPacket(vector.getX());\r\n        long y = ClientboundMoveEntityPacket.entityToPacket(vector.getY());\r\n        long z = ClientboundMoveEntityPacket.entityToPacket(vector.getZ());\r\n        ClientboundMoveEntityPacket packet = new ClientboundMoveEntityPacket.Pos(entity.getEntityId(), (short) x, (short) y, (short) z, entity.isOnGround());\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void fakeTeleport(Entity entity, Location location) {\r\n        FriendlyByteBuf packetData = new FriendlyByteBuf(Unpooled.buffer());\r\n        // Referenced from ClientboundTeleportEntityPacket source\r\n        packetData.writeVarInt(entity.getEntityId());\r\n        packetData.writeDouble(location.getX());\r\n        packetData.writeDouble(location.getY());\r\n        packetData.writeDouble(location.getZ());\r\n        packetData.writeByte((byte)((int)(location.getYaw() * 256.0F / 360.0F)));\r\n        packetData.writeByte((byte)((int)(location.getPitch() * 256.0F / 360.0F)));\r\n        packetData.writeBoolean(entity.isOnGround());\r\n        ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(packetData);\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void clientResetLoc(Entity entity) {\r\n        ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(((CraftEntity) entity).getHandle());\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Entity entity, Location loc) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        nmsEntity.setYRot(loc.getYaw());\r\n        nmsEntity.setXRot(loc.getPitch());\r\n        if (nmsEntity instanceof ServerPlayer) {\r\n            nmsEntity.teleportTo(loc.getX(), loc.getY(), loc.getZ());\r\n        }\r\n        nmsEntity.setPos(loc.getX(), loc.getY(), loc.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void setBoundingBox(Entity entity, BoundingBox box) {\r\n        ((CraftEntity) entity).getHandle().setBoundingBox(new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()));\r\n    }\r\n\r\n    @Override\r\n    public void setTicksLived(Entity entity, int ticks) {\r\n        // Bypass Spigot's must-be-at-least-1-tick requirement, as negative tick counts are useful\r\n        ((CraftEntity) entity).getHandle().tickCount = ticks;\r\n        if (entity instanceof CraftFallingBlock) {\r\n            ((CraftFallingBlock) entity).getHandle().time = ticks;\r\n        }\r\n        else if (entity instanceof CraftItem) {\r\n            ((ItemEntity) ((CraftItem) entity).getHandle()).age = ticks;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHeadAngle(LivingEntity entity, float angle) {\r\n        net.minecraft.world.entity.LivingEntity handle = ((CraftLivingEntity) entity).getHandle();\r\n        handle.yHeadRot = angle;\r\n        handle.setYHeadRot(angle);\r\n    }\r\n\r\n    @Override\r\n    public void setGhastAttacking(Ghast ghast, boolean attacking) {\r\n        ((CraftGhast) ghast).getHandle().setCharging(attacking);\r\n    }\r\n\r\n    @Override\r\n    public void setEndermanAngry(Enderman enderman, boolean angry) {\r\n        ((CraftEnderman) enderman).getHandle().getEntityData().set(ENTITY_ENDERMAN_DATAWATCHER_SCREAMING, angry);\r\n    }\r\n\r\n    public static class FakeDamageSrc extends DamageSource { public DamageSource real; public FakeDamageSrc(DamageSource src) { super(\"fake\"); real = src; } }\r\n\r\n    public static DamageSource getSourceFor(net.minecraft.world.entity.Entity nmsSource, EntityDamageEvent.DamageCause cause) {\r\n        DamageSource src = DamageSource.GENERIC;\r\n        if (nmsSource != null) {\r\n            if (nmsSource instanceof net.minecraft.world.entity.player.Player) {\r\n                src = DamageSource.playerAttack((net.minecraft.world.entity.player.Player) nmsSource);\r\n            }\r\n            else if (nmsSource instanceof net.minecraft.world.entity.LivingEntity) {\r\n                src = DamageSource.mobAttack((net.minecraft.world.entity.LivingEntity) nmsSource);\r\n            }\r\n        }\r\n        if (cause == null) {\r\n            return src;\r\n        }\r\n        switch (cause) {\r\n            case CONTACT:\r\n                return DamageSource.CACTUS;\r\n            case ENTITY_ATTACK:\r\n                return DamageSource.mobAttack(nmsSource instanceof net.minecraft.world.entity.LivingEntity ? (net.minecraft.world.entity.LivingEntity) nmsSource : null);\r\n            case ENTITY_SWEEP_ATTACK:\r\n                if (src != DamageSource.GENERIC) {\r\n                    src.sweep();\r\n                }\r\n                return src;\r\n            case PROJECTILE:\r\n                return DamageSource.thrown(nmsSource, nmsSource != null && nmsSource.getBukkitEntity() instanceof Projectile\r\n                        && ((Projectile) nmsSource.getBukkitEntity()).getShooter() instanceof Entity ? ((CraftEntity) ((Projectile) nmsSource.getBukkitEntity()).getShooter()).getHandle() : null);\r\n            case SUFFOCATION:\r\n                return DamageSource.IN_WALL;\r\n            case FALL:\r\n                return DamageSource.FALL;\r\n            case FIRE:\r\n                return DamageSource.IN_FIRE;\r\n            case FIRE_TICK:\r\n                return DamageSource.ON_FIRE;\r\n            case MELTING:\r\n                return CraftEventFactory.MELTING;\r\n            case LAVA:\r\n                return DamageSource.LAVA;\r\n            case DROWNING:\r\n                return DamageSource.DROWN;\r\n            case BLOCK_EXPLOSION:\r\n                return DamageSource.explosion(nmsSource instanceof TNTPrimed && ((TNTPrimed) nmsSource).getSource() instanceof net.minecraft.world.entity.LivingEntity ? (net.minecraft.world.entity.LivingEntity) ((TNTPrimed) nmsSource).getSource() : null);\r\n            case ENTITY_EXPLOSION:\r\n                return DamageSource.explosion(nmsSource instanceof net.minecraft.world.entity.LivingEntity ? (net.minecraft.world.entity.LivingEntity) nmsSource : null);\r\n            case VOID:\r\n                return DamageSource.OUT_OF_WORLD;\r\n            case LIGHTNING:\r\n                return DamageSource.LIGHTNING_BOLT;\r\n            case STARVATION:\r\n                return DamageSource.STARVE;\r\n            case POISON:\r\n                return CraftEventFactory.POISON;\r\n            case MAGIC:\r\n                return DamageSource.MAGIC;\r\n            case WITHER:\r\n                return DamageSource.WITHER;\r\n            case FALLING_BLOCK:\r\n                return DamageSource.FALLING_BLOCK;\r\n            case THORNS:\r\n                return DamageSource.thorns(nmsSource);\r\n            case DRAGON_BREATH:\r\n                return DamageSource.DRAGON_BREATH;\r\n            case CUSTOM:\r\n                return DamageSource.GENERIC;\r\n            case FLY_INTO_WALL:\r\n                return DamageSource.FLY_INTO_WALL;\r\n            case HOT_FLOOR:\r\n                return DamageSource.HOT_FLOOR;\r\n            case CRAMMING:\r\n                return DamageSource.CRAMMING;\r\n            case DRYOUT:\r\n                return DamageSource.DRY_OUT;\r\n            //case SUICIDE:\r\n            default:\r\n                return new FakeDamageSrc(src);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void damage(LivingEntity target, float amount, EntityTag source, Location sourceLoc, EntityDamageEvent.DamageCause cause) {\r\n        if (target == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.LivingEntity nmsTarget = ((CraftLivingEntity) target).getHandle();\r\n        net.minecraft.world.entity.Entity nmsSource = source == null ? null : ((CraftEntity) source.getBukkitEntity()).getHandle();\r\n        CraftEventFactory.entityDamage = nmsSource;\r\n        CraftEventFactory.blockDamage = sourceLoc == null ? null : sourceLoc.getBlock();\r\n        try {\r\n            DamageSource src = getSourceFor(nmsSource, cause);\r\n            if (src instanceof FakeDamageSrc) {\r\n                src = ((FakeDamageSrc) src).real;\r\n                EntityDamageEvent ede = fireFakeDamageEvent(target, source, sourceLoc, cause, amount);\r\n                if (ede.isCancelled()) {\r\n                    return;\r\n                }\r\n            }\r\n            nmsTarget.hurt(src, amount);\r\n        }\r\n        finally {\r\n            CraftEventFactory.entityDamage = null;\r\n            CraftEventFactory.blockDamage = null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setLastHurtBy(LivingEntity mob, LivingEntity damager) {\r\n        ((CraftLivingEntity) mob).getHandle().setLastHurtByMob(((CraftLivingEntity) damager).getHandle());\r\n    }\r\n\r\n    public static final MethodHandle FALLINGBLOCK_TYPE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.entity.item.FallingBlockEntity.class, BlockState.class);\r\n\r\n    @Override\r\n    public void setFallingBlockType(FallingBlock fallingBlock, BlockData block) {\r\n        BlockState state = ((CraftBlockData) block).getState();\r\n        FallingBlockEntity nmsEntity = ((CraftFallingBlock) fallingBlock).getHandle();\r\n        try {\r\n            FALLINGBLOCK_TYPE_SETTER.invoke(nmsEntity, state);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public EntityTag getMobSpawnerDisplayEntity(CreatureSpawner spawner) {\r\n        SpawnerBlockEntity nmsSpawner = BlockHelperImpl.getTE((CraftCreatureSpawner) spawner);\r\n        net.minecraft.world.entity.Entity nmsEntity = nmsSpawner.getSpawner().getOrCreateDisplayEntity(((CraftWorld) spawner.getWorld()).getHandle());\r\n        return new EntityTag(nmsEntity.getBukkitEntity());\r\n    }\r\n\r\n    @Override\r\n    public void setFireworkLifetime(Firework firework, int ticks) {\r\n        ((CraftFirework) firework).getHandle().lifetime = ticks;\r\n    }\r\n\r\n    @Override\r\n    public int getFireworkLifetime(Firework firework) {\r\n        return ((CraftFirework) firework).getHandle().lifetime;\r\n    }\r\n\r\n    public static final Field ZOMBIE_INWATERTIME = ReflectionHelper.getFields(net.minecraft.world.entity.monster.Zombie.class).get(ReflectionMappingsInfo.Zombie_inWaterTime, int.class);\r\n\r\n    @Override\r\n    public int getInWaterTime(Zombie zombie) {\r\n        try {\r\n            return ZOMBIE_INWATERTIME.getInt(((CraftZombie) zombie).getHandle());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return 0;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setInWaterTime(Zombie zombie, int ticks) {\r\n        try {\r\n            ZOMBIE_INWATERTIME.setInt(((CraftZombie) zombie).getHandle(), ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final MethodHandle TRACKING_RANGE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ChunkMap.TrackedEntity.class, int.class);\r\n\r\n    @Override\r\n    public void setTrackingRange(Entity entity, int range) {\r\n        try {\r\n            ChunkMap map = ((CraftWorld) entity.getWorld()).getHandle().getChunkSource().chunkMap;\r\n            ChunkMap.TrackedEntity entry = map.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                TRACKING_RANGE_SETTER.invoke(entry, range);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean isAggressive(org.bukkit.entity.Mob mob) {\r\n        return ((CraftMob) mob).getHandle().isAggressive();\r\n    }\r\n\r\n    @Override\r\n    public void setAggressive(org.bukkit.entity.Mob mob, boolean aggressive) {\r\n        ((CraftMob) mob).getHandle().setAggressive(aggressive);\r\n    }\r\n\r\n    @Override\r\n    public void openHorseInventory(Player player, AbstractHorse horse) {\r\n        net.minecraft.world.entity.animal.horse.AbstractHorse nmsHorse = ((CraftAbstractHorse) horse).getHandle();\r\n        ((CraftPlayer) player).getHandle().openHorseInventory(nmsHorse, nmsHorse.inventory);\r\n    }\r\n\r\n    public static class EntityEntersVehicleScriptEventImpl extends EntityEntersVehicleScriptEvent {\r\n        @EventHandler\r\n        public void onEntityMount(EntityMountEvent event) {\r\n            fire(event, event.getMount());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Class<? extends EntityEntersVehicleScriptEvent> getEntersVehicleEventImpl() {\r\n        return EntityEntersVehicleScriptEventImpl.class;\r\n    }\r\n\r\n    public static class EntityExitsVehicleScriptEventImpl extends EntityExitsVehicleScriptEvent {\r\n        @EventHandler\r\n        public void onEntityMount(EntityDismountEvent event) {\r\n            fire(event, event.getDismounted());\r\n        }\r\n    }\r\n\r\n    public Class<? extends EntityExitsVehicleScriptEvent> getExitsVehicleEventImpl() {\r\n        return EntityExitsVehicleScriptEventImpl.class;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/FishingHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FishingHelper;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.entity.projectile.FishingHook;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.item.enchantment.Enchantments;\r\nimport net.minecraft.world.level.storage.loot.BuiltInLootTables;\r\nimport net.minecraft.world.level.storage.loot.LootContext;\r\nimport net.minecraft.world.level.storage.loot.LootTables;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParams;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftFishHook;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.entity.FishHook;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.List;\r\n\r\npublic class FishingHelperImpl implements FishingHelper {\r\n\r\n    @Override\r\n    public org.bukkit.inventory.ItemStack getResult(FishHook fishHook, CatchType catchType) {\r\n        ItemStack result = null;\r\n        FishingHook nmsHook = ((CraftFishHook) fishHook).getHandle();\r\n        if (catchType == CatchType.DEFAULT) {\r\n            float f = ((CraftWorld) fishHook.getWorld()).getHandle().random.nextFloat();\r\n            int i = EnchantmentHelper.getMobLooting(nmsHook.getPlayerOwner());\r\n            int j = EnchantmentHelper.getEnchantmentLevel(Enchantments.FISHING_LUCK, nmsHook.getPlayerOwner());\r\n            float f1 = 0.1F - (float) i * 0.025F - (float) j * 0.01F;\r\n            float f2 = 0.05F + (float) i * 0.01F - (float) j * 0.01F;\r\n\r\n            f1 = Mth.clamp(f1, 0.0F, 1.0F);\r\n            f2 = Mth.clamp(f2, 0.0F, 1.0F);\r\n            if (f < f1) {\r\n                result = catchRandomJunk(nmsHook);\r\n            }\r\n            else {\r\n                f -= f1;\r\n                if (f < f2) {\r\n                    result = catchRandomTreasure(nmsHook);\r\n                }\r\n                else {\r\n                    result = catchRandomFish(nmsHook);\r\n                }\r\n            }\r\n        }\r\n        else if (catchType == CatchType.JUNK) {\r\n            result = catchRandomJunk(nmsHook);\r\n        }\r\n        else if (catchType == CatchType.TREASURE) {\r\n            result = catchRandomTreasure(nmsHook);\r\n        }\r\n        else if (catchType == CatchType.FISH) {\r\n            result = catchRandomFish(nmsHook);\r\n        }\r\n        if (result != null) {\r\n            return CraftItemStack.asBukkitCopy(result);\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public ItemStack getRandomReward(FishingHook hook, ResourceLocation key) {\r\n        ServerLevel worldServer = (ServerLevel) hook.level;\r\n        LootContext.Builder playerFishEvent2 = new LootContext.Builder(worldServer);\r\n        LootTables registry = ((ServerLevel) hook.level).getServer().getLootTables();\r\n        // registry.getLootTable(key).getLootContextParameterSet()\r\n        LootContext info = playerFishEvent2.withOptionalParameter(LootContextParams.ORIGIN, new Vec3(hook.getX(), hook.getY(), hook.getZ()))\r\n                .withOptionalParameter(LootContextParams.TOOL, new ItemStack(Items.FISHING_ROD)).create(LootContextParamSets.FISHING);\r\n        List<ItemStack> itemStacks = registry.get(key).getRandomItems(info);\r\n        return itemStacks.get(worldServer.random.nextInt(itemStacks.size()));\r\n    }\r\n\r\n    @Override\r\n    public FishHook spawnHook(Location location, Player player) {\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        FishingHook hook = new FishingHook(((CraftPlayer) player).getHandle(), nmsWorld, 0, 0);\r\n        nmsWorld.addFreshEntity(hook, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return (FishHook) hook.getBukkitEntity();\r\n    }\r\n\r\n    private ItemStack catchRandomJunk(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_JUNK);\r\n    }\r\n\r\n    private ItemStack catchRandomTreasure(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_TREASURE);\r\n    }\r\n\r\n    private ItemStack catchRandomFish(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_FISH);\r\n    }\r\n\r\n    public static Field FISHING_HOOK_NIBBLE_SETTER = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_nibble, int.class);\r\n    public static Field FISHING_HOOK_LURE_TIME_SETTER = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_timeUntilLured, int.class);\r\n    public static Field FISHING_HOOK_HOOK_TIME_SETTER = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_timeUntilHooked, int.class);\r\n\r\n    @Override\r\n    public FishHook getHookFrom(Player player) {\r\n        FishingHook hook = ((CraftPlayer) player).getHandle().fishing;\r\n        if (hook == null) {\r\n            return null;\r\n        }\r\n        return (FishHook) hook.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public void setNibble(FishHook hook, int ticks) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_NIBBLE_SETTER.setInt(nmsEntity, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHookTime(FishHook hook, int ticks) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_HOOK_TIME_SETTER.setInt(nmsEntity, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int getLureTime(FishHook hook) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            return FISHING_HOOK_LURE_TIME_SETTER.getInt(nmsEntity);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public void setLureTime(FishHook hook, int ticks) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_LURE_TIME_SETTER.setInt(nmsEntity, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/ItemHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.ItemHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.*;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;\r\nimport net.kyori.adventure.nbt.BinaryTag;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Direction;\r\nimport net.minecraft.core.NonNullList;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.nbt.ListTag;\r\nimport net.minecraft.nbt.NbtUtils;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.world.item.BlockItem;\r\nimport net.minecraft.world.item.Item;\r\nimport net.minecraft.world.item.alchemy.PotionBrewing;\r\nimport net.minecraft.world.item.crafting.*;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport net.minecraft.world.level.material.MaterialColor;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryPlayer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftRecipe;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftNamespacedKey;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.RecipeChoice;\r\nimport org.bukkit.inventory.ShapedRecipe;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class ItemHelperImpl extends ItemHelper {\r\n\r\n    public static net.minecraft.world.item.crafting.Recipe<?> getNMSRecipe(NamespacedKey key) {\r\n        ResourceLocation nmsKey = CraftNamespacedKey.toMinecraft(key);\r\n        for (Object2ObjectLinkedOpenHashMap<ResourceLocation, net.minecraft.world.item.crafting.Recipe<?>> recipeMap : ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().recipes.values()) {\r\n            net.minecraft.world.item.crafting.Recipe<?> recipe = recipeMap.get(nmsKey);\r\n            if (recipe != null) {\r\n                return recipe;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public void setMaxStackSize(Material material, int size) {\r\n        try {\r\n            ReflectionHelper.getFinalSetter(Material.class, \"maxStack\").invoke(material, size);\r\n            ReflectionHelper.getFinalSetter(Item.class, ReflectionMappingsInfo.Item_maxStackSize).invoke(Registry.ITEM.get(CraftNamespacedKey.toMinecraft(material.getKey())), size);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Integer burnTime(Material material) {\r\n        return AbstractFurnaceBlockEntity.getFuel().get(CraftMagicNumbers.getItem(material));\r\n    }\r\n\r\n    public static Field RECIPE_MANAGER_BY_NAME = ReflectionHelper.getFields(RecipeManager.class).get(ReflectionMappingsInfo.RecipeManager_byName, Map.class);\r\n\r\n    @Override\r\n    public void setShapedRecipeIngredient(ShapedRecipe recipe, char c, ItemStack[] item, boolean exact) {\r\n        if (item.length == 1 && item[0].getType() == Material.AIR) {\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(Material.AIR));\r\n        }\r\n        else if (exact) {\r\n            recipe.setIngredient(c, new RecipeChoice.ExactChoice(item));\r\n        }\r\n        else {\r\n            Material[] mats = new Material[item.length];\r\n            for (int i = 0; i < item.length; i++) {\r\n                mats[i] = item[i].getType();\r\n            }\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(mats));\r\n        }\r\n    }\r\n\r\n    public static Ingredient itemArrayToRecipe(ItemStack[] items, boolean exact) {\r\n        Ingredient.ItemValue[] stacks = new Ingredient.ItemValue[items.length];\r\n        for (int i = 0; i < items.length; i++) {\r\n            stacks[i] = new Ingredient.ItemValue(CraftItemStack.asNMSCopy(items[i]));\r\n        }\r\n        Ingredient itemRecipe = new Ingredient(Arrays.stream(stacks));\r\n        itemRecipe.exact = exact;\r\n        return itemRecipe;\r\n    }\r\n\r\n    @Override\r\n    public void registerFurnaceRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, float exp, int time, String type, boolean exact, String category) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        AbstractCookingRecipe recipe;\r\n        if (type.equalsIgnoreCase(\"smoker\")) {\r\n            recipe = new SmokingRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"blast\")) {\r\n            recipe = new BlastingRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"campfire\")) {\r\n            recipe = new CampfireCookingRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else {\r\n            recipe = new SmeltingRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public void registerStonecuttingRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, boolean exact) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        StonecutterRecipe recipe = new StonecutterRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result));\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public void registerSmithingRecipe(String keyName, ItemStack result, ItemStack[] baseItem, boolean baseExact, ItemStack[] upgradeItem, boolean upgradeExact, ItemStack[] templateItem, boolean templateExact) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient baseItemRecipe = itemArrayToRecipe(baseItem, baseExact);\r\n        Ingredient upgradeItemRecipe = itemArrayToRecipe(upgradeItem, upgradeExact);\r\n        UpgradeRecipe recipe = new UpgradeRecipe(key, baseItemRecipe, upgradeItemRecipe, CraftItemStack.asNMSCopy(result));\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public void registerShapelessRecipe(String keyName, String group, ItemStack result, List<ItemStack[]> ingredients, boolean[] exact, String category) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        ArrayList<Ingredient> ingredientList = new ArrayList<>();\r\n        for (int i = 0; i < ingredients.size(); i++) {\r\n            ingredientList.add(itemArrayToRecipe(ingredients.get(i), exact[i]));\r\n        }\r\n        ShapelessRecipe recipe = new ShapelessRecipe(key, group, CraftItemStack.asNMSCopy(result), NonNullList.of(null, ingredientList.toArray(new Ingredient[0])));\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public String getJsonString(ItemStack itemStack) {\r\n        String json = CraftItemStack.asNMSCopy(itemStack).getDisplayName().getStyle().toString().replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\");\r\n        return json.substring(176, json.length() - 185);\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getSkullSkin(ItemStack is) {\r\n        net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(is);\r\n        if (itemStack.hasTag()) {\r\n            net.minecraft.nbt.CompoundTag tag = itemStack.getTag();\r\n            if (tag.contains(\"SkullOwner\", 10)) {\r\n                GameProfile profile = NbtUtils.readGameProfile(tag.getCompound(\"SkullOwner\"));\r\n                if (profile != null) {\r\n                    Property property = Iterables.getFirst(profile.getProperties().get(\"textures\"), null);\r\n                    return new PlayerProfile(profile.getName(), profile.getId(),\r\n                            property != null ? property.getValue() : null,\r\n                            property != null ? property.getSignature() : null);\r\n                }\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setSkullSkin(ItemStack itemStack, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        if (playerProfile.hasTexture()) {\r\n            gameProfile.getProperties().get(\"textures\").clear();\r\n            if (playerProfile.getTextureSignature() != null) {\r\n                gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n            }\r\n            else {\r\n                gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture()));\r\n            }\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        net.minecraft.nbt.CompoundTag tag = nmsItemStack.hasTag() ? nmsItemStack.getTag() : new net.minecraft.nbt.CompoundTag();\r\n        tag.put(\"SkullOwner\", NbtUtils.writeGameProfile(new net.minecraft.nbt.CompoundTag(), gameProfile));\r\n        nmsItemStack.setTag(tag);\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack addNbtData(ItemStack itemStack, String key, BinaryTag value) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.getOrCreateTag().put(key, NBTAdapter.toNMS(value));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(ItemStack itemStack) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        if (nmsItemStack != null && nmsItemStack.hasTag()) {\r\n            return NBTAdapter.toAPI(nmsItemStack.getTag());\r\n        }\r\n        return CompoundBinaryTag.empty();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setNbtData(ItemStack itemStack, CompoundBinaryTag compoundTag) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.setTag(NBTAdapter.toNMS(compoundTag));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public void setInventoryItem(Inventory inventory, ItemStack item, int slot) {\r\n        if (inventory instanceof CraftInventoryPlayer && ((CraftInventoryPlayer) inventory).getInventory().player == null) {\r\n            ((CraftInventoryPlayer) inventory).getInventory().setItem(slot, CraftItemStack.asNMSCopy(item));\r\n        }\r\n        else {\r\n            inventory.setItem(slot, item);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getDisplayName(ItemTag item) {\r\n        if (!item.getItemMeta().hasDisplayName()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        String jsonText = ((net.minecraft.nbt.CompoundTag) nmsItemStack.getTag().get(\"display\")).getString(\"Name\");\r\n        BaseComponent[] nameComponent = ComponentSerializer.parse(jsonText);\r\n        return FormattedTextHelper.stringify(nameComponent);\r\n    }\r\n\r\n    @Override\r\n    public List<String> getLore(ItemTag item) {\r\n        if (!item.getItemMeta().hasLore()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        ListTag list = ((net.minecraft.nbt.CompoundTag) nmsItemStack.getTag().get(\"display\")).getList(\"Lore\", 8);\r\n        List<String> outList = new ArrayList<>();\r\n        for (int i = 0; i < list.size(); i++) {\r\n            BaseComponent[] lineComponent = ComponentSerializer.parse(list.getString(i));\r\n            outList.add(FormattedTextHelper.stringify(lineComponent));\r\n        }\r\n        return outList;\r\n    }\r\n\r\n    @Override\r\n    public void setDisplayName(ItemTag item, String name) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        net.minecraft.nbt.CompoundTag tag = nmsItemStack.getOrCreateTag();\r\n        net.minecraft.nbt.CompoundTag display = tag.getCompound(\"display\");\r\n        if (!tag.contains(\"display\")) {\r\n            tag.put(\"display\", display);\r\n        }\r\n        if (name == null || name.isEmpty()) {\r\n            display.put(\"Name\", null);\r\n            return;\r\n        }\r\n        BaseComponent[] components = FormattedTextHelper.parse(name, ChatColor.WHITE);\r\n        display.put(\"Name\", net.minecraft.nbt.StringTag.valueOf(ComponentSerializer.toString(components)));\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    @Override\r\n    public void setLore(ItemTag item, List<String> lore) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        net.minecraft.nbt.CompoundTag tag = nmsItemStack.getOrCreateTag();\r\n        net.minecraft.nbt.CompoundTag display = tag.getCompound(\"display\");\r\n        if (!tag.contains(\"display\")) {\r\n            tag.put(\"display\", display);\r\n        }\r\n        if (lore == null || lore.isEmpty()) {\r\n            display.put(\"Lore\", null);\r\n        }\r\n        else {\r\n            ListTag tagList = new ListTag();\r\n            for (String line : lore) {\r\n                tagList.add(net.minecraft.nbt.StringTag.valueOf(ComponentSerializer.toString(FormattedTextHelper.parse(line, ChatColor.WHITE))));\r\n            }\r\n            display.put(\"Lore\", tagList);\r\n        }\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.getCorrectStateForFluidBlock, and rewritten as reflection due to Spigot bug - refer to bottom of BlockHelperImpl for detail.\r\n     */\r\n    public static BlockState getCorrectStateForFluidBlock(Level world, BlockState iblockdata, BlockPos blockposition) {\r\n        try {\r\n            // FluidState fluid = iblockdata.getFluidState();\r\n            Object fluid = BlockHelperImpl.BLOCKSTATEBASE_GETFLUIDSTATE.invoke(iblockdata);\r\n            //return !fluid.isEmpty() && !iblockdata.isFaceSturdy(world, blockposition, Direction.UP) ? fluid.createLegacyBlock() : iblockdata;\r\n            boolean isEmpty = (boolean) BlockHelperImpl.FLUIDSTATE_ISEMPTY.invoke(fluid);\r\n            if (!isEmpty && !iblockdata.isFaceSturdy(world, blockposition, Direction.UP)) {\r\n                return (BlockState) BlockHelperImpl.FLUIDSTATE_CREATELEGACYBLOCK.invoke(fluid);\r\n            }\r\n            else {\r\n                return iblockdata;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return iblockdata;\r\n        }\r\n    }\r\n\r\n    public static boolean blockStateFluidIsEmpty(BlockState iblockdata) {\r\n        try {\r\n            // return iblockdata.getFluidState().isEmpty();\r\n            Object fluid = BlockHelperImpl.BLOCKSTATEBASE_GETFLUIDSTATE.invoke(iblockdata);\r\n            return (boolean) BlockHelperImpl.FLUIDSTATE_ISEMPTY.invoke(fluid);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return false;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.update, redesigned slightly to render totally rather than just relative to a player.\r\n     * Some variables manually renamed for readability.\r\n     * Also contains reflection fixes for Spigot's FluidState bug.\r\n     */\r\n    public static void renderFullMap(MapItemSavedData worldmap, int xMin, int zMin, int xMax, int zMax) {\r\n        Level world = ((CraftWorld) worldmap.mapView.getWorld()).getHandle();\r\n        int scale = 1 << worldmap.scale;\r\n        int mapX = worldmap.x;\r\n        int mapZ = worldmap.z;\r\n        for (int x = xMin; x < xMax; x++) {\r\n            double d0 = 0.0D;\r\n            for (int z = zMin; z < zMax; z++) {\r\n                int k2 = (mapX / scale + x - 64) * scale;\r\n                int l2 = (mapZ / scale + z - 64) * scale;\r\n                Multiset<MaterialColor> multiset = LinkedHashMultiset.create();\r\n                LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2));\r\n                if (!chunk.isEmpty()) {\r\n                    ChunkPos chunkcoordintpair = chunk.getPos();\r\n                    int i3 = k2 & 15;\r\n                    int j3 = l2 & 15;\r\n                    int k3 = 0;\r\n                    double d1 = 0.0D;\r\n                    if (world.dimensionType().hasCeiling()) {\r\n                        int l3 = k2 + l2 * 231871;\r\n                        l3 = l3 * l3 * 31287121 + l3 * 11;\r\n                        if ((l3 >> 20 & 1) == 0) {\r\n                            multiset.add(Blocks.DIRT.defaultBlockState().getMapColor(world, BlockPos.ZERO), 10);\r\n                        }\r\n                        else {\r\n                            multiset.add(Blocks.STONE.defaultBlockState().getMapColor(world, BlockPos.ZERO), 100);\r\n                        }\r\n\r\n                        d1 = 100.0D;\r\n                    }\r\n                    else {\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition1 = new BlockPos.MutableBlockPos();\r\n                        for (int i4 = 0; i4 < scale; ++i4) {\r\n                            for (int j4 = 0; j4 < scale; ++j4) {\r\n                                int k4 = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i4 + i3, j4 + j3) + 1;\r\n                                BlockState iblockdata;\r\n                                if (k4 <= world.getMinBuildHeight() + 1) {\r\n                                    iblockdata = Blocks.BEDROCK.defaultBlockState();\r\n                                }\r\n                                else {\r\n                                    do {\r\n                                        --k4;\r\n                                        blockposition_mutableblockposition.set(chunkcoordintpair.getMinBlockX() + i4 + i3, k4, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                        iblockdata = chunk.getBlockState(blockposition_mutableblockposition);\r\n                                    } while (iblockdata.getMapColor(world, blockposition_mutableblockposition) == MaterialColor.NONE && k4 > world.getMinBuildHeight());\r\n                                    if (k4 > world.getMinBuildHeight() && !blockStateFluidIsEmpty(iblockdata)) {\r\n                                        int l4 = k4 - 1;\r\n                                        blockposition_mutableblockposition1.set(blockposition_mutableblockposition);\r\n\r\n                                        BlockState iblockdata1;\r\n                                        do {\r\n                                            blockposition_mutableblockposition1.setY(l4--);\r\n                                            iblockdata1 = chunk.getBlockState(blockposition_mutableblockposition1);\r\n                                            k3++;\r\n                                        } while (l4 > world.getMinBuildHeight() && !blockStateFluidIsEmpty(iblockdata1));\r\n                                        iblockdata = getCorrectStateForFluidBlock(world, iblockdata, blockposition_mutableblockposition);\r\n                                    }\r\n                                }\r\n                                worldmap.checkBanners(world, chunkcoordintpair.getMinBlockX() + i4 + i3, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                d1 += (double) k4 / (double) (scale * scale);\r\n                                multiset.add(iblockdata.getMapColor(world, blockposition_mutableblockposition));\r\n                            }\r\n                        }\r\n                    }\r\n                    k3 /= scale * scale;\r\n                    double d2 = (d1 - d0) * 4.0D / (double) (scale + 4) + ((double) (x + z & 1) - 0.5D) * 0.4D;\r\n                    byte b0 = 1;\r\n                    if (d2 > 0.6D) {\r\n                        b0 = 2;\r\n                    }\r\n                    if (d2 < -0.6D) {\r\n                        b0 = 0;\r\n                    }\r\n                    MaterialColor materialmapcolor = Iterables.getFirst(Multisets.copyHighestCountFirst(multiset), MaterialColor.NONE);\r\n                    if (materialmapcolor == MaterialColor.WATER) {\r\n                        d2 = (double) k3 * 0.1D + (double) (x + z & 1) * 0.2D;\r\n                        b0 = 1;\r\n                        if (d2 < 0.5D) {\r\n                            b0 = 2;\r\n                        }\r\n                        if (d2 > 0.9D) {\r\n                            b0 = 0;\r\n                        }\r\n                    }\r\n                    d0 = d1;\r\n                    worldmap.updateColor(x, z, (byte) (materialmapcolor.id * 4 + b0));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean renderEntireMap(int mapId, int xMin, int zMin, int xMax, int zMax) {\r\n        MapItemSavedData worldmap = ((CraftServer) Bukkit.getServer()).getServer().getLevel(net.minecraft.world.level.Level.OVERWORLD).getMapData(\"map_\" + mapId);\r\n        if (worldmap == null) {\r\n            return false;\r\n        }\r\n        renderFullMap(worldmap, xMin, zMin, xMax, zMax);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public BlockData getPlacedBlock(Material material) {\r\n        Item nmsItem = Registry.ITEM.getOptional(CraftNamespacedKey.toMinecraft(material.getKey())).orElse(null);\r\n        if (nmsItem instanceof BlockItem) {\r\n            Block block = ((BlockItem) nmsItem).getBlock();\r\n            return CraftBlockData.fromData(block.defaultBlockState());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public boolean isValidMix(ItemStack input, ItemStack ingredient) {\r\n        net.minecraft.world.item.ItemStack nmsInput = CraftItemStack.asNMSCopy(input);\r\n        net.minecraft.world.item.ItemStack nmsIngredient = CraftItemStack.asNMSCopy(ingredient);\r\n        return PotionBrewing.hasMix(nmsInput, nmsIngredient);\r\n    }\r\n\r\n    public static Class<?> PaperPotionMix_CLASS = null;\r\n    public static Map<NamespacedKey, BrewingRecipe> customBrewingRecipes = null;\r\n\r\n    @Override\r\n    public Map<NamespacedKey, BrewingRecipe> getCustomBrewingRecipes() {\r\n        if (customBrewingRecipes == null) {\r\n            customBrewingRecipes = Maps.transformValues((Map<NamespacedKey, ?>) ReflectionHelper.getFieldValue(PotionBrewing.class, \"CUSTOM_MIXES\", null), paperMix -> {\r\n                if (PaperPotionMix_CLASS == null) {\r\n                    PaperPotionMix_CLASS = paperMix.getClass();\r\n                }\r\n                RecipeChoice ingredient = CraftRecipe.toBukkit(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"ingredient\", paperMix));\r\n                RecipeChoice input = CraftRecipe.toBukkit(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"input\", paperMix));\r\n                ItemStack result = CraftItemStack.asBukkitCopy(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"result\", paperMix));\r\n                return new BrewingRecipe(ingredient, input, result);\r\n            });\r\n        }\r\n        return customBrewingRecipes;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/NBTAdapter.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\n\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport net.kyori.adventure.nbt.*;\nimport net.minecraft.nbt.*;\n\nimport java.lang.invoke.MethodHandle;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class NBTAdapter {\n\n    public static final MethodHandle COMPOUND_TAG_MAP_CONSTRUCTOR = ReflectionHelper.getConstructor(CompoundTag.class, Map.class);\n\n    public static Tag toNMS(BinaryTag tag) {\n        if (tag instanceof ByteBinaryTag byteTag) {\n            return switch (byteTag.value()) {\n                case 0 -> ByteTag.ZERO;\n                case 1 -> ByteTag.ONE;\n                default -> ByteTag.valueOf(byteTag.value());\n            };\n        }\n        else if (tag instanceof ShortBinaryTag shortTag) {\n            return ShortTag.valueOf(shortTag.value());\n        }\n        else if (tag instanceof IntBinaryTag intTag) {\n            return IntTag.valueOf(intTag.value());\n        }\n        else if (tag instanceof LongBinaryTag longTag) {\n            return LongTag.valueOf(longTag.value());\n        }\n        else if (tag instanceof FloatBinaryTag floatTag) {\n            return FloatTag.valueOf(floatTag.value());\n        }\n        else if (tag instanceof DoubleBinaryTag doubleTag) {\n            return DoubleTag.valueOf(doubleTag.value());\n        }\n        else if (tag instanceof ByteArrayBinaryTag byteArrayTag) {\n            return new ByteArrayTag(byteArrayTag.value());\n        }\n        else if (tag instanceof IntArrayBinaryTag intArrayTag) {\n            return new IntArrayTag(intArrayTag.value());\n        }\n        else if (tag instanceof LongArrayBinaryTag longArrayTag) {\n            return new LongArrayTag(longArrayTag.value());\n        }\n        else if (tag instanceof StringBinaryTag stringTag) {\n            return StringTag.valueOf(stringTag.value());\n        }\n        else if (tag instanceof ListBinaryTag listTag) {\n            return toNMS(listTag);\n        }\n        else if (tag instanceof CompoundBinaryTag compoundTag) {\n            return toNMS(compoundTag);\n        }\n        else if (tag instanceof EndBinaryTag) {\n            return EndTag.INSTANCE;\n        }\n        throw new IllegalStateException(\"Unrecognized API tag of type '\" + tag.type() + \"': \" + tag);\n    }\n\n    public static BinaryTag toAPI(Tag nmsTag) {\n        if (nmsTag instanceof ByteTag nmsByteTag) {\n            return ByteBinaryTag.byteBinaryTag(nmsByteTag.getAsByte());\n        }\n        else if (nmsTag instanceof ShortTag nmsShortTag) {\n            return ShortBinaryTag.shortBinaryTag(nmsShortTag.getAsShort());\n        }\n        else if (nmsTag instanceof IntTag nmsIntTag) {\n            return IntBinaryTag.intBinaryTag(nmsIntTag.getAsInt());\n        }\n        else if (nmsTag instanceof LongTag nmsLongTag) {\n            return LongBinaryTag.longBinaryTag(nmsLongTag.getAsLong());\n        }\n        else if (nmsTag instanceof FloatTag nmsFloatTag) {\n            return FloatBinaryTag.floatBinaryTag(nmsFloatTag.getAsFloat());\n        }\n        else if (nmsTag instanceof DoubleTag nmsDoubleTag) {\n            return DoubleBinaryTag.doubleBinaryTag(nmsDoubleTag.getAsDouble());\n        }\n        else if (nmsTag instanceof ByteArrayTag nmsByteArrayTag) {\n            return ByteArrayBinaryTag.byteArrayBinaryTag(nmsByteArrayTag.getAsByteArray());\n        }\n        else if (nmsTag instanceof IntArrayTag nmsIntArrayTag) {\n            return IntArrayBinaryTag.intArrayBinaryTag(nmsIntArrayTag.getAsIntArray());\n        }\n        else if (nmsTag instanceof LongArrayTag nmsLongArrayTag) {\n            return LongArrayBinaryTag.longArrayBinaryTag(nmsLongArrayTag.getAsLongArray());\n        }\n        else if (nmsTag instanceof StringTag nmsStringTag) {\n            return StringBinaryTag.stringBinaryTag(nmsStringTag.getAsString());\n        }\n        else if (nmsTag instanceof ListTag nmsListTag) {\n            return toAPI(nmsListTag);\n        }\n        else if (nmsTag instanceof CompoundTag nmsCompoundTag) {\n            return toAPI(nmsCompoundTag);\n        }\n        else if (nmsTag instanceof EndTag) {\n            return EndBinaryTag.endBinaryTag();\n        }\n        throw new IllegalStateException(\"Unrecognized NMS tag of type '\" + nmsTag.getClass().getName() + '/' + nmsTag.getType().getName() + \"': \" + nmsTag);\n    }\n\n    public static ListBinaryTag toAPI(ListTag nmsListTag) {\n        ListBinaryTag.Builder<BinaryTag> builder = ListBinaryTag.builder(nmsListTag.size());\n        for (Tag nmsValue : nmsListTag) {\n            builder.add(toAPI(nmsValue));\n        }\n        return builder.build();\n    }\n\n    public static ListTag toNMS(ListBinaryTag listTag) {\n        ListTag nmsListTag = new ListTag();\n        for (BinaryTag value : listTag) {\n            nmsListTag.add(toNMS(value));\n        }\n        return nmsListTag;\n    }\n\n    public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) {\n        CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size());\n        for (String key : nmsCompoundTag.getAllKeys()) {\n            builder.put(key, toAPI(nmsCompoundTag.get(key)));\n        }\n        return builder.build();\n    }\n\n    public static CompoundTag toNMS(CompoundBinaryTag compoundTag) {\n        Map<String, Tag> nmsTags = new HashMap<>(compoundTag.size());\n        for (Map.Entry<String, ? extends BinaryTag> entry : compoundTag) {\n            nmsTags.put(entry.getKey(), toNMS(entry.getValue()));\n        }\n        try {\n            return (CompoundTag) COMPOUND_TAG_MAP_CONSTRUCTOR.invokeExact(nmsTags);\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/PacketHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.PacketHelper;\r\nimport com.denizenscript.denizen.nms.v1_18.Handler;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizen.utilities.maps.MapImage;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.datafixers.util.Pair;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.kyori.adventure.nbt.BinaryTagTypes;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.kyori.adventure.nbt.ListBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.EquipmentSlot;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.monster.CaveSpider;\r\nimport net.minecraft.world.entity.monster.Creeper;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.entity.monster.Spider;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.entity.BlockEntityType;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Team;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.EntityEffect;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.banner.Pattern;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_18_R2.map.CraftMapCanvas;\r\nimport org.bukkit.craftbukkit.v1_18_R2.map.CraftMapView;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.EntityEquipment;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapPalette;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.*;\r\n\r\npublic class PacketHelperImpl implements PacketHelper {\r\n\r\n    public static final EntityDataAccessor<Float> ENTITY_HUMAN_DATA_WATCHER_ABSORPTION = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, ReflectionMappingsInfo.Player_DATA_PLAYER_ABSORPTION_ID, null);\r\n\r\n    public static final EntityDataAccessor<Byte> ENTITY_DATA_WATCHER_FLAGS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_SHARED_FLAGS_ID, null);\r\n\r\n    public static final MethodHandle ABILITIES_PACKET_FOV_SETTER = ReflectionHelper.getFinalSetter(ClientboundPlayerAbilitiesPacket.class, ReflectionMappingsInfo.ClientboundPlayerAbilitiesPacket_walkingSpeed);\r\n\r\n    public static MethodHandle ENTITY_METADATA_LIST_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundSetEntityDataPacket.class, List.class); // packedItems\r\n\r\n    public static Field ENTITY_TRACKER_ENTRY_GETTER = ReflectionHelper.getFields(ChunkMap.TrackedEntity.class).getFirstOfType(ServerEntity.class);\r\n\r\n    public static MethodHandle CANVAS_GET_BUFFER = ReflectionHelper.getMethodHandle(CraftMapCanvas.class, \"getBuffer\");\r\n    public static Field MAPVIEW_WORLDMAP = ReflectionHelper.getFields(CraftMapView.class).get(\"worldMap\");\r\n\r\n    public static MethodHandle BLOCK_ENTITY_DATA_PACKET_CONSTRUCTOR = ReflectionHelper.getConstructor(ClientboundBlockEntityDataPacket.class, BlockPos.class, BlockEntityType.class, net.minecraft.nbt.CompoundTag.class);\r\n\r\n    public static EntityDataAccessor<Optional<Component>> ENTITY_CUSTOM_NAME_METADATA;\r\n    public static EntityDataAccessor<Boolean> ENTITY_CUSTOM_NAME_VISIBLE_METADATA;\r\n\r\n    static {\r\n        try {\r\n            ENTITY_CUSTOM_NAME_METADATA = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_CUSTOM_NAME, null);\r\n            ENTITY_CUSTOM_NAME_VISIBLE_METADATA = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_CUSTOM_NAME_VISIBLE, null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setFakeAbsorption(Player player, float value) {\r\n        SynchedEntityData dw = new SynchedEntityData(null);\r\n        dw.define(ENTITY_HUMAN_DATA_WATCHER_ABSORPTION, value);\r\n        send(player, new ClientboundSetEntityDataPacket(player.getEntityId(), dw, true));\r\n    }\r\n\r\n    @Override\r\n    public void setSlot(Player player, int slot, ItemStack itemStack, boolean playerOnly) {\r\n        AbstractContainerMenu menu = ((CraftPlayer) player).getHandle().containerMenu;\r\n        int windowId = playerOnly ? 0 : menu.containerId;\r\n        send(player, new ClientboundContainerSetSlotPacket(windowId, menu.incrementStateId(), slot, CraftItemStack.asNMSCopy(itemStack)));\r\n    }\r\n\r\n    @Override\r\n    public void setFieldOfView(Player player, float fov) {\r\n        ClientboundPlayerAbilitiesPacket packet = new ClientboundPlayerAbilitiesPacket(((CraftPlayer) player).getHandle().getAbilities());\r\n        if (!Float.isNaN(fov)) {\r\n            try {\r\n                ABILITIES_PACKET_FOV_SETTER.invoke(packet, fov);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void respawn(Player player) {\r\n        ((CraftPlayer) player).getHandle().connection.handleClientCommand(new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN));\r\n    }\r\n\r\n    @Override\r\n    public void setVision(Player player, EntityType entityType) {\r\n        final net.minecraft.world.entity.LivingEntity entity;\r\n        if (entityType == EntityType.CREEPER) {\r\n            entity = new Creeper(net.minecraft.world.entity.EntityType.CREEPER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.SPIDER) {\r\n            entity = new Spider(net.minecraft.world.entity.EntityType.SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.CAVE_SPIDER) {\r\n            entity = new CaveSpider(net.minecraft.world.entity.EntityType.CAVE_SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.ENDERMAN) {\r\n            entity = new EnderMan(net.minecraft.world.entity.EntityType.ENDERMAN, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n\r\n        // Spectating an entity then immediately respawning the player prevents a client shader update,\r\n        // allowing the player to retain whatever vision the mob they spectated had.\r\n        send(player, new ClientboundAddMobPacket(entity));\r\n        send(player, new ClientboundSetCameraPacket(entity));\r\n        ((CraftServer) Bukkit.getServer()).getHandle().respawn(((CraftPlayer) player).getHandle(),\r\n                ((CraftWorld) player.getWorld()).getHandle(), true, player.getLocation(), false);\r\n    }\r\n\r\n    @Override\r\n    public void showBlockAction(Player player, Location location, int action, int state) {\r\n        BlockPos position = new BlockPos(location.getX(), location.getY(), location.getZ());\r\n        Block block = ((CraftWorld) location.getWorld()).getHandle().getBlockState(position).getBlock();\r\n        send(player, new ClientboundBlockEventPacket(position, block, action, state));\r\n    }\r\n\r\n    @Override\r\n    public void showBlockCrack(Player player, int id, Location location, int progress) {\r\n        BlockPos position = new BlockPos(location.getX(), location.getY(), location.getZ());\r\n        send(player, new ClientboundBlockDestructionPacket(id, position, progress));\r\n    }\r\n\r\n    @Override\r\n    public void showTileEntityData(Player player, Location location, int action, CompoundBinaryTag compoundTag) {\r\n        BlockPos position = new BlockPos(location.getX(), location.getY(), location.getZ());\r\n        try {\r\n            ClientboundBlockEntityDataPacket packet = (ClientboundBlockEntityDataPacket) BLOCK_ENTITY_DATA_PACKET_CONSTRUCTOR.invoke(position, action, NBTAdapter.toNMS(compoundTag));\r\n            send(player, packet);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void showBannerUpdate(Player player, Location location, List<Pattern> patterns) {\r\n        ListBinaryTag.Builder<CompoundBinaryTag> nbtPatterns = ListBinaryTag.builder(BinaryTagTypes.COMPOUND);\r\n        for (Pattern pattern : patterns) {\r\n            nbtPatterns.add(CompoundBinaryTag.builder()\r\n                    .putInt(\"Color\", pattern.getColor().getDyeData())\r\n                    .putString(\"Pattern\", pattern.getPattern().getIdentifier())\r\n                    .build());\r\n        }\r\n        CompoundBinaryTag compoundTag = NMSHandler.blockHelper.getNbtData(location.getBlock()).put(\"Patterns\", nbtPatterns.build());\r\n        showTileEntityData(player, location, 3, compoundTag);\r\n    }\r\n\r\n    @Override\r\n    public void showTabListHeaderFooter(Player player, String header, String footer) {\r\n        Component cHeader = Handler.componentToNMS(FormattedTextHelper.parse(header, ChatColor.WHITE));\r\n        Component cFooter = Handler.componentToNMS(FormattedTextHelper.parse(footer, ChatColor.WHITE));\r\n        ClientboundTabListPacket packet = new ClientboundTabListPacket(cHeader, cFooter);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void showTitle(Player player, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) {\r\n        send(player, new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks));\r\n        if (title != null) {\r\n            send(player, new ClientboundSetTitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE))));\r\n        }\r\n        if (subtitle != null) {\r\n            send(player, new ClientboundSetSubtitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(subtitle, ChatColor.WHITE))));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void resetEquipment(Player player, LivingEntity entity) {\r\n        EntityEquipment equipment = entity.getEquipment();\r\n        List<Pair<EquipmentSlot, net.minecraft.world.item.ItemStack>> pairList = new ArrayList<>();\r\n        pairList.add(new Pair<>(EquipmentSlot.MAINHAND, CraftItemStack.asNMSCopy(equipment.getItemInMainHand())));\r\n        pairList.add(new Pair<>(EquipmentSlot.OFFHAND, CraftItemStack.asNMSCopy(equipment.getItemInOffHand())));\r\n        pairList.add(new Pair<>(EquipmentSlot.HEAD, CraftItemStack.asNMSCopy(equipment.getHelmet())));\r\n        pairList.add(new Pair<>(EquipmentSlot.CHEST, CraftItemStack.asNMSCopy(equipment.getChestplate())));\r\n        pairList.add(new Pair<>(EquipmentSlot.LEGS, CraftItemStack.asNMSCopy(equipment.getLeggings())));\r\n        pairList.add(new Pair<>(EquipmentSlot.FEET, CraftItemStack.asNMSCopy(equipment.getBoots())));\r\n        send(player, new ClientboundSetEquipmentPacket(entity.getEntityId(), pairList));\r\n    }\r\n\r\n    @Override\r\n    public void showHealth(Player player, float health, int food, float saturation) {\r\n        send(player, new ClientboundSetHealthPacket(health, food, saturation));\r\n    }\r\n\r\n    @Override\r\n    public void showMobHealth(Player player, LivingEntity mob, double health, double maxHealth) {\r\n        AttributeInstance attr = new AttributeInstance(Attributes.MAX_HEALTH, (a) -> { });\r\n        attr.setBaseValue(maxHealth);\r\n        send(player, new ClientboundUpdateAttributesPacket(mob.getEntityId(), Collections.singletonList(attr)));\r\n        FriendlyByteBuf healthData = new FriendlyByteBuf(Unpooled.buffer());\r\n        healthData.writeVarInt(mob.getEntityId());\r\n        healthData.writeByte(9); // health id\r\n        healthData.writeVarInt(2); // type = float\r\n        healthData.writeFloat((float) health);\r\n        healthData.writeByte(255); // Mark end of packet\r\n        send(player, new ClientboundSetEntityDataPacket(healthData));\r\n    }\r\n\r\n    @Override\r\n    public void resetHealth(Player player) {\r\n        showHealth(player, (float) player.getHealth(), player.getFoodLevel(), player.getSaturation());\r\n    }\r\n\r\n    @Override\r\n    public void showSignEditor(Player player, Location location) {\r\n        LocationTag fakeSign = new LocationTag(player.getLocation());\r\n        fakeSign.setY(0);\r\n        FakeBlock.showFakeBlockTo(Collections.singletonList(new PlayerTag(player)), fakeSign, new MaterialTag(Material.OAK_WALL_SIGN), new DurationTag(1), true);\r\n        BlockPos pos = new BlockPos(fakeSign.getX(), 0, fakeSign.getZ());\r\n        ((DenizenNetworkManagerImpl) ((CraftPlayer) player).getHandle().connection.connection).packetListener.fakeSignExpected = pos;\r\n        send(player, new ClientboundOpenSignEditorPacket(pos));\r\n    }\r\n\r\n    @Override\r\n    public void forceSpectate(Player player, Entity entity) {\r\n        send(player, new ClientboundSetCameraPacket(((CraftEntity) entity).getHandle()));\r\n    }\r\n\r\n    public static void forceRespawnPlayerEntity(Entity entity, Player viewer) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        if (entityTracker != null) {\r\n            try {\r\n                ServerEntity entry = (ServerEntity) ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n                if (entry != null) {\r\n                    entry.removePairing(((CraftPlayer) viewer).getHandle());\r\n                    entry.addPairing(((CraftPlayer) viewer).getHandle());\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendRename(Player player, Entity entity, String name, boolean listMode) {\r\n        try {\r\n            if (entity.getType() == EntityType.PLAYER) {\r\n                if (listMode) {\r\n                    send(player, new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, ((CraftPlayer) player).getHandle()));\r\n                }\r\n                else {\r\n                    // For player entities, force a respawn packet and let the dynamic intercept correct the details\r\n                    forceRespawnPlayerEntity(entity, player);\r\n                }\r\n                return;\r\n            }\r\n            SynchedEntityData fakeData = new SynchedEntityData(((CraftEntity) entity).getHandle());\r\n            ClientboundSetEntityDataPacket packet = new ClientboundSetEntityDataPacket(entity.getEntityId(), fakeData, false);\r\n            List<SynchedEntityData.DataItem<?>> list = new ArrayList<>();\r\n            list.add(new SynchedEntityData.DataItem<>(ENTITY_CUSTOM_NAME_METADATA, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)))));\r\n            list.add(new SynchedEntityData.DataItem<>(ENTITY_CUSTOM_NAME_VISIBLE_METADATA, true));\r\n            ENTITY_METADATA_LIST_SETTER.invoke(packet, list);\r\n            send(player, packet);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static HashMap<UUID, HashMap<UUID, PlayerTeam>> noCollideTeamMap = new HashMap<>();\r\n\r\n    @Override\r\n    public void generateNoCollideTeam(Player player, UUID noCollide) {\r\n        removeNoCollideTeam(player, noCollide);\r\n        PlayerTeam team = new PlayerTeam(SidebarImpl.dummyScoreboard, Utilities.generateRandomColors(8));\r\n        team.getPlayers().add(noCollide.toString());\r\n        team.setCollisionRule(Team.CollisionRule.NEVER);\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>());\r\n        map.put(noCollide, team);\r\n        send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n    }\r\n\r\n    @Override\r\n    public void removeNoCollideTeam(Player player, UUID noCollide) {\r\n        if (noCollide == null || !player.isOnline()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n            return;\r\n        }\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.get(player.getUniqueId());\r\n        if (map == null) {\r\n            return;\r\n        }\r\n        PlayerTeam team = map.remove(noCollide);\r\n        if (team != null) {\r\n            send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        if (map.isEmpty()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityMetadataFlagsUpdate(Player player, Entity entity) {\r\n        SynchedEntityData dw = new SynchedEntityData(null);\r\n        dw.define(ENTITY_DATA_WATCHER_FLAGS, ((CraftEntity) entity).getHandle().getEntityData().get(ENTITY_DATA_WATCHER_FLAGS));\r\n        send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), dw, true));\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityEffect(Player player, Entity entity, EntityEffect effect) {\r\n        send(player, new ClientboundEntityEventPacket(((CraftEntity) entity).getHandle(), effect.getData()));\r\n    }\r\n\r\n    @Override\r\n    public int getPacketStats(Player player, boolean sent) {\r\n        DenizenNetworkManagerImpl netMan = (DenizenNetworkManagerImpl) ((CraftPlayer) player).getHandle().connection.connection;\r\n        return sent ? netMan.packetsSent : netMan.packetsReceived;\r\n    }\r\n\r\n    @Override\r\n    public void setMapData(MapCanvas canvas, byte[] bytes, int x, int y, MapImage image) {\r\n        if (x > 127 || y > 127) {\r\n            return;\r\n        }\r\n        int width = Math.min(image.width, 128 - x),\r\n                height = Math.min(image.height, 128 - y);\r\n        if (x + width <= 0 || y + height <= 0) {\r\n            return;\r\n        }\r\n        try {\r\n            boolean anyChanged = false;\r\n            byte[] buffer = (byte[]) CANVAS_GET_BUFFER.invoke(canvas);\r\n            for (int x2 = x < 0 ? -x : 0; x2 < width; ++x2) {\r\n                for (int y2 = y < 0 ? -y : 0; y2 < height; ++y2) {\r\n                    byte p = bytes[y2 * image.width + x2];\r\n                    if (p != MapPalette.TRANSPARENT) {\r\n                        int index = (y2 + y) * 128 + (x2 + x);\r\n                        if (buffer[index] != p) {\r\n                            buffer[index] = p;\r\n                            anyChanged = true;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            if (anyChanged) {\r\n                // Flag the whole image as dirty\r\n                MapItemSavedData map = (MapItemSavedData) MAPVIEW_WORLDMAP.get(canvas.getMapView());\r\n                map.setColorsDirty(Math.max(x, 0), Math.max(y, 0));\r\n                map.setColorsDirty(width + x - 1, height + y - 1);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setNetworkManagerFor(Player player) {\r\n        DenizenNetworkManagerImpl.setNetworkManager(player);\r\n    }\r\n\r\n    @Override\r\n    public void enableNetworkManager() {\r\n        DenizenNetworkManagerImpl.enableNetworkManager();\r\n    }\r\n\r\n    @Override\r\n    public void showDebugTestMarker(Player player, Location location, ColorTag color, String name, int time) {\r\n        ResourceLocation packetKey = new ResourceLocation(\"minecraft\", \"debug/game_test_add_marker\");\r\n        FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());\r\n        buf.writeBlockPos(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()));\r\n        int colorInt = color.blue | (color.green << 8) | (color.red << 16) | (color.alpha << 24);\r\n        buf.writeInt(colorInt);\r\n        buf.writeByteArray(name.getBytes(StandardCharsets.UTF_8));\r\n        buf.writeInt(time);\r\n        ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(packetKey, buf);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void clearDebugTestMarker(Player player) {\r\n        ResourceLocation packetKey = new ResourceLocation(\"minecraft\", \"debug/game_test_clear\");\r\n        FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());\r\n        ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(packetKey, buf);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendBrand(Player player, String brand) {\r\n        ResourceLocation packetKey = new ResourceLocation(\"minecraft\", \"brand\");\r\n        FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());\r\n        buf.writeUtf(brand);\r\n        ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(packetKey, buf);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendCollectItemEntity(Player player, Entity taker, Entity item, int amount) {\r\n        ClientboundTakeItemEntityPacket packet = new ClientboundTakeItemEntityPacket(item.getEntityId(), taker.getEntityId(), amount);\r\n        send(player, packet);\r\n    }\r\n\r\n    public static void send(Player player, Packet packet) {\r\n        ((CraftPlayer) player).getHandle().connection.send(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/PlayerHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.enums.CustomEntityType;\r\nimport com.denizenscript.denizen.nms.interfaces.PlayerHelper;\r\nimport com.denizenscript.denizen.nms.v1_18.Handler;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.ImprovedOfflinePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.network.handlers.AbstractListenerPlayInImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.entity.DenizenEntityType;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport it.unimi.dsi.fastutil.ints.IntList;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.players.ServerOpList;\r\nimport net.minecraft.server.players.ServerOpListEntry;\r\nimport net.minecraft.stats.ServerRecipeBook;\r\nimport net.minecraft.tags.BlockTags;\r\nimport net.minecraft.tags.TagNetworkSerialization;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.item.crafting.Recipe;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.GameType;\r\nimport net.minecraft.world.level.Level;\r\nimport org.bukkit.*;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.boss.CraftBossBar;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class PlayerHelperImpl extends PlayerHelper {\r\n\r\n    public static final Field ATTACK_COOLDOWN_TICKS = ReflectionHelper.getFields(LivingEntity.class).get(ReflectionMappingsInfo.LivingEntity_attackStrengthTicker, int.class);\r\n\r\n    public static final Field FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundTickCount, int.class);\r\n    public static final Field VEHICLE_FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundVehicleTickCount, int.class);\r\n    public static final MethodHandle PLAYER_RESPAWNFORCED_SETTER = ReflectionHelper.getFinalSetter(ServerPlayer.class, ReflectionMappingsInfo.ServerPlayer_respawnForced, boolean.class);\r\n\r\n    public static final EntityDataAccessor<Byte> ENTITY_HUMAN_SKINLAYERS_DATAWATCHER;\r\n\r\n    static {\r\n        EntityDataAccessor<Byte> skinlayers = null;\r\n        try {\r\n            skinlayers = (EntityDataAccessor<Byte>) ReflectionHelper.getFields(net.minecraft.world.entity.player.Player.class).get(ReflectionMappingsInfo.Player_DATA_PLAYER_MODE_CUSTOMISATION).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n        ENTITY_HUMAN_SKINLAYERS_DATAWATCHER = skinlayers;\r\n    }\r\n\r\n    @Override\r\n    public void stopSound(Player player, NamespacedKey sound, SoundCategory category) {\r\n        ResourceLocation soundKey = sound == null ? null : CraftNamespacedKey.toMinecraft(sound);\r\n        net.minecraft.sounds.SoundSource nmsCategory = category == null ? null : net.minecraft.sounds.SoundSource.valueOf(category.name());\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundStopSoundPacket(soundKey, nmsCategory));\r\n    }\r\n\r\n    @Override\r\n    public void deTrackEntity(Player player, Entity entity) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerLevel world = (ServerLevel) nmsPlayer.level;\r\n        ChunkMap.TrackedEntity tracker = world.getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n        if (tracker == null) {\r\n            if (NMSHandler.debugPackets) {\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Failed to de-track entity \" + entity.getEntityId() + \" for \" + player.getName() + \": tracker null\");\r\n            }\r\n            return;\r\n        }\r\n        sendEntityDestroy(player, entity);\r\n        tracker.removePlayer(nmsPlayer);\r\n    }\r\n\r\n    public static class TrackerData {\r\n        public PlayerTag player;\r\n        public ServerEntity tracker;\r\n    }\r\n\r\n    @Override\r\n    public FakeEntity sendEntitySpawn(List<PlayerTag> players, DenizenEntityType entityType, LocationTag location, ArrayList<Mechanism> mechanisms, int customId, UUID customUUID, boolean autoTrack) {\r\n        CraftWorld world = ((CraftWorld) location.getWorld());\r\n        net.minecraft.world.entity.Entity nmsEntity;\r\n        if (entityType.isCustom()) {\r\n            if (entityType.customEntityType == CustomEntityType.ITEM_PROJECTILE) {\r\n                org.bukkit.inventory.ItemStack itemStack = new ItemStack(Material.STONE);\r\n                for (Mechanism mechanism : mechanisms) {\r\n                    if (mechanism.matches(\"item\") && mechanism.requireObject(ItemTag.class)) {\r\n                        itemStack = mechanism.valueAsType(ItemTag.class).getItemStack();\r\n                    }\r\n                }\r\n                nmsEntity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n            }\r\n            else if (entityType.customEntityType == CustomEntityType.FAKE_PLAYER) {\r\n                String name = null;\r\n                String skin = null;\r\n                String blob = null;\r\n                for (Mechanism mechanism : new ArrayList<>(mechanisms)) {\r\n                    if (mechanism.matches(\"name\")) {\r\n                        name = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin\")) {\r\n                        skin = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin_blob\")) {\r\n                        blob = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    if (name != null && (skin != null || blob != null)) {\r\n                        break;\r\n                    }\r\n                }\r\n                nmsEntity = ((CraftFakePlayerImpl) NMSHandler.customEntityHelper.spawnFakePlayer(location, name, skin, blob, false)).getHandle();\r\n            }\r\n            else {\r\n                throw new IllegalArgumentException(\"entityType\");\r\n            }\r\n        }\r\n        else {\r\n            nmsEntity = world.createEntity(location, entityType.getBukkitEntityType().getEntityClass());\r\n        }\r\n        if (customUUID != null) {\r\n            nmsEntity.setId(customId);\r\n            nmsEntity.setUUID(customUUID);\r\n        }\r\n        EntityTag entity = new EntityTag(nmsEntity.getBukkitEntity());\r\n        entity.isFake = true;\r\n        entity.isFakeValid = true;\r\n        for (Mechanism mechanism : mechanisms) {\r\n            entity.safeAdjustDuplicate(mechanism);\r\n        }\r\n        nmsEntity.unsetRemoved();\r\n        FakeEntity fake = new FakeEntity(players, location, entity.getBukkitEntity().getEntityId());\r\n        fake.entity = new EntityTag(entity.getBukkitEntity());\r\n        fake.entity.isFake = true;\r\n        fake.entity.isFakeValid = true;\r\n        List<TrackerData> trackers = new ArrayList<>();\r\n        fake.triggerSpawnPacket = (player) -> {\r\n            ServerPlayer nmsPlayer = ((CraftPlayer) player.getPlayerEntity()).getHandle();\r\n            ServerGamePacketListenerImpl conn = nmsPlayer.connection;\r\n            final ServerEntity tracker = new ServerEntity(world.getHandle(), nmsEntity, 1, true, conn::send, Collections.singleton(nmsPlayer.connection));\r\n            tracker.addPairing(nmsPlayer);\r\n            final TrackerData data = new TrackerData();\r\n            data.player = player;\r\n            data.tracker = tracker;\r\n            trackers.add(data);\r\n            if (autoTrack) {\r\n                new BukkitRunnable() {\r\n                    boolean wasOnline = true;\r\n                    @Override\r\n                    public void run() {\r\n                        if (!fake.entity.isFakeValid) {\r\n                            cancel();\r\n                            return;\r\n                        }\r\n                        if (player.isOnline()) {\r\n                            if (!wasOnline) {\r\n                                tracker.addPairing(((CraftPlayer) player.getPlayerEntity()).getHandle());\r\n                                wasOnline = true;\r\n                            }\r\n                            tracker.sendChanges();\r\n                        }\r\n                        else if (wasOnline) {\r\n                            wasOnline = false;\r\n                        }\r\n                    }\r\n                }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n            }\r\n        };\r\n        for (PlayerTag player : players) {\r\n            fake.triggerSpawnPacket.accept(player);\r\n        }\r\n        fake.triggerUpdatePacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.sendChanges();\r\n                }\r\n            }\r\n        };\r\n        fake.triggerDestroyPacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.removePairing(((CraftPlayer) tracker.player.getPlayerEntity()).getHandle());\r\n                }\r\n            }\r\n            trackers.clear();\r\n        };\r\n        return fake;\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityDestroy(Player player, Entity entity) {\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId()));\r\n    }\r\n\r\n    @Override\r\n    public int getFlyKickCooldown(Player player) {\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl) {\r\n            conn = ((AbstractListenerPlayInImpl) conn).oldListener;\r\n        }\r\n        try {\r\n            return Math.max(80 - Math.max(FLY_TICKS.getInt(conn), VEHICLE_FLY_TICKS.getInt(conn)), 0);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return 80;\r\n    }\r\n\r\n    @Override\r\n    public void setFlyKickCooldown(Player player, int ticks) {\r\n        ticks = 80 - ticks;\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl) {\r\n            conn = ((AbstractListenerPlayInImpl) conn).oldListener;\r\n        }\r\n        try {\r\n            FLY_TICKS.setInt(conn, ticks);\r\n            VEHICLE_FLY_TICKS.setInt(conn, ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int ticksPassedDuringCooldown(Player player) {\r\n        try {\r\n            return ATTACK_COOLDOWN_TICKS.getInt(((CraftPlayer) player).getHandle());\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public float getMaxAttackCooldownTicks(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getCurrentItemAttackStrengthDelay() + 3;\r\n    }\r\n\r\n    @Override\r\n    public void setAttackCooldown(Player player, int ticks) {\r\n        try {\r\n            ATTACK_COOLDOWN_TICKS.setInt(((CraftPlayer) player).getHandle(), ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean hasChunkLoaded(Player player, Chunk chunk) {\r\n        return ((CraftWorld) chunk.getWorld()).getHandle().getChunkSource().chunkMap\r\n                .getPlayers(new ChunkPos(chunk.getX(), chunk.getZ()), false).stream()\r\n                .anyMatch(entityPlayer -> entityPlayer.getUUID().equals(player.getUniqueId()));\r\n    }\r\n\r\n    @Override\r\n    public void setTemporaryOp(Player player, boolean op) {\r\n        MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();\r\n        GameProfile profile = ((CraftPlayer) player).getProfile();\r\n        ServerOpList opList = server.getPlayerList().getOps();\r\n        if (op) {\r\n            int permLevel = server.getOperatorUserPermissionLevel();\r\n            opList.add(new ServerOpListEntry(profile, permLevel, opList.canBypassPlayerLimit(profile)));\r\n        }\r\n        else {\r\n            opList.remove(profile);\r\n        }\r\n        player.recalculatePermissions();\r\n    }\r\n\r\n    @Override\r\n    public void showEndCredits(Player player) {\r\n        ((CraftPlayer) player).getHandle().wonGame = true;\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1f));\r\n    }\r\n\r\n    @Override\r\n    public ImprovedOfflinePlayer getOfflineData(UUID uuid) {\r\n        return new ImprovedOfflinePlayerImpl(uuid);\r\n    }\r\n\r\n    @Override\r\n    public void resendRecipeDetails(Player player) {\r\n        Collection<Recipe<?>> recipes = ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().getRecipes();\r\n        ClientboundUpdateRecipesPacket updatePacket = new ClientboundUpdateRecipesPacket(recipes);\r\n        ((CraftPlayer) player).getHandle().connection.send(updatePacket);\r\n    }\r\n\r\n    @Override\r\n    public void resendDiscoveredRecipes(Player player) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        recipeBook.sendInitialRecipeBook(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    @Override\r\n    public void quietlyAddRecipe(Player player, NamespacedKey key) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        Recipe<?> recipe = ItemHelperImpl.getNMSRecipe(key);\r\n        if (recipe == null) {\r\n            Debug.echoError(\"Cannot add recipe '\" + key + \"': it does not exist.\");\r\n            return;\r\n        }\r\n        recipeBook.add(recipe);\r\n        recipeBook.addHighlight(recipe);\r\n    }\r\n\r\n    @Override\r\n    public String getClientBrand(Player player) {\r\n        return ((DenizenNetworkManagerImpl) ((CraftPlayer) player).getHandle().connection.connection).packetListener.brand;\r\n    }\r\n\r\n    @Override\r\n    public byte getSkinLayers(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getEntityData().get(ENTITY_HUMAN_SKINLAYERS_DATAWATCHER);\r\n    }\r\n\r\n    @Override\r\n    public void setSkinLayers(Player player, byte flags) {\r\n        ((CraftPlayer) player).getHandle().getEntityData().set(ENTITY_HUMAN_SKINLAYERS_DATAWATCHER, flags);\r\n    }\r\n\r\n    @Override\r\n    public void setBossBarTitle(BossBar bar, String title) {\r\n        ((CraftBossBar) bar).getHandle().name = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        ((CraftBossBar) bar).getHandle().broadcast(ClientboundBossEventPacket::createUpdateNamePacket);\r\n    }\r\n\r\n    @Override\r\n    public boolean getSpawnForced(Player player) {\r\n        return ((CraftPlayer) player).getHandle().isRespawnForced();\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnForced(Player player, boolean forced) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        try {\r\n            PLAYER_RESPAWNFORCED_SETTER.invoke(nmsPlayer, forced);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Location getBedSpawnLocation(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        BlockPos spawnPosition = nmsPlayer.getRespawnPosition();\r\n        if (spawnPosition == null) {\r\n            return null;\r\n        }\r\n        Level nmsWorld = MinecraftServer.getServer().getLevel(nmsPlayer.getRespawnDimension());\r\n        if (nmsWorld == null) {\r\n            return null;\r\n        }\r\n        return new Location(nmsWorld.getWorld(), spawnPosition.getX(), spawnPosition.getY(), spawnPosition.getZ(), nmsPlayer.getRespawnAngle(), 0);\r\n    }\r\n\r\n    @Override\r\n    public long getLastActionTime(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getLastActionTime();\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerInfoAddPacket(Player player, EnumSet<ProfileEditMode> editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) {\r\n        ProfileEditMode editMode = editModes.stream().findFirst().get();\r\n        ClientboundPlayerInfoPacket.Action action = editMode == ProfileEditMode.ADD ? ClientboundPlayerInfoPacket.Action.ADD_PLAYER :\r\n                (editMode == ProfileEditMode.UPDATE_DISPLAY ? ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME : ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY);\r\n        ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(action);\r\n        GameProfile profile = new GameProfile(id, name);\r\n        if (texture != null) {\r\n            profile.getProperties().put(\"textures\", new Property(\"textures\", texture, signature));\r\n        }\r\n        packet.getEntries().add(new ClientboundPlayerInfoPacket.PlayerUpdate(profile, latency, gameMode == null ? null : GameType.byId(gameMode.getValue()), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE))));\r\n        PacketHelperImpl.send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerInfoRemovePacket(Player player, UUID id) {\r\n        ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.REMOVE_PLAYER);\r\n        GameProfile profile = new GameProfile(id, \"name\");\r\n        packet.getEntries().add(new ClientboundPlayerInfoPacket.PlayerUpdate(profile, 0, null, null));\r\n        PacketHelperImpl.send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendClimbableMaterials(Player player, List<Material> materials) {\r\n        Map<ResourceKey<? extends Registry<?>>, TagNetworkSerialization.NetworkPayload> packetInput = TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registryAccess());\r\n        Map<ResourceLocation, IntList> tags = ReflectionHelper.getFieldValue(TagNetworkSerialization.NetworkPayload.class, ReflectionMappingsInfo.TagNetworkSerialization_NetworkPayload_tags, packetInput.get(Registry.BLOCK_REGISTRY));\r\n        IntList intList = tags.get(BlockTags.CLIMBABLE.location());\r\n        intList.clear();\r\n        for (Material material : materials) {\r\n            intList.add(Registry.BLOCK.getId(CraftMagicNumbers.getBlock(material)));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundUpdateTagsPacket(packetInput));\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/WorldHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.WorldHelper;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.objects.BiomeTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.mojang.datafixers.util.Pair;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.players.SleepStatus;\r\nimport net.minecraft.world.DifficultyInstance;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.storage.PrimaryLevelData;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\n\r\npublic class WorldHelperImpl implements WorldHelper {\r\n\r\n    @Override\r\n    public boolean isStatic(World world) {\r\n        return ((CraftWorld) world).getHandle().isClientSide;\r\n    }\r\n\r\n    @Override\r\n    public void setStatic(World world, boolean isStatic) {\r\n        ServerLevel worldServer = ((CraftWorld) world).getHandle();\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.Level.class, ReflectionMappingsInfo.Level_isClientSide, worldServer, isStatic);\r\n    }\r\n\r\n    @Override\r\n    public float getLocalDifficulty(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        DifficultyInstance scaler = ((CraftWorld) location.getWorld()).getHandle().getCurrentDifficultyAt(pos);\r\n        return scaler.getEffectiveDifficulty();\r\n    }\r\n\r\n    @Override\r\n    public Location getNearestBiomeLocation(Location start, BiomeTag biome) {\r\n        Pair<BlockPos, Holder<Biome>> result = ((CraftWorld) start.getWorld()).getHandle()\r\n                .findNearestBiome(b -> b.is(((BiomeNMSImpl) biome.getBiome()).biomeBase.unwrapKey().get()), new BlockPos(start.getBlockX(), start.getBlockY(), start.getBlockZ()), 6400, 8);\r\n        if (result == null || result.getFirst() == null) {\r\n            return null;\r\n        }\r\n        return new Location(start.getWorld(), result.getFirst().getX(), result.getFirst().getY(), result.getFirst().getZ());\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughSleeping(World world, int percentage) {\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, ((CraftWorld) world).getHandle());\r\n        return status.areEnoughSleeping(percentage);\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughDeepSleeping(World world, int percentage) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, level);\r\n        return status.areEnoughDeepSleeping(percentage, level.players());\r\n    }\r\n\r\n    @Override\r\n    public int getSkyDarken(World world) {\r\n        return ((CraftWorld) world).getHandle().getSkyDarken();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDay(World world) {\r\n        return ((CraftWorld) world).getHandle().isDay();\r\n    }\r\n\r\n    @Override\r\n    public boolean isNight(World world) {\r\n        return ((CraftWorld) world).getHandle().isNight();\r\n    }\r\n\r\n    @Override\r\n    public void setDayTime(World world, long time) {\r\n        ((CraftWorld) world).getHandle().setDayTime(time);\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#wakeUpAllPlayers()\r\n    @Override\r\n    public void wakeUpAllPlayers(World world) {\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, nmsWorld);\r\n        status.removeAllSleepers();\r\n        nmsWorld.getPlayers(LivingEntity::isSleeping).forEach((player) -> player.stopSleepInBed(false, false));\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#resetWeatherCycle()\r\n    @Override\r\n    public void clearWeather(World world) {\r\n        PrimaryLevelData data = ((CraftWorld) world).getHandle().M;\r\n        data.setRaining(false);\r\n        if (!data.isRaining()) {\r\n            data.setRainTime(0);\r\n        }\r\n        data.setThundering(false);\r\n        if (!data.isThundering()) {\r\n            data.setThunderTime(0);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/BiomeNMSImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.random.WeightedRandomList;\r\nimport net.minecraft.world.entity.MobCategory;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.BiomeSpecialEffects;\r\nimport net.minecraft.world.level.biome.MobSpawnSettings;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Locale;\r\nimport java.util.Optional;\r\n\r\npublic class BiomeNMSImpl extends BiomeNMS {\r\n\r\n    public Holder<Biome> biomeBase;\r\n\r\n    public ServerLevel world;\r\n\r\n    public BiomeNMSImpl(ServerLevel world, NamespacedKey key) {\r\n        super(world.getWorld(), key);\r\n        this.world = world;\r\n        biomeBase = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getHolder(ResourceKey.create(Registry.BIOME_REGISTRY, CraftNamespacedKey.toMinecraft(key))).orElse(null);\r\n    }\r\n\r\n    @Override\r\n    public DownfallType getDownfallType() {\r\n        Biome.Precipitation nmsType = biomeBase.value().getPrecipitation();\r\n        return switch (nmsType) {\r\n            case RAIN -> DownfallType.RAIN;\r\n            case SNOW -> DownfallType.SNOW;\r\n            case NONE -> DownfallType.NONE;\r\n            default -> throw new UnsupportedOperationException();\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public float getHumidity() {\r\n        return biomeBase.value().getDownfall();\r\n    }\r\n\r\n    @Override\r\n    public float getBaseTemperature() {\r\n        return biomeBase.value().getBaseTemperature();\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getAmbientEntities() {\r\n        return getSpawnableEntities(MobCategory.AMBIENT);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getCreatureEntities() {\r\n        return getSpawnableEntities(MobCategory.CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getMonsterEntities() {\r\n        return getSpawnableEntities(MobCategory.MONSTER);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getWaterEntities() {\r\n        return getSpawnableEntities(MobCategory.WATER_CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public int getFoliageColor() {\r\n        // Check if the biome already has a default color\r\n        if (biomeBase.value().getFoliageColor() != 0) {\r\n            return biomeBase.value().getFoliageColor();\r\n        }\r\n        // Based on net.minecraft.world.level.biome.Biome#getFoliageColorFromTexture()\r\n        float temperature = clampColor(getBaseTemperature());\r\n        float humidity = clampColor(getHumidity());\r\n        // Based on net.minecraft.world.level.FoliageColor#get()\r\n        humidity *= temperature;\r\n        int humidityValue = (int)((1.0f - humidity) * 255.0f);\r\n        int temperatureValue = (int)((1.0f - temperature) * 255.0f);\r\n        int index = temperatureValue << 8 | humidityValue;\r\n        return index >= 65536 ? 4764952 : getColor(index / 256, index % 256).asRGB();\r\n    }\r\n\r\n    public Object getClimate() {\r\n        return ReflectionHelper.getFieldValue(net.minecraft.world.level.biome.Biome.class, ReflectionMappingsInfo.Biome_climateSettings, biomeBase.value());\r\n    }\r\n\r\n    @Override\r\n    public void setHumidity(float humidity) {\r\n        Object climate = getClimate();\r\n        ReflectionHelper.setFieldValue(climate.getClass(), ReflectionMappingsInfo.Biome_ClimateSettings_downfall, climate, humidity);\r\n    }\r\n\r\n    @Override\r\n    public void setBaseTemperature(float temperature) {\r\n        Object climate = getClimate();\r\n        ReflectionHelper.setFieldValue(climate.getClass(), ReflectionMappingsInfo.Biome_ClimateSettings_temperature, climate, temperature);\r\n    }\r\n\r\n    @Override\r\n    public void setPrecipitation(DownfallType type) {\r\n        Biome.Precipitation nmsType = switch (type) {\r\n            case RAIN -> Biome.Precipitation.RAIN;\r\n            case SNOW -> Biome.Precipitation.SNOW;\r\n            case NONE -> Biome.Precipitation.NONE;\r\n            default -> throw new UnsupportedOperationException();\r\n        };\r\n        Object climate = getClimate();\r\n        ReflectionHelper.setFieldValue(climate.getClass(), ReflectionMappingsInfo.Biome_ClimateSettings_precipitation, climate, nmsType);\r\n    }\r\n\r\n    @Override\r\n    public void setFoliageColor(int color) {\r\n        try {\r\n            ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_foliageColorOverride, biomeBase.value().getSpecialEffects(), Optional.of(color));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    private List<EntityType> getSpawnableEntities(MobCategory creatureType) {\r\n        MobSpawnSettings mobs = biomeBase.value().getMobSettings();\r\n        WeightedRandomList<MobSpawnSettings.SpawnerData> typeSettingList = mobs.getMobs(creatureType);\r\n        List<EntityType> entityTypes = new ArrayList<>();\r\n        if (typeSettingList == null) {\r\n            return entityTypes;\r\n        }\r\n        for (MobSpawnSettings.SpawnerData meta : typeSettingList.unwrap()) {\r\n            try {\r\n                String n = net.minecraft.world.entity.EntityType.getKey(meta.type).getPath();\r\n                EntityType et = EntityType.fromName(n);\r\n                if (et == null) {\r\n                    et = EntityType.valueOf(n.toUpperCase(Locale.ENGLISH));\r\n                }\r\n                entityTypes.add(et);\r\n            }\r\n            catch (Throwable e) {\r\n                // Ignore the error. Likely from invalid entity type name output.\r\n            }\r\n        }\r\n        return entityTypes;\r\n    }\r\n\r\n    @Override\r\n    public void setTo(Block block) {\r\n        if (((CraftWorld) block.getWorld()).getHandle() != this.world) {\r\n            NMSHandler.instance.getBiomeNMS(block.getWorld(), getKey()).setTo(block);\r\n            return;\r\n        }\r\n        // Based on CraftWorld source\r\n        BlockPos pos = new BlockPos(block.getX(), 0, block.getZ());\r\n        if (world.hasChunkAt(pos)) {\r\n            LevelChunk chunk = world.getChunkAt(pos);\r\n            if (chunk != null) {\r\n                chunk.setBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2, biomeBase);\r\n                chunk.setUnsaved(true);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/ImprovedOfflinePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.v1_18.helpers.NBTAdapter;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.kyori.adventure.nbt.BinaryTagTypes;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.nbt.ListTag;\r\nimport net.minecraft.nbt.NbtIo;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeMap;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.attributes.DefaultAttributes;\r\nimport net.minecraft.world.inventory.PlayerEnderChestContainer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryPlayer;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryHolder;\r\n\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.FileOutputStream;\r\nimport java.util.UUID;\r\n\r\npublic class ImprovedOfflinePlayerImpl extends ImprovedOfflinePlayer {\r\n\r\n    public ImprovedOfflinePlayerImpl(UUID playeruuid) {\r\n        super(playeruuid);\r\n    }\r\n\r\n    public static class OfflinePlayerInventory extends net.minecraft.world.entity.player.Inventory {\r\n\r\n        public OfflinePlayerInventory(net.minecraft.world.entity.player.Player entityhuman) {\r\n            super(entityhuman);\r\n        }\r\n\r\n        @Override\r\n        public InventoryHolder getOwner() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static class OfflineCraftInventoryPlayer extends CraftInventoryPlayer {\r\n\r\n        public OfflineCraftInventoryPlayer(net.minecraft.world.entity.player.Inventory inventory) {\r\n            super(inventory);\r\n        }\r\n\r\n        @Override\r\n        public HumanEntity getHolder() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public org.bukkit.inventory.PlayerInventory getInventory() {\r\n        if (inventory == null) {\r\n            net.minecraft.world.entity.player.Inventory newInv = new OfflinePlayerInventory(null);\r\n            newInv.load(NBTAdapter.toNMS(this.compound.getList(\"Inventory\", BinaryTagTypes.COMPOUND)));\r\n            inventory = new OfflineCraftInventoryPlayer(newInv);\r\n        }\r\n        return inventory;\r\n    }\r\n\r\n    @Override\r\n    public void setInventory(org.bukkit.inventory.PlayerInventory inventory) {\r\n        CraftInventoryPlayer inv = (CraftInventoryPlayer) inventory;\r\n        this.compound = compound.put(\"Inventory\", NBTAdapter.toAPI(inv.getInventory().save(new ListTag())));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    public Inventory getEnderChest() {\r\n        if (enderchest == null) {\r\n            PlayerEnderChestContainer endchest = new PlayerEnderChestContainer(null);\r\n            endchest.fromTag(NBTAdapter.toNMS(this.compound.getList(\"EnderItems\", BinaryTagTypes.COMPOUND)));\r\n            enderchest = new CraftInventory(endchest);\r\n        }\r\n        return enderchest;\r\n    }\r\n\r\n    @Override\r\n    public void setEnderChest(Inventory inventory) {\r\n        this.compound = compound.put(\"EnderItems\", NBTAdapter.toAPI(((PlayerEnderChestContainer) ((CraftInventory) inventory).getInventory()).createTag()));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    public double getMaxHealth() {\r\n        AttributeInstance maxHealth = getAttributes().getInstance(Attributes.MAX_HEALTH);\r\n        return maxHealth == null ? Attributes.MAX_HEALTH.getDefaultValue() : maxHealth.getValue();\r\n    }\r\n\r\n    @Override\r\n    public void setMaxHealth(double input) {\r\n        AttributeMap attributes = getAttributes();\r\n        AttributeInstance maxHealth = attributes.getInstance(Attributes.MAX_HEALTH);\r\n        maxHealth.setBaseValue(input);\r\n        setAttributes(attributes);\r\n    }\r\n\r\n    private AttributeMap getAttributes() {\r\n        AttributeMap amb = new AttributeMap(DefaultAttributes.getSupplier(net.minecraft.world.entity.EntityType.PLAYER));\r\n        amb.load(NBTAdapter.toNMS(this.compound.getList(\"Attributes\", BinaryTagTypes.COMPOUND)));\r\n        return amb;\r\n    }\r\n\r\n    public void setAttributes(AttributeMap attributes) {\r\n        this.compound = compound.put(\"Attributes\", NBTAdapter.toAPI(attributes.save()));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    protected boolean loadPlayerData(UUID uuid) {\r\n        try {\r\n            this.player = uuid;\r\n            for (org.bukkit.World w : Bukkit.getWorlds()) {\r\n                this.file = new File(w.getWorldFolder(), \"playerdata\" + File.separator + this.player + \".dat\");\r\n                if (this.file.exists()) {\r\n                    this.compound = NBTAdapter.toAPI(NbtIo.readCompressed(new FileInputStream(this.file)));\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void saveInternal(CompoundBinaryTag compound) {\r\n        try {\r\n            NbtIo.writeCompressed(NBTAdapter.toNMS(compound), new FileOutputStream(this.file));\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/ProfileEditorImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.v1_18.Handler;\r\nimport com.denizenscript.denizen.nms.v1_18.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic class ProfileEditorImpl extends ProfileEditor {\r\n\r\n    @Override\r\n    protected void updatePlayer(final Player player, final boolean isSkinChanging) {\r\n        final ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle();\r\n        final UUID uuid = player.getUniqueId();\r\n        ClientboundPlayerInfoPacket playerInfo = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, entityPlayer);\r\n        for (Player otherPlayer : Bukkit.getServer().getOnlinePlayers()) {\r\n            PacketHelperImpl.send(otherPlayer, playerInfo);\r\n        }\r\n        for (Player otherPlayer : NMSHandler.entityHelper.getPlayersThatSee(player)) {\r\n            if (!otherPlayer.getUniqueId().equals(uuid)) {\r\n                PacketHelperImpl.forceRespawnPlayerEntity(player, otherPlayer);\r\n            }\r\n        }\r\n        if (isSkinChanging) {\r\n            ((CraftServer) Bukkit.getServer()).getHandle().respawn(entityPlayer, (ServerLevel) entityPlayer.level, true, player.getLocation(), false);\r\n        }\r\n        player.updateInventory();\r\n    }\r\n\r\n    public static boolean handleAlteredProfiles(ClientboundPlayerInfoPacket packet, DenizenNetworkManagerImpl manager) {\r\n        if (ProfileEditor.mirrorUUIDs.isEmpty() && !RenameCommand.hasAnyDynamicRenames()) {\r\n            return true;\r\n        }\r\n        ClientboundPlayerInfoPacket.Action action = packet.getAction();\r\n        if (action != ClientboundPlayerInfoPacket.Action.ADD_PLAYER && action != ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {\r\n            return true;\r\n        }\r\n        List<ClientboundPlayerInfoPacket.PlayerUpdate> dataList = packet.getEntries();\r\n        if (dataList == null) {\r\n            return true;\r\n        }\r\n        try {\r\n            boolean any = false;\r\n            for (ClientboundPlayerInfoPacket.PlayerUpdate data : dataList) {\r\n                if (ProfileEditor.mirrorUUIDs.contains(data.getProfile().getId()) || RenameCommand.customNames.containsKey(data.getProfile().getId())) {\r\n                    any = true;\r\n                }\r\n            }\r\n            if (!any) {\r\n                return true;\r\n            }\r\n            GameProfile ownProfile = manager.player.getGameProfile();\r\n            for (ClientboundPlayerInfoPacket.PlayerUpdate data : dataList) {\r\n                if (!ProfileEditor.mirrorUUIDs.contains(data.getProfile().getId()) && !RenameCommand.customNames.containsKey(data.getProfile().getId())) {\r\n                    ClientboundPlayerInfoPacket newPacket = new ClientboundPlayerInfoPacket(action);\r\n                    List<ClientboundPlayerInfoPacket.PlayerUpdate> newPacketDataList = newPacket.getEntries();\r\n                    newPacketDataList.add(data);\r\n                    manager.oldManager.send(newPacket);\r\n                }\r\n                else {\r\n                    String rename = RenameCommand.getCustomNameFor(data.getProfile().getId(), manager.player.getBukkitEntity(), false);\r\n                    ClientboundPlayerInfoPacket newPacket = new ClientboundPlayerInfoPacket(action);\r\n                    List<ClientboundPlayerInfoPacket.PlayerUpdate> newPacketDataList = newPacket.getEntries();\r\n                    GameProfile patchedProfile = new GameProfile(data.getProfile().getId(), rename != null ? (rename.length() > 16 ? rename.substring(0, 16) : rename) : data.getProfile().getName());\r\n                    if (ProfileEditor.mirrorUUIDs.contains(data.getProfile().getId())) {\r\n                        patchedProfile.getProperties().putAll(ownProfile.getProperties());\r\n                    }\r\n                    else {\r\n                        patchedProfile.getProperties().putAll(data.getProfile().getProperties());\r\n                    }\r\n                    String listRename = RenameCommand.getCustomNameFor(data.getProfile().getId(), manager.player.getBukkitEntity(), true);\r\n                    Component displayName = listRename != null ? Handler.componentToNMS(FormattedTextHelper.parse(listRename, ChatColor.WHITE)) : data.getDisplayName();\r\n                    ClientboundPlayerInfoPacket.PlayerUpdate newData = new ClientboundPlayerInfoPacket.PlayerUpdate(patchedProfile, data.getLatency(), data.getGameMode(), displayName);\r\n                    newPacketDataList.add(newData);\r\n                    manager.oldManager.send(newPacket);\r\n                }\r\n            }\r\n            return false;\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n            return true;\r\n        }\r\n    }\r\n\r\n    public static void updatePlayerProfiles(ClientboundPlayerInfoPacket packet) {\r\n        ClientboundPlayerInfoPacket.Action action = packet.getAction();\r\n        if (action != ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {\r\n            return;\r\n        }\r\n        List<ClientboundPlayerInfoPacket.PlayerUpdate> dataList = packet.getEntries();\r\n        if (dataList != null) {\r\n            try {\r\n                for (ClientboundPlayerInfoPacket.PlayerUpdate data : dataList) {\r\n                    GameProfile gameProfile = data.getProfile();\r\n                    if (fakeProfiles.containsKey(gameProfile.getId())) {\r\n                        playerInfoData_gameProfile_Setter.invoke(data, getGameProfile(fakeProfiles.get(gameProfile.getId())));\r\n                    }\r\n                }\r\n            }\r\n            catch (Throwable e) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n    }\r\n\r\n    private static GameProfile getGameProfile(PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        if (playerProfile.hasTexture()) {\r\n            gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n        }\r\n        return gameProfile;\r\n    }\r\n\r\n    public static final MethodHandle playerInfoData_gameProfile_Setter = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundPlayerInfoPacket.PlayerUpdate.class, GameProfile.class);\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/SidebarImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl;\r\n\r\nimport com.denizenscript.denizen.nms.v1_18.Handler;\r\nimport com.denizenscript.denizen.nms.v1_18.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.MutableComponent;\r\nimport net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetScorePacket;\r\nimport net.minecraft.server.ServerScoreboard;\r\nimport net.minecraft.world.scores.Objective;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Scoreboard;\r\nimport net.minecraft.world.scores.criteria.ObjectiveCriteria;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.reflect.Constructor;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class SidebarImpl extends Sidebar {\r\n\r\n    public static Scoreboard dummyScoreboard = new Scoreboard();\r\n    public static ObjectiveCriteria dummyCriteria;\r\n\r\n    static {\r\n        try {\r\n            Constructor<ObjectiveCriteria> constructor = ObjectiveCriteria.class.getDeclaredConstructor(String.class);\r\n            constructor.setAccessible(true);\r\n            dummyCriteria = constructor.newInstance(\"dummy\");\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public Objective obj1;\r\n    public Objective obj2;\r\n\r\n    public SidebarImpl(Player player) {\r\n        super(player);\r\n        MutableComponent chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        this.obj1 = new Objective(dummyScoreboard, \"dummy_1\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER);\r\n        this.obj2 = new Objective(dummyScoreboard, \"dummy_2\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER);\r\n    }\r\n\r\n    @Override\r\n    protected void setDisplayName(String title) {\r\n        if (this.obj1 != null) {\r\n            MutableComponent chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n            this.obj1.setDisplayName(chatComponentTitle);\r\n            this.obj2.setDisplayName(chatComponentTitle);\r\n        }\r\n    }\r\n\r\n    public List<PlayerTeam> generatedTeams = new ArrayList<>();\r\n\r\n    @Override\r\n    public void sendUpdate() {\r\n        List<PlayerTeam> oldTeams = generatedTeams;\r\n        generatedTeams = new ArrayList<>();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj1, 0));\r\n        for (int i = 0; i < this.lines.length; i++) {\r\n            String line = this.lines[i];\r\n            if (line == null) {\r\n                break;\r\n            }\r\n            String lineId = Utilities.generateRandomColors(8);\r\n            PlayerTeam team = new PlayerTeam(dummyScoreboard, lineId);\r\n            team.getPlayers().add(lineId);\r\n            team.setPlayerPrefix(Handler.componentToNMS(FormattedTextHelper.parse(line, ChatColor.WHITE)));\r\n            generatedTeams.add(team);\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n            PacketHelperImpl.send(player, new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, obj1.getName(), lineId, this.scores[i]));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundSetDisplayObjectivePacket(1, this.obj1));\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n        Objective temp = this.obj2;\r\n        this.obj2 = this.obj1;\r\n        this.obj1 = temp;\r\n        for (PlayerTeam team : oldTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        for (PlayerTeam team : generatedTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        generatedTeams.clear();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/blocks/BlockLightImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.blocks;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;\r\nimport net.minecraft.server.level.ServerChunkCache;\r\nimport net.minecraft.server.level.ThreadedLevelLightEngine;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.LightLayer;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.ChunkStatus;\r\nimport net.minecraft.world.level.chunk.DataLayer;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.lighting.LayerLightEventListener;\r\nimport net.minecraft.world.level.lighting.LevelLightEngine;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.CraftBlock;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.BitSet;\r\nimport java.util.List;\r\n\r\npublic class BlockLightImpl extends BlockLight {\r\n\r\n    public static final Class LIGHTENGINETHREADED_TASKTYPE = Arrays.stream(ThreadedLevelLightEngine.class.getDeclaredClasses()).filter(c -> c.isEnum()).findFirst().get(); // TaskType\r\n    public static final Object LIGHTENGINETHREADED_TASKTYPE_PRE;\r\n\r\n    static {\r\n        Object preObj = null;\r\n        try {\r\n            preObj = ReflectionHelper.getFields(LIGHTENGINETHREADED_TASKTYPE).get(ReflectionMappingsInfo.ThreadedLevelLightEngine_TaskType_PRE_UPDATE).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n        LIGHTENGINETHREADED_TASKTYPE_PRE = preObj;\r\n    }\r\n\r\n    public static final MethodHandle LIGHTENGINETHREADED_QUEUERUNNABLE = ReflectionHelper.getMethodHandle(ThreadedLevelLightEngine.class, ReflectionMappingsInfo.ThreadedLevelLightEngine_addTask,\r\n            int.class, int.class,  LIGHTENGINETHREADED_TASKTYPE, Runnable.class);\r\n\r\n    public static void enqueueRunnable(LevelChunk chunk, Runnable runnable) {\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        if (lightEngine instanceof ThreadedLevelLightEngine) {\r\n            ChunkPos coord = chunk.getPos();\r\n            try {\r\n                LIGHTENGINETHREADED_QUEUERUNNABLE.invoke(lightEngine, coord.x, coord.z, LIGHTENGINETHREADED_TASKTYPE_PRE, runnable);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        else {\r\n            runnable.run();\r\n        }\r\n    }\r\n\r\n    private BlockLightImpl(Location location, long ticks) {\r\n        super(location, ticks);\r\n    }\r\n\r\n    public static BlockLight createLight(Location location, int lightLevel, long ticks) {\r\n        location = location.getBlock().getLocation();\r\n        BlockLight blockLight;\r\n        if (lightsByLocation.containsKey(location)) {\r\n            blockLight = lightsByLocation.get(location);\r\n            if (blockLight.removeTask != null) {\r\n                blockLight.removeTask.cancel();\r\n                blockLight.removeTask = null;\r\n            }\r\n            if (blockLight.updateTask != null) {\r\n                blockLight.updateTask.cancel();\r\n                blockLight.updateTask = null;\r\n            }\r\n            blockLight.removeLater(ticks);\r\n        }\r\n        else {\r\n            blockLight = new BlockLightImpl(location, ticks);\r\n            lightsByLocation.put(location, blockLight);\r\n            if (!lightsByChunk.containsKey(blockLight.chunkCoord)) {\r\n                lightsByChunk.put(blockLight.chunkCoord, new ArrayList<>());\r\n            }\r\n            lightsByChunk.get(blockLight.chunkCoord).add(blockLight);\r\n        }\r\n        blockLight.intendedLevel = lightLevel;\r\n        blockLight.update(lightLevel, true);\r\n        return blockLight;\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundBlockUpdatePacket packet, Level world) {\r\n        try {\r\n            BlockPos pos = packet.getPos();\r\n            int chunkX = pos.getX() >> 4;\r\n            int chunkZ = pos.getZ() >> 4;\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                LevelChunk chunk = world.getChunk(chunkX, chunkZ);\r\n                boolean any = false;\r\n                for (Vector vec : RELATIVE_CHUNKS) {\r\n                    ChunkAccess other = world.getChunk(chunkX + vec.getBlockX(), chunkZ + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n                    if (other instanceof LevelChunk) {\r\n                        List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(((LevelChunk) other).bukkitChunk));\r\n                        if (lights != null) {\r\n                            any = true;\r\n                            for (BlockLight light : lights) {\r\n                                Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates(chunk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundLightUpdatePacket packet, Level world) {\r\n        if (doNotCheck) {\r\n            return;\r\n        }\r\n        try {\r\n            int cX = packet.getX();\r\n            int cZ = packet.getZ();\r\n            BitSet bitMask = packet.getLightData().getBlockYMask();\r\n            List<byte[]> blockData = packet.getLightData().getBlockUpdates();\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                ChunkAccess chk = world.getChunk(cX, cZ, ChunkStatus.FULL, false);\r\n                if (!(chk instanceof LevelChunk)) {\r\n                    return;\r\n                }\r\n                List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(((LevelChunk) chk).bukkitChunk));\r\n                if (lights == null) {\r\n                    return;\r\n                }\r\n                boolean any = false;\r\n                for (BlockLight light : lights) {\r\n                    if (((BlockLightImpl) light).checkIfChangedBy(bitMask, blockData)) {\r\n                        Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                        any = true;\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates((LevelChunk) chk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static boolean doNotCheck = false;\r\n\r\n    public boolean checkIfChangedBy(BitSet bitmask, List<byte[]> data) {\r\n        Location blockLoc = block.getLocation();\r\n        int layer = (blockLoc.getBlockY() >> 4) + 1;\r\n        if (!bitmask.get(layer)) {\r\n            return false;\r\n        }\r\n        int found = 0;\r\n        for (int i = 0; i < 16; i++) {\r\n            if (bitmask.get(i)) {\r\n                if (i == layer) {\r\n                    byte[] blocks = data.get(found);\r\n                    DataLayer arr = new DataLayer(blocks);\r\n                    int x = blockLoc.getBlockX() - (chunkCoord.x << 4);\r\n                    int y = blockLoc.getBlockY() % 16;\r\n                    int z = blockLoc.getBlockZ() - (chunkCoord.z << 4);\r\n                    int level = arr.get(x, y, z);\r\n                    return intendedLevel != level;\r\n                }\r\n                found++;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static void runResetFor(final LevelChunk chunk, final BlockPos pos) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            engineBlock.checkBlock(pos);\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    public static void runSetFor(final LevelChunk chunk, final BlockPos pos, final int level) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            engineBlock.onBlockEmissionIncrease(pos, level);\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    @Override\r\n    public void reset(boolean updateChunk) {\r\n        runResetFor(((CraftChunk) getChunk()).getHandle(), ((CraftBlock) block).getPosition());\r\n        if (updateChunk) {\r\n            // This runnable cast is necessary despite what your IDE may claim\r\n            updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(int lightLevel, boolean updateChunk) {\r\n        runResetFor(((CraftChunk) getChunk()).getHandle(), ((CraftBlock) block).getPosition());\r\n        updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), () -> {\r\n            updateTask = null;\r\n            runSetFor(((CraftChunk) chunk).getHandle(), ((CraftBlock) block).getPosition(), lightLevel);\r\n            if (updateChunk) {\r\n                // This runnable cast is necessary despite what your IDE may claim\r\n                updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n            }\r\n        }, 1);\r\n    }\r\n\r\n    public static final Vector[] RELATIVE_CHUNKS = new Vector[] {\r\n            new Vector(0, 0, 0),\r\n            new Vector(-1, 0, 0), new Vector(1, 0, 0), new Vector(0, 0, -1), new Vector(0, 0, 1),\r\n            new Vector(-1, 0, -1), new Vector(-1, 0, 1), new Vector(1, 0, -1), new Vector(1, 0, 1)\r\n    };\r\n\r\n    public void sendNearbyChunkUpdates() {\r\n        sendNearbyChunkUpdates(((CraftChunk) getChunk()).getHandle());\r\n    }\r\n\r\n    public static void sendNearbyChunkUpdates(LevelChunk chunk) {\r\n        ChunkPos pos = chunk.getPos();\r\n        for (Vector vec : RELATIVE_CHUNKS) {\r\n            ChunkAccess other = chunk.getLevel().getChunk(pos.x + vec.getBlockX(), pos.z + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n            if (other instanceof LevelChunk) {\r\n                sendSingleChunkUpdate((LevelChunk) other);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void sendSingleChunkUpdate(LevelChunk chunk) {\r\n        doNotCheck = true;\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        ChunkPos pos = chunk.getPos();\r\n        ClientboundLightUpdatePacket packet = new ClientboundLightUpdatePacket(pos, lightEngine, null, null, true); // TODO: 1.16: should 'trust edges' be true here?\r\n        ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(pos, false).forEach((player) -> {\r\n            player.connection.send(packet);\r\n        });\r\n        doNotCheck = false;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/entities/CraftFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport net.minecraft.world.entity.projectile.AbstractArrow;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftArrow;\r\n\r\npublic class CraftFakeArrowImpl extends CraftArrow implements FakeArrow {\r\n\r\n    public CraftFakeArrowImpl(CraftServer craftServer, AbstractArrow entityArrow) {\r\n        super(craftServer, entityArrow);\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        if (getPassenger() != null) {\r\n            return;\r\n        }\r\n        super.remove();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_ARROW\";\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/entities/CraftFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;\r\nimport org.bukkit.metadata.FixedMetadataValue;\r\nimport org.bukkit.metadata.MetadataValue;\r\nimport org.bukkit.plugin.Plugin;\r\n\r\nimport java.util.List;\r\nimport java.util.Set;\r\n\r\npublic class CraftFakePlayerImpl extends CraftPlayer implements FakePlayer {\r\n\r\n    private final CraftServer server;\r\n    public String fullName;\r\n\r\n    public CraftFakePlayerImpl(CraftServer server, EntityFakePlayerImpl entity) {\r\n        super(server, entity);\r\n        this.server = server;\r\n        setMetadata(\"NPC\", new FixedMetadataValue(NMSHandler.getJavaPlugin(), true));\r\n    }\r\n\r\n    @Override\r\n    public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {\r\n        this.server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue);\r\n    }\r\n\r\n    @Override\r\n    public List<MetadataValue> getMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().getMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().hasMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public void removeMetadata(String metadataKey, Plugin owningPlugin) {\r\n        this.server.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin);\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_PLAYER\";\r\n    }\r\n\r\n    @Override\r\n    public String getFullName() {\r\n        return fullName;\r\n    }\r\n\r\n    @Override\r\n    public Block getTargetBlock(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public List<Block> getLastTwoTargetBlocks(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/entities/CraftItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\n\r\nimport java.util.UUID;\r\n\r\npublic class CraftItemProjectileImpl extends CraftEntity implements ItemProjectile {\r\n\r\n    private boolean doesBounce;\r\n\r\n    public CraftItemProjectileImpl(CraftServer server, EntityItemProjectileImpl entity) {\r\n        super(server, entity);\r\n    }\r\n\r\n    @Override\r\n    public EntityItemProjectileImpl getHandle() {\r\n        return (EntityItemProjectileImpl) super.getHandle();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return getType().name();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack getItemStack() {\r\n        return CraftItemStack.asBukkitCopy(getHandle().getItemStack());\r\n    }\r\n\r\n    @Override\r\n    public void setItemStack(ItemStack itemStack) {\r\n        getHandle().setItemStack(CraftItemStack.asNMSCopy(itemStack));\r\n    }\r\n\r\n    @Override\r\n    public int getPickupDelay() {\r\n        return 0;\r\n    }\r\n\r\n    @Override\r\n    public void setPickupDelay(int i) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public void setUnlimitedLifetime(boolean b) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnlimitedLifetime() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void setOwner(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getOwner() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setThrower(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getThrower() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ProjectileSource getShooter() {\r\n        return getHandle().projectileSource;\r\n    }\r\n\r\n    @Override\r\n    public void setShooter(ProjectileSource projectileSource) {\r\n        if (projectileSource instanceof CraftEntity) {\r\n            getHandle().setOwner(((CraftEntity) projectileSource).getHandle());\r\n        }\r\n        else {\r\n            getHandle().projectileSource = projectileSource;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean doesBounce() {\r\n        return doesBounce;\r\n    }\r\n\r\n    @Override\r\n    public void setBounce(boolean doesBounce) {\r\n        this.doesBounce = doesBounce;\r\n    }\r\n\r\n    @Override\r\n    public EntityType getType() {\r\n        return EntityType.DROPPED_ITEM;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/entities/EntityFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_18.Handler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.world.entity.projectile.SpectralArrow;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakeArrowImpl extends SpectralArrow {\r\n\r\n    public EntityFakeArrowImpl(CraftWorld craftWorld, Location location) {\r\n        super(net.minecraft.world.entity.EntityType.SPECTRAL_ARROW, craftWorld.getHandle());\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakeArrowImpl((CraftServer) Bukkit.getServer(), this));\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        level.addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    protected ItemStack getPickupItem() {\r\n        return new ItemStack(Items.ARROW);\r\n    }\r\n\r\n    @Override\r\n    public CraftFakeArrowImpl getBukkitEntity() {\r\n        return (CraftFakeArrowImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/entities/EntityFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_18.Handler;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.network.fakes.FakeNetworkManagerImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.network.fakes.FakePlayerConnectionImpl;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.player.Player;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftServer;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakePlayerImpl extends ServerPlayer {\r\n\r\n    public EntityFakePlayerImpl(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile, boolean doAdd) {\r\n        super(minecraftserver, worldserver, gameprofile);\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakePlayerImpl((CraftServer) Bukkit.getServer(), this));\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        Connection networkManager = new FakeNetworkManagerImpl(PacketFlow.CLIENTBOUND);\r\n        connection = new FakePlayerConnectionImpl(minecraftserver, networkManager, this);\r\n        networkManager.setListener(connection);\r\n        getEntityData().set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) 127);\r\n        if (doAdd) {\r\n            worldserver.addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public CraftFakePlayerImpl getBukkitEntity() {\r\n        return (CraftFakePlayerImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/entities/EntityItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.base.Preconditions;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.projectile.ThrowableProjectile;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport org.bukkit.Location;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\n\r\npublic class EntityItemProjectileImpl extends ThrowableProjectile {\r\n\r\n    public static MethodHandle setBukkitEntityMethod = ReflectionHelper.getFinalSetter(Entity.class, \"bukkitEntity\");\r\n\r\n    public static final EntityDataAccessor<ItemStack> ITEM;\r\n\r\n    static {\r\n        EntityDataAccessor<ItemStack> watcher = null;\r\n        try {\r\n            watcher = (EntityDataAccessor<ItemStack>) ReflectionHelper.getFields(ItemEntity.class).get(ReflectionMappingsInfo.ItemEntity_DATA_ITEM).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        ITEM = watcher;\r\n    }\r\n\r\n    public EntityItemProjectileImpl(Level world, Location location, ItemStack item) {\r\n        super((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.ITEM, world);\r\n        try {\r\n            setBukkitEntityMethod.invoke(this, new CraftItemProjectileImpl(((ServerLevel) world).getServer().server, this));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        setItemStack(item);\r\n    }\r\n\r\n    @Override\r\n    protected void defineSynchedData() {\r\n        this.getEntityData().define(ITEM, ItemStack.EMPTY);\r\n    }\r\n\r\n    public ItemStack getItemStack() {\r\n        return this.getEntityData().get(ITEM);\r\n    }\r\n\r\n    public void setItemStack(ItemStack itemstack) {\r\n        Preconditions.checkArgument(!itemstack.isEmpty(), \"Cannot drop air\");\r\n        this.getEntityData().set(ITEM, itemstack);\r\n        this.getEntityData().markDirty(ITEM);\r\n    }\r\n\r\n    @Override\r\n    protected void onHitBlock(BlockHitResult movingobjectpositionblock) {\r\n        super.onHitBlock(movingobjectpositionblock);\r\n        remove(RemovalReason.KILLED);\r\n    }\r\n\r\n    @Override\r\n    public void onSyncedDataUpdated(EntityDataAccessor<?> datawatcherobject) {\r\n        super.onSyncedDataUpdated(datawatcherobject);\r\n        if (ITEM.equals(datawatcherobject)) {\r\n            this.getItemStack().setEntityRepresentation(this);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean save(net.minecraft.nbt.CompoundTag nbttagcompound) {\r\n        if (!this.getItemStack().isEmpty()) {\r\n            nbttagcompound.put(\"Item\", this.getItemStack().save(new net.minecraft.nbt.CompoundTag()));\r\n        }\r\n        super.save(nbttagcompound);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public void load(net.minecraft.nbt.CompoundTag nbttagcompound) {\r\n        net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttagcompound.getCompound(\"Item\");\r\n        this.setItemStack(ItemStack.of(nbttagcompound1));\r\n        if (this.getItemStack().isEmpty()) {\r\n            this.remove(RemovalReason.KILLED);\r\n        }\r\n        super.load(nbttagcompound);\r\n    }\r\n\r\n    @Override\r\n    public CraftItemProjectileImpl getBukkitEntity() {\r\n        return (CraftItemProjectileImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/network/fakes/FakeChannelImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.network.fakes;\r\n\r\nimport io.netty.channel.*;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeChannelImpl extends AbstractChannel {\r\n\r\n    private final ChannelConfig config = new DefaultChannelConfig(this);\r\n\r\n    protected FakeChannelImpl(Channel parent) {\r\n        super(parent);\r\n    }\r\n\r\n    @Override\r\n    public ChannelConfig config() {\r\n        config.setAutoRead(true);\r\n        return config;\r\n    }\r\n\r\n    @Override\r\n    protected AbstractUnsafe newUnsafe() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected boolean isCompatible(EventLoop eventLoop) {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress localAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress remoteAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected void doBind(SocketAddress socketAddress) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doDisconnect() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doClose() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doBeginRead() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doWrite(ChannelOutboundBuffer channelOutboundBuffer) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean isOpen() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActive() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public ChannelMetadata metadata() {\r\n        return new ChannelMetadata(true);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/network/fakes/FakeNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeNetworkManagerImpl extends Connection {\r\n\r\n    public FakeNetworkManagerImpl(PacketFlow enumprotocoldirection) {\r\n        super(enumprotocoldirection);\r\n        channel = new FakeChannelImpl(null);\r\n        address = new SocketAddress() {\r\n        };\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/network/fakes/FakePlayerConnectionImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\n\r\npublic class FakePlayerConnectionImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public FakePlayerConnectionImpl(MinecraftServer minecraftserver, Connection networkmanager, ServerPlayer entityplayer) {\r\n        super(minecraftserver, networkmanager, entityplayer);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet packet) {\r\n        // Do nothing\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/network/handlers/AbstractListenerPlayInImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport io.netty.util.concurrent.Future;\r\nimport io.netty.util.concurrent.GenericFutureListener;\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\n\r\nimport java.util.Set;\r\n\r\npublic class AbstractListenerPlayInImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public final ServerGamePacketListenerImpl oldListener;\r\n    public final DenizenNetworkManagerImpl denizenNetworkManager;\r\n\r\n    public AbstractListenerPlayInImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer, ServerGamePacketListenerImpl oldListener) {\r\n        super(MinecraftServer.getServer(), networkManager, entityPlayer);\r\n        this.oldListener = oldListener;\r\n        this.denizenNetworkManager = networkManager;\r\n    }\r\n\r\n    /*\r\n    @Override\r\n    public CraftPlayer getPlayer() {\r\n        return oldListener.getPlayer();\r\n    }*/\r\n\r\n    @Override\r\n    public Connection getConnection() {\r\n        return this.connection;\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        oldListener.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(String s) {\r\n        oldListener.disconnect(s);\r\n    }\r\n\r\n    @Override\r\n    public void dismount(double d0, double d1, double d2, float f, float f1) {\r\n        oldListener.dismount(d0, d1, d2, f, f1);\r\n    }\r\n\r\n    @Override\r\n    public void dismount(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) {\r\n        oldListener.dismount(d0, d1, d2, f, f1, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1) {\r\n        oldListener.teleport(d0, d1, d2, f, f1);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) {\r\n        oldListener.teleport(d0, d1, d2, f, f1, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1, Set<ClientboundPlayerPositionPacket.RelativeArgument> set) {\r\n        oldListener.teleport(d0, d1, d2, f, f1, set);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1, Set<ClientboundPlayerPositionPacket.RelativeArgument> set, PlayerTeleportEvent.TeleportCause cause) {\r\n        oldListener.teleport(d0, d1, d2, f, f1, set, cause);\r\n    }\r\n\r\n    @Override\r\n    public boolean teleport(double d0, double d1, double d2, float f, float f1, Set<ClientboundPlayerPositionPacket.RelativeArgument> set, boolean flag, PlayerTeleportEvent.TeleportCause cause) {\r\n        return oldListener.teleport(d0, d1, d2, f, f1, set, flag, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Location dest) {\r\n        oldListener.teleport(dest);\r\n    }\r\n\r\n    @Override\r\n    public void chat(String s, boolean async) {\r\n        oldListener.chat(s, async);\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldListener.tick();\r\n    }\r\n\r\n    @Override\r\n    public void resetPosition() {\r\n        oldListener.resetPosition();\r\n    }\r\n\r\n    @Override\r\n    public void onDisconnect(Component ichatbasecomponent) {\r\n        oldListener.onDisconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        oldListener.send(packet);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        oldListener.send(packet, genericfuturelistener);\r\n    }\r\n\r\n    public void handlePacketIn(Packet<ServerGamePacketListener> packet) {\r\n        denizenNetworkManager.packetsReceived++;\r\n        if (NMSHandler.debugPackets) {\r\n            DenizenNetworkManagerImpl.doPacketOutput(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent from \" + player.getScoreboardName());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(ServerboundPlayerInputPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePlayerInput(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleMoveVehicle(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleAcceptTeleportPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookSeenRecipePacket(ServerboundRecipeBookSeenRecipePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleRecipeBookSeenRecipePacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleRecipeBookChangeSettingsPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSeenAdvancements(ServerboundSeenAdvancementsPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSeenAdvancements(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleCustomCommandSuggestions(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandBlock(ServerboundSetCommandBlockPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetCommandBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandMinecart(ServerboundSetCommandMinecartPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetCommandMinecart(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePickItem(ServerboundPickItemPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePickItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRenameItem(ServerboundRenameItemPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleRenameItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetBeaconPacket(ServerboundSetBeaconPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetBeaconPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetStructureBlock(ServerboundSetStructureBlockPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetStructureBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetJigsawBlock(ServerboundSetJigsawBlockPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetJigsawBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleJigsawGenerate(ServerboundJigsawGeneratePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleJigsawGenerate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSelectTrade(ServerboundSelectTradePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSelectTrade(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEditBook(ServerboundEditBookPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleEditBook(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEntityTagQuery(ServerboundEntityTagQuery packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleEntityTagQuery(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleBlockEntityTagQuery(ServerboundBlockEntityTagQuery packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleBlockEntityTagQuery(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleMovePlayer(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItemOn(ServerboundUseItemOnPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleUseItemOn(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleTeleportToEntityPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePaddleBoat(ServerboundPaddleBoatPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePaddleBoat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePong(ServerboundPongPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePong(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChat(ServerboundChatPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleChat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePlayerCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleInteract(ServerboundInteractPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleInteract(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientCommand(ServerboundClientCommandPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleClientCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClose(ServerboundContainerClosePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleContainerClose(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePlaceRecipe(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleContainerButtonClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCreativeModeSlot(ServerboundSetCreativeModeSlotPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSetCreativeModeSlot(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleKeepAlive(ServerboundKeepAlivePacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleKeepAlive(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handlePlayerAbilities(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientInformation(ServerboundClientInformationPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleClientInformation(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleChangeDifficulty(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleLockDifficulty(ServerboundLockDifficultyPacket packet) {\r\n        handlePacketIn(packet);\r\n        oldListener.handleLockDifficulty(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/network/handlers/DenizenNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerHearsSoundScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerReceivesActionbarScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerReceivesMessageScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerReceivesTablistUpdateScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.v1_18.Handler;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.blocks.BlockLightImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.entities.EntityFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.network.packets.PacketOutChatImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.*;\r\nimport com.denizenscript.denizen.scripts.commands.player.DisguiseCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizen.utilities.entity.HideEntitiesHelper;\r\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\r\nimport com.denizenscript.denizen.utilities.packets.HideParticles;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.mojang.datafixers.util.Pair;\r\nimport io.netty.buffer.Unpooled;\r\nimport io.netty.channel.ChannelHandlerContext;\r\nimport io.netty.util.concurrent.Future;\r\nimport io.netty.util.concurrent.GenericFutureListener;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.NonNullList;\r\nimport net.minecraft.core.SectionPos;\r\nimport net.minecraft.core.particles.ParticleOptions;\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.ConnectionProtocol;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.PacketListener;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.network.ServerPlayerConnection;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.GameType;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Particle;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftParticle;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport javax.crypto.Cipher;\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.net.SocketAddress;\r\nimport java.util.*;\r\nimport java.util.stream.Collectors;\r\n\r\npublic class DenizenNetworkManagerImpl extends Connection {\r\n\r\n    public static FriendlyByteBuf copyPacket(Packet<?> original) {\r\n        try {\r\n            FriendlyByteBuf copier = new FriendlyByteBuf(Unpooled.buffer());\r\n            original.write(copier);\r\n            return copier;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public final Connection oldManager;\r\n    public final DenizenPacketListenerImpl packetListener;\r\n    public final ServerPlayer player;\r\n    public int packetsSent, packetsReceived;\r\n\r\n    public DenizenNetworkManagerImpl(ServerPlayer entityPlayer, Connection oldManager) {\r\n        super(getProtocolDirection(oldManager));\r\n        this.oldManager = oldManager;\r\n        this.channel = oldManager.channel;\r\n        this.packetListener = new DenizenPacketListenerImpl(this, entityPlayer);\r\n        oldManager.setListener(packetListener);\r\n        this.player = this.packetListener.player;\r\n    }\r\n\r\n    public static void setNetworkManager(Player player) {\r\n        ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerGamePacketListenerImpl playerConnection = entityPlayer.connection;\r\n        setNetworkManager(playerConnection, new DenizenNetworkManagerImpl(entityPlayer, playerConnection.connection));\r\n    }\r\n\r\n    public static void enableNetworkManager() {\r\n        for (World w : Bukkit.getWorlds()) {\r\n            for (ChunkMap.TrackedEntity tracker : ((CraftWorld) w).getHandle().getChunkSource().chunkMap.entityMap.values()) {\r\n                ArrayList<ServerPlayerConnection> connections = new ArrayList<>(tracker.seenBy);\r\n                tracker.seenBy.clear();\r\n                for (ServerPlayerConnection connection : connections) {\r\n                    tracker.seenBy.add(connection.getPlayer().connection);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int hashCode() {\r\n        return oldManager.hashCode();\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object c2) {\r\n        return oldManager.equals(c2);\r\n    }\r\n\r\n    @Override\r\n    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelRegistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelUnregistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception {\r\n        oldManager.channelActive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public void setProtocol(ConnectionProtocol enumprotocol) {\r\n        oldManager.setProtocol(enumprotocol);\r\n    }\r\n\r\n    @Override\r\n    public void channelInactive(ChannelHandlerContext channelhandlercontext) {\r\n        oldManager.channelInactive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public boolean isSharable() {\r\n        return oldManager.isSharable();\r\n    }\r\n\r\n    @Override\r\n    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerAdded(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerRemoved(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) {\r\n        oldManager.exceptionCaught(channelhandlercontext, throwable);\r\n    }\r\n\r\n    @Override\r\n    protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) {\r\n        if (oldManager.channel.isOpen()) {\r\n            try {\r\n                packet.handle(this.packetListener);\r\n            }\r\n            catch (Exception e) {\r\n                // Do nothing\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setListener(PacketListener packetlistener) {\r\n        oldManager.setListener(packetlistener);\r\n    }\r\n\r\n    public static Field ENTITY_ID_PACKVELENT = ReflectionHelper.getFields(ClientboundSetEntityMotionPacket.class).get(ReflectionMappingsInfo.ClientboundSetEntityMotionPacket_id, int.class);\r\n    public static Field ENTITY_ID_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_id, int.class);\r\n    public static Field POS_X_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_x, double.class);\r\n    public static Field POS_Y_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_y, double.class);\r\n    public static Field POS_Z_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_z, double.class);\r\n    public static Field YAW_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_yRot, byte.class);\r\n    public static Field PITCH_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_xRot, byte.class);\r\n    public static Field POS_X_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_xa, short.class);\r\n    public static Field POS_Y_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_ya, short.class);\r\n    public static Field POS_Z_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_za, short.class);\r\n    public static Field YAW_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_yRot, byte.class);\r\n    public static Field PITCH_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_xRot, byte.class);\r\n    public static Field SECTIONPOS_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_sectionPos, SectionPos.class);\r\n    public static Field OFFSETARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_positions, short[].class);\r\n    public static Field BLOCKARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_states, BlockState[].class);\r\n    public static Field ENTITY_METADATA_LIST = ReflectionHelper.getFields(ClientboundSetEntityDataPacket.class).get(ReflectionMappingsInfo.ClientboundSetEntityDataPacket_packedItems, List.class);\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        send(packet, null);\r\n    }\r\n\r\n    public static void doPacketOutput(String text) {\r\n        if (!NMSHandler.debugPackets) {\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPacketFilter == null || NMSHandler.debugPacketFilter.trim().isEmpty()\r\n                || CoreUtilities.toLowerCase(text).contains(NMSHandler.debugPacketFilter)) {\r\n            Debug.log(text);\r\n        }\r\n    }\r\n\r\n    public void debugOutputPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundSetEntityDataPacket) {\r\n            StringBuilder output = new StringBuilder(128);\r\n            output.append(\"Packet: ClientboundSetEntityDataPacket sent to \").append(player.getScoreboardName()).append(\" for entity ID: \").append(((ClientboundSetEntityDataPacket) packet).getId()).append(\": \");\r\n            List<SynchedEntityData.DataItem<?>> list = ((ClientboundSetEntityDataPacket) packet).getUnpackedData();\r\n            if (list == null) {\r\n                output.append(\"None\");\r\n            }\r\n            else {\r\n                for (SynchedEntityData.DataItem<?> data : list) {\r\n                    output.append('[').append(data.getAccessor().getId()).append(\": \").append(data.getValue()).append(\"], \");\r\n                }\r\n            }\r\n            doPacketOutput(output.toString());\r\n        }\r\n        else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n            ClientboundSetEntityMotionPacket velPacket = (ClientboundSetEntityMotionPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundSetEntityMotionPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + velPacket.getId() + \": \" + velPacket.getXa() + \",\" + velPacket.getYa() + \",\" + velPacket.getZa());\r\n        }\r\n        else if (packet instanceof ClientboundAddEntityPacket) {\r\n            ClientboundAddEntityPacket addEntityPacket = (ClientboundAddEntityPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundAddEntityPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + addEntityPacket.getId() + \": \" + \"uuid: \" + addEntityPacket.getUUID()\r\n                    + \", type: \" + addEntityPacket.getType() + \", at: \" + addEntityPacket.getX() + \",\" + addEntityPacket.getY() + \",\" + addEntityPacket.getZ() + \", data: \" + addEntityPacket.getData());\r\n        }\r\n        else if (packet instanceof ClientboundMapItemDataPacket) {\r\n            ClientboundMapItemDataPacket mapPacket = (ClientboundMapItemDataPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundMapItemDataPacket sent to \" + player.getScoreboardName() + \" for map ID: \" + mapPacket.getMapId() + \", scale: \" + mapPacket.getScale() + \", locked: \" + mapPacket.isLocked());\r\n        }\r\n        else if (packet instanceof ClientboundRemoveEntitiesPacket) {\r\n            ClientboundRemoveEntitiesPacket removePacket = (ClientboundRemoveEntitiesPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundRemoveEntitiesPacket sent to \" + player.getScoreboardName() + \" for entities: \" + removePacket.getEntityIds().stream().map(Object::toString).collect(Collectors.joining(\", \")));\r\n        }\r\n        else {\r\n            doPacketOutput(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            if (Settings.cache_warnOnAsyncPackets\r\n                    && !(packet instanceof ClientboundChatPacket) // Vanilla supports an async chat system, though it's normally disabled, some plugins use this as justification for sending messages async\r\n                    && !(packet instanceof ClientboundCommandSuggestionsPacket)) { // Async tab complete is wholly unsupported in Spigot (and will cause an exception), however Paper explicitly adds async support (for unclear reasons), so let it through too\r\n                Debug.echoError(\"Warning: packet sent off main thread! This is completely unsupported behavior! Denizen network interceptor ignoring packet to avoid crash. Packet class: \"\r\n                        + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName() + \" identify the sender of the packet from the stack trace:\");\r\n                try {\r\n                    throw new RuntimeException(\"Trace\");\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(ex);\r\n                }\r\n            }\r\n            oldManager.send(packet, genericfuturelistener);\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPackets) {\r\n            debugOutputPacket(packet);\r\n        }\r\n        packetsSent++;\r\n        if (processAttachToForPacket(packet)\r\n            || processHiddenEntitiesForPacket(packet)\r\n            || processPacketHandlerForPacket(packet)\r\n            || processMirrorForPacket(packet)\r\n            || processParticlesForPacket(packet)\r\n            || processSoundPacket(packet)\r\n            || processTablistPacket(packet, genericfuturelistener)\r\n            || processActionbarPacket(packet, genericfuturelistener)\r\n            || processDisguiseForPacket(packet, genericfuturelistener)\r\n            || processMetadataChangesForPacket(packet, genericfuturelistener)\r\n            || processEquipmentForPacket(packet, genericfuturelistener)\r\n            || processShowFakeForPacket(packet, genericfuturelistener)) {\r\n            if (NMSHandler.debugPackets) {\r\n                doPacketOutput(\"DENIED PACKET \" + packet.getClass().getCanonicalName() + \" DENIED FROM SEND TO \" + player.getScoreboardName());\r\n            }\r\n            return;\r\n        }\r\n        processBlockLightForPacket(packet);\r\n        processFakePlayerSpawnForPacket(packet);\r\n        oldManager.send(packet, genericfuturelistener);\r\n    }\r\n\r\n    public boolean processTablistPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (!PlayerReceivesTablistUpdateScriptEvent.instance.eventData.isEnabled) {\r\n            return false;\r\n        }\r\n        if (packet instanceof ClientboundPlayerInfoPacket) {\r\n            ClientboundPlayerInfoPacket infoPacket = (ClientboundPlayerInfoPacket) packet;\r\n            String mode;\r\n            switch (infoPacket.getAction()) {\r\n                case ADD_PLAYER:\r\n                    mode = \"add\";\r\n                    break;\r\n                case REMOVE_PLAYER:\r\n                    mode = \"remove\";\r\n                    break;\r\n                case UPDATE_LATENCY:\r\n                    mode = \"update_latency\";\r\n                    break;\r\n                case UPDATE_GAME_MODE:\r\n                    mode = \"update_gamemode\";\r\n                    break;\r\n                case UPDATE_DISPLAY_NAME:\r\n                    mode = \"update_display\";\r\n                    break;\r\n                default:\r\n                    return false;\r\n            }\r\n            boolean isOverriding = false;\r\n            for (ClientboundPlayerInfoPacket.PlayerUpdate update : infoPacket.getEntries()) {\r\n                GameProfile profile = update.getProfile();\r\n                String texture = null, signature = null;\r\n                if (profile.getProperties().containsKey(\"textures\")) {\r\n                    Property property = profile.getProperties().get(\"textures\").stream().findFirst().get();\r\n                    texture = property.getValue();\r\n                    signature = property.getSignature();\r\n                }\r\n                String modeText = update.getGameMode() == null ? null : update.getGameMode().name();\r\n                PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(mode, profile.getId(), true, profile.getName(),\r\n                        update.getDisplayName() == null ? null : FormattedTextHelper.stringify(Handler.componentToSpigot(update.getDisplayName())), modeText, texture, signature, update.getLatency());\r\n                PlayerReceivesTablistUpdateScriptEvent.fire(player.getBukkitEntity(), data);\r\n                if (data.modified) {\r\n                    if (!isOverriding) {\r\n                        isOverriding = true;\r\n                        ClientboundPlayerInfoPacket priorsPacket = new ClientboundPlayerInfoPacket(infoPacket.getAction());\r\n                        for (ClientboundPlayerInfoPacket.PlayerUpdate priorUpdate : infoPacket.getEntries()) {\r\n                            if (priorUpdate == update) {\r\n                                break;\r\n                            }\r\n                            priorsPacket.getEntries().add(priorUpdate);\r\n                        }\r\n                        if (!priorsPacket.getEntries().isEmpty()) {\r\n                            oldManager.send(priorsPacket, genericfuturelistener);\r\n                        }\r\n                    }\r\n                    if (!data.cancelled) {\r\n                        ClientboundPlayerInfoPacket newPacket = new ClientboundPlayerInfoPacket(infoPacket.getAction());\r\n                        GameProfile newProfile = new GameProfile(data.id, data.name);\r\n                        if (data.texture != null) {\r\n                            newProfile.getProperties().put(\"textures\", new Property(\"textures\", data.texture, data.signature));\r\n                        }\r\n                        newPacket.getEntries().add(new ClientboundPlayerInfoPacket.PlayerUpdate(newProfile, data.latency, data.gamemode == null ? null : GameType.byName(CoreUtilities.toLowerCase(data.gamemode)),\r\n                                data.display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(data.display, ChatColor.WHITE))));\r\n                        oldManager.send(newPacket, genericfuturelistener);\r\n                    }\r\n                }\r\n                else if (isOverriding) {\r\n                    ClientboundPlayerInfoPacket newPacket = new ClientboundPlayerInfoPacket(infoPacket.getAction());\r\n                    newPacket.getEntries().add(update);\r\n                    oldManager.send(newPacket, genericfuturelistener);\r\n                }\r\n            }\r\n            return isOverriding;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processActionbarPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (!PlayerReceivesActionbarScriptEvent.instance.loaded) {\r\n            return false;\r\n        }\r\n        if (packet instanceof ClientboundSetActionBarTextPacket) {\r\n            ClientboundSetActionBarTextPacket actionbarPacket = (ClientboundSetActionBarTextPacket) packet;\r\n            PlayerReceivesActionbarScriptEvent event = PlayerReceivesActionbarScriptEvent.instance;\r\n            Component baseComponent = actionbarPacket.getText();\r\n            event.reset();\r\n            event.message = new ElementTag(FormattedTextHelper.stringify(Handler.componentToSpigot(baseComponent)));\r\n            event.rawJson = new ElementTag(Component.Serializer.toJson(baseComponent));\r\n            event.system = new ElementTag(false);\r\n            event.player = PlayerTag.mirrorBukkitPlayer(player.getBukkitEntity());\r\n            event = (PlayerReceivesActionbarScriptEvent) event.triggerNow();\r\n            if (event.cancelled) {\r\n                return true;\r\n            }\r\n            if (event.modified) {\r\n                Component component = Handler.componentToNMS(event.altMessageDetermination);\r\n                ClientboundSetActionBarTextPacket newPacket = new ClientboundSetActionBarTextPacket(component);\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processSoundPacket(Packet<?> packet) {\r\n        if (!PlayerHearsSoundScriptEvent.instance.eventData.isEnabled) {\r\n            return false;\r\n        }\r\n        if (packet instanceof ClientboundSoundPacket) {\r\n            ClientboundSoundPacket spacket = (ClientboundSoundPacket) packet;\r\n            return PlayerHearsSoundScriptEvent.instance.run(player.getBukkitEntity(), spacket.getSound().getLocation().getPath(), spacket.getSource().name(),\r\n                    false, null, new Location(player.getBukkitEntity().getWorld(), spacket.getX(), spacket.getY(), spacket.getZ()), spacket.getVolume(), spacket.getPitch());\r\n        }\r\n        else if (packet instanceof ClientboundSoundEntityPacket) {\r\n            ClientboundSoundEntityPacket spacket = (ClientboundSoundEntityPacket) packet;\r\n            Entity entity = player.getLevel().getEntity(spacket.getId());\r\n            if (entity == null) {\r\n                return false;\r\n            }\r\n            return PlayerHearsSoundScriptEvent.instance.run(player.getBukkitEntity(), spacket.getSound().getLocation().getPath(), spacket.getSource().name(),\r\n                    false, entity.getBukkitEntity(), null, spacket.getVolume(), spacket.getPitch());\r\n        }\r\n        else if (packet instanceof ClientboundCustomSoundPacket) {\r\n            ClientboundCustomSoundPacket spacket = (ClientboundCustomSoundPacket) packet;\r\n            return PlayerHearsSoundScriptEvent.instance.run(player.getBukkitEntity(), spacket.getName().toString(), spacket.getSource().name(),\r\n                    true, null, new Location(player.getBukkitEntity().getWorld(), spacket.getX(), spacket.getY(), spacket.getZ()), spacket.getVolume(), spacket.getPitch());\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processEquipmentForPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (FakeEquipCommand.overrides.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundSetEquipmentPacket) {\r\n                int eid = ((ClientboundSetEquipmentPacket) packet).getEntity();\r\n                Entity ent = player.level.getEntity(eid);\r\n                if (ent == null) {\r\n                    return false;\r\n                }\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(ent.getUUID(), player.getBukkitEntity());\r\n                if (override == null) {\r\n                    return false;\r\n                }\r\n                List<Pair<net.minecraft.world.entity.EquipmentSlot, ItemStack>> equipment = new ArrayList<>(((ClientboundSetEquipmentPacket) packet).getSlots());\r\n                ClientboundSetEquipmentPacket newPacket = new ClientboundSetEquipmentPacket(eid, equipment);\r\n                for (int i = 0; i < equipment.size(); i++) {\r\n                    Pair<net.minecraft.world.entity.EquipmentSlot, ItemStack> pair =  equipment.get(i);\r\n                    ItemStack use = pair.getSecond();\r\n                    switch (pair.getFirst()) {\r\n                        case MAINHAND:\r\n                            use = override.hand == null ? use : CraftItemStack.asNMSCopy(override.hand.getItemStack());\r\n                            break;\r\n                        case OFFHAND:\r\n                            use = override.offhand == null ? use : CraftItemStack.asNMSCopy(override.offhand.getItemStack());\r\n                            break;\r\n                        case CHEST:\r\n                            use = override.chest == null ? use : CraftItemStack.asNMSCopy(override.chest.getItemStack());\r\n                            break;\r\n                        case HEAD:\r\n                            use = override.head == null ? use : CraftItemStack.asNMSCopy(override.head.getItemStack());\r\n                            break;\r\n                        case LEGS:\r\n                            use = override.legs == null ? use : CraftItemStack.asNMSCopy(override.legs.getItemStack());\r\n                            break;\r\n                        case FEET:\r\n                            use = override.boots == null ? use : CraftItemStack.asNMSCopy(override.boots.getItemStack());\r\n                            break;\r\n                    }\r\n                    equipment.set(i, new Pair<>(pair.getFirst(), use));\r\n                }\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundEntityEventPacket) {\r\n                Entity ent = ((ClientboundEntityEventPacket) packet).getEntity(player.level);\r\n                if (!(ent instanceof net.minecraft.world.entity.LivingEntity)) {\r\n                    return false;\r\n                }\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(ent.getUUID(), player.getBukkitEntity());\r\n                if (override == null || (override.hand == null && override.offhand == null)) {\r\n                    return false;\r\n                }\r\n                if (((ClientboundEntityEventPacket) packet).getEventId() != (byte) 55) {\r\n                    return false;\r\n                }\r\n                List<Pair<net.minecraft.world.entity.EquipmentSlot, ItemStack>> equipment = new ArrayList<>();\r\n                ItemStack hand = override.hand != null ? CraftItemStack.asNMSCopy(override.hand.getItemStack()) : ((net.minecraft.world.entity.LivingEntity) ent).getMainHandItem();\r\n                ItemStack offhand = override.offhand != null ? CraftItemStack.asNMSCopy(override.offhand.getItemStack()) : ((net.minecraft.world.entity.LivingEntity) ent).getOffhandItem();\r\n                equipment.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.MAINHAND, hand));\r\n                equipment.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.OFFHAND, offhand));\r\n                ClientboundSetEquipmentPacket newPacket = new ClientboundSetEquipmentPacket(ent.getId(), equipment);\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundContainerSetContentPacket) {\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), player.getBukkitEntity());\r\n                if (override == null) {\r\n                    return false;\r\n                }\r\n                int window = ((ClientboundContainerSetContentPacket) packet).getContainerId();\r\n                if (window != 0) {\r\n                    return false;\r\n                }\r\n                NonNullList<ItemStack> items = (NonNullList<ItemStack>) ((ClientboundContainerSetContentPacket) packet).getItems();\r\n                if (override.head != null) {\r\n                    items.set(5, CraftItemStack.asNMSCopy(override.head.getItemStack()));\r\n                }\r\n                if (override.chest != null) {\r\n                    items.set(6, CraftItemStack.asNMSCopy(override.chest.getItemStack()));\r\n                }\r\n                if (override.legs != null) {\r\n                    items.set(7, CraftItemStack.asNMSCopy(override.legs.getItemStack()));\r\n                }\r\n                if (override.boots != null) {\r\n                    items.set(8, CraftItemStack.asNMSCopy(override.boots.getItemStack()));\r\n                }\r\n                if (override.offhand != null) {\r\n                    items.set(45, CraftItemStack.asNMSCopy(override.offhand.getItemStack()));\r\n                }\r\n                if (override.hand != null) {\r\n                    items.set(player.getInventory().selected + 36, CraftItemStack.asNMSCopy(override.hand.getItemStack()));\r\n                }\r\n                ClientboundContainerSetContentPacket newPacket = new ClientboundContainerSetContentPacket(window, ((ClientboundContainerSetContentPacket) packet).getStateId(), items, ((ClientboundContainerSetContentPacket) packet).getCarriedItem());\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundContainerSetSlotPacket) {\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), player.getBukkitEntity());\r\n                if (override == null) {\r\n                    return false;\r\n                }\r\n                int window = ((ClientboundContainerSetSlotPacket) packet).getContainerId();\r\n                if (window != 0) {\r\n                    return false;\r\n                }\r\n                int slot = ((ClientboundContainerSetSlotPacket) packet).getSlot();\r\n                org.bukkit.inventory.ItemStack item = null;\r\n                if (slot == 5 && override.head != null) {\r\n                    item = override.head.getItemStack();\r\n                }\r\n                else if (slot == 6 && override.chest != null) {\r\n                    item = override.chest.getItemStack();\r\n                }\r\n                else if (slot == 7 && override.legs != null) {\r\n                    item = override.legs.getItemStack();\r\n                }\r\n                else if (slot == 8 && override.boots != null) {\r\n                    item = override.boots.getItemStack();\r\n                }\r\n                else if (slot == 45 && override.offhand != null) {\r\n                    item = override.offhand.getItemStack();\r\n                }\r\n                else if (slot == player.getInventory().selected + 36 && override.hand != null) {\r\n                    item = override.hand.getItemStack();\r\n                }\r\n                if (item == null) {\r\n                    return false;\r\n                }\r\n                ClientboundContainerSetSlotPacket newPacket = new ClientboundContainerSetSlotPacket(window, ((ClientboundContainerSetSlotPacket) packet).getStateId(), slot, CraftItemStack.asNMSCopy(item));\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processParticlesForPacket(Packet<?> packet) {\r\n        if (HideParticles.hidden.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundLevelParticlesPacket) {\r\n                HashSet<Particle> hidden = HideParticles.hidden.get(player.getUUID());\r\n                if (hidden == null) {\r\n                    return false;\r\n                }\r\n                ParticleOptions particle = ((ClientboundLevelParticlesPacket) packet).getParticle();\r\n                Particle bukkitParticle = CraftParticle.toBukkit(particle);\r\n                if (hidden.contains(bukkitParticle)) {\r\n                    return true;\r\n                }\r\n                return false;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    private boolean antiDuplicate = false;\r\n\r\n    public boolean processDisguiseForPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (DisguiseCommand.disguises.isEmpty() || antiDuplicate) {\r\n            return false;\r\n        }\r\n        try {\r\n            int ider = -1;\r\n            if (packet instanceof ClientboundSetEntityDataPacket) {\r\n                ider = ((ClientboundSetEntityDataPacket) packet).getId();\r\n            }\r\n            if (packet instanceof ClientboundUpdateAttributesPacket) {\r\n                ider = ((ClientboundUpdateAttributesPacket) packet).getEntityId();\r\n            }\r\n            if (packet instanceof ClientboundAddPlayerPacket) {\r\n                ider = ((ClientboundAddPlayerPacket) packet).getEntityId();\r\n            }\r\n            else if (packet instanceof ClientboundAddEntityPacket) {\r\n                ider = ((ClientboundAddEntityPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundAddMobPacket) {\r\n                ider = ((ClientboundAddMobPacket) packet).getId();\r\n            }\r\n            if (ider != -1) {\r\n                Entity e = player.getLevel().getEntity(ider);\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                HashMap<UUID, DisguiseCommand.TrackedDisguise> playerMap = DisguiseCommand.disguises.get(e.getUUID());\r\n                if (playerMap == null) {\r\n                    return false;\r\n                }\r\n                DisguiseCommand.TrackedDisguise disguise = playerMap.get(player.getUUID());\r\n                if (disguise == null) {\r\n                    disguise = playerMap.get(null);\r\n                    if (disguise == null) {\r\n                        return false;\r\n                    }\r\n                }\r\n                if (!disguise.isActive) {\r\n                    return false;\r\n                }\r\n                if (NMSHandler.debugPackets) {\r\n                    doPacketOutput(\"DISGUISED packet \" + packet.getClass().getName() + \" for entity \" + ider + \" to player \" + player.getScoreboardName());\r\n                }\r\n                if (packet instanceof ClientboundSetEntityDataPacket) {\r\n                    ClientboundSetEntityDataPacket metadataPacket = (ClientboundSetEntityDataPacket) packet;\r\n                    if (e.getId() == player.getId()) {\r\n                        if (!disguise.shouldFake) {\r\n                            return false;\r\n                        }\r\n                        List<SynchedEntityData.DataItem<?>> data = metadataPacket.getUnpackedData();\r\n                        for (SynchedEntityData.DataItem item : data) {\r\n                            EntityDataAccessor<?> watcherObject = item.getAccessor();\r\n                            int watcherId = watcherObject.getId();\r\n                            if (watcherId == 0) { // Entity flags\r\n                                ClientboundSetEntityDataPacket altPacket = new ClientboundSetEntityDataPacket(copyPacket(metadataPacket));\r\n                                data = new ArrayList<>(data);\r\n                                ENTITY_METADATA_LIST.set(altPacket, data);\r\n                                data.remove(item);\r\n                                byte flags = (byte) item.getValue();\r\n                                flags |= 0x20; // Invisible flag\r\n                                data.add(new SynchedEntityData.DataItem(watcherObject, flags));\r\n                                ClientboundSetEntityDataPacket updatedPacket = getModifiedMetadataFor(altPacket);\r\n                                oldManager.send(updatedPacket == null ? altPacket : updatedPacket, genericfuturelistener);\r\n                                return true;\r\n                            }\r\n                        }\r\n                    }\r\n                    else {\r\n                        ClientboundSetEntityDataPacket altPacket = new ClientboundSetEntityDataPacket(e.getId(), ((CraftEntity) disguise.toOthers.entity.entity).getHandle().getEntityData(), true);\r\n                        oldManager.send(altPacket, genericfuturelistener);\r\n                        return true;\r\n                    }\r\n                    return false;\r\n                }\r\n                if (packet instanceof ClientboundUpdateAttributesPacket) {\r\n                    FakeEntity fake = ider == player.getId() ? disguise.fakeToSelf : disguise.toOthers;\r\n                    if (fake == null) {\r\n                        return false;\r\n                    }\r\n                    if (fake.entity.entity instanceof LivingEntity) {\r\n                        return false;\r\n                    }\r\n                    return true; // Non-living don't have attributes\r\n                }\r\n                antiDuplicate = true;\r\n                disguise.sendTo(Collections.singletonList(new PlayerTag(player.getBukkitEntity())));\r\n                antiDuplicate = false;\r\n                return true;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            antiDuplicate = false;\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public ClientboundSetEntityDataPacket getModifiedMetadataFor(ClientboundSetEntityDataPacket metadataPacket) {\r\n        if (!RenameCommand.hasAnyDynamicRenames() && SneakCommand.forceSetSneak.isEmpty() && InvisibleCommand.helper.noOverrides() && GlowCommand.helper.noOverrides()) {\r\n            return null;\r\n        }\r\n        try {\r\n            int eid = metadataPacket.getId();\r\n            Entity ent = player.level.getEntity(eid);\r\n            if (ent == null) {\r\n                return null; // If it doesn't exist on-server, it's definitely not relevant, so move on\r\n            }\r\n            String nameToApply = RenameCommand.getCustomNameFor(ent.getUUID(), player.getBukkitEntity(), false);\r\n            Boolean forceSneak = SneakCommand.shouldSneak(ent.getUUID(), player.getUUID());\r\n            Boolean isInvisible = InvisibleCommand.helper.getState(ent.getBukkitEntity(), player.getUUID(), true);\r\n            Boolean isGlowing = GlowCommand.helper.getState(ent.getBukkitEntity(), player.getUUID(), true);\r\n            if (nameToApply == null && forceSneak == null && isInvisible == null && isGlowing == null) {\r\n                return null;\r\n            }\r\n            List<SynchedEntityData.DataItem<?>> data = new ArrayList<>(metadataPacket.getUnpackedData());\r\n            boolean any = false;\r\n            for (int i = 0; i < data.size(); i++) {\r\n                SynchedEntityData.DataItem<?> item = data.get(i);\r\n                EntityDataAccessor<?> watcherObject = item.getAccessor();\r\n                int watcherId = watcherObject.getId();\r\n                if (watcherId == 0 && (forceSneak != null || isInvisible != null || isGlowing != null)) { // 0: Entity flags\r\n                    byte val = (Byte) item.getValue();\r\n                    if (forceSneak != null) {\r\n                        if (forceSneak) {\r\n                            val |= 0x02; // 8: Crouching\r\n                        }\r\n                        else {\r\n                            val &= ~0x02;\r\n                        }\r\n                    }\r\n                    if (isInvisible != null) {\r\n                        if (isInvisible) {\r\n                            val |= 0x20;\r\n                        }\r\n                        else {\r\n                            val &= ~0x20;\r\n                        }\r\n                    }\r\n                    if (isGlowing != null) {\r\n                        if (isGlowing) {\r\n                            val |= 0x40;\r\n                        }\r\n                        else {\r\n                            val &= ~0x40;\r\n                        }\r\n                    }\r\n                    data.set(i, new SynchedEntityData.DataItem(watcherObject, val));\r\n                    any = true;\r\n                }\r\n                else if (watcherId == 2 && nameToApply != null) { // 2: Custom name metadata\r\n                    Optional<Component> name = Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(nameToApply, ChatColor.WHITE)));\r\n                    data.set(i, new SynchedEntityData.DataItem(watcherObject, name));\r\n                    any = true;\r\n                }\r\n                else if (watcherId == 3 && nameToApply != null) { // 3: custom name visible metadata\r\n                    data.set(i, new SynchedEntityData.DataItem(watcherObject, true));\r\n                    any = true;\r\n                }\r\n            }\r\n            if (!any) {\r\n                return null;\r\n            }\r\n            ClientboundSetEntityDataPacket altPacket = new ClientboundSetEntityDataPacket(copyPacket(metadataPacket));\r\n            ENTITY_METADATA_LIST.set(altPacket, data);\r\n            return altPacket;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public boolean processMetadataChangesForPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (!(packet instanceof ClientboundSetEntityDataPacket)) {\r\n            return false;\r\n        }\r\n        ClientboundSetEntityDataPacket altPacket = getModifiedMetadataFor((ClientboundSetEntityDataPacket) packet);\r\n        if (altPacket == null) {\r\n            return false;\r\n        }\r\n        oldManager.send(altPacket, genericfuturelistener);\r\n        return true;\r\n    }\r\n\r\n    public void tryProcessMovePacketForAttach(ClientboundMoveEntityPacket packet, Entity e) throws IllegalAccessException {\r\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n        if (attList != null) {\r\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                if (attMap.attached.isValid() && att != null) {\r\n                    ClientboundMoveEntityPacket pNew;\r\n                    int newId = att.attached.getBukkitEntity().getEntityId();\r\n                    if (packet instanceof ClientboundMoveEntityPacket.Pos) {\r\n                        pNew = new ClientboundMoveEntityPacket.Pos(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.isOnGround());\r\n                    }\r\n                    else if (packet instanceof ClientboundMoveEntityPacket.Rot) {\r\n                        pNew = new ClientboundMoveEntityPacket.Rot(newId, packet.getyRot(), packet.getxRot(), packet.isOnGround());\r\n                    }\r\n                    else if (packet instanceof ClientboundMoveEntityPacket.PosRot) {\r\n                        pNew = new ClientboundMoveEntityPacket.PosRot(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.getyRot(), packet.getxRot(), packet.isOnGround());\r\n                    }\r\n                    else {\r\n                        if (CoreConfiguration.debugVerbose) {\r\n                            Debug.echoError(\"Impossible move-entity packet class: \" + packet.getClass().getCanonicalName());\r\n                        }\r\n                        return;\r\n                    }\r\n                    if (att.positionalOffset != null && (packet instanceof ClientboundMoveEntityPacket.Pos || packet instanceof ClientboundMoveEntityPacket.PosRot)) {\r\n                        boolean isRotate = packet instanceof ClientboundMoveEntityPacket.PosRot;\r\n                        byte yaw, pitch;\r\n                        if (att.noRotate) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            yaw = EntityAttachmentHelper.compressAngle(attachedEntity.getYRot());\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        else if (isRotate) {\r\n                            yaw = packet.getyRot();\r\n                            pitch = packet.getxRot();\r\n                        }\r\n                        else {\r\n                            yaw = EntityAttachmentHelper.compressAngle(e.getYRot());\r\n                            pitch = EntityAttachmentHelper.compressAngle(e.getXRot());\r\n                        }\r\n                        if (att.noPitch) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        byte newYaw = yaw;\r\n                        if (isRotate) {\r\n                            newYaw = EntityAttachmentHelper.adaptedCompressedAngle(newYaw, att.positionalOffset.getYaw());\r\n                            pitch = EntityAttachmentHelper.adaptedCompressedAngle(pitch, att.positionalOffset.getPitch());\r\n                        }\r\n                        Vector goalPosition = att.fixedForOffset(new Vector(e.getX(), e.getY(), e.getZ()), e.getYRot(), e.getXRot());\r\n                        Vector oldPos = att.visiblePositions.get(player.getUUID());\r\n                        boolean forceTele = false;\r\n                        if (oldPos == null) {\r\n                            oldPos = att.attached.getLocation().toVector();\r\n                            forceTele = true;\r\n                        }\r\n                        Vector moveNeeded = goalPosition.clone().subtract(oldPos);\r\n                        att.visiblePositions.put(player.getUUID(), goalPosition.clone());\r\n                        int offX = (int) (moveNeeded.getX() * (32 * 128));\r\n                        int offY = (int) (moveNeeded.getY() * (32 * 128));\r\n                        int offZ = (int) (moveNeeded.getZ() * (32 * 128));\r\n                        if (forceTele || offX < Short.MIN_VALUE || offX > Short.MAX_VALUE\r\n                                || offY < Short.MIN_VALUE || offY > Short.MAX_VALUE\r\n                                || offZ < Short.MIN_VALUE || offZ > Short.MAX_VALUE) {\r\n                            ClientboundTeleportEntityPacket newTeleportPacket = new ClientboundTeleportEntityPacket(e);\r\n                            ENTITY_ID_PACKTELENT.setInt(newTeleportPacket, att.attached.getBukkitEntity().getEntityId());\r\n                            POS_X_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getX());\r\n                            POS_Y_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getY());\r\n                            POS_Z_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getZ());\r\n                            YAW_PACKTELENT.setByte(newTeleportPacket, newYaw);\r\n                            PITCH_PACKTELENT.setByte(newTeleportPacket, pitch);\r\n                            if (NMSHandler.debugPackets) {\r\n                                doPacketOutput(\"Attach Move-Tele Packet: \" + newTeleportPacket.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getScoreboardName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\r\n                            }\r\n                            oldManager.send(newTeleportPacket);\r\n                        }\r\n                        else {\r\n                            POS_X_PACKENT.setShort(pNew, (short) Mth.clamp(offX, Short.MIN_VALUE, Short.MAX_VALUE));\r\n                            POS_Y_PACKENT.setShort(pNew, (short) Mth.clamp(offY, Short.MIN_VALUE, Short.MAX_VALUE));\r\n                            POS_Z_PACKENT.setShort(pNew, (short) Mth.clamp(offZ, Short.MIN_VALUE, Short.MAX_VALUE));\r\n                            if (isRotate) {\r\n                                YAW_PACKENT.setByte(pNew, yaw);\r\n                                PITCH_PACKENT.setByte(pNew, pitch);\r\n                            }\r\n                            if (NMSHandler.debugPackets) {\r\n                                doPacketOutput(\"Attach Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getScoreboardName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\r\n                            }\r\n                            oldManager.send(pNew);\r\n                        }\r\n                    }\r\n                    else {\r\n                        if (NMSHandler.debugPackets) {\r\n                            doPacketOutput(\"Attach Replica-Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getScoreboardName());\r\n                        }\r\n                        oldManager.send(pNew);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (e.passengers != null && !e.passengers.isEmpty()) {\r\n            for (Entity ent : e.passengers) {\r\n                tryProcessMovePacketForAttach(packet, ent);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void tryProcessVelocityPacketForAttach(ClientboundSetEntityMotionPacket packet, Entity e) throws IllegalAccessException {\r\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n        if (attList != null) {\r\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                if (attMap.attached.isValid() && att != null) {\r\n                    ClientboundSetEntityMotionPacket pNew = new ClientboundSetEntityMotionPacket(copyPacket(packet));\r\n                    ENTITY_ID_PACKVELENT.setInt(pNew, att.attached.getBukkitEntity().getEntityId());\r\n                    if (NMSHandler.debugPackets) {\r\n                        doPacketOutput(\"Attach Velocity Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getScoreboardName());\r\n                    }\r\n                    oldManager.send(pNew);\r\n                }\r\n            }\r\n        }\r\n        if (e.passengers != null && !e.passengers.isEmpty()) {\r\n            for (Entity ent : e.passengers) {\r\n                tryProcessVelocityPacketForAttach(packet, ent);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void tryProcessTeleportPacketForAttach(ClientboundTeleportEntityPacket packet, Entity e, Vector relative) throws IllegalAccessException {\r\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n        if (attList != null) {\r\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                if (attMap.attached.isValid() && att != null) {\r\n                    ClientboundTeleportEntityPacket pNew = new ClientboundTeleportEntityPacket(copyPacket(packet));\r\n                    ENTITY_ID_PACKTELENT.setInt(pNew, att.attached.getBukkitEntity().getEntityId());\r\n                    Vector resultPos = new Vector(POS_X_PACKTELENT.getDouble(pNew), POS_Y_PACKTELENT.getDouble(pNew), POS_Z_PACKTELENT.getDouble(pNew)).add(relative);\r\n                    if (att.positionalOffset != null) {\r\n                        resultPos = att.fixedForOffset(resultPos, e.getYRot(), e.getXRot());\r\n                        byte yaw, pitch;\r\n                        if (att.noRotate) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            yaw = EntityAttachmentHelper.compressAngle(attachedEntity.getYRot());\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        else {\r\n                            yaw = packet.getyRot();\r\n                            pitch = packet.getxRot();\r\n                        }\r\n                        if (att.noPitch) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        byte newYaw = EntityAttachmentHelper.adaptedCompressedAngle(yaw, att.positionalOffset.getYaw());\r\n                        pitch = EntityAttachmentHelper.adaptedCompressedAngle(pitch, att.positionalOffset.getPitch());\r\n                        POS_X_PACKTELENT.setDouble(pNew, resultPos.getX());\r\n                        POS_Y_PACKTELENT.setDouble(pNew, resultPos.getY());\r\n                        POS_Z_PACKTELENT.setDouble(pNew, resultPos.getZ());\r\n                        YAW_PACKTELENT.setByte(pNew, newYaw);\r\n                        PITCH_PACKTELENT.setByte(pNew, pitch);\r\n                        if (NMSHandler.debugPackets) {\r\n                            doPacketOutput(\"Attach Teleport Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID()\r\n                                    + \" sent to \" + player.getScoreboardName() + \" with raw yaw \" + yaw + \" adapted to \" + newYaw);\r\n                        }\r\n                    }\r\n                    att.visiblePositions.put(player.getUUID(), resultPos.clone());\r\n                    oldManager.send(pNew);\r\n                }\r\n            }\r\n        }\r\n        if (e.passengers != null && !e.passengers.isEmpty()) {\r\n            for (Entity ent : e.passengers) {\r\n                tryProcessTeleportPacketForAttach(packet, ent, new Vector(ent.getX() - e.getX(), ent.getY() - e.getY(), ent.getZ() - e.getZ()));\r\n            }\r\n        }\r\n    }\r\n\r\n    public static Vector VECTOR_ZERO = new Vector(0, 0, 0);\r\n\r\n    public boolean processAttachToForPacket(Packet<?> packet) {\r\n        if (EntityAttachmentHelper.toEntityToData.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundMoveEntityPacket) {\r\n                Entity e = ((ClientboundMoveEntityPacket) packet).getEntity(player.getLevel());\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                if (!e.isPassenger()) {\r\n                    tryProcessMovePacketForAttach((ClientboundMoveEntityPacket) packet, e);\r\n                }\r\n                return EntityAttachmentHelper.denyOriginalPacketSend(player.getUUID(), e.getUUID());\r\n            }\r\n            else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n                int ider = ((ClientboundSetEntityMotionPacket) packet).getId();\r\n                Entity e = player.getLevel().getEntity(ider);\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                tryProcessVelocityPacketForAttach((ClientboundSetEntityMotionPacket) packet, e);\r\n                return EntityAttachmentHelper.denyOriginalPacketSend(player.getUUID(), e.getUUID());\r\n            }\r\n            else if (packet instanceof ClientboundTeleportEntityPacket) {\r\n                int ider = ((ClientboundTeleportEntityPacket) packet).getId();\r\n                Entity e = player.getLevel().getEntity(ider);\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                tryProcessTeleportPacketForAttach((ClientboundTeleportEntityPacket) packet, e, VECTOR_ZERO);\r\n                return EntityAttachmentHelper.denyOriginalPacketSend(player.getUUID(), e.getUUID());\r\n            }\r\n            else if (packet instanceof ClientboundRemoveEntitiesPacket) {\r\n                for (int id : ((ClientboundRemoveEntitiesPacket) packet).getEntityIds()) {\r\n                    Entity e = player.getLevel().getEntity(id);\r\n                    if (e != null) {\r\n                        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n                        if (attList != null) {\r\n                            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                                if (attMap.attached.isValid() && att != null) {\r\n                                    att.visiblePositions.remove(player.getUUID());\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean isHidden(Entity entity) {\r\n        return entity != null && HideEntitiesHelper.playerShouldHide(player.getBukkitEntity().getUniqueId(), entity.getBukkitEntity());\r\n    }\r\n\r\n    public boolean processHiddenEntitiesForPacket(Packet<?> packet) {\r\n        if (!HideEntitiesHelper.hasAnyHides()) {\r\n            return false;\r\n        }\r\n        try {\r\n            int ider = -1;\r\n            Entity e = null;\r\n            if (packet instanceof ClientboundAddPlayerPacket) {\r\n                ider = ((ClientboundAddPlayerPacket) packet).getEntityId();\r\n            }\r\n            else if (packet instanceof ClientboundAddEntityPacket) {\r\n                ider = ((ClientboundAddEntityPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundAddMobPacket) {\r\n                ider = ((ClientboundAddMobPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundAddPaintingPacket) {\r\n                ider = ((ClientboundAddPaintingPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundAddExperienceOrbPacket) {\r\n                ider = ((ClientboundAddExperienceOrbPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundMoveEntityPacket) {\r\n                e = ((ClientboundMoveEntityPacket) packet).getEntity(player.getLevel());\r\n            }\r\n            else if (packet instanceof ClientboundSetEntityDataPacket) {\r\n                ider = ((ClientboundSetEntityDataPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n                ider = ((ClientboundSetEntityMotionPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundTeleportEntityPacket) {\r\n                ider = ((ClientboundTeleportEntityPacket) packet).getId();\r\n            }\r\n            if (e == null && ider != -1) {\r\n                e = player.getLevel().getEntity(ider);\r\n            }\r\n            if (e != null) {\r\n                if (isHidden(e)) {\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void processFakePlayerSpawnForPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundAddPlayerPacket) {\r\n            int id = ((ClientboundAddPlayerPacket) packet).getEntityId();\r\n            if (id != -1) {\r\n                Entity e = player.getLevel().getEntity(id);\r\n                processFakePlayerSpawn(e);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void processFakePlayerSpawn(Entity entity) {\r\n        if (entity instanceof EntityFakePlayerImpl) {\r\n            final EntityFakePlayerImpl fakePlayer = (EntityFakePlayerImpl) entity;\r\n            send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, fakePlayer));\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(),\r\n                    () -> send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.REMOVE_PLAYER, fakePlayer)), 5);\r\n        }\r\n    }\r\n\r\n    public boolean processMirrorForPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundPlayerInfoPacket) {\r\n            ClientboundPlayerInfoPacket playerInfo = (ClientboundPlayerInfoPacket) packet;\r\n            ProfileEditorImpl.updatePlayerProfiles(playerInfo);\r\n            if (!ProfileEditorImpl.handleAlteredProfiles(playerInfo, this)) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processPacketHandlerForPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundChatPacket && DenizenPacketHandler.instance.shouldInterceptChatPacket()) {\r\n            PacketOutChatImpl packetHelper = new PacketOutChatImpl((ClientboundChatPacket) packet);\r\n            PlayerReceivesMessageScriptEvent result = DenizenPacketHandler.instance.sendPacket(player.getBukkitEntity(), packetHelper);\r\n            if (result != null) {\r\n                if (result.cancelled) {\r\n                    return true;\r\n                }\r\n                if (result.modified) {\r\n                    packetHelper.setRawJson(ComponentSerializer.toString(result.altMessageDetermination));\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processShowFakeForPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {\r\n        if (FakeBlock.blocks.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundLevelChunkWithLightPacket) {\r\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(player.getUUID());\r\n                if (map == null) {\r\n                    return false;\r\n                }\r\n                int chunkX = ((ClientboundLevelChunkWithLightPacket) packet).getX();\r\n                int chunkZ = ((ClientboundLevelChunkWithLightPacket) packet).getZ();\r\n                ChunkCoordinate chunkCoord = new ChunkCoordinate(chunkX, chunkZ, player.getLevel().getWorld().getName());\r\n                List<FakeBlock> blocks = FakeBlock.getFakeBlocksFor(player.getUUID(), chunkCoord);\r\n                if (blocks == null || blocks.isEmpty()) {\r\n                    return false;\r\n                }\r\n                ClientboundLevelChunkWithLightPacket newPacket = FakeBlockHelper.handleMapChunkPacket(player.getBukkitEntity().getWorld(), (ClientboundLevelChunkWithLightPacket) packet, chunkX, chunkZ, blocks);\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundSectionBlocksUpdatePacket) {\r\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(player.getUUID());\r\n                if (map == null) {\r\n                    return false;\r\n                }\r\n                SectionPos coord = (SectionPos) SECTIONPOS_MULTIBLOCKCHANGE.get(packet);\r\n                ChunkCoordinate coordinateDenizen = new ChunkCoordinate(coord.getX(), coord.getZ(), player.getLevel().getWorld().getName());\r\n                if (!map.byChunk.containsKey(coordinateDenizen)) {\r\n                    return false;\r\n                }\r\n                ClientboundSectionBlocksUpdatePacket newPacket = new ClientboundSectionBlocksUpdatePacket(copyPacket(packet));\r\n                LocationTag location = new LocationTag(player.getLevel().getWorld(), 0, 0, 0);\r\n                short[] originalOffsetArray = (short[])OFFSETARRAY_MULTIBLOCKCHANGE.get(newPacket);\r\n                BlockState[] originalDataArray = (BlockState[])BLOCKARRAY_MULTIBLOCKCHANGE.get(newPacket);\r\n                short[] offsetArray = Arrays.copyOf(originalOffsetArray, originalOffsetArray.length);\r\n                BlockState[] dataArray = Arrays.copyOf(originalDataArray, originalDataArray.length);\r\n                OFFSETARRAY_MULTIBLOCKCHANGE.set(newPacket, offsetArray);\r\n                BLOCKARRAY_MULTIBLOCKCHANGE.set(newPacket, dataArray);\r\n                for (int i = 0; i < offsetArray.length; i++) {\r\n                    short offset = offsetArray[i];\r\n                    BlockPos pos = coord.relativeToBlockPos(offset);\r\n                    location.setX(pos.getX());\r\n                    location.setY(pos.getY());\r\n                    location.setZ(pos.getZ());\r\n                    FakeBlock block = map.byLocation.get(location);\r\n                    if (block != null) {\r\n                        dataArray[i] = FakeBlockHelper.getNMSState(block);\r\n                    }\r\n                }\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundBlockUpdatePacket) {\r\n                BlockPos pos = ((ClientboundBlockUpdatePacket) packet).getPos();\r\n                LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ());\r\n                FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc);\r\n                if (block != null) {\r\n                    ClientboundBlockUpdatePacket newPacket = new ClientboundBlockUpdatePacket(((ClientboundBlockUpdatePacket) packet).getPos(), FakeBlockHelper.getNMSState(block));\r\n                    oldManager.send(newPacket, genericfuturelistener);\r\n                    return true;\r\n                }\r\n            }\r\n            else if (packet instanceof ClientboundBlockBreakAckPacket) {\r\n                ClientboundBlockBreakAckPacket origPack = (ClientboundBlockBreakAckPacket) packet;\r\n                BlockPos pos = origPack.pos();\r\n                LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ());\r\n                FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc);\r\n                if (block != null) {\r\n                    ClientboundBlockBreakAckPacket newPacket = new ClientboundBlockBreakAckPacket(origPack.pos(), FakeBlockHelper.getNMSState(block), origPack.action(), false);\r\n                    oldManager.send(newPacket, genericfuturelistener);\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void processBlockLightForPacket(Packet<?> packet) {\r\n        if (BlockLight.lightsByChunk.isEmpty()) {\r\n            return;\r\n        }\r\n        if (packet instanceof ClientboundLightUpdatePacket) {\r\n            BlockLightImpl.checkIfLightsBrokenByPacket((ClientboundLightUpdatePacket) packet, player.level);\r\n        }\r\n        else if (packet instanceof ClientboundBlockUpdatePacket) {\r\n            BlockLightImpl.checkIfLightsBrokenByPacket((ClientboundBlockUpdatePacket) packet, player.level);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldManager.tick();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRemoteAddress() {\r\n        return oldManager.getRemoteAddress();\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        if (!player.getBukkitEntity().isOnline()) { // Workaround Paper duplicate quit event issue\r\n            return;\r\n        }\r\n        oldManager.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public boolean isMemoryConnection() {\r\n        return oldManager.isMemoryConnection();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getReceiving() {\r\n        return oldManager.getReceiving();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getSending() {\r\n        return oldManager.getSending();\r\n    }\r\n\r\n    @Override\r\n    public void setEncryptionKey(Cipher cipher, Cipher cipher1) {\r\n        oldManager.setEncryptionKey(cipher, cipher1);\r\n    }\r\n\r\n    @Override\r\n    public boolean isEncrypted() {\r\n        return oldManager.isEncrypted();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnected() {\r\n        return oldManager.isConnected();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnecting() {\r\n        return oldManager.isConnecting();\r\n    }\r\n\r\n    @Override\r\n    public PacketListener getPacketListener() {\r\n        return oldManager.getPacketListener();\r\n    }\r\n\r\n    @Override\r\n    public Component getDisconnectedReason() {\r\n        return oldManager.getDisconnectedReason();\r\n    }\r\n\r\n    @Override\r\n    public void setReadOnly() {\r\n        oldManager.setReadOnly();\r\n    }\r\n\r\n    @Override\r\n    public void setupCompression(int i, boolean b) {\r\n        oldManager.setupCompression(i, b);\r\n    }\r\n\r\n    @Override\r\n    public void handleDisconnection() {\r\n        oldManager.handleDisconnection();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageReceivedPackets() {\r\n        return oldManager.getAverageReceivedPackets();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageSentPackets() {\r\n        return oldManager.getAverageSentPackets();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRawAddress() {\r\n        return oldManager.getRawAddress();\r\n    }\r\n\r\n    //////////////////////////////////\r\n    //// Reflection Methods/Fields\r\n    ///////////\r\n\r\n    private static final Field protocolDirectionField = ReflectionHelper.getFields(Connection.class).get(ReflectionMappingsInfo.Connection_receiving, PacketFlow.class);\r\n    private static final MethodHandle networkManagerField = ReflectionHelper.getFinalSetter(ServerGamePacketListenerImpl.class, ReflectionMappingsInfo.ServerGamePacketListenerImpl_connection);\r\n\r\n    private static PacketFlow getProtocolDirection(Connection networkManager) {\r\n        PacketFlow direction = null;\r\n        try {\r\n            direction = (PacketFlow) protocolDirectionField.get(networkManager);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return direction;\r\n    }\r\n\r\n    private static void setNetworkManager(ServerGamePacketListenerImpl playerConnection, Connection networkManager) {\r\n        try {\r\n            networkManagerField.invoke(playerConnection, networkManager);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean acceptInboundMessage(Object msg) throws Exception {\r\n        return oldManager.acceptInboundMessage(msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\r\n        oldManager.channelRead(ctx, msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelReadComplete(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\r\n        oldManager.userEventTriggered(ctx, evt);\r\n    }\r\n\r\n    @Override\r\n    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelWritabilityChanged(ctx);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/network/handlers/DenizenPacketListenerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerChangesSignScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerSteersEntityScriptEvent;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.network.packets.PacketInResourcePackStatusImpl;\r\nimport com.denizenscript.denizen.nms.v1_18.impl.network.packets.PacketInSteerVehicleImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\r\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.event.block.SignChangeEvent;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\n\r\npublic class DenizenPacketListenerImpl extends AbstractListenerPlayInImpl {\r\n\r\n    public String brand = \"unknown\";\r\n\r\n    public BlockPos fakeSignExpected;\r\n\r\n    public DenizenPacketListenerImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer) {\r\n        super(networkManager, entityPlayer, entityPlayer.connection);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(final ServerboundPlayerInputPacket packet) {\r\n        if (!PlayerSteersEntityScriptEvent.instance.eventData.isEnabled) {\r\n            super.handlePlayerInput(packet);\r\n            return;\r\n        }\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInSteerVehicleImpl(packet), () -> super.handlePlayerInput(packet));\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInResourcePackStatusImpl(packet));\r\n        super.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        DenizenPacketHandler.instance.receivePlacePacket(player.getBukkitEntity());\r\n        super.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        DenizenPacketHandler.instance.receiveDigPacket(player.getBukkitEntity());\r\n        super.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && (override.hand != null || override.offhand != null)) {\r\n            player.getBukkitEntity().updateInventory();\r\n        }\r\n        super.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && override.hand != null) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 2);\r\n        }\r\n        super.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && packet.getContainerId() == 0) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 1);\r\n        }\r\n        super.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        if (NMSHandler.debugPackets) {\r\n            Debug.log(\"Custom packet payload: \" + packet.identifier.toString() + \" sent from \" + player.getScoreboardName());\r\n        }\r\n        if (packet.identifier.getNamespace().equals(\"minecraft\") && packet.identifier.getPath().equals(\"brand\")) {\r\n            FriendlyByteBuf newData = new FriendlyByteBuf(packet.data.copy());\r\n            int i = newData.readVarInt(); // read off the varInt of length to get rid of it\r\n            brand = StandardCharsets.UTF_8.decode(newData.nioBuffer()).toString();\r\n        }\r\n        super.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        if (fakeSignExpected != null && packet.getPos().equals(fakeSignExpected)) {\r\n            fakeSignExpected = null;\r\n            PlayerChangesSignScriptEvent evt = (PlayerChangesSignScriptEvent) PlayerChangesSignScriptEvent.instance.clone();\r\n            evt.material = new MaterialTag(org.bukkit.Material.OAK_WALL_SIGN);\r\n            evt.location = new LocationTag(player.getBukkitEntity().getLocation());\r\n            LocationTag loc = evt.location.clone();\r\n            loc.setY(0);\r\n            evt.event = new SignChangeEvent(loc.getBlock(), player.getBukkitEntity(), packet.getLines());\r\n            evt.fire(evt.event);\r\n        }\r\n        super.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        if (DenizenPacketHandler.forceNoclip.contains(player.getUUID())) {\r\n            player.noPhysics = true;\r\n        }\r\n        super.handleMovePlayer(packet);\r\n    }\r\n\r\n    // For compatibility with other plugins using Reflection weirdly...\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        super.send(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/network/handlers/FakeBlockHelper.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.v1_18.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.Biomes;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.PalettedContainer;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_18_R2.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_18_R2.block.data.CraftBlockData;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Constructor;\r\nimport java.lang.reflect.Field;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\nimport java.util.ListIterator;\r\n\r\npublic class FakeBlockHelper {\r\n\r\n    public static Field CHUNKDATA_BLOCK_ENTITIES = ReflectionHelper.getFields(ClientboundLevelChunkPacketData.class).getFirstOfType(List.class);\r\n    public static MethodHandle CHUNKDATA_BUFFER_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkPacketData.class, byte[].class);\r\n    public static Class CHUNKDATA_BLOCKENTITYINFO_CLASS = ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0];\r\n    public static Field CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get(ReflectionMappingsInfo.ClientboundLevelChunkPacketData_BlockEntityInfo_packedXZ);\r\n    public static Field CHUNKDATA_BLOCKENTITYINFO_Y = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get(ReflectionMappingsInfo.ClientboundLevelChunkPacketData_BlockEntityInfo_y);\r\n    public static MethodHandle CHUNKPACKET_CHUNKDATA_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class);\r\n    public static Constructor<?> PALETTEDCONTAINER_CTOR = Arrays.stream(PalettedContainer.class.getConstructors()).filter(c -> c.getParameterCount() == 3).findFirst().get();\r\n\r\n    public static BlockState getNMSState(FakeBlock block) {\r\n        return ((CraftBlockData) block.material.getModernData()).getState();\r\n    }\r\n\r\n    public static boolean anyBlocksInSection(List<FakeBlock> blocks, int y) {\r\n        int minY = y << 4;\r\n        int maxY = (y << 4) + 16;\r\n        for (FakeBlock block : blocks) {\r\n            int blockY = block.location.getBlockY();\r\n            if (blockY >= minY && blockY < maxY) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static Field PAPER_CHUNK_READY;\r\n    public static boolean tryPaperPatch = true;\r\n\r\n    public static void copyPacketPaperPatch(ClientboundLevelChunkWithLightPacket newPacket, ClientboundLevelChunkWithLightPacket oldPacket) {\r\n        if (!Denizen.supportsPaper || !tryPaperPatch) {\r\n            return;\r\n        }\r\n        try {\r\n            if (PAPER_CHUNK_READY == null) {\r\n                PAPER_CHUNK_READY = ReflectionHelper.getFields(ClientboundLevelChunkWithLightPacket.class).get(\"ready\");\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            tryPaperPatch = false;\r\n            Debug.echoError(\"Paper packet patch failed:\");\r\n            Debug.echoError(ex);\r\n            return;\r\n        }\r\n        try {\r\n            PAPER_CHUNK_READY.setBoolean(newPacket, true);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static ClientboundLevelChunkWithLightPacket handleMapChunkPacket(World world, ClientboundLevelChunkWithLightPacket originalPacket, int chunkX, int chunkZ, List<FakeBlock> blocks) {\r\n        try {\r\n            ClientboundLevelChunkWithLightPacket duplicateCorePacket = new ClientboundLevelChunkWithLightPacket(DenizenNetworkManagerImpl.copyPacket(originalPacket));\r\n            copyPacketPaperPatch(duplicateCorePacket, originalPacket);\r\n            FriendlyByteBuf copier = new FriendlyByteBuf(Unpooled.buffer());\r\n            originalPacket.getChunkData().write(copier);\r\n            ClientboundLevelChunkPacketData packet = new ClientboundLevelChunkPacketData(copier, chunkX, chunkZ);\r\n            FriendlyByteBuf serial = originalPacket.getChunkData().getReadBuffer();\r\n            FriendlyByteBuf outputSerial = new FriendlyByteBuf(Unpooled.buffer(serial.readableBytes()));\r\n            List blockEntities = new ArrayList((List) CHUNKDATA_BLOCK_ENTITIES.get(originalPacket.getChunkData()));\r\n            CHUNKDATA_BLOCK_ENTITIES.set(packet, blockEntities);\r\n            ListIterator iterator = blockEntities.listIterator();\r\n            while (iterator.hasNext()) {\r\n                Object blockEnt = iterator.next();\r\n                int xz = CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ.getInt(blockEnt);\r\n                int y = CHUNKDATA_BLOCKENTITYINFO_Y.getInt(blockEnt);\r\n                int x = (chunkX << 4) + ((xz >> 4) & 15);\r\n                int z = (chunkZ << 4) + (xz & 15);\r\n                for (FakeBlock block : blocks) {\r\n                    LocationTag loc = block.location;\r\n                    if (loc.getBlockX() == x && loc.getBlockY() == y && loc.getBlockZ() == z && block.material != null) {\r\n                        iterator.remove();\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            int worldMinY = world.getMinHeight();\r\n            int worldMaxY = world.getMaxHeight();\r\n            int minChunkY = worldMinY >> 4;\r\n            int maxChunkY = worldMaxY >> 4;\r\n            Registry<Biome> biomeRegistry = ((CraftWorld) world).getHandle().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);\r\n            for (int y = minChunkY; y < maxChunkY; y++) {\r\n                int blockCount = serial.readShort();\r\n                // reflected constructors as workaround for spigot remapper bug - Mojang \"IdMap\" became Spigot \"IRegistry\" but should be \"Registry\"\r\n                PalettedContainer<BlockState> states = (PalettedContainer<BlockState>) PALETTEDCONTAINER_CTOR.newInstance(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);\r\n                states.read(serial);\r\n                PalettedContainer<Biome> biomes = (PalettedContainer<Biome>) PALETTEDCONTAINER_CTOR.newInstance(biomeRegistry, biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);\r\n                biomes.read(serial);\r\n                if (anyBlocksInSection(blocks, y)) {\r\n                    int minY = y << 4;\r\n                    int maxY = (y << 4) + 16;\r\n                    for (FakeBlock block : blocks) {\r\n                        int blockY = block.location.getBlockY();\r\n                        if (blockY >= minY && blockY < maxY && block.material != null) {\r\n                            int blockX = block.location.getBlockX();\r\n                            int blockZ = block.location.getBlockZ();\r\n                            blockX -= (blockX >> 4) * 16;\r\n                            blockY -= (blockY >> 4) * 16;\r\n                            blockZ -= (blockZ >> 4) * 16;\r\n                            BlockState oldState = states.get(blockX, blockY, blockZ);\r\n                            BlockState newState = getNMSState(block);\r\n                            if (oldState.isAir() && !newState.isAir()) {\r\n                                blockCount++;\r\n                            }\r\n                            else if (newState.isAir() && !oldState.isAir()) {\r\n                                blockCount--;\r\n                            }\r\n                            states.set(blockX, blockY, blockZ, newState);\r\n                        }\r\n                    }\r\n                }\r\n                outputSerial.writeShort(blockCount);\r\n                states.write(outputSerial);\r\n                biomes.write(outputSerial);\r\n            }\r\n            byte[] outputBytes = outputSerial.array();\r\n            CHUNKDATA_BUFFER_SETTER.invoke(packet, outputBytes);\r\n            CHUNKPACKET_CHUNKDATA_SETTER.invoke(duplicateCorePacket, packet);\r\n            return duplicateCorePacket;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/network/packets/PacketInResourcePackStatusImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInResourcePackStatus;\r\nimport net.minecraft.network.protocol.game.ServerboundResourcePackPacket;\r\n\r\npublic class PacketInResourcePackStatusImpl implements PacketInResourcePackStatus {\r\n\r\n    private ServerboundResourcePackPacket internal;\r\n\r\n    public PacketInResourcePackStatusImpl(ServerboundResourcePackPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public String getStatus() {\r\n        return internal.action.name();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/network/packets/PacketInSteerVehicleImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInSteerVehicle;\r\nimport net.minecraft.network.protocol.game.ServerboundPlayerInputPacket;\r\n\r\npublic class PacketInSteerVehicleImpl implements PacketInSteerVehicle {\r\n\r\n    private ServerboundPlayerInputPacket internal;\r\n\r\n    public PacketInSteerVehicleImpl(ServerboundPlayerInputPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public float getLeftwardInput() {\r\n        return internal.getXxa();\r\n    }\r\n\r\n    @Override\r\n    public float getForwardInput() {\r\n        return internal.getZza();\r\n    }\r\n\r\n    @Override\r\n    public boolean getJumpInput() {\r\n        return internal.isJumping();\r\n    }\r\n\r\n    @Override\r\n    public boolean getDismountInput() {\r\n        return internal.isShiftKeyDown();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/impl/network/packets/PacketOutChatImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_18.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizen.nms.v1_18.Handler;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.network.chat.ChatType;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.game.ClientboundChatPacket;\r\n\r\nimport java.lang.reflect.Field;\r\n\r\npublic class PacketOutChatImpl extends PacketOutChat {\r\n\r\n    private ClientboundChatPacket internal;\r\n    private String message;\r\n    private String rawJson;\r\n    private boolean bungee;\r\n    private ChatType position;\r\n\r\n    public PacketOutChatImpl(ClientboundChatPacket internal) {\r\n        this.internal = internal;\r\n        try {\r\n            Component baseComponent = (Component) MESSAGE.get(internal);\r\n            if (baseComponent != null) {\r\n                message = FormattedTextHelper.stringify(Handler.componentToSpigot(baseComponent));\r\n                rawJson = Component.Serializer.toJson(baseComponent);\r\n            }\r\n            else {\r\n                if (internal.components != null) {\r\n                    message = FormattedTextHelper.stringify(internal.components);\r\n                    rawJson = ComponentSerializer.toString(internal.components);\r\n                }\r\n                bungee = true;\r\n            }\r\n            position = (ChatType) POSITION.get(internal);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean isSystem() {\r\n        return position == ChatType.SYSTEM;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActionbar() {\r\n        return position == ChatType.GAME_INFO;\r\n    }\r\n\r\n    @Override\r\n    public String getMessage() {\r\n        return message;\r\n    }\r\n\r\n    @Override\r\n    public String getRawJson() {\r\n        return rawJson;\r\n    }\r\n\r\n    public void setRawJson(String rawJson) {\r\n        try {\r\n            if (!bungee) {\r\n                MESSAGE.set(internal, Component.Serializer.fromJson(rawJson));\r\n            }\r\n            else {\r\n                internal.components = ComponentSerializer.parse(rawJson);\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    private static final Field MESSAGE, POSITION;\r\n\r\n    static {\r\n        MESSAGE = ReflectionHelper.getFields(ClientboundChatPacket.class).getFirstOfType(Component.class);\r\n        POSITION = ReflectionHelper.getFields(ClientboundChatPacket.class).getFirstOfType(ChatType.class);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.denizenscript</groupId>\n    <artifactId>denizen-v1_19</artifactId>\n    <version>1.3.2-SNAPSHOT</version>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen</artifactId>\n            <version>${project.version}</version>\n            <scope>compile</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>1.19.4-R0.1-SNAPSHOT</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot</artifactId>\n            <version>1.19.4-R0.1-SNAPSHOT</version>\n            <classifier>remapped-mojang</classifier>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>net.md-5</groupId>\n                <artifactId>specialsource-maven-plugin</artifactId>\n                <version>1.2.5</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>remap</goal>\n                        </goals>\n                        <id>remap-obf</id>\n                        <configuration>\n                            <srgIn>org.spigotmc:minecraft-server:1.19.4-R0.1-SNAPSHOT:txt:maps-mojang</srgIn>\n                            <reverse>true</reverse>\n                            <remappedDependencies>org.spigotmc:spigot:1.19.4-R0.1-SNAPSHOT:jar:remapped-mojang</remappedDependencies>\n                            <remappedArtifactAttached>true</remappedArtifactAttached>\n                            <remappedClassifierName>remapped-obf</remappedClassifierName>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>remap</goal>\n                        </goals>\n                        <id>remap-spigot</id>\n                        <configuration>\n                            <inputFile>${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar</inputFile>\n                            <srgIn>org.spigotmc:minecraft-server:1.19.4-R0.1-SNAPSHOT:csrg:maps-spigot</srgIn>\n                            <remappedDependencies>org.spigotmc:spigot:1.19.4-R0.1-SNAPSHOT:jar:remapped-obf</remappedDependencies>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.10.1</version>\n                <configuration>\n                    <source>16</source>\n                    <target>16</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/Handler.java",
    "content": "package com.denizenscript.denizen.nms.v1_19;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_19.helpers.*;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.blocks.BlockLightImpl;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.nbt.ByteArrayTag;\r\nimport net.minecraft.nbt.StringTag;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.chat.MutableComponent;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.BossEvent;\r\nimport net.minecraft.world.Container;\r\nimport net.minecraft.world.Nameable;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.World;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.boss.CraftBossBar;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryCustom;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView;\r\nimport org.bukkit.craftbukkit.v1_19_R3.persistence.CraftPersistentDataContainer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftChatMessage;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryView;\r\nimport org.bukkit.persistence.PersistentDataContainer;\r\nimport org.spigotmc.AsyncCatcher;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\n\r\npublic class Handler extends NMSHandler {\r\n\r\n    public Handler() {\r\n        advancementHelper = new AdvancementHelperImpl();\r\n        animationHelper = new AnimationHelperImpl();\r\n        blockHelper = new BlockHelperImpl();\r\n        chunkHelper = new ChunkHelperImpl();\r\n        customEntityHelper = new CustomEntityHelperImpl();\r\n        entityHelper = new EntityHelperImpl();\r\n        fishingHelper = new FishingHelperImpl();\r\n        itemHelper = new ItemHelperImpl();\r\n        packetHelper = new PacketHelperImpl();\r\n        playerHelper = new PlayerHelperImpl();\r\n        worldHelper = new WorldHelperImpl();\r\n        enchantmentHelper = new EnchantmentHelperImpl();\r\n    }\r\n\r\n    private final ProfileEditor profileEditor = new ProfileEditorImpl();\r\n\r\n    private boolean wasAsyncCatcherEnabled;\r\n\r\n    @Override\r\n    public void disableAsyncCatcher() {\r\n        wasAsyncCatcherEnabled = AsyncCatcher.enabled;\r\n        AsyncCatcher.enabled = false;\r\n    }\r\n\r\n    @Override\r\n    public void undisableAsyncCatcher() {\r\n        AsyncCatcher.enabled = wasAsyncCatcherEnabled;\r\n    }\r\n\r\n    @Override\r\n    public boolean isExactServerVersionMatch() {\r\n        return ((CraftMagicNumbers) CraftMagicNumbers.INSTANCE).getMappingsVersion().equals(\"3009edc0fff87fa34680686663bd59df\");\r\n    }\r\n\r\n    @Override\r\n    public double[] getRecentTps() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().recentTps;\r\n    }\r\n\r\n    @Override\r\n    public Sidebar createSidebar(Player player) {\r\n        return new SidebarImpl(player);\r\n    }\r\n\r\n    @Override\r\n    public BlockLight createBlockLight(Location location, int lightLevel, long ticks) {\r\n        return BlockLightImpl.createLight(location, lightLevel, ticks);\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile fillPlayerProfile(PlayerProfile playerProfile) {\r\n        if (playerProfile == null) {\r\n            return null;\r\n        }\r\n        if (playerProfile.getName() == null && playerProfile.getUniqueId() == null) {\r\n            return playerProfile; // Cannot fill without lookup data\r\n        }\r\n        if (playerProfile.hasTexture() && playerProfile.hasTextureSignature() && playerProfile.getName() != null && playerProfile.getUniqueId() != null) {\r\n            return playerProfile; // Already filled\r\n        }\r\n        try {\r\n            GameProfile profile = null;\r\n            MinecraftServer minecraftServer = ((CraftServer) Bukkit.getServer()).getServer();\r\n            if (playerProfile.getUniqueId() != null) {\r\n                profile = minecraftServer.getProfileCache().get(playerProfile.getUniqueId()).orElse(null);\r\n            }\r\n            if (profile == null && playerProfile.getName() != null) {\r\n                profile = minecraftServer.getProfileCache().get(playerProfile.getName()).orElse(null);\r\n            }\r\n            if (profile == null) {\r\n                profile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n            }\r\n            Property textures = profile.getProperties().containsKey(\"textures\") ? Iterables.getFirst(profile.getProperties().get(\"textures\"), null) : null;\r\n            if (textures == null || !textures.hasSignature() || profile.getName() == null || profile.getId() == null) {\r\n                profile = minecraftServer.getSessionService().fillProfileProperties(profile, true);\r\n                textures = profile.getProperties().containsKey(\"textures\") ? Iterables.getFirst(profile.getProperties().get(\"textures\"), null) : null;\r\n            }\r\n            return new PlayerProfile(profile.getName(), profile.getId(), textures == null ? null : textures.getValue(), textures == null ? null : textures.getSignature());\r\n        }\r\n        catch (Exception e) {\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static MethodHandle PAPER_INVENTORY_TITLE_GETTER;\r\n\r\n    @Override\r\n    public String getTitle(Inventory inventory) {\r\n        Container nms = ((CraftInventory) inventory).getInventory();\r\n        if (inventory instanceof CraftInventoryCustom && Denizen.supportsPaper) {\r\n            try {\r\n                if (PAPER_INVENTORY_TITLE_GETTER == null) {\r\n                    PAPER_INVENTORY_TITLE_GETTER = ReflectionHelper.getMethodHandle(nms.getClass(), \"title\");\r\n                }\r\n                return PaperAPITools.instance.parseComponent(PAPER_INVENTORY_TITLE_GETTER.invoke(nms));\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        if (nms instanceof Nameable) {\r\n            return CraftChatMessage.fromComponent(((Nameable) nms).getDisplayName());\r\n        }\r\n        else if (MINECRAFT_INVENTORY.isInstance(nms)) {\r\n            try {\r\n                return (String) INVENTORY_TITLE.get(nms);\r\n            }\r\n            catch (IllegalAccessException e) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return \"Chest\";\r\n    }\r\n\r\n    public static MethodHandle AbstractContainerMenu_title_SETTER = ReflectionHelper.getFinalSetter(AbstractContainerMenu.class, \"title\");\r\n\r\n    @Override\r\n    public void setInventoryTitle(InventoryView view, String title) {\r\n        AbstractContainerMenu menu = ((CraftInventoryView) view).getHandle();\r\n        try {\r\n            AbstractContainerMenu_title_SETTER.invoke(menu, componentToNMS(FormattedTextHelper.parse(title, ChatColor.DARK_GRAY)));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final Class MINECRAFT_INVENTORY;\r\n    public static final Field INVENTORY_TITLE;\r\n    public static final Field ENTITY_BUKKITYENTITY = ReflectionHelper.getFields(Entity.class).get(\"bukkitEntity\");\r\n\r\n    static {\r\n        Class minecraftInv = null;\r\n        Field title = null;\r\n        try {\r\n            for (Class clzz : CraftInventoryCustom.class.getDeclaredClasses()) {\r\n                if (CoreUtilities.toLowerCase(clzz.getName()).contains(\"minecraftinventory\")) { // MinecraftInventory.\r\n                    minecraftInv = clzz;\r\n                    title = clzz.getDeclaredField(\"title\");\r\n                    title.setAccessible(true);\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            e.printStackTrace();\r\n        }\r\n        MINECRAFT_INVENTORY = minecraftInv;\r\n        INVENTORY_TITLE = title;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Player player) {\r\n        GameProfile gameProfile = ((CraftPlayer) player).getProfile();\r\n        Property property = Iterables.getFirst(gameProfile.getProperties().get(\"textures\"), null);\r\n        return new PlayerProfile(gameProfile.getName(), gameProfile.getId(),\r\n                property != null ? property.getValue() : null,\r\n                property != null ? property.getSignature() : null);\r\n    }\r\n\r\n    @Override\r\n    public ProfileEditor getProfileEditor() {\r\n        return profileEditor;\r\n    }\r\n\r\n    @Override\r\n    public List<BiomeNMS> getBiomes(World world) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        ArrayList<BiomeNMS> output = new ArrayList<>();\r\n        for (Map.Entry<ResourceKey<Biome>, Biome> pair : level.registryAccess().registryOrThrow(Registries.BIOME).entrySet()) {\r\n            output.add(new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(pair.getKey().location())));\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeNMS(World world, NamespacedKey key) {\r\n        BiomeNMSImpl impl = new BiomeNMSImpl(((CraftWorld) world).getHandle(), key);\r\n        if (impl.biomeHolder == null) {\r\n            return null;\r\n        }\r\n        return impl;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeAt(Block block) {\r\n        // Based on CraftWorld source\r\n        ServerLevel level = ((CraftWorld) block.getWorld()).getHandle();\r\n        Holder<Biome> biome = level.getNoiseBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2);\r\n        ResourceLocation key = level.registryAccess().registryOrThrow(Registries.BIOME).getKey(biome.value());\r\n        return new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key));\r\n    }\r\n\r\n    @Override\r\n    public ArrayList<String> containerListFlags(PersistentDataContainer container, String prefix) {\r\n        prefix = \"denizen:\" + prefix;\r\n        ArrayList<String> output = new ArrayList<>();\r\n        for (String key : ((CraftPersistentDataContainer) container).getRaw().keySet()) {\r\n            if (key.startsWith(prefix)) {\r\n                output.add(key.substring(prefix.length()));\r\n            }\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public boolean containerHas(PersistentDataContainer container, String key) {\r\n        return ((CraftPersistentDataContainer) container).getRaw().containsKey(key);\r\n    }\r\n\r\n    @Override\r\n    public String containerGetString(PersistentDataContainer container, String key) {\r\n        net.minecraft.nbt.Tag base = ((CraftPersistentDataContainer) container).getRaw().get(key);\r\n        if (base instanceof StringTag) {\r\n            return base.getAsString();\r\n        }\r\n        else if (base instanceof ByteArrayTag) {\r\n            return new String(((ByteArrayTag) base).getAsByteArray(), StandardCharsets.UTF_8);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public UUID getBossbarUUID(BossBar bar) {\r\n        return ((CraftBossBar) bar).getHandle().getId();\r\n    }\r\n\r\n    public static MethodHandle BOSSBAR_ID_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BossEvent.class, UUID.class);\r\n\r\n    @Override\r\n    public void setBossbarUUID(BossBar bar, UUID id) {\r\n        try {\r\n            BOSSBAR_ID_SETTER.invoke(((CraftBossBar) bar).getHandle(), id);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static BaseComponent[] componentToSpigot(Component nms) {\r\n        if (nms == null) {\r\n            return null;\r\n        }\r\n        String json = Component.Serializer.toJson(nms);\r\n        return ComponentSerializer.parse(json);\r\n    }\r\n\r\n    public static MutableComponent componentToNMS(BaseComponent[] spigot) {\r\n        if (spigot == null) {\r\n            return null;\r\n        }\r\n        String json = FormattedTextHelper.componentToJson(spigot);\r\n        return Component.Serializer.fromJson(json);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/ReflectionMappingsInfo.java",
    "content": "package com.denizenscript.denizen.nms.v1_19;\r\n\r\npublic class ReflectionMappingsInfo {\r\n\r\n    // Content generated by ReflectionMappingsGenerator - https://github.com/DenizenScript/ReflectionMappingsGenerator\r\n\r\n    // net.minecraft.advancements.AdvancementList\r\n    public static String AdvancementList_roots = \"c\";\r\n    public static String AdvancementList_tasks = \"d\";\r\n\r\n    // net.minecraft.world.level.block.state.BlockBehaviour\r\n    public static String BlockBehaviour_explosionResistance = \"aH\";\r\n\r\n    // net.minecraft.core.MappedRegistry\r\n    public static String MappedRegistry_frozen = \"l\";\r\n\r\n    // net.minecraft.world.item.crafting.RecipeManager\r\n    public static String RecipeManager_byName = \"d\";\r\n\r\n    // net.minecraft.world.entity.Entity\r\n    public static String Entity_onGround = \"N\";\r\n    public static String Entity_DATA_SHARED_FLAGS_ID = \"an\";\r\n    public static String Entity_DATA_CUSTOM_NAME = \"aR\";\r\n    public static String Entity_DATA_CUSTOM_NAME_VISIBLE = \"aS\";\r\n\r\n    // net.minecraft.world.entity.LivingEntity\r\n    public static String LivingEntity_attackStrengthTicker = \"aO\";\r\n    public static String LivingEntity_autoSpinAttackTicks = \"bx\";\r\n    public static String LivingEntity_setLivingEntityFlag_method = \"c\";\r\n\r\n    // net.minecraft.world.entity.player.Player\r\n    public static String Player_DATA_PLAYER_ABSORPTION_ID = \"e\";\r\n    public static String Player_DATA_PLAYER_MODE_CUSTOMISATION = \"bJ\";\r\n\r\n    // net.minecraft.server.level.ServerPlayer\r\n    public static String ServerPlayer_respawnForced = \"cP\";\r\n\r\n    // net.minecraft.world.entity.monster.EnderMan\r\n    public static String EnderMan_DATA_CREEPY = \"bU\";\r\n\r\n    // net.minecraft.world.entity.monster.Zombie\r\n    public static String Zombie_inWaterTime = \"cc\";\r\n\r\n    // net.minecraft.world.item.Item\r\n    public static String Item_maxStackSize = \"d\";\r\n\r\n    // net.minecraft.world.level.Level\r\n    public static String Level_isClientSide = \"B\";\r\n\r\n    // net.minecraft.server.level.ThreadedLevelLightEngine\r\n    public static String ThreadedLevelLightEngine_addTask_method = \"a\";\r\n\r\n    // net.minecraft.server.level.ThreadedLevelLightEngine$TaskType\r\n    public static String ThreadedLevelLightEngineTaskType_PRE_UPDATE = \"a\";\r\n\r\n    // net.minecraft.world.entity.item.ItemEntity\r\n    public static String ItemEntity_DATA_ITEM = \"c\";\r\n\r\n    // net.minecraft.world.level.biome.Biome\r\n    public static String Biome_climateSettings = \"i\";\r\n\r\n    // net.minecraft.world.level.biome.BiomeSpecialEffects\r\n    public static String BiomeSpecialEffects_foliageColorOverride = \"f\";\r\n    public static String BiomeSpecialEffects_fogColor = \"b\";\r\n    public static String BiomeSpecialEffects_waterFogColor = \"d\";\r\n\r\n    // net.minecraft.network.Connection\r\n    public static String Connection_receiving = \"k\";\r\n\r\n    // net.minecraft.server.network.ServerGamePacketListenerImpl\r\n    public static String ServerGamePacketListenerImpl_aboveGroundTickCount = \"H\";\r\n    public static String ServerGamePacketListenerImpl_aboveGroundVehicleTickCount = \"J\";\r\n    public static String ServerGamePacketListenerImpl_connection = \"h\";\r\n    public static String ServerGamePacketListenerImpl_awaitingPositionFromClient = \"D\";\r\n    public static String ServerGamePacketListenerImpl_awaitingTeleport = \"E\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket\r\n    public static String ClientboundPlayerAbilitiesPacket_walkingSpeed = \"j\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket\r\n    public static String ClientboundSectionBlocksUpdatePacket_sectionPos = \"b\";\r\n    public static String ClientboundSectionBlocksUpdatePacket_positions = \"c\";\r\n    public static String ClientboundSectionBlocksUpdatePacket_states = \"d\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundMoveEntityPacket\r\n    public static String ClientboundMoveEntityPacket_xa = \"b\";\r\n    public static String ClientboundMoveEntityPacket_ya = \"c\";\r\n    public static String ClientboundMoveEntityPacket_za = \"d\";\r\n    public static String ClientboundMoveEntityPacket_yRot = \"e\";\r\n    public static String ClientboundMoveEntityPacket_xRot = \"f\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket\r\n    public static String ClientboundSetEntityMotionPacket_id = \"a\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket\r\n    public static String ClientboundTeleportEntityPacket_id = \"a\";\r\n    public static String ClientboundTeleportEntityPacket_x = \"b\";\r\n    public static String ClientboundTeleportEntityPacket_y = \"c\";\r\n    public static String ClientboundTeleportEntityPacket_z = \"d\";\r\n    public static String ClientboundTeleportEntityPacket_yRot = \"e\";\r\n    public static String ClientboundTeleportEntityPacket_xRot = \"f\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo\r\n    public static String ClientboundLevelChunkPacketDataBlockEntityInfo_packedXZ = \"a\";\r\n    public static String ClientboundLevelChunkPacketDataBlockEntityInfo_y = \"b\";\r\n\r\n    // net.minecraft.world.entity.projectile.FishingHook\r\n    public static String FishingHook_nibble = \"j\";\r\n    public static String FishingHook_timeUntilLured = \"k\";\r\n    public static String FishingHook_timeUntilHooked = \"l\";\r\n\r\n    // net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase\r\n    public static String BlockBehaviourBlockStateBase_getFluidState_method = \"r\";\r\n\r\n    // net.minecraft.world.level.material.FluidState\r\n    public static String FluidState_isRandomlyTicking_method = \"f\";\r\n    public static String FluidState_isEmpty_method = \"c\";\r\n    public static String FluidState_createLegacyBlock_method = \"g\";\r\n    public static String FluidState_animateTick_method = \"a\";\r\n\r\n    // net.minecraft.tags.TagNetworkSerialization$NetworkPayload\r\n    public static String TagNetworkSerializationNetworkPayload_tags = \"a\";\r\n\r\n    // net.minecraft.core.HolderSet$Named\r\n    public static String HolderSetNamed_bind_method = \"b\";\r\n\r\n    // net.minecraft.core.Holder$Reference\r\n    public static String HolderReference_bindTags_method = \"a\";\r\n\r\n    // net.minecraft.server.level.ServerLevel\r\n    public static String ServerLevel_sleepStatus = \"N\";\r\n\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/AdvancementHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.AdvancementHelper;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizen.nms.v1_19.Handler;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.advancements.*;\r\nimport net.minecraft.advancements.critereon.ImpossibleTrigger;\r\nimport net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.PlayerAdvancements;\r\nimport net.minecraft.server.ServerAdvancementManager;\r\nimport net.minecraft.server.dedicated.DedicatedServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.Collections;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.Set;\r\n\r\npublic class AdvancementHelperImpl extends AdvancementHelper {\r\n\r\n    private static final String IMPOSSIBLE_KEY = \"impossible\";\r\n    private static final Map<String, Criterion> IMPOSSIBLE_CRITERIA = Collections.singletonMap(IMPOSSIBLE_KEY, new Criterion(new ImpossibleTrigger.TriggerInstance()));\r\n    private static final String[][] IMPOSSIBLE_REQUIREMENTS = new String[][]{{IMPOSSIBLE_KEY}};\r\n\r\n    public static ServerAdvancementManager getAdvancementDataWorld() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getAdvancements();\r\n    }\r\n\r\n    public static Field FIELD_ADVANCEMENTLIST_LISTENER = ReflectionHelper.getFields(AdvancementList.class).getFirstOfType(AdvancementList.Listener.class);\r\n\r\n    @Override\r\n    public void register(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || advancement.registered) {\r\n            return;\r\n        }\r\n        Advancement nms = asNMSCopy(advancement);\r\n        if (advancement.parent == null) {\r\n            Set<Advancement> roots = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_roots, getAdvancementDataWorld().advancements);\r\n            roots.add(nms);\r\n            AdvancementList.Listener something = ReflectionHelper.getFieldValue(AdvancementList.class, FIELD_ADVANCEMENTLIST_LISTENER.getName(), getAdvancementDataWorld().advancements);\r\n            if (something != null) {\r\n                something.onAddAdvancementRoot(nms);\r\n            }\r\n        }\r\n        else {\r\n            Set<Advancement> branches = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_tasks, getAdvancementDataWorld().advancements);\r\n            branches.add(nms);\r\n            AdvancementList.Listener something = ReflectionHelper.getFieldValue(AdvancementList.class, FIELD_ADVANCEMENTLIST_LISTENER.getName(), getAdvancementDataWorld().advancements);\r\n            if (something != null) {\r\n                something.onAddAdvancementTask(nms);\r\n            }\r\n        }\r\n        getAdvancementDataWorld().advancements.advancements.put(nms.getId(), nms);\r\n        advancement.registered = true;\r\n        if (!advancement.hidden && advancement.parent != null) {\r\n            ((CraftServer) Bukkit.getServer()).getHandle().broadcastAll(new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nms), Collections.emptySet(), Collections.emptyMap()), (net.minecraft.world.entity.player.Player) null);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void unregister(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || !advancement.registered) {\r\n            return;\r\n        }\r\n        Map<ResourceLocation, Advancement> advancements = getAdvancementDataWorld().advancements.advancements;\r\n        ResourceLocation key = asResourceLocation(advancement.key);\r\n        Advancement nms = advancements.get(key);\r\n        if (advancement.parent == null) {\r\n            Set<Advancement> roots = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_roots, getAdvancementDataWorld().advancements);\r\n            roots.remove(nms);\r\n        }\r\n        else {\r\n            Set<Advancement> branches = ReflectionHelper.getFieldValue(AdvancementList.class, ReflectionMappingsInfo.AdvancementList_tasks, getAdvancementDataWorld().advancements);\r\n            branches.remove(nms);\r\n        }\r\n        advancements.remove(key);\r\n        advancement.registered = false;\r\n        ((CraftServer) Bukkit.getServer()).getHandle().broadcastAll(new ClientboundUpdateAdvancementsPacket(false,\r\n                Collections.emptySet(), Collections.singleton(key), Collections.emptyMap()), (net.minecraft.world.entity.player.Player) null);\r\n    }\r\n\r\n    @Override\r\n    public void grantPartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) {\r\n        if (advancement.length <= 1) {\r\n            grant(advancement, player);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            Advancement nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            Map<String, Criterion> criteria = new HashMap<>();\r\n            String[][] requirements = new String[advancement.length][];\r\n            for (int i = 0; i < advancement.length; i++) {\r\n                criteria.put(IMPOSSIBLE_KEY + i, new Criterion(new ImpossibleTrigger.TriggerInstance()));\r\n                requirements[i] = new String[] { IMPOSSIBLE_KEY + i };\r\n            }\r\n            progress.update(IMPOSSIBLE_CRITERIA, IMPOSSIBLE_REQUIREMENTS);\r\n            for (int i = 0; i < len; i++) {\r\n                progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria\r\n            }\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nmsAdvancement),\r\n                    Collections.emptySet(),\r\n                    Collections.singletonMap(nmsAdvancement.getId(), progress)));\r\n        }\r\n        else {\r\n            Advancement nmsAdvancement = getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.key));\r\n            for (int i = 0; i < len; i++) {\r\n                ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY + i);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void grant(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.length > 1) {\r\n            grantPartial(advancement, player, advancement.length);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            Advancement nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(IMPOSSIBLE_CRITERIA, IMPOSSIBLE_REQUIREMENTS);\r\n            progress.grantProgress(IMPOSSIBLE_KEY); // complete impossible criteria\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nmsAdvancement),\r\n                    Collections.emptySet(),\r\n                    Collections.singletonMap(nmsAdvancement.getId(), progress)));\r\n        }\r\n        else {\r\n            Advancement nmsAdvancement = getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.key));\r\n            ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void revoke(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.temporary) {\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.emptySet(),\r\n                    Collections.singleton(asResourceLocation(advancement.key)),\r\n                    Collections.emptyMap()));\r\n        }\r\n        else {\r\n            Advancement nmsAdvancement = getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.key));\r\n            ((CraftPlayer) player).getHandle().getAdvancements().revoke(nmsAdvancement, IMPOSSIBLE_KEY);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        nmsPlayer.connection.send(new ClientboundUpdateAdvancementsPacket(true,\r\n                Collections.emptySet(),\r\n                Collections.emptySet(),\r\n                Collections.emptyMap()));\r\n        PlayerAdvancements data = nmsPlayer.getAdvancements();\r\n        data.save(); // save progress\r\n        data.reload(DedicatedServer.getServer().getAdvancements()); // clear progress\r\n        data.flushDirty(nmsPlayer); // load progress and update client\r\n    }\r\n\r\n    private static Advancement asNMSCopy(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        ResourceLocation key = asResourceLocation(advancement.key);\r\n        Advancement parent = advancement.parent != null\r\n                ? getAdvancementDataWorld().advancements.advancements.get(asResourceLocation(advancement.parent))\r\n                : null;\r\n        DisplayInfo display = new DisplayInfo(CraftItemStack.asNMSCopy(advancement.icon),\r\n                Handler.componentToNMS(FormattedTextHelper.parse(advancement.title, ChatColor.WHITE)), Handler.componentToNMS(FormattedTextHelper.parse(advancement.description, ChatColor.WHITE)),\r\n                asResourceLocation(advancement.background), FrameType.valueOf(advancement.frame.name()),\r\n                advancement.toast, advancement.announceToChat, advancement.hidden);\r\n        display.setLocation(advancement.xOffset, advancement.yOffset);\r\n        Map<String, Criterion> criteria = IMPOSSIBLE_CRITERIA;\r\n        String[][] requirements = IMPOSSIBLE_REQUIREMENTS;\r\n        if (advancement.length > 1) {\r\n            criteria = new HashMap<>();\r\n            requirements = new String[advancement.length][];\r\n            for (int i = 0; i < advancement.length; i++) {\r\n                criteria.put(IMPOSSIBLE_KEY + i, new Criterion(new ImpossibleTrigger.TriggerInstance()));\r\n                requirements[i] = new String[] { IMPOSSIBLE_KEY + i };\r\n            }\r\n        }\r\n        return new Advancement(key, parent, display, AdvancementRewards.EMPTY, criteria, requirements);\r\n    }\r\n\r\n    private static ResourceLocation asResourceLocation(NamespacedKey key) {\r\n        return key != null ? new ResourceLocation(key.getNamespace(), key.getKey()) : null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/AnimationHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.AnimationHelper;\r\nimport net.minecraft.world.entity.Entity;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftHorse;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftPolarBear;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Horse;\r\nimport org.bukkit.entity.IronGolem;\r\n\r\npublic class AnimationHelperImpl extends AnimationHelper {\r\n\r\n    public AnimationHelperImpl() {\r\n        register(\"POLAR_BEAR_START_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(true);\r\n            }\r\n        });\r\n        register(\"POLAR_BEAR_STOP_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(false);\r\n            }\r\n        });\r\n        register(\"HORSE_START_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().setStanding(true);\r\n            }\r\n        });\r\n        register(\"HORSE_STOP_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().setStanding(false);\r\n            }\r\n        });\r\n        register(\"HORSE_BUCK\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().makeMad();\r\n            }\r\n        });\r\n        register(\"IRON_GOLEM_ATTACK\", entity -> {\r\n            if (entity instanceof IronGolem) {\r\n                Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n                nmsEntity.level.broadcastEntityEvent(nmsEntity, (byte) 4);\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/BlockHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.BlockHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.VanillaTagHelper;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.HolderSet;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.protocol.game.ClientboundUpdateTagsPacket;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.tags.TagKey;\r\nimport net.minecraft.tags.TagNetworkSerialization;\r\nimport net.minecraft.util.InclusiveRange;\r\nimport net.minecraft.util.RandomSource;\r\nimport net.minecraft.util.random.SimpleWeightedRandomList;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.level.BaseSpawner;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.SpawnData;\r\nimport net.minecraft.world.level.block.entity.BlockEntity;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockBehaviour;\r\nimport net.minecraft.world.level.block.state.properties.NoteBlockInstrument;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.ChunkStatus;\r\nimport net.minecraft.world.level.material.PushReaction;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.Skull;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockEntityState;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.CraftSkull;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_19_R3.tag.CraftBlockTag;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.stream.Collectors;\r\n\r\npublic class BlockHelperImpl implements BlockHelper {\r\n\r\n    public static final Field craftBlockEntityState_tileEntity = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"tileEntity\");\r\n    public static final Field craftBlockEntityState_snapshot = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"snapshot\");\r\n    public static final Field craftSkull_profile = ReflectionHelper.getFields(CraftSkull.class).get(\"profile\");\r\n\r\n    @Override\r\n    public void applyPhysics(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        ((CraftWorld) location.getWorld()).getHandle().updateNeighborsAt(pos, CraftMagicNumbers.getBlock(location.getBlock().getType()));\r\n    }\r\n\r\n    public static <T extends BlockEntity> T getTE(CraftBlockEntityState<T> cbs) {\r\n        try {\r\n            return (T) craftBlockEntityState_tileEntity.get(cbs);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Skull skull) {\r\n        GameProfile profile = getTE(((CraftSkull) skull)).owner;\r\n        if (profile == null) {\r\n            return null;\r\n        }\r\n        String name = profile.getName();\r\n        UUID id = profile.getId();\r\n        com.mojang.authlib.properties.Property property = Iterables.getFirst(profile.getProperties().get(\"textures\"), null);\r\n        return new PlayerProfile(name, id, property != null ? property.getValue() : null);\r\n    }\r\n\r\n    @Override\r\n    public void setPlayerProfile(Skull skull, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        if (playerProfile.hasTexture()) {\r\n            gameProfile.getProperties().put(\"textures\",\r\n                    new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n        }\r\n        try {\r\n            craftSkull_profile.set(skull, gameProfile);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        skull.update();\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Block block) {\r\n        BlockEntity te = ((CraftWorld) block.getWorld()).getHandle().getBlockEntity(new BlockPos(block.getX(), block.getY(), block.getZ()), true);\r\n        if (te != null) {\r\n            CompoundTag nmsData = te.saveWithFullMetadata();\r\n            return NBTAdapter.toAPI(nmsData);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Block block, CompoundBinaryTag ctag) {\r\n        CompoundTag nmsData = NBTAdapter.toNMS(ctag);\r\n        nmsData.putInt(\"x\", block.getX());\r\n        nmsData.putInt(\"y\", block.getY());\r\n        nmsData.putInt(\"z\", block.getZ());\r\n        BlockPos blockPos = new BlockPos(block.getX(), block.getY(), block.getZ());\r\n        BlockEntity te = ((CraftWorld) block.getWorld()).getHandle().getBlockEntity(blockPos, true);\r\n        te.load(nmsData);\r\n    }\r\n\r\n    @Override\r\n    public boolean setBlockResistance(Material material, float resistance) {\r\n        net.minecraft.world.level.block.Block block = getMaterialBlock(material);\r\n        if (block == null) {\r\n            return false;\r\n        }\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, ReflectionMappingsInfo.BlockBehaviour_explosionResistance, block, resistance);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public float getBlockResistance(Material material) {\r\n        net.minecraft.world.level.block.Block block = getMaterialBlock(material);\r\n        if (block == null) {\r\n            return 0;\r\n        }\r\n        return ReflectionHelper.getFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, ReflectionMappingsInfo.BlockBehaviour_explosionResistance, block);\r\n    }\r\n\r\n    public static final Field BLOCK_MATERIAL = ReflectionHelper.getFields(net.minecraft.world.level.block.state.BlockBehaviour.class).getFirstOfType(net.minecraft.world.level.material.Material.class);\r\n\r\n    public static final MethodHandle MATERIAL_PUSH_REACTION_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.material.Material.class, PushReaction.class);\r\n\r\n    public static final MethodHandle BLOCK_STRENGTH_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase.class, float.class); // destroySpeed\r\n\r\n    public net.minecraft.world.level.block.Block getMaterialBlock(Material bukkitMaterial) {\r\n        if (!bukkitMaterial.isBlock()) {\r\n            return null;\r\n        }\r\n        return ((CraftBlockData) bukkitMaterial.createBlockData()).getState().getBlock();\r\n    }\r\n\r\n    public net.minecraft.world.level.material.Material getInternalMaterial(Material bukkitMaterial) {\r\n        try {\r\n            return (net.minecraft.world.level.material.Material) BLOCK_MATERIAL.get(getMaterialBlock(bukkitMaterial));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setPushReaction(Material mat, PistonPushReaction reaction) {\r\n        try {\r\n            MATERIAL_PUSH_REACTION_SETTER.invoke(getInternalMaterial(mat), PushReaction.values()[reaction.ordinal()]);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBlockStrength(Material mat) {\r\n        return getMaterialBlock(mat).defaultBlockState().destroySpeed;\r\n    }\r\n\r\n    @Override\r\n    public void setBlockStrength(Material mat, float strength) {\r\n        try {\r\n            BLOCK_STRENGTH_SETTER.invoke(getMaterialBlock(mat).defaultBlockState(), strength);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    // This is to debork Spigot's class remapper mishandling 'getFluidState' which remaps 'FluidState' to 'material.FluidType' (incorrectly) in the call and thus errors out.\r\n    // TODO: 1.18: This might be fixed by Spigot and can be switched to raw method calls\r\n    // Relevant issue: https://hub.spigotmc.org/jira/browse/SPIGOT-6696\r\n    // NOTE: Not fixed as of 1.19 initial update\r\n    public static MethodHandle BLOCKSTATEBASE_GETFLUIDSTATE = ReflectionHelper.getMethodHandle(BlockBehaviour.BlockStateBase.class, ReflectionMappingsInfo.BlockBehaviourBlockStateBase_getFluidState_method);\r\n    public static MethodHandle FLUIDSTATE_ISRANDOMLYTICKING = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), ReflectionMappingsInfo.FluidState_isRandomlyTicking_method);\r\n    public static MethodHandle FLUIDSTATE_ISEMPTY = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), ReflectionMappingsInfo.FluidState_isEmpty_method);\r\n    public static MethodHandle FLUIDSTATE_CREATELEGACYBLOCK = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), ReflectionMappingsInfo.FluidState_createLegacyBlock_method);\r\n    public static MethodHandle FLUIDSTATE_ANIMATETICK = ReflectionHelper.getMethodHandle(BLOCKSTATEBASE_GETFLUIDSTATE.type().returnType(), ReflectionMappingsInfo.FluidState_animateTick_method, Level.class, BlockPos.class, RandomSource.class);\r\n\r\n    @Override\r\n    public void doRandomTick(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        ChunkAccess nmsChunk = ((CraftChunk) location.getChunk()).getHandle(ChunkStatus.FULL);\r\n        net.minecraft.world.level.block.state.BlockState nmsBlock = nmsChunk.getBlockState(pos);\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        if (nmsBlock.isRandomlyTicking()) {\r\n            nmsBlock.randomTick(nmsWorld, pos, nmsWorld.random);\r\n        }\r\n        try {\r\n            // FluidState fluid = nmsBlock.getFluidState();\r\n            // if (fluid.isRandomlyTicking()) {\r\n            //     fluid.animateTick(nmsWorld, pos, nmsWorld.random);\r\n            // }\r\n            Object fluid = BLOCKSTATEBASE_GETFLUIDSTATE.invoke(nmsBlock);\r\n            if ((boolean) FLUIDSTATE_ISRANDOMLYTICKING.invoke(fluid)) {\r\n                FLUIDSTATE_ANIMATETICK.invoke(fluid, nmsWorld, pos, nmsWorld.random);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Instrument getInstrumentFor(Material mat) {\r\n        net.minecraft.world.level.block.Block blockType = getMaterialBlock(mat);\r\n        Optional<NoteBlockInstrument> aboveInstrument = NoteBlockInstrument.byStateAbove(blockType.defaultBlockState());\r\n        NoteBlockInstrument nmsInstrument = aboveInstrument.orElse(NoteBlockInstrument.byStateBelow(blockType.defaultBlockState()));\r\n        return Instrument.values()[(nmsInstrument.ordinal())];\r\n    }\r\n\r\n    @Override\r\n    public int getExpDrop(Block block, org.bukkit.inventory.ItemStack item) {\r\n        net.minecraft.world.level.block.Block blockType = getMaterialBlock(block.getType());\r\n        if (blockType == null) {\r\n            return 0;\r\n        }\r\n        return blockType.getExpDrop(((CraftBlock) block).getNMS(), ((CraftBlock) block).getCraftWorld().getHandle(), ((CraftBlock) block).getPosition(),\r\n                item == null ? null : CraftItemStack.asNMSCopy(item), true);\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnerSpawnedType(CreatureSpawner spawner, EntityTag entity) {\r\n        spawner.setSpawnedType(entity.getBukkitEntityType());\r\n        if (entity.getWaitingMechanisms() == null || entity.getWaitingMechanisms().size() == 0) {\r\n            return;\r\n        }\r\n        try {\r\n            // Wrangle a fake entity\r\n            Entity nmsEntity = ((CraftWorld) spawner.getWorld()).createEntity(spawner.getLocation(), entity.getBukkitEntityType().getEntityClass());\r\n            EntityTag entityTag = new EntityTag(nmsEntity.getBukkitEntity());\r\n            entityTag.isFake = true;\r\n            entityTag.isFakeValid = true;\r\n            for (Mechanism mechanism : entity.getWaitingMechanisms()) {\r\n                entityTag.safeAdjustDuplicate(mechanism);\r\n            }\r\n            nmsEntity.unsetRemoved();\r\n            // Store it into the spawner\r\n            CraftCreatureSpawner bukkitSpawner = (CraftCreatureSpawner) spawner;\r\n            SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(bukkitSpawner);\r\n            BaseSpawner nmsSpawner = nmsSnapshot.getSpawner();\r\n            SpawnData toSpawn = nmsSpawner.nextSpawnData;\r\n            CompoundTag tag = toSpawn.getEntityToSpawn();\r\n            nmsEntity.saveWithoutId(tag);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnerCustomRules(CreatureSpawner spawner, int skyMin, int skyMax, int blockMin, int blockMax) {\r\n        try {\r\n            CraftCreatureSpawner bukkitSpawner = (CraftCreatureSpawner) spawner;\r\n            SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(bukkitSpawner);\r\n            BaseSpawner nmsSpawner = nmsSnapshot.getSpawner();\r\n            SpawnData toSpawn = nmsSpawner.nextSpawnData;\r\n            SpawnData.CustomSpawnRules rules = skyMin == -1 ? null : new SpawnData.CustomSpawnRules(new InclusiveRange<>(skyMin, skyMax), new InclusiveRange<>(blockMin, blockMax));\r\n            nmsSpawner.nextSpawnData = new SpawnData(toSpawn.entityToSpawn(), Optional.ofNullable(rules));\r\n            nmsSpawner.spawnPotentials = SimpleWeightedRandomList.empty();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Color getMapColor(Block block) {\r\n        CraftBlock craftBlock = (CraftBlock) block;\r\n        return Color.fromRGB(craftBlock.getNMS().getMapColor(craftBlock.getHandle(), craftBlock.getPosition()).col);\r\n    }\r\n\r\n    public static MethodHandle HolderSet_Named_bind = ReflectionHelper.getMethodHandle(HolderSet.Named.class, ReflectionMappingsInfo.HolderSetNamed_bind_method, List.class);\r\n    public static MethodHandle Holder_Reference_bindTags = ReflectionHelper.getMethodHandle(Holder.Reference.class, ReflectionMappingsInfo.HolderReference_bindTags_method, Collection.class);\r\n\r\n    @Override\r\n    public void setVanillaTags(Material material, Set<NamespacedKey> tags) {\r\n        Holder<net.minecraft.world.level.block.Block> nmsHolder = getMaterialBlock(material).builtInRegistryHolder();\r\n        nmsHolder.tags().forEach(nmsTag -> {\r\n            HolderSet.Named<net.minecraft.world.level.block.Block> nmsHolderSet = BuiltInRegistries.BLOCK.getTag(nmsTag).orElse(null);\r\n            if (nmsHolderSet == null) {\r\n                return;\r\n            }\r\n            List<Holder<net.minecraft.world.level.block.Block>> nmsHolders = nmsHolderSet.stream().collect(Collectors.toCollection(ArrayList::new));\r\n            nmsHolders.remove(nmsHolder);\r\n            try {\r\n                HolderSet_Named_bind.invoke(nmsHolderSet, nmsHolders);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            VanillaTagHelper.updateMaterialTag(new CraftBlockTag(BuiltInRegistries.BLOCK, nmsTag));\r\n        });\r\n        List<TagKey<net.minecraft.world.level.block.Block>> newNmsTags = new ArrayList<>();\r\n        for (NamespacedKey tag : tags) {\r\n            TagKey<net.minecraft.world.level.block.Block> newNmsTag = TagKey.create(BuiltInRegistries.BLOCK.key(), CraftNamespacedKey.toMinecraft(tag));\r\n            HolderSet.Named<net.minecraft.world.level.block.Block> nmsHolderSet = BuiltInRegistries.BLOCK.getOrCreateTag(newNmsTag);\r\n            List<Holder<net.minecraft.world.level.block.Block>> nmsHolders = nmsHolderSet.stream().collect(Collectors.toCollection(ArrayList::new));\r\n            nmsHolders.add(nmsHolder);\r\n            try {\r\n                HolderSet_Named_bind.invoke(nmsHolderSet, nmsHolders);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            newNmsTags.add(newNmsTag);\r\n            VanillaTagHelper.addOrUpdateMaterialTag(new CraftBlockTag(BuiltInRegistries.BLOCK, newNmsTag));\r\n        }\r\n        try {\r\n            Holder_Reference_bindTags.invoke(nmsHolder, newNmsTags);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        ClientboundUpdateTagsPacket tagsPacket = new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registries()));\r\n        for (Player player : Bukkit.getOnlinePlayers()) {\r\n            PacketHelperImpl.send(player, tagsPacket);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/ChunkHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.nms.interfaces.ChunkHelper;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.QuartPos;\r\nimport net.minecraft.server.level.ServerChunkCache;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.LevelHeightAccessor;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.chunk.*;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport org.bukkit.World;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\n\r\npublic class ChunkHelperImpl implements ChunkHelper {\r\n\r\n    public final static Field chunkProviderServerThreadField;\r\n    public final static MethodHandle chunkProviderServerThreadFieldSetter;\r\n    public final static Field worldThreadField;\r\n    public final static MethodHandle worldThreadFieldSetter;\r\n\r\n    static {\r\n        chunkProviderServerThreadField = ReflectionHelper.getFields(ServerChunkCache.class).getFirstOfType(Thread.class);\r\n        chunkProviderServerThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(ServerChunkCache.class, Thread.class);\r\n        worldThreadField = ReflectionHelper.getFields(net.minecraft.world.level.Level.class).getFirstOfType(Thread.class);\r\n        worldThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.Level.class, Thread.class);\r\n    }\r\n\r\n    public Thread resetServerThread;\r\n\r\n    @Override\r\n    public void changeChunkServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread != null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkSource();\r\n        try {\r\n            resetServerThread = (Thread) chunkProviderServerThreadField.get(provider);\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, Thread.currentThread());\r\n            worldThreadFieldSetter.invoke(nmsWorld, Thread.currentThread());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void restoreServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread == null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkSource();\r\n        try {\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, resetServerThread);\r\n            worldThreadFieldSetter.invoke(nmsWorld, resetServerThread);\r\n            resetServerThread = null;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int[] getHeightMap(Chunk chunk) {\r\n        Heightmap map = ((CraftChunk) chunk).getHandle(ChunkStatus.HEIGHTMAPS).heightmaps.get(Heightmap.Types.MOTION_BLOCKING);\r\n        int[] outputMap = new int[256];\r\n        for (int x = 0; x < 16; x++) {\r\n            for (int y = 0; y < 16; y++) {\r\n                outputMap[x * 16 + y] = map.getFirstAvailable(x, y);\r\n            }\r\n        }\r\n        return outputMap;\r\n    }\r\n\r\n    @Override\r\n    public void setAllBiomes(Chunk chunk, BiomeNMS biome) {\r\n        Holder<Biome> nmsBiome = ((BiomeNMSImpl) biome).biomeHolder;\r\n        ChunkAccess nmsChunk = ((CraftChunk) chunk).getHandle(ChunkStatus.BIOMES);\r\n        ChunkPos chunkcoordintpair = nmsChunk.getPos();\r\n        int i = QuartPos.fromBlock(chunkcoordintpair.getMinBlockX());\r\n        int j = QuartPos.fromBlock(chunkcoordintpair.getMinBlockZ());\r\n        LevelHeightAccessor levelheightaccessor = nmsChunk.getHeightAccessorForGeneration();\r\n        for(int k = levelheightaccessor.getMinSection(); k < levelheightaccessor.getMaxSection(); ++k) {\r\n            LevelChunkSection chunksection = nmsChunk.getSection(nmsChunk.getSectionIndexFromSectionY(k));\r\n            PalettedContainer<Holder<Biome>> datapaletteblock = (PalettedContainer<Holder<Biome>>) chunksection.getBiomes();\r\n            datapaletteblock.acquire();\r\n            for(int l = 0; l < 4; ++l) {\r\n                for(int i1 = 0; i1 < 4; ++i1) {\r\n                    for(int j1 = 0; j1 < 4; ++j1) {\r\n                        datapaletteblock.getAndSetUnchecked(l, i1, j1, nmsBiome);\r\n                    }\r\n                }\r\n            }\r\n            datapaletteblock.release();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/CustomEntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.v1_19.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.entities.EntityFakeArrowImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.entities.EntityFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.CustomEntityHelper;\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scoreboard.Scoreboard;\r\nimport org.bukkit.scoreboard.Team;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.UUID;\r\n\r\npublic class CustomEntityHelperImpl implements CustomEntityHelper {\r\n\r\n    @Override\r\n    public FakeArrow spawnFakeArrow(Location location) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityFakeArrowImpl arrow = new EntityFakeArrowImpl(world, location);\r\n        return arrow.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public ItemProjectile spawnItemProjectile(Location location, ItemStack itemStack) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityItemProjectileImpl entity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n        world.getHandle().addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return entity.getBukkitEntity();\r\n    }\r\n\r\n    public FakePlayer spawnFakePlayer(Location location, String name, String skin, String blob, boolean doAdd) throws IllegalArgumentException {\r\n        String fullName = name;\r\n        String prefix = null;\r\n        String suffix = null;\r\n        if (name == null) {\r\n            Debug.echoError(\"FAKE_PLAYER: null name, cannot spawn\");\r\n            return null;\r\n        }\r\n        else if (fullName.length() > 16) {\r\n            prefix = fullName.substring(0, 16);\r\n            if (fullName.length() > 30) {\r\n                int len = 30;\r\n                name = fullName.substring(16, 30);\r\n                if (name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    if (fullName.length() >= 32) {\r\n                        len = 32;\r\n                        name = fullName.substring(16, 32);\r\n                    }\r\n                    else if (fullName.length() == 31) {\r\n                        len = 31;\r\n                        name = fullName.substring(16, 31);\r\n                    }\r\n                }\r\n                else if (name.length() > 46) {\r\n                    throw new IllegalArgumentException(\"You must specify a name with no more than 46 characters for FAKE_PLAYER entities!\");\r\n                }\r\n                else {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                suffix = fullName.substring(len);\r\n            }\r\n            else {\r\n                name = fullName.substring(16);\r\n                if (!name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                if (name.length() > 16) {\r\n                    suffix = name.substring(16);\r\n                    name = name.substring(0, 16);\r\n                }\r\n            }\r\n        }\r\n        if (skin != null && skin.length() > 16) {\r\n            throw new IllegalArgumentException(\"You must specify a name with no more than 16 characters for FAKE_PLAYER entity skins!\");\r\n        }\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        ServerLevel worldServer = world.getHandle();\r\n        PlayerProfile playerProfile = new PlayerProfile(name, null);\r\n        if (blob != null) {\r\n            int sc = blob.indexOf(';');\r\n            if (sc != -1) {\r\n                playerProfile.setTexture(blob.substring(0, sc));\r\n                playerProfile.setTextureSignature(blob.substring(sc + 1));\r\n            }\r\n        }\r\n        else if (skin == null && !name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n            playerProfile = NMSHandler.instance.fillPlayerProfile(playerProfile);\r\n        }\r\n        if (skin != null) {\r\n            PlayerProfile skinProfile = new PlayerProfile(skin, null);\r\n            skinProfile = NMSHandler.instance.fillPlayerProfile(skinProfile);\r\n            playerProfile.setTexture(skinProfile.getTexture());\r\n            playerProfile.setTextureSignature(skinProfile.getTextureSignature());\r\n        }\r\n        UUID uuid = UUID.randomUUID();\r\n        playerProfile.setUniqueId(uuid);\r\n\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        gameProfile.getProperties().put(\"textures\",\r\n                new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n\r\n        final EntityFakePlayerImpl fakePlayer = new EntityFakePlayerImpl(worldServer.getServer(), worldServer, gameProfile, doAdd);\r\n\r\n        fakePlayer.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(),\r\n                location.getYaw(), location.getPitch());\r\n        CraftFakePlayerImpl craftFakePlayer = fakePlayer.getBukkitEntity();\r\n        craftFakePlayer.fullName = fullName;\r\n        if (prefix != null) {\r\n            Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();\r\n            String teamName = \"FAKE_PLAYER_TEAM_\" + fullName;\r\n            String hash = null;\r\n            try {\r\n                hash = CoreUtilities.hash_md5(teamName.getBytes(StandardCharsets.UTF_8)).substring(0, 16);\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(e);\r\n            }\r\n            if (hash != null) {\r\n                Team team = scoreboard.getTeam(hash);\r\n                if (team == null) {\r\n                    team = scoreboard.registerNewTeam(hash);\r\n                    team.setPrefix(prefix);\r\n                    if (suffix != null) {\r\n                        team.setSuffix(suffix);\r\n                    }\r\n                }\r\n                team.addPlayer(craftFakePlayer);\r\n            }\r\n        }\r\n        return craftFakePlayer;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/EnchantmentHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.EnchantmentHelper;\r\nimport com.denizenscript.denizen.nms.v1_19.Handler;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.scripts.containers.core.EnchantmentScriptContainer;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport net.minecraft.core.MappedRegistry;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.world.damagesource.DamageSource;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.entity.EquipmentSlot;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.entity.MobType;\r\nimport net.minecraft.world.item.enchantment.EnchantmentCategory;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.craftbukkit.v1_19_R3.enchantments.CraftEnchantment;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftNamespacedKey;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.Map;\r\n\r\npublic class EnchantmentHelperImpl extends EnchantmentHelper {\r\n\r\n    public static Map<NamespacedKey, Enchantment> ENCHANTMENTS_BY_KEY = ReflectionHelper.getFieldValue(org.bukkit.enchantments.Enchantment.class, \"byKey\", null);\r\n    public static Map<String, org.bukkit.enchantments.Enchantment> ENCHANTMENTS_BY_NAME = ReflectionHelper.getFieldValue(org.bukkit.enchantments.Enchantment.class, \"byName\", null);\r\n    public static Field REGISTRY_FROZEN = ReflectionHelper.getFields(MappedRegistry.class).get(ReflectionMappingsInfo.MappedRegistry_frozen, boolean.class);\r\n\r\n    @Override\r\n    public org.bukkit.enchantments.Enchantment registerFakeEnchantment(EnchantmentScriptContainer.EnchantmentReference script) {\r\n        try {\r\n            EquipmentSlot[] slots = new EquipmentSlot[script.script.slots.size()];\r\n            for (int i = 0; i < slots.length; i++) {\r\n                slots[i] = EquipmentSlot.valueOf(CoreUtilities.toUpperCase(script.script.slots.get(i)));\r\n            }\r\n            net.minecraft.world.item.enchantment.Enchantment nmsEnchant = new net.minecraft.world.item.enchantment.Enchantment(net.minecraft.world.item.enchantment.Enchantment.Rarity.valueOf(script.script.rarity), EnchantmentCategory.valueOf(script.script.category), slots) {\r\n                @Override\r\n                public int getMinLevel() {\r\n                    return script.script.minLevel;\r\n                }\r\n                @Override\r\n                public int getMaxLevel() {\r\n                    return script.script.maxLevel;\r\n                }\r\n                @Override\r\n                public int getMinCost(int level) {\r\n                    return script.script.getMinCost(level);\r\n                }\r\n                @Override\r\n                public int getMaxCost(int level) {\r\n                    return script.script.getMaxCost(level);\r\n                }\r\n                @Override\r\n                public int getDamageProtection(int level, DamageSource src) {\r\n                    return script.script.getDamageProtection(level, src.getMsgId(), src.getEntity() == null ? null : src.getEntity().getBukkitEntity());\r\n                }\r\n                @Override\r\n                public float getDamageBonus(int level, MobType type) {\r\n                    String typeName = \"UNDEFINED\";\r\n                    if (type == MobType.ARTHROPOD) {\r\n                        typeName = \"ARTHROPOD\";\r\n                    }\r\n                    else if (type == MobType.ILLAGER) {\r\n                        typeName = \"ILLAGER\";\r\n                    }\r\n                    else if (type == MobType.UNDEAD) {\r\n                        typeName = \"UNDEAD\";\r\n                    }\r\n                    else if (type == MobType.WATER) {\r\n                        typeName = \"WATER\";\r\n                    }\r\n                    return script.script.getDamageBonus(level, typeName);\r\n                }\r\n                @Override\r\n                protected boolean checkCompatibility(net.minecraft.world.item.enchantment.Enchantment nmsEnchantment) {\r\n                    ResourceLocation nmsKey = BuiltInRegistries.ENCHANTMENT.getKey(nmsEnchantment);\r\n                    NamespacedKey bukkitKey = CraftNamespacedKey.fromMinecraft(nmsKey);\r\n                    org.bukkit.enchantments.Enchantment bukkitEnchant = CraftEnchantment.getByKey(bukkitKey);\r\n                    return script.script.isCompatible(bukkitEnchant);\r\n                }\r\n                @Override\r\n                protected String getOrCreateDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public String getDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public Component getFullname(int level) {\r\n                    return Handler.componentToNMS(script.script.getFullName(level));\r\n                }\r\n                @Override\r\n                public boolean canEnchant(net.minecraft.world.item.ItemStack var0) {\r\n                    return super.canEnchant(var0) && script.script.canEnchant(CraftItemStack.asBukkitCopy(var0));\r\n                }\r\n                @Override\r\n                public void doPostAttack(LivingEntity attacker, Entity victim, int level) {\r\n                    script.script.doPostAttack(attacker.getBukkitEntity(), victim.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public void doPostHurt(LivingEntity victim, Entity attacker, int level) {\r\n                    script.script.doPostHurt(victim.getBukkitEntity(), attacker.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public boolean isTreasureOnly() {\r\n                    return script.script.isTreasureOnly;\r\n                }\r\n                @Override\r\n                public boolean isCurse() {\r\n                    return script.script.isCurse;\r\n                }\r\n                @Override\r\n                public boolean isTradeable() {\r\n                    return script.script.isTradable;\r\n                }\r\n                @Override\r\n                public boolean isDiscoverable() {\r\n                    return script.script.isDiscoverable;\r\n                }\r\n            };\r\n            String enchName = CoreUtilities.toUpperCase(script.script.id);\r\n            boolean wasFrozen = REGISTRY_FROZEN.getBoolean(BuiltInRegistries.ENCHANTMENT);\r\n            REGISTRY_FROZEN.setBoolean(BuiltInRegistries.ENCHANTMENT, false);\r\n            Registry.register(BuiltInRegistries.ENCHANTMENT, \"denizen:\" + script.script.id, nmsEnchant);\r\n            CraftEnchantment ench = new CraftEnchantment(nmsEnchant) {\r\n                @Override\r\n                public String getName() {\r\n                    return enchName;\r\n                }\r\n            };\r\n            if (wasFrozen) {\r\n                ((MappedRegistry) BuiltInRegistries.ENCHANTMENT).freeze();\r\n            }\r\n            ENCHANTMENTS_BY_KEY.put(ench.getKey(), ench);\r\n            ENCHANTMENTS_BY_NAME.put(enchName, ench);\r\n            return ench;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(\"Failed to register enchantment \" + script.script.id);\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getRarity(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getRarity().name();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDiscoverable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isDiscoverable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isTradable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isTradeable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isCurse(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isCurse();\r\n    }\r\n\r\n    @Override\r\n    public int getMinCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMinCost(level);\r\n    }\r\n\r\n    @Override\r\n    public int getMaxCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMaxCost(level);\r\n    }\r\n\r\n    @Override\r\n    public String getFullName(Enchantment enchantment, int level) {\r\n        return FormattedTextHelper.stringify(Handler.componentToSpigot(((CraftEnchantment) enchantment).getHandle().getFullname(level)));\r\n    }\r\n\r\n    @Override\r\n    public float getDamageBonus(Enchantment enchantment, int level, String type) {\r\n        MobType mobType = MobType.UNDEFINED;\r\n        switch (type) {\r\n            case \"illager\":\r\n                mobType = MobType.ILLAGER;\r\n                break;\r\n            case \"undead\":\r\n                mobType = MobType.UNDEAD;\r\n                break;\r\n            case \"water\":\r\n                mobType = MobType.WATER;\r\n                break;\r\n            case \"arthropod\":\r\n                mobType = MobType.ARTHROPOD;\r\n                break;\r\n        }\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageBonus(level, mobType);\r\n    }\r\n\r\n    @Override\r\n    public int getDamageProtection(Enchantment enchantment, int level, EntityDamageEvent.DamageCause type, org.bukkit.entity.Entity attacker) {\r\n        Entity nmsAtacker = attacker == null ? null : ((CraftEntity) attacker).getHandle();\r\n        DamageSource src = EntityHelperImpl.getSourceFor(nmsAtacker, type, nmsAtacker);\r\n        if (src instanceof EntityHelperImpl.FakeDamageSrc) {\r\n            src = ((EntityHelperImpl.FakeDamageSrc) src).real;\r\n        }\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageProtection(level, src);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/EntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.events.entity.EntityEntersVehicleScriptEvent;\r\nimport com.denizenscript.denizen.events.entity.EntityExitsVehicleScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.commands.arguments.EntityAnchorArgument;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.server.dedicated.DedicatedPlayerList;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerPlayerConnection;\r\nimport net.minecraft.server.players.PlayerList;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.InteractionHand;\r\nimport net.minecraft.world.damagesource.CombatRules;\r\nimport net.minecraft.world.damagesource.DamageSource;\r\nimport net.minecraft.world.damagesource.DamageSources;\r\nimport net.minecraft.world.entity.Mob;\r\nimport net.minecraft.world.entity.MobType;\r\nimport net.minecraft.world.entity.MoverType;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.goal.Goal;\r\nimport net.minecraft.world.entity.ai.navigation.PathNavigation;\r\nimport net.minecraft.world.entity.item.FallingBlockEntity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.level.ClipContext;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.pathfinder.Path;\r\nimport net.minecraft.world.phys.AABB;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport net.minecraft.world.phys.HitResult;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Art;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.attribute.Attribute;\r\nimport org.bukkit.attribute.AttributeInstance;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.*;\r\nimport org.bukkit.craftbukkit.v1_19_R3.event.CraftEventFactory;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.EventHandler;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\nimport org.bukkit.util.BoundingBox;\r\nimport org.bukkit.util.Vector;\r\nimport org.spigotmc.event.entity.EntityDismountEvent;\r\nimport org.spigotmc.event.entity.EntityMountEvent;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class EntityHelperImpl extends EntityHelper {\r\n\r\n    public static final MethodHandle ENTITY_ONGROUND_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_onGround, boolean.class);\r\n\r\n    public static final EntityDataAccessor<Boolean> ENTITY_ENDERMAN_DATAWATCHER_SCREAMING = ReflectionHelper.getFieldValue(EnderMan.class, ReflectionMappingsInfo.EnderMan_DATA_CREEPY, null);\r\n\r\n    @Override\r\n    public int getBlockHeight(Art art) {\r\n        return art.getBlockHeight();\r\n    }\r\n\r\n    @Override\r\n    public int getBlockWidth(Art art) {\r\n        return art.getBlockWidth();\r\n    }\r\n\r\n    @Override\r\n    public void setInvisible(Entity entity, boolean invisible) {\r\n        ((CraftEntity) entity).getHandle().setInvisible(invisible);\r\n    }\r\n\r\n    @Override\r\n    public boolean isInvisible(Entity entity) {\r\n        return ((CraftEntity) entity).getHandle().isInvisible();\r\n    }\r\n\r\n    @Override\r\n    public void setPose(Entity entity, Pose pose) {\r\n        ((CraftEntity) entity).getHandle().setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]);\r\n    }\r\n\r\n    @Override\r\n    public double getDamageTo(LivingEntity attacker, Entity target) {\r\n        MobType monsterType;\r\n        if (target instanceof LivingEntity) {\r\n            monsterType = ((CraftLivingEntity) target).getHandle().getMobType();\r\n        }\r\n        else {\r\n            monsterType = MobType.UNDEFINED;\r\n        }\r\n        double damage = 0;\r\n        AttributeInstance attrib = attacker.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE);\r\n        if (attrib != null) {\r\n            damage = attrib.getValue();\r\n        }\r\n        if (attacker.getEquipment() != null && attacker.getEquipment().getItemInMainHand() != null) {\r\n            damage += EnchantmentHelper.getDamageBonus(CraftItemStack.asNMSCopy(attacker.getEquipment().getItemInMainHand()), monsterType);\r\n        }\r\n        if (damage <= 0) {\r\n            return 0;\r\n        }\r\n        if (target != null) {\r\n            DamageSource source;\r\n            net.minecraft.world.entity.Entity nmsTarget = ((CraftEntity) target).getHandle();\r\n            if (attacker instanceof Player) {\r\n                source = nmsTarget.level.damageSources().playerAttack(((CraftPlayer) attacker).getHandle());\r\n            }\r\n            else {\r\n                source = nmsTarget.level.damageSources().mobAttack(((CraftLivingEntity) attacker).getHandle());\r\n            }\r\n            if (nmsTarget.isInvulnerableTo(source)) {\r\n                return 0;\r\n            }\r\n            if (!(nmsTarget instanceof net.minecraft.world.entity.LivingEntity)) {\r\n                return damage;\r\n            }\r\n            net.minecraft.world.entity.LivingEntity livingTarget = (net.minecraft.world.entity.LivingEntity) nmsTarget;\r\n            damage = CombatRules.getDamageAfterAbsorb((float) damage, (float) livingTarget.getArmorValue(), (float) livingTarget.getAttributeValue(Attributes.ARMOR_TOUGHNESS));\r\n            int enchantDamageModifier = EnchantmentHelper.getDamageProtection(livingTarget.getArmorSlots(), source);\r\n            if (enchantDamageModifier > 0) {\r\n                damage = CombatRules.getDamageAfterMagicAbsorb((float) damage, (float) enchantDamageModifier);\r\n            }\r\n        }\r\n        return damage;\r\n    }\r\n\r\n    public static final MethodHandle LIVINGENTITY_AUTOSPINATTACK_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.LivingEntity.class, ReflectionMappingsInfo.LivingEntity_autoSpinAttackTicks);\r\n    public static final MethodHandle LIVINGENTITY_SETLIVINGENTITYFLAG = ReflectionHelper.getMethodHandle(net.minecraft.world.entity.LivingEntity.class, ReflectionMappingsInfo.LivingEntity_setLivingEntityFlag_method, int.class, boolean.class);\r\n\r\n    @Override\r\n    public void setRiptide(Entity entity, boolean state) {\r\n        try {\r\n            net.minecraft.world.entity.LivingEntity nmsEntity = ((CraftLivingEntity) entity).getHandle();\r\n            LIVINGENTITY_AUTOSPINATTACK_SETTER.invoke(nmsEntity, state ? 0 : 1);\r\n            LIVINGENTITY_SETLIVINGENTITYFLAG.invoke(nmsEntity, 4, true);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void forceInteraction(Player player, Location location) {\r\n        CraftPlayer craftPlayer = (CraftPlayer) player;\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        ((CraftBlock) location.getBlock()).getNMS().use(((CraftWorld) location.getWorld()).getHandle(),\r\n                craftPlayer != null ? craftPlayer.getHandle() : null, InteractionHand.MAIN_HAND,\r\n                new BlockHitResult(new Vec3(0, 0, 0), null, pos, false));\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Entity entity) {\r\n        CompoundTag compound = new CompoundTag();\r\n        ((CraftEntity) entity).getHandle().saveAsPassenger(compound);\r\n        return NBTAdapter.toAPI(compound);\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Entity entity, CompoundBinaryTag compoundTag) {\r\n        ((CraftEntity) entity).getHandle().load(NBTAdapter.toNMS(compoundTag));\r\n    }\r\n\r\n    /*\r\n        Entity Movement\r\n     */\r\n\r\n    private final static Map<UUID, BukkitTask> followTasks = new HashMap<>();\r\n\r\n    @Override\r\n    public void stopFollowing(Entity follower) {\r\n        if (follower == null) {\r\n            return;\r\n        }\r\n        UUID uuid = follower.getUniqueId();\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void stopWalking(Entity entity) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        if (!(nmsEntity instanceof Mob)) {\r\n            return;\r\n        }\r\n        ((Mob) nmsEntity).getNavigation().stop();\r\n    }\r\n\r\n    @Override\r\n    public void follow(final Entity target, final Entity follower, final double speed, final double lead,\r\n                       final double maxRange, final boolean allowWander, final boolean teleport) {\r\n        if (target == null || follower == null) {\r\n            return;\r\n        }\r\n\r\n        final net.minecraft.world.entity.Entity nmsEntityFollower = ((CraftEntity) follower).getHandle();\r\n        if (!(nmsEntityFollower instanceof Mob)) {\r\n            return;\r\n        }\r\n        final Mob nmsFollower = (Mob) nmsEntityFollower;\r\n        final PathNavigation followerNavigation = nmsFollower.getNavigation();\r\n\r\n        UUID uuid = follower.getUniqueId();\r\n\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n\r\n        final int locationNearInt = (int) Math.floor(lead);\r\n        final boolean hasMax = maxRange > lead;\r\n\r\n        followTasks.put(follower.getUniqueId(), new BukkitRunnable() {\r\n\r\n            private boolean inRadius = false;\r\n\r\n            public void run() {\r\n                if (!target.isValid() || !follower.isValid()) {\r\n                    this.cancel();\r\n                }\r\n                followerNavigation.setSpeedModifier(2D);\r\n                Location targetLocation = target.getLocation();\r\n                Path path;\r\n\r\n                if (hasMax && !Utilities.checkLocation(targetLocation, follower.getLocation(), maxRange)\r\n                        && !target.isDead() && target.isOnGround()) {\r\n                    if (!inRadius) {\r\n                        if (teleport) {\r\n                            follower.teleport(Utilities.getWalkableLocationNear(targetLocation, locationNearInt));\r\n                        }\r\n                        else {\r\n                            cancel();\r\n                        }\r\n                    }\r\n                    else {\r\n                        inRadius = false;\r\n                        path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                        if (path != null) {\r\n                            followerNavigation.moveTo(path, 1D);\r\n                            followerNavigation.setSpeedModifier(2D);\r\n                        }\r\n                    }\r\n                }\r\n                else if (!inRadius && !Utilities.checkLocation(targetLocation, follower.getLocation(), lead)) {\r\n                    path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                    if (path != null) {\r\n                        followerNavigation.moveTo(path, 1D);\r\n                        followerNavigation.setSpeedModifier(2D);\r\n                    }\r\n                }\r\n                else {\r\n                    inRadius = true;\r\n                }\r\n                if (inRadius && !allowWander) {\r\n                    followerNavigation.stop();\r\n                }\r\n                nmsFollower.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n        }.runTaskTimer(NMSHandler.getJavaPlugin(), 0, 10));\r\n    }\r\n\r\n    @Override\r\n    public void walkTo(final LivingEntity entity, Location location, Double speed, final Runnable callback) {\r\n        if (entity == null || location == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        if (!(nmsEntity instanceof final Mob nmsMob)) {\r\n            return;\r\n        }\r\n        final PathNavigation entityNavigation = nmsMob.getNavigation();\r\n        final Path path;\r\n        final boolean aiDisabled = !entity.hasAI();\r\n        if (aiDisabled) {\r\n            entity.setAI(true);\r\n            try {\r\n                ENTITY_ONGROUND_SETTER.invoke(nmsMob, true);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        path = entityNavigation.createPath(location.getX(), location.getY(), location.getZ(), 1);\r\n        if (path != null) {\r\n            nmsMob.goalSelector.enableControlFlag(Goal.Flag.MOVE);\r\n            entityNavigation.moveTo(path, 1D);\r\n            final double oldSpeed = nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).getBaseValue();\r\n            if (speed != null) {\r\n                nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    if (!entity.isValid()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        cancel();\r\n                        return;\r\n                    }\r\n                    if (aiDisabled && entity instanceof Wolf wolf) {\r\n                        wolf.setAngry(false);\r\n                    }\r\n                    if (entityNavigation.isDone() || path.isDone()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        if (speed != null) {\r\n                            nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(oldSpeed);\r\n                        }\r\n                        if (aiDisabled) {\r\n                            entity.setAI(false);\r\n                        }\r\n                        cancel();\r\n                    }\r\n                }\r\n            }.runTaskTimer(NMSHandler.getJavaPlugin(), 1, 1);\r\n        }\r\n        //if (!Utilities.checkLocation(location, entity.getLocation(), 20)) {\r\n        // TODO: generate waypoints to the target location?\r\n        else {\r\n            entity.teleport(location);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public List<Player> getPlayersThatSee(Entity entity) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        ArrayList<Player> output = new ArrayList<>();\r\n        if (entityTracker == null) {\r\n            return output;\r\n        }\r\n        for (ServerPlayerConnection player : entityTracker.seenBy) {\r\n            output.add(player.getPlayer().getBukkitEntity());\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public void sendAllUpdatePackets(Entity entity) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        if (entityTracker == null) {\r\n            return;\r\n        }\r\n        try {\r\n            ServerEntity serverEntity = (ServerEntity) PacketHelperImpl.ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n            serverEntity.sendChanges();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    /*\r\n        Hide Entity\r\n     */\r\n\r\n    @Override\r\n    public void sendHidePacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player) {\r\n            pl.hidePlayer(Denizen.getInstance(), (Player) entity);\r\n            return;\r\n        }\r\n        CraftPlayer craftPlayer = (CraftPlayer) pl;\r\n        ServerPlayer entityPlayer = craftPlayer.getHandle();\r\n        if (entityPlayer.connection != null && !craftPlayer.equals(entity)) {\r\n            ChunkMap tracker = ((ServerLevel) craftPlayer.getHandle().level).getChunkSource().chunkMap;\r\n            net.minecraft.world.entity.Entity other = ((CraftEntity) entity).getHandle();\r\n            ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId());\r\n            if (entry != null) {\r\n                entry.removePlayer(entityPlayer);\r\n            }\r\n            if (Denizen.supportsPaper) { // Workaround for Paper issue\r\n                entityPlayer.connection.send(new ClientboundRemoveEntitiesPacket(other.getId()));\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendShowPacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player) {\r\n            pl.showPlayer(Denizen.getInstance(), (Player) entity);\r\n            return;\r\n        }\r\n        CraftPlayer craftPlayer = (CraftPlayer) pl;\r\n        ServerPlayer entityPlayer = craftPlayer.getHandle();\r\n        if (entityPlayer.connection != null && !craftPlayer.equals(entity)) {\r\n            ChunkMap tracker = ((ServerLevel) craftPlayer.getHandle().level).getChunkSource().chunkMap;\r\n            net.minecraft.world.entity.Entity other = ((CraftEntity) entity).getHandle();\r\n            ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId());\r\n            if (entry != null) {\r\n                entry.removePlayer(entityPlayer);\r\n                entry.updatePlayer(entityPlayer);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void rotate(Entity entity, float yaw, float pitch) {\r\n        // If this entity is a real player instead of a player type NPC,\r\n        // it will appear to be online\r\n        if (entity instanceof Player && ((Player) entity).isOnline()) {\r\n            NetworkInterceptHelper.enable();\r\n            float relYaw = (yaw - entity.getLocation().getYaw()) % 360;\r\n            if (relYaw > 180) {\r\n                relYaw -= 360;\r\n            }\r\n            final float actualRelYaw = relYaw;\r\n            float relPitch = pitch - entity.getLocation().getPitch();\r\n            NMSHandler.packetHelper.sendRelativeLookPacket((Player) entity, actualRelYaw, relPitch);\r\n        }\r\n        else if (entity instanceof LivingEntity) {\r\n            if (entity instanceof EnderDragon) {\r\n                yaw = normalizeYaw(yaw - 180);\r\n            }\r\n            look(entity, yaw, pitch);\r\n        }\r\n        else {\r\n            net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n            handle.setYRot(yaw - 360);\r\n            handle.setXRot(pitch);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBaseYaw(LivingEntity entity) {\r\n        return ((CraftLivingEntity) entity).getHandle().yBodyRot;\r\n    }\r\n\r\n    @Override\r\n    public void look(Entity entity, float yaw, float pitch) {\r\n        net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n        if (handle != null) {\r\n            handle.setYRot(yaw);\r\n            if (handle instanceof net.minecraft.world.entity.LivingEntity) {\r\n                net.minecraft.world.entity.LivingEntity livingHandle = (net.minecraft.world.entity.LivingEntity) handle;\r\n                while (yaw < -180.0F) {\r\n                    yaw += 360.0F;\r\n                }\r\n                while (yaw >= 180.0F) {\r\n                    yaw -= 360.0F;\r\n                }\r\n                livingHandle.yBodyRotO = yaw;\r\n                if (!(handle instanceof net.minecraft.world.entity.player.Player)) {\r\n                    livingHandle.setYBodyRot(yaw);\r\n                }\r\n                livingHandle.setYHeadRot(yaw);\r\n            }\r\n            handle.setXRot(pitch);\r\n        }\r\n        else {\r\n            Debug.echoError(\"Cannot set look direction for unspawned entity \" + entity.getUniqueId());\r\n        }\r\n    }\r\n\r\n    private static HitResult rayTrace(World world, Vector start, Vector end) {\r\n        try {\r\n            NMSHandler.chunkHelper.changeChunkServerThread(world);\r\n            return ((CraftWorld) world).getHandle().clip(new ClipContext(new Vec3(start.getX(), start.getY(), start.getZ()),\r\n                    new Vec3(end.getX(), end.getY(), end.getZ()),\r\n                    ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, null));\r\n        }\r\n        finally {\r\n            NMSHandler.chunkHelper.restoreServerThread(world);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean canTrace(World world, Vector start, Vector end) {\r\n        HitResult pos = rayTrace(world, start, end);\r\n        if (pos == null) {\r\n            return true;\r\n        }\r\n        return pos.getType() == HitResult.Type.MISS;\r\n    }\r\n\r\n    @Override\r\n    public void snapPositionTo(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().setPosRaw(vector.getX(), vector.getY(), vector.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void move(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().move(MoverType.SELF, new Vec3(vector.getX(), vector.getY(), vector.getZ()));\r\n    }\r\n\r\n    @Override\r\n    public boolean internalLook(Player player, Location at) {\r\n        ClientboundPlayerLookAtPacket packet = new ClientboundPlayerLookAtPacket(EntityAnchorArgument.Anchor.EYES, at.getX(), at.getY(), at.getZ());\r\n        PacketHelperImpl.send(player, packet);\r\n        return true;\r\n    }\r\n\r\n    public static long entityToPacket(double x) {\r\n        return Mth.lfloor(x * 4096.0D);\r\n    }\r\n\r\n    @Override\r\n    public void fakeMove(Entity entity, Vector vector) {\r\n        long x = entityToPacket(vector.getX());\r\n        long y = entityToPacket(vector.getY());\r\n        long z = entityToPacket(vector.getZ());\r\n        ClientboundMoveEntityPacket packet = new ClientboundMoveEntityPacket.Pos(entity.getEntityId(), (short) x, (short) y, (short) z, entity.isOnGround());\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void fakeTeleport(Entity entity, Location location) {\r\n        FriendlyByteBuf packetData = new FriendlyByteBuf(Unpooled.buffer());\r\n        // Referenced from ClientboundTeleportEntityPacket source\r\n        packetData.writeVarInt(entity.getEntityId());\r\n        packetData.writeDouble(location.getX());\r\n        packetData.writeDouble(location.getY());\r\n        packetData.writeDouble(location.getZ());\r\n        packetData.writeByte((byte)((int)(location.getYaw() * 256.0F / 360.0F)));\r\n        packetData.writeByte((byte)((int)(location.getPitch() * 256.0F / 360.0F)));\r\n        packetData.writeBoolean(entity.isOnGround());\r\n        ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(packetData);\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void clientResetLoc(Entity entity) {\r\n        ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(((CraftEntity) entity).getHandle());\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Entity entity, Location loc) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        nmsEntity.setYRot(loc.getYaw());\r\n        nmsEntity.setXRot(loc.getPitch());\r\n        if (nmsEntity instanceof ServerPlayer) {\r\n            nmsEntity.teleportTo(loc.getX(), loc.getY(), loc.getZ());\r\n        }\r\n        nmsEntity.setPos(loc.getX(), loc.getY(), loc.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void setBoundingBox(Entity entity, BoundingBox box) {\r\n        ((CraftEntity) entity).getHandle().setBoundingBox(new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()));\r\n    }\r\n\r\n    @Override\r\n    public void setTicksLived(Entity entity, int ticks) {\r\n        // Bypass Spigot's must-be-at-least-1-tick requirement, as negative tick counts are useful\r\n        ((CraftEntity) entity).getHandle().tickCount = ticks;\r\n        if (entity instanceof CraftFallingBlock) {\r\n            ((CraftFallingBlock) entity).getHandle().time = ticks;\r\n        }\r\n        else if (entity instanceof CraftItem) {\r\n            ((ItemEntity) ((CraftItem) entity).getHandle()).age = ticks;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHeadAngle(LivingEntity entity, float angle) {\r\n        net.minecraft.world.entity.LivingEntity handle = ((CraftLivingEntity) entity).getHandle();\r\n        handle.yHeadRot = angle;\r\n        handle.setYHeadRot(angle);\r\n    }\r\n\r\n    @Override\r\n    public void setEndermanAngry(Enderman enderman, boolean angry) {\r\n        ((CraftEnderman) enderman).getHandle().getEntityData().set(ENTITY_ENDERMAN_DATAWATCHER_SCREAMING, angry);\r\n    }\r\n\r\n    public static class FakeDamageSrc extends DamageSource { public DamageSource real; public FakeDamageSrc(DamageSource src) { super(null); real = src; } }\r\n\r\n    public static DamageSources backupDamageSources;\r\n\r\n    public static DamageSources getReusableDamageSources() {\r\n        if (backupDamageSources == null) {\r\n            backupDamageSources = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle().damageSources();\r\n        }\r\n        return backupDamageSources;\r\n    }\r\n\r\n    public static DamageSource getSourceFor(net.minecraft.world.entity.Entity nmsSource, EntityDamageEvent.DamageCause cause, net.minecraft.world.entity.Entity nmsSourceProvider) {\r\n        DamageSources sources = nmsSourceProvider == null ? getReusableDamageSources() : nmsSourceProvider.level.damageSources();\r\n        DamageSource src = sources.generic();\r\n        if (nmsSource != null) {\r\n            if (nmsSource instanceof net.minecraft.world.entity.player.Player) {\r\n                src = nmsSource.level.damageSources().playerAttack((net.minecraft.world.entity.player.Player) nmsSource);\r\n            }\r\n            else if (nmsSource instanceof net.minecraft.world.entity.LivingEntity) {\r\n                src = nmsSource.level.damageSources().mobAttack((net.minecraft.world.entity.LivingEntity) nmsSource);\r\n            }\r\n        }\r\n        if (cause == null) {\r\n            return src;\r\n        }\r\n        switch (cause) {\r\n            case CONTACT:\r\n                return sources.cactus();\r\n            case ENTITY_ATTACK:\r\n                return sources.mobAttack(nmsSource instanceof net.minecraft.world.entity.LivingEntity ? (net.minecraft.world.entity.LivingEntity) nmsSource : null);\r\n            case ENTITY_SWEEP_ATTACK:\r\n                if (src != sources.generic()) {\r\n                    src.sweep();\r\n                }\r\n                return src;\r\n            case PROJECTILE:\r\n                return sources.thrown(nmsSource, nmsSource != null && nmsSource.getBukkitEntity() instanceof Projectile\r\n                        && ((Projectile) nmsSource.getBukkitEntity()).getShooter() instanceof Entity ? ((CraftEntity) ((Projectile) nmsSource.getBukkitEntity()).getShooter()).getHandle() : null);\r\n            case SUFFOCATION:\r\n                return sources.inWall();\r\n            case FALL:\r\n                return sources.fall();\r\n            case FIRE:\r\n                return sources.inFire();\r\n            case FIRE_TICK:\r\n                return sources.onFire();\r\n            case MELTING:\r\n                return sources.melting;\r\n            case LAVA:\r\n                return sources.lava();\r\n            case DROWNING:\r\n                return sources.drown();\r\n            case BLOCK_EXPLOSION:\r\n                return sources.explosion(nmsSource instanceof TNTPrimed && ((TNTPrimed) nmsSource).getSource() instanceof net.minecraft.world.entity.LivingEntity ? (net.minecraft.world.entity.LivingEntity) ((TNTPrimed) nmsSource).getSource() : null, null);\r\n            case ENTITY_EXPLOSION:\r\n                return sources.explosion(nmsSource, null);\r\n            case VOID:\r\n                return sources.outOfWorld();\r\n            case LIGHTNING:\r\n                return sources.lightningBolt();\r\n            case STARVATION:\r\n                return sources.starve();\r\n            case POISON:\r\n                return sources.poison;\r\n            case MAGIC:\r\n                return sources.magic();\r\n            case WITHER:\r\n                return sources.wither();\r\n            case FALLING_BLOCK:\r\n                return sources.fallingBlock(nmsSource);\r\n            case THORNS:\r\n                return sources.thorns(nmsSource);\r\n            case DRAGON_BREATH:\r\n                return sources.dragonBreath();\r\n            case CUSTOM:\r\n                return sources.generic();\r\n            case FLY_INTO_WALL:\r\n                return sources.flyIntoWall();\r\n            case HOT_FLOOR:\r\n                return sources.hotFloor();\r\n            case CRAMMING:\r\n                return sources.cramming();\r\n            case DRYOUT:\r\n                return sources.dryOut();\r\n            //case SUICIDE:\r\n            default:\r\n                return new FakeDamageSrc(src);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void damage(LivingEntity target, float amount, EntityTag source, Location sourceLoc, EntityDamageEvent.DamageCause cause) {\r\n        if (target == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.LivingEntity nmsTarget = ((CraftLivingEntity) target).getHandle();\r\n        net.minecraft.world.entity.Entity nmsSource = source == null ? null : ((CraftEntity) source.getBukkitEntity()).getHandle();\r\n        CraftEventFactory.entityDamage = nmsSource;\r\n        CraftEventFactory.blockDamage = sourceLoc == null ? null : sourceLoc.getBlock();\r\n        try {\r\n            DamageSource src = getSourceFor(nmsSource, cause, nmsTarget);\r\n            if (src instanceof FakeDamageSrc) {\r\n                src = ((FakeDamageSrc) src).real;\r\n                EntityDamageEvent ede = fireFakeDamageEvent(target, source, sourceLoc, cause, amount);\r\n                if (ede.isCancelled()) {\r\n                    return;\r\n                }\r\n            }\r\n            nmsTarget.hurt(src, amount);\r\n        }\r\n        finally {\r\n            CraftEventFactory.entityDamage = null;\r\n            CraftEventFactory.blockDamage = null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setLastHurtBy(LivingEntity mob, LivingEntity damager) {\r\n        ((CraftLivingEntity) mob).getHandle().setLastHurtByMob(((CraftLivingEntity) damager).getHandle());\r\n    }\r\n\r\n    public static final MethodHandle FALLINGBLOCK_TYPE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.entity.item.FallingBlockEntity.class, BlockState.class);\r\n\r\n    @Override\r\n    public void setFallingBlockType(FallingBlock fallingBlock, BlockData block) {\r\n        BlockState state = ((CraftBlockData) block).getState();\r\n        FallingBlockEntity nmsEntity = ((CraftFallingBlock) fallingBlock).getHandle();\r\n        try {\r\n            FALLINGBLOCK_TYPE_SETTER.invoke(nmsEntity, state);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public EntityTag getMobSpawnerDisplayEntity(CreatureSpawner spawner) {\r\n        SpawnerBlockEntity nmsSpawner = BlockHelperImpl.getTE((CraftCreatureSpawner) spawner);\r\n        ServerLevel level = ((CraftWorld) spawner.getWorld()).getHandle();\r\n        net.minecraft.world.entity.Entity nmsEntity = nmsSpawner.getSpawner().getOrCreateDisplayEntity(level, level.random, nmsSpawner.getBlockPos());\r\n        return new EntityTag(nmsEntity.getBukkitEntity());\r\n    }\r\n\r\n    public static final Field ZOMBIE_INWATERTIME = ReflectionHelper.getFields(net.minecraft.world.entity.monster.Zombie.class).get(ReflectionMappingsInfo.Zombie_inWaterTime, int.class);\r\n\r\n    @Override\r\n    public int getInWaterTime(Zombie zombie) {\r\n        try {\r\n            return ZOMBIE_INWATERTIME.getInt(((CraftZombie) zombie).getHandle());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return 0;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setInWaterTime(Zombie zombie, int ticks) {\r\n        try {\r\n            ZOMBIE_INWATERTIME.setInt(((CraftZombie) zombie).getHandle(), ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final MethodHandle TRACKING_RANGE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ChunkMap.TrackedEntity.class, int.class);\r\n\r\n    @Override\r\n    public void setTrackingRange(Entity entity, int range) {\r\n        try {\r\n            ChunkMap map = ((CraftWorld) entity.getWorld()).getHandle().getChunkSource().chunkMap;\r\n            ChunkMap.TrackedEntity entry = map.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                TRACKING_RANGE_SETTER.invoke(entry, range);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean isAggressive(org.bukkit.entity.Mob mob) {\r\n        return ((CraftMob) mob).getHandle().isAggressive();\r\n    }\r\n\r\n    @Override\r\n    public void setAggressive(org.bukkit.entity.Mob mob, boolean aggressive) {\r\n        ((CraftMob) mob).getHandle().setAggressive(aggressive);\r\n    }\r\n\r\n    public static final Field ENTITY_REMOVALREASON = ReflectionHelper.getFields(net.minecraft.world.entity.Entity.class).getFirstOfType(net.minecraft.world.entity.Entity.RemovalReason.class);\r\n    public static final MethodHandle PLAYERLIST_REMOVE = ReflectionHelper.getMethodHandle(PlayerList.class, \"remove\", ServerPlayer.class);\r\n\r\n    @Override\r\n    public void setUUID(Entity entity, UUID id) {\r\n        try {\r\n            net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n            nmsEntity.stopRiding();\r\n            nmsEntity.getPassengers().forEach(net.minecraft.world.entity.Entity::stopRiding);\r\n            Level level = nmsEntity.level;\r\n            DedicatedPlayerList playerList = ((CraftServer) Bukkit.getServer()).getHandle();\r\n            if (nmsEntity instanceof ServerPlayer) {\r\n                PLAYERLIST_REMOVE.invoke(playerList, nmsEntity);\r\n            }\r\n            else {\r\n                nmsEntity.remove(net.minecraft.world.entity.Entity.RemovalReason.DISCARDED);\r\n            }\r\n            ENTITY_REMOVALREASON.set(nmsEntity, null);\r\n            nmsEntity.setUUID(id);\r\n            if (nmsEntity instanceof ServerPlayer) {\r\n                playerList.placeNewPlayer(DenizenNetworkManagerImpl.getConnection((ServerPlayer) nmsEntity), (ServerPlayer) nmsEntity);\r\n            }\r\n            else {\r\n                level.addFreshEntity(nmsEntity);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getStepHeight(Entity entity) {\r\n        return ((CraftEntity) entity).getHandle().maxUpStep();\r\n    }\r\n\r\n    @Override\r\n    public void setStepHeight(Entity entity, float stepHeight) {\r\n        ((CraftEntity) entity).getHandle().setMaxUpStep(stepHeight);\r\n    }\r\n\r\n    @Override\r\n    public void openHorseInventory(Player player, AbstractHorse horse) {\r\n        net.minecraft.world.entity.animal.horse.AbstractHorse nmsHorse = ((CraftAbstractHorse) horse).getHandle();\r\n        ((CraftPlayer) player).getHandle().openHorseInventory(nmsHorse, nmsHorse.inventory);\r\n    }\r\n\r\n    public static class EntityEntersVehicleScriptEventImpl extends EntityEntersVehicleScriptEvent {\r\n        @EventHandler\r\n        public void onEntityMount(EntityMountEvent event) {\r\n            fire(event, event.getMount());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Class<? extends EntityEntersVehicleScriptEvent> getEntersVehicleEventImpl() {\r\n        return EntityEntersVehicleScriptEventImpl.class;\r\n    }\r\n\r\n    public static class EntityExitsVehicleScriptEventImpl extends EntityExitsVehicleScriptEvent {\r\n        @EventHandler\r\n        public void onEntityMount(EntityDismountEvent event) {\r\n            fire(event, event.getDismounted());\r\n        }\r\n    }\r\n\r\n    public Class<? extends EntityExitsVehicleScriptEvent> getExitsVehicleEventImpl() {\r\n        return EntityExitsVehicleScriptEventImpl.class;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/FishingHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FishingHelper;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.entity.projectile.FishingHook;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.item.enchantment.Enchantments;\r\nimport net.minecraft.world.level.storage.loot.BuiltInLootTables;\r\nimport net.minecraft.world.level.storage.loot.LootContext;\r\nimport net.minecraft.world.level.storage.loot.LootTables;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParams;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftFishHook;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.entity.FishHook;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.List;\r\n\r\npublic class FishingHelperImpl implements FishingHelper {\r\n\r\n    @Override\r\n    public org.bukkit.inventory.ItemStack getResult(FishHook fishHook, CatchType catchType) {\r\n        ItemStack result = null;\r\n        FishingHook nmsHook = ((CraftFishHook) fishHook).getHandle();\r\n        if (catchType == CatchType.DEFAULT) {\r\n            float f = ((CraftWorld) fishHook.getWorld()).getHandle().random.nextFloat();\r\n            int i = EnchantmentHelper.getMobLooting(nmsHook.getPlayerOwner());\r\n            int j = EnchantmentHelper.getEnchantmentLevel(Enchantments.FISHING_LUCK, nmsHook.getPlayerOwner());\r\n            float f1 = 0.1F - (float) i * 0.025F - (float) j * 0.01F;\r\n            float f2 = 0.05F + (float) i * 0.01F - (float) j * 0.01F;\r\n\r\n            f1 = Mth.clamp(f1, 0.0F, 1.0F);\r\n            f2 = Mth.clamp(f2, 0.0F, 1.0F);\r\n            if (f < f1) {\r\n                result = catchRandomJunk(nmsHook);\r\n            }\r\n            else {\r\n                f -= f1;\r\n                if (f < f2) {\r\n                    result = catchRandomTreasure(nmsHook);\r\n                }\r\n                else {\r\n                    result = catchRandomFish(nmsHook);\r\n                }\r\n            }\r\n        }\r\n        else if (catchType == CatchType.JUNK) {\r\n            result = catchRandomJunk(nmsHook);\r\n        }\r\n        else if (catchType == CatchType.TREASURE) {\r\n            result = catchRandomTreasure(nmsHook);\r\n        }\r\n        else if (catchType == CatchType.FISH) {\r\n            result = catchRandomFish(nmsHook);\r\n        }\r\n        if (result != null) {\r\n            return CraftItemStack.asBukkitCopy(result);\r\n        }\r\n        else {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public ItemStack getRandomReward(FishingHook hook, ResourceLocation key) {\r\n        ServerLevel worldServer = (ServerLevel) hook.level;\r\n        LootContext.Builder playerFishEvent2 = new LootContext.Builder(worldServer);\r\n        LootTables registry = ((ServerLevel) hook.level).getServer().getLootTables();\r\n        // registry.getLootTable(key).getLootContextParameterSet()\r\n        LootContext info = playerFishEvent2.withOptionalParameter(LootContextParams.ORIGIN, new Vec3(hook.getX(), hook.getY(), hook.getZ()))\r\n                .withOptionalParameter(LootContextParams.TOOL, new ItemStack(Items.FISHING_ROD)).create(LootContextParamSets.FISHING);\r\n        List<ItemStack> itemStacks = registry.get(key).getRandomItems(info);\r\n        return itemStacks.get(worldServer.random.nextInt(itemStacks.size()));\r\n    }\r\n\r\n    @Override\r\n    public FishHook spawnHook(Location location, Player player) {\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        FishingHook hook = new FishingHook(((CraftPlayer) player).getHandle(), nmsWorld, 0, 0);\r\n        nmsWorld.addFreshEntity(hook, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return (FishHook) hook.getBukkitEntity();\r\n    }\r\n\r\n    private ItemStack catchRandomJunk(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_JUNK);\r\n    }\r\n\r\n    private ItemStack catchRandomTreasure(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_TREASURE);\r\n    }\r\n\r\n    private ItemStack catchRandomFish(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_FISH);\r\n    }\r\n\r\n    public static Field FISHING_HOOK_NIBBLE_SETTER = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_nibble, int.class);\r\n    public static Field FISHING_HOOK_LURE_TIME_SETTER = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_timeUntilLured, int.class);\r\n    public static Field FISHING_HOOK_HOOK_TIME_SETTER = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_timeUntilHooked, int.class);\r\n\r\n    @Override\r\n    public FishHook getHookFrom(Player player) {\r\n        FishingHook hook = ((CraftPlayer) player).getHandle().fishing;\r\n        if (hook == null) {\r\n            return null;\r\n        }\r\n        return (FishHook) hook.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public void setNibble(FishHook hook, int ticks) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_NIBBLE_SETTER.setInt(nmsEntity, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHookTime(FishHook hook, int ticks) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_HOOK_TIME_SETTER.setInt(nmsEntity, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int getLureTime(FishHook hook) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            return FISHING_HOOK_LURE_TIME_SETTER.getInt(nmsEntity);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public void setLureTime(FishHook hook, int ticks) {\r\n        FishingHook nmsEntity = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_LURE_TIME_SETTER.setInt(nmsEntity, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/ItemHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.ItemHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.*;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;\r\nimport net.kyori.adventure.nbt.BinaryTag;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Direction;\r\nimport net.minecraft.core.NonNullList;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.nbt.ListTag;\r\nimport net.minecraft.nbt.NbtUtils;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.world.item.BlockItem;\r\nimport net.minecraft.world.item.Item;\r\nimport net.minecraft.world.item.alchemy.PotionBrewing;\r\nimport net.minecraft.world.item.crafting.*;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport net.minecraft.world.level.material.MaterialColor;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryPlayer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftRecipe;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftNamespacedKey;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.RecipeChoice;\r\nimport org.bukkit.inventory.ShapedRecipe;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class ItemHelperImpl extends ItemHelper {\r\n\r\n    public static net.minecraft.world.item.crafting.Recipe<?> getNMSRecipe(NamespacedKey key) {\r\n        ResourceLocation nmsKey = CraftNamespacedKey.toMinecraft(key);\r\n        for (Object2ObjectLinkedOpenHashMap<ResourceLocation, net.minecraft.world.item.crafting.Recipe<?>> recipeMap : ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().recipes.values()) {\r\n            net.minecraft.world.item.crafting.Recipe<?> recipe = recipeMap.get(nmsKey);\r\n            if (recipe != null) {\r\n                return recipe;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public void setMaxStackSize(Material material, int size) {\r\n        try {\r\n            ReflectionHelper.getFinalSetter(Material.class, \"maxStack\").invoke(material, size);\r\n            ReflectionHelper.getFinalSetter(Item.class, ReflectionMappingsInfo.Item_maxStackSize).invoke(BuiltInRegistries.ITEM.get(CraftNamespacedKey.toMinecraft(material.getKey())), size);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Integer burnTime(Material material) {\r\n        return AbstractFurnaceBlockEntity.getFuel().get(CraftMagicNumbers.getItem(material));\r\n    }\r\n\r\n    public static Field RECIPE_MANAGER_BY_NAME = ReflectionHelper.getFields(RecipeManager.class).get(ReflectionMappingsInfo.RecipeManager_byName, Map.class);\r\n\r\n    @Override\r\n    public void setShapedRecipeIngredient(ShapedRecipe recipe, char c, ItemStack[] item, boolean exact) {\r\n        if (item.length == 1 && item[0].getType() == Material.AIR) {\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(Material.AIR));\r\n        }\r\n        else if (exact) {\r\n            recipe.setIngredient(c, new RecipeChoice.ExactChoice(item));\r\n        }\r\n        else {\r\n            Material[] mats = new Material[item.length];\r\n            for (int i = 0; i < item.length; i++) {\r\n                mats[i] = item[i].getType();\r\n            }\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(mats));\r\n        }\r\n    }\r\n\r\n    public static Ingredient itemArrayToRecipe(ItemStack[] items, boolean exact) {\r\n        Ingredient.ItemValue[] stacks = new Ingredient.ItemValue[items.length];\r\n        for (int i = 0; i < items.length; i++) {\r\n            stacks[i] = new Ingredient.ItemValue(CraftItemStack.asNMSCopy(items[i]));\r\n        }\r\n        Ingredient itemRecipe = new Ingredient(Arrays.stream(stacks));\r\n        itemRecipe.exact = exact;\r\n        return itemRecipe;\r\n    }\r\n\r\n    @Override\r\n    public void registerFurnaceRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, float exp, int time, String type, boolean exact, String category) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        AbstractCookingRecipe recipe;\r\n        CookingBookCategory categoryValue = category == null ? CookingBookCategory.MISC : CookingBookCategory.valueOf(CoreUtilities.toUpperCase(category));\r\n        if (type.equalsIgnoreCase(\"smoker\")) {\r\n            recipe = new SmokingRecipe(key, group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"blast\")) {\r\n            recipe = new BlastingRecipe(key, group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"campfire\")) {\r\n            recipe = new CampfireCookingRecipe(key, group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else {\r\n            recipe = new SmeltingRecipe(key, group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public void registerStonecuttingRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, boolean exact) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        StonecutterRecipe recipe = new StonecutterRecipe(key, group, itemRecipe, CraftItemStack.asNMSCopy(result));\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public void registerSmithingRecipe(String keyName, ItemStack result, ItemStack[] baseItem, boolean baseExact, ItemStack[] upgradeItem, boolean upgradeExact, ItemStack[] templateItem, boolean templateExact) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient baseItemRecipe = itemArrayToRecipe(baseItem, baseExact);\r\n        Ingredient upgradeItemRecipe = itemArrayToRecipe(upgradeItem, upgradeExact);\r\n        LegacyUpgradeRecipe recipe = new LegacyUpgradeRecipe(key, baseItemRecipe, upgradeItemRecipe, CraftItemStack.asNMSCopy(result));\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public void registerShapelessRecipe(String keyName, String group, ItemStack result, List<ItemStack[]> ingredients, boolean[] exact, String category) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        ArrayList<Ingredient> ingredientList = new ArrayList<>();\r\n        CraftingBookCategory categoryValue = category == null ? CraftingBookCategory.MISC : CraftingBookCategory.valueOf(CoreUtilities.toUpperCase(category));\r\n        for (int i = 0; i < ingredients.size(); i++) {\r\n            ingredientList.add(itemArrayToRecipe(ingredients.get(i), exact[i]));\r\n        }\r\n        // TODO: 1.19.3: Add support for choosing a CraftingBookCategory\r\n        ShapelessRecipe recipe = new ShapelessRecipe(key, group, categoryValue, CraftItemStack.asNMSCopy(result), NonNullList.of(null, ingredientList.toArray(new Ingredient[0])));\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(recipe);\r\n    }\r\n\r\n    @Override\r\n    public String getJsonString(ItemStack itemStack) {\r\n        String json = CraftItemStack.asNMSCopy(itemStack).getDisplayName().getStyle().toString().replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\");\r\n        return json.substring(176, json.length() - 185);\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getSkullSkin(ItemStack is) {\r\n        net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(is);\r\n        if (itemStack.hasTag()) {\r\n            net.minecraft.nbt.CompoundTag tag = itemStack.getTag();\r\n            if (tag.contains(\"SkullOwner\", 10)) {\r\n                GameProfile profile = NbtUtils.readGameProfile(tag.getCompound(\"SkullOwner\"));\r\n                if (profile != null) {\r\n                    Property property = Iterables.getFirst(profile.getProperties().get(\"textures\"), null);\r\n                    return new PlayerProfile(profile.getName(), profile.getId(),\r\n                            property != null ? property.getValue() : null,\r\n                            property != null ? property.getSignature() : null);\r\n                }\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setSkullSkin(ItemStack itemStack, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        if (playerProfile.hasTexture()) {\r\n            gameProfile.getProperties().get(\"textures\").clear();\r\n            if (playerProfile.getTextureSignature() != null) {\r\n                gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n            }\r\n            else {\r\n                gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture()));\r\n            }\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        net.minecraft.nbt.CompoundTag tag = nmsItemStack.hasTag() ? nmsItemStack.getTag() : new net.minecraft.nbt.CompoundTag();\r\n        tag.put(\"SkullOwner\", NbtUtils.writeGameProfile(new net.minecraft.nbt.CompoundTag(), gameProfile));\r\n        nmsItemStack.setTag(tag);\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack addNbtData(ItemStack itemStack, String key, BinaryTag value) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.getOrCreateTag().put(key, NBTAdapter.toNMS(value));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(ItemStack itemStack) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        if (nmsItemStack != null && nmsItemStack.hasTag()) {\r\n            return NBTAdapter.toAPI(nmsItemStack.getTag());\r\n        }\r\n        return CompoundBinaryTag.empty();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setNbtData(ItemStack itemStack, CompoundBinaryTag compoundTag) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.setTag(NBTAdapter.toNMS(compoundTag));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public void setInventoryItem(Inventory inventory, ItemStack item, int slot) {\r\n        if (inventory instanceof CraftInventoryPlayer && ((CraftInventoryPlayer) inventory).getInventory().player == null) {\r\n            ((CraftInventoryPlayer) inventory).getInventory().setItem(slot, CraftItemStack.asNMSCopy(item));\r\n        }\r\n        else {\r\n            inventory.setItem(slot, item);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getDisplayName(ItemTag item) {\r\n        if (!item.getItemMeta().hasDisplayName()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        String jsonText = ((net.minecraft.nbt.CompoundTag) nmsItemStack.getTag().get(\"display\")).getString(\"Name\");\r\n        BaseComponent[] nameComponent = ComponentSerializer.parse(jsonText);\r\n        return FormattedTextHelper.stringify(nameComponent);\r\n    }\r\n\r\n    @Override\r\n    public List<String> getLore(ItemTag item) {\r\n        if (!item.getItemMeta().hasLore()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        ListTag list = ((net.minecraft.nbt.CompoundTag) nmsItemStack.getTag().get(\"display\")).getList(\"Lore\", 8);\r\n        List<String> outList = new ArrayList<>();\r\n        for (int i = 0; i < list.size(); i++) {\r\n            BaseComponent[] lineComponent = ComponentSerializer.parse(list.getString(i));\r\n            outList.add(FormattedTextHelper.stringify(lineComponent));\r\n        }\r\n        return outList;\r\n    }\r\n\r\n    @Override\r\n    public void setDisplayName(ItemTag item, String name) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        net.minecraft.nbt.CompoundTag tag = nmsItemStack.getOrCreateTag();\r\n        net.minecraft.nbt.CompoundTag display = tag.getCompound(\"display\");\r\n        if (!tag.contains(\"display\")) {\r\n            tag.put(\"display\", display);\r\n        }\r\n        if (name == null || name.isEmpty()) {\r\n            display.put(\"Name\", null);\r\n            return;\r\n        }\r\n        BaseComponent[] components = FormattedTextHelper.parse(name, ChatColor.WHITE);\r\n        display.put(\"Name\", net.minecraft.nbt.StringTag.valueOf(FormattedTextHelper.componentToJson(components)));\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    @Override\r\n    public void setLore(ItemTag item, List<String> lore) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        net.minecraft.nbt.CompoundTag tag = nmsItemStack.getOrCreateTag();\r\n        net.minecraft.nbt.CompoundTag display = tag.getCompound(\"display\");\r\n        if (!tag.contains(\"display\")) {\r\n            tag.put(\"display\", display);\r\n        }\r\n        if (lore == null || lore.isEmpty()) {\r\n            display.remove(\"Lore\");\r\n            if (display.isEmpty()) {\r\n                tag.remove(\"display\");\r\n            }\r\n        }\r\n        else {\r\n            ListTag tagList = new ListTag();\r\n            for (String line : lore) {\r\n                tagList.add(net.minecraft.nbt.StringTag.valueOf(FormattedTextHelper.componentToJson(FormattedTextHelper.parse(line, ChatColor.WHITE))));\r\n            }\r\n            display.put(\"Lore\", tagList);\r\n        }\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.getCorrectStateForFluidBlock, and rewritten as reflection due to Spigot bug - refer to bottom of BlockHelperImpl for detail.\r\n     */\r\n    public static BlockState getCorrectStateForFluidBlock(Level world, BlockState iblockdata, BlockPos blockposition) {\r\n        try {\r\n            // FluidState fluid = iblockdata.getFluidState();\r\n            Object fluid = BlockHelperImpl.BLOCKSTATEBASE_GETFLUIDSTATE.invoke(iblockdata);\r\n            //return !fluid.isEmpty() && !iblockdata.isFaceSturdy(world, blockposition, Direction.UP) ? fluid.createLegacyBlock() : iblockdata;\r\n            boolean isEmpty = (boolean) BlockHelperImpl.FLUIDSTATE_ISEMPTY.invoke(fluid);\r\n            if (!isEmpty && !iblockdata.isFaceSturdy(world, blockposition, Direction.UP)) {\r\n                return (BlockState) BlockHelperImpl.FLUIDSTATE_CREATELEGACYBLOCK.invoke(fluid);\r\n            }\r\n            else {\r\n                return iblockdata;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return iblockdata;\r\n        }\r\n    }\r\n\r\n    public static boolean blockStateFluidIsEmpty(BlockState iblockdata) {\r\n        try {\r\n            // return iblockdata.getFluidState().isEmpty();\r\n            Object fluid = BlockHelperImpl.BLOCKSTATEBASE_GETFLUIDSTATE.invoke(iblockdata);\r\n            return (boolean) BlockHelperImpl.FLUIDSTATE_ISEMPTY.invoke(fluid);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return false;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.update, redesigned slightly to render totally rather than just relative to a player.\r\n     * Some variables manually renamed for readability.\r\n     * Also contains reflection fixes for Spigot's FluidState bug.\r\n     */\r\n    public static void renderFullMap(MapItemSavedData worldmap, int xMin, int zMin, int xMax, int zMax) {\r\n        Level world = ((CraftWorld) worldmap.mapView.getWorld()).getHandle();\r\n        int scale = 1 << worldmap.scale;\r\n        int mapX = worldmap.centerX;\r\n        int mapZ = worldmap.centerZ;\r\n        for (int x = xMin; x < xMax; x++) {\r\n            double d0 = 0.0D;\r\n            for (int z = zMin; z < zMax; z++) {\r\n                int k2 = (mapX / scale + x - 64) * scale;\r\n                int l2 = (mapZ / scale + z - 64) * scale;\r\n                Multiset<MaterialColor> multiset = LinkedHashMultiset.create();\r\n                LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2));\r\n                if (!chunk.isEmpty()) {\r\n                    ChunkPos chunkcoordintpair = chunk.getPos();\r\n                    int i3 = k2 & 15;\r\n                    int j3 = l2 & 15;\r\n                    int k3 = 0;\r\n                    double d1 = 0.0D;\r\n                    if (world.dimensionType().hasCeiling()) {\r\n                        int l3 = k2 + l2 * 231871;\r\n                        l3 = l3 * l3 * 31287121 + l3 * 11;\r\n                        if ((l3 >> 20 & 1) == 0) {\r\n                            multiset.add(Blocks.DIRT.defaultBlockState().getMapColor(world, BlockPos.ZERO), 10);\r\n                        }\r\n                        else {\r\n                            multiset.add(Blocks.STONE.defaultBlockState().getMapColor(world, BlockPos.ZERO), 100);\r\n                        }\r\n\r\n                        d1 = 100.0D;\r\n                    }\r\n                    else {\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition1 = new BlockPos.MutableBlockPos();\r\n                        for (int i4 = 0; i4 < scale; ++i4) {\r\n                            for (int j4 = 0; j4 < scale; ++j4) {\r\n                                int k4 = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i4 + i3, j4 + j3) + 1;\r\n                                BlockState iblockdata;\r\n                                if (k4 <= world.getMinBuildHeight() + 1) {\r\n                                    iblockdata = Blocks.BEDROCK.defaultBlockState();\r\n                                }\r\n                                else {\r\n                                    do {\r\n                                        --k4;\r\n                                        blockposition_mutableblockposition.set(chunkcoordintpair.getMinBlockX() + i4 + i3, k4, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                        iblockdata = chunk.getBlockState(blockposition_mutableblockposition);\r\n                                    } while (iblockdata.getMapColor(world, blockposition_mutableblockposition) == MaterialColor.NONE && k4 > world.getMinBuildHeight());\r\n                                    if (k4 > world.getMinBuildHeight() && !blockStateFluidIsEmpty(iblockdata)) {\r\n                                        int l4 = k4 - 1;\r\n                                        blockposition_mutableblockposition1.set(blockposition_mutableblockposition);\r\n\r\n                                        BlockState iblockdata1;\r\n                                        do {\r\n                                            blockposition_mutableblockposition1.setY(l4--);\r\n                                            iblockdata1 = chunk.getBlockState(blockposition_mutableblockposition1);\r\n                                            k3++;\r\n                                        } while (l4 > world.getMinBuildHeight() && !blockStateFluidIsEmpty(iblockdata1));\r\n                                        iblockdata = getCorrectStateForFluidBlock(world, iblockdata, blockposition_mutableblockposition);\r\n                                    }\r\n                                }\r\n                                worldmap.checkBanners(world, chunkcoordintpair.getMinBlockX() + i4 + i3, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                d1 += (double) k4 / (double) (scale * scale);\r\n                                multiset.add(iblockdata.getMapColor(world, blockposition_mutableblockposition));\r\n                            }\r\n                        }\r\n                    }\r\n                    k3 /= scale * scale;\r\n                    double d2 = (d1 - d0) * 4.0D / (double) (scale + 4) + ((double) (x + z & 1) - 0.5D) * 0.4D;\r\n                    byte b0 = 1;\r\n                    if (d2 > 0.6D) {\r\n                        b0 = 2;\r\n                    }\r\n                    if (d2 < -0.6D) {\r\n                        b0 = 0;\r\n                    }\r\n                    MaterialColor materialmapcolor = Iterables.getFirst(Multisets.copyHighestCountFirst(multiset), MaterialColor.NONE);\r\n                    if (materialmapcolor == MaterialColor.WATER) {\r\n                        d2 = (double) k3 * 0.1D + (double) (x + z & 1) * 0.2D;\r\n                        b0 = 1;\r\n                        if (d2 < 0.5D) {\r\n                            b0 = 2;\r\n                        }\r\n                        if (d2 > 0.9D) {\r\n                            b0 = 0;\r\n                        }\r\n                    }\r\n                    d0 = d1;\r\n                    worldmap.updateColor(x, z, (byte) (materialmapcolor.id * 4 + b0));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean renderEntireMap(int mapId, int xMin, int zMin, int xMax, int zMax) {\r\n        MapItemSavedData worldmap = ((CraftServer) Bukkit.getServer()).getServer().getLevel(net.minecraft.world.level.Level.OVERWORLD).getMapData(\"map_\" + mapId);\r\n        if (worldmap == null) {\r\n            return false;\r\n        }\r\n        renderFullMap(worldmap, xMin, zMin, xMax, zMax);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public BlockData getPlacedBlock(Material material) {\r\n        Item nmsItem = BuiltInRegistries.ITEM.getOptional(CraftNamespacedKey.toMinecraft(material.getKey())).orElse(null);\r\n        if (nmsItem instanceof BlockItem) {\r\n            Block block = ((BlockItem) nmsItem).getBlock();\r\n            return CraftBlockData.fromData(block.defaultBlockState());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public boolean isValidMix(ItemStack input, ItemStack ingredient) {\r\n        net.minecraft.world.item.ItemStack nmsInput = CraftItemStack.asNMSCopy(input);\r\n        net.minecraft.world.item.ItemStack nmsIngredient = CraftItemStack.asNMSCopy(ingredient);\r\n        return PotionBrewing.hasMix(nmsInput, nmsIngredient);\r\n    }\r\n\r\n    public static Class<?> PaperPotionMix_CLASS = null;\r\n    public static Map<NamespacedKey, BrewingRecipe> customBrewingRecipes = null;\r\n\r\n    @Override\r\n    public Map<NamespacedKey, BrewingRecipe> getCustomBrewingRecipes() {\r\n        if (customBrewingRecipes == null) {\r\n            customBrewingRecipes = Maps.transformValues((Map<NamespacedKey, ?>) ReflectionHelper.getFieldValue(PotionBrewing.class, \"CUSTOM_MIXES\", null), paperMix -> {\r\n                if (PaperPotionMix_CLASS == null) {\r\n                    PaperPotionMix_CLASS = paperMix.getClass();\r\n                }\r\n                RecipeChoice ingredient = CraftRecipe.toBukkit(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"ingredient\", paperMix));\r\n                RecipeChoice input = CraftRecipe.toBukkit(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"input\", paperMix));\r\n                ItemStack result = CraftItemStack.asBukkitCopy(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"result\", paperMix));\r\n                return new BrewingRecipe(ingredient, input, result);\r\n            });\r\n        }\r\n        return customBrewingRecipes;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/NBTAdapter.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\n\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport net.kyori.adventure.nbt.*;\nimport net.minecraft.nbt.*;\n\nimport java.lang.invoke.MethodHandle;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class NBTAdapter {\n\n    public static final MethodHandle COMPOUND_TAG_MAP_CONSTRUCTOR = ReflectionHelper.getConstructor(CompoundTag.class, Map.class);\n\n    public static Tag toNMS(BinaryTag tag) {\n        if (tag instanceof ByteBinaryTag byteTag) {\n            return switch (byteTag.value()) {\n                case 0 -> ByteTag.ZERO;\n                case 1 -> ByteTag.ONE;\n                default -> ByteTag.valueOf(byteTag.value());\n            };\n        }\n        else if (tag instanceof ShortBinaryTag shortTag) {\n            return ShortTag.valueOf(shortTag.value());\n        }\n        else if (tag instanceof IntBinaryTag intTag) {\n            return IntTag.valueOf(intTag.value());\n        }\n        else if (tag instanceof LongBinaryTag longTag) {\n            return LongTag.valueOf(longTag.value());\n        }\n        else if (tag instanceof FloatBinaryTag floatTag) {\n            return FloatTag.valueOf(floatTag.value());\n        }\n        else if (tag instanceof DoubleBinaryTag doubleTag) {\n            return DoubleTag.valueOf(doubleTag.value());\n        }\n        else if (tag instanceof ByteArrayBinaryTag byteArrayTag) {\n            return new ByteArrayTag(byteArrayTag.value());\n        }\n        else if (tag instanceof IntArrayBinaryTag intArrayTag) {\n            return new IntArrayTag(intArrayTag.value());\n        }\n        else if (tag instanceof LongArrayBinaryTag longArrayTag) {\n            return new LongArrayTag(longArrayTag.value());\n        }\n        else if (tag instanceof StringBinaryTag stringTag) {\n            return StringTag.valueOf(stringTag.value());\n        }\n        else if (tag instanceof ListBinaryTag listTag) {\n            return toNMS(listTag);\n        }\n        else if (tag instanceof CompoundBinaryTag compoundTag) {\n            return toNMS(compoundTag);\n        }\n        else if (tag instanceof EndBinaryTag) {\n            return EndTag.INSTANCE;\n        }\n        throw new IllegalStateException(\"Unrecognized API tag of type '\" + tag.type() + \"': \" + tag);\n    }\n\n    public static BinaryTag toAPI(Tag nmsTag) {\n        if (nmsTag instanceof ByteTag nmsByteTag) {\n            return ByteBinaryTag.byteBinaryTag(nmsByteTag.getAsByte());\n        }\n        else if (nmsTag instanceof ShortTag nmsShortTag) {\n            return ShortBinaryTag.shortBinaryTag(nmsShortTag.getAsShort());\n        }\n        else if (nmsTag instanceof IntTag nmsIntTag) {\n            return IntBinaryTag.intBinaryTag(nmsIntTag.getAsInt());\n        }\n        else if (nmsTag instanceof LongTag nmsLongTag) {\n            return LongBinaryTag.longBinaryTag(nmsLongTag.getAsLong());\n        }\n        else if (nmsTag instanceof FloatTag nmsFloatTag) {\n            return FloatBinaryTag.floatBinaryTag(nmsFloatTag.getAsFloat());\n        }\n        else if (nmsTag instanceof DoubleTag nmsDoubleTag) {\n            return DoubleBinaryTag.doubleBinaryTag(nmsDoubleTag.getAsDouble());\n        }\n        else if (nmsTag instanceof ByteArrayTag nmsByteArrayTag) {\n            return ByteArrayBinaryTag.byteArrayBinaryTag(nmsByteArrayTag.getAsByteArray());\n        }\n        else if (nmsTag instanceof IntArrayTag nmsIntArrayTag) {\n            return IntArrayBinaryTag.intArrayBinaryTag(nmsIntArrayTag.getAsIntArray());\n        }\n        else if (nmsTag instanceof LongArrayTag nmsLongArrayTag) {\n            return LongArrayBinaryTag.longArrayBinaryTag(nmsLongArrayTag.getAsLongArray());\n        }\n        else if (nmsTag instanceof StringTag nmsStringTag) {\n            return StringBinaryTag.stringBinaryTag(nmsStringTag.getAsString());\n        }\n        else if (nmsTag instanceof ListTag nmsListTag) {\n            return toAPI(nmsListTag);\n        }\n        else if (nmsTag instanceof CompoundTag nmsCompoundTag) {\n            return toAPI(nmsCompoundTag);\n        }\n        else if (nmsTag instanceof EndTag) {\n            return EndBinaryTag.endBinaryTag();\n        }\n        throw new IllegalStateException(\"Unrecognized NMS tag of type '\" + nmsTag.getClass().getName() + '/' + nmsTag.getType().getName() + \"': \" + nmsTag);\n    }\n\n    public static ListBinaryTag toAPI(ListTag nmsListTag) {\n        ListBinaryTag.Builder<BinaryTag> builder = ListBinaryTag.builder(nmsListTag.size());\n        for (Tag nmsValue : nmsListTag) {\n            builder.add(toAPI(nmsValue));\n        }\n        return builder.build();\n    }\n\n    public static ListTag toNMS(ListBinaryTag listTag) {\n        ListTag nmsListTag = new ListTag();\n        for (BinaryTag value : listTag) {\n            nmsListTag.add(toNMS(value));\n        }\n        return nmsListTag;\n    }\n\n    public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) {\n        CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size());\n        for (String key : nmsCompoundTag.getAllKeys()) {\n            builder.put(key, toAPI(nmsCompoundTag.get(key)));\n        }\n        return builder.build();\n    }\n\n    public static CompoundTag toNMS(CompoundBinaryTag compoundTag) {\n        Map<String, Tag> nmsTags = new HashMap<>(compoundTag.size());\n        for (Map.Entry<String, ? extends BinaryTag> entry : compoundTag) {\n            nmsTags.put(entry.getKey(), toNMS(entry.getValue()));\n        }\n        try {\n            return (CompoundTag) COMPOUND_TAG_MAP_CONSTRUCTOR.invokeExact(nmsTags);\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/PacketHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.PacketHelper;\r\nimport com.denizenscript.denizen.nms.v1_19.Handler;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizen.utilities.maps.MapImage;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.objects.core.DurationTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.kyori.adventure.nbt.BinaryTagTypes;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.kyori.adventure.nbt.ListBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.RelativeMovement;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.monster.CaveSpider;\r\nimport net.minecraft.world.entity.monster.Creeper;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.entity.monster.Spider;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.entity.BlockEntityType;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Team;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.EntityEffect;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.banner.Pattern;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_19_R3.map.CraftMapCanvas;\r\nimport org.bukkit.craftbukkit.v1_19_R3.map.CraftMapView;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.player.PlayerRespawnEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapPalette;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.*;\r\n\r\npublic class PacketHelperImpl implements PacketHelper {\r\n\r\n    public static final EntityDataAccessor<Float> PLAYER_DATA_ACCESSOR_ABSORPTION = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, ReflectionMappingsInfo.Player_DATA_PLAYER_ABSORPTION_ID, null);\r\n\r\n    public static final EntityDataAccessor<Byte> ENTITY_DATA_ACCESSOR_FLAGS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_SHARED_FLAGS_ID, null);\r\n\r\n    public static final MethodHandle ABILITIES_PACKET_FOV_SETTER = ReflectionHelper.getFinalSetter(ClientboundPlayerAbilitiesPacket.class, ReflectionMappingsInfo.ClientboundPlayerAbilitiesPacket_walkingSpeed);\r\n\r\n    public static Field ENTITY_TRACKER_ENTRY_GETTER = ReflectionHelper.getFields(ChunkMap.TrackedEntity.class).getFirstOfType(ServerEntity.class);\r\n\r\n    public static MethodHandle CANVAS_GET_BUFFER = ReflectionHelper.getMethodHandle(CraftMapCanvas.class, \"getBuffer\");\r\n    public static Field MAPVIEW_WORLDMAP = ReflectionHelper.getFields(CraftMapView.class).get(\"worldMap\");\r\n\r\n    public static MethodHandle BLOCK_ENTITY_DATA_PACKET_CONSTRUCTOR = ReflectionHelper.getConstructor(ClientboundBlockEntityDataPacket.class, BlockPos.class, BlockEntityType.class, net.minecraft.nbt.CompoundTag.class);\r\n\r\n    public static EntityDataAccessor<Optional<Component>> ENTITY_DATA_ACCESSOR_CUSTOM_NAME = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_CUSTOM_NAME, null);\r\n    public static EntityDataAccessor<Boolean> ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_CUSTOM_NAME_VISIBLE, null);\r\n\r\n    @Override\r\n    public void setFakeAbsorption(Player player, float value) {\r\n        SynchedEntityData dw = new SynchedEntityData(null);\r\n        dw.define(PLAYER_DATA_ACCESSOR_ABSORPTION, value);\r\n        send(player, new ClientboundSetEntityDataPacket(player.getEntityId(), dw.packDirty()));\r\n    }\r\n\r\n    @Override\r\n    public void setSlot(Player player, int slot, ItemStack itemStack, boolean playerOnly) {\r\n        AbstractContainerMenu menu = ((CraftPlayer) player).getHandle().containerMenu;\r\n        int windowId = playerOnly ? 0 : menu.containerId;\r\n        send(player, new ClientboundContainerSetSlotPacket(windowId, menu.incrementStateId(), slot, CraftItemStack.asNMSCopy(itemStack)));\r\n    }\r\n\r\n    @Override\r\n    public void setFieldOfView(Player player, float fov) {\r\n        ClientboundPlayerAbilitiesPacket packet = new ClientboundPlayerAbilitiesPacket(((CraftPlayer) player).getHandle().getAbilities());\r\n        if (!Float.isNaN(fov)) {\r\n            try {\r\n                ABILITIES_PACKET_FOV_SETTER.invoke(packet, fov);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void respawn(Player player) {\r\n        ((CraftPlayer) player).getHandle().connection.handleClientCommand(new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN));\r\n    }\r\n\r\n    @Override\r\n    public void setVision(Player player, EntityType entityType) {\r\n        final net.minecraft.world.entity.LivingEntity entity;\r\n        if (entityType == EntityType.CREEPER) {\r\n            entity = new Creeper(net.minecraft.world.entity.EntityType.CREEPER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.SPIDER) {\r\n            entity = new Spider(net.minecraft.world.entity.EntityType.SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.CAVE_SPIDER) {\r\n            entity = new CaveSpider(net.minecraft.world.entity.EntityType.CAVE_SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.ENDERMAN) {\r\n            entity = new EnderMan(net.minecraft.world.entity.EntityType.ENDERMAN, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n\r\n        // Spectating an entity then immediately respawning the player prevents a client shader update,\r\n        // allowing the player to retain whatever vision the mob they spectated had.\r\n        send(player, new ClientboundAddEntityPacket(entity));\r\n        send(player, new ClientboundSetCameraPacket(entity));\r\n        ((CraftServer) Bukkit.getServer()).getHandle().respawn(((CraftPlayer) player).getHandle(),\r\n                ((CraftWorld) player.getWorld()).getHandle(), true, player.getLocation(), false, PlayerRespawnEvent.RespawnReason.PLUGIN);\r\n    }\r\n\r\n    @Override\r\n    public void showBlockAction(Player player, Location location, int action, int state) {\r\n        BlockPos position = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        Block block = ((CraftWorld) location.getWorld()).getHandle().getBlockState(position).getBlock();\r\n        send(player, new ClientboundBlockEventPacket(position, block, action, state));\r\n    }\r\n\r\n    @Override\r\n    public void showBlockCrack(Player player, int id, Location location, int progress) {\r\n        BlockPos position = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        send(player, new ClientboundBlockDestructionPacket(id, position, progress));\r\n    }\r\n\r\n    @Override\r\n    public void showTileEntityData(Player player, Location location, int action, CompoundBinaryTag compoundTag) {\r\n        BlockPos position = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        try {\r\n            ClientboundBlockEntityDataPacket packet = (ClientboundBlockEntityDataPacket) BLOCK_ENTITY_DATA_PACKET_CONSTRUCTOR.invoke(position, action, NBTAdapter.toNMS(compoundTag));\r\n            send(player, packet);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void showBannerUpdate(Player player, Location location, List<Pattern> patterns) {\r\n        ListBinaryTag.Builder<CompoundBinaryTag> nbtPatterns = ListBinaryTag.builder(BinaryTagTypes.COMPOUND);\r\n        for (Pattern pattern : patterns) {\r\n            nbtPatterns.add(CompoundBinaryTag.builder()\r\n                    .putInt(\"Color\", pattern.getColor().getDyeData())\r\n                    .putString(\"Pattern\", pattern.getPattern().getIdentifier())\r\n                    .build());\r\n        }\r\n        CompoundBinaryTag compoundTag = NMSHandler.blockHelper.getNbtData(location.getBlock()).put(\"Patterns\", nbtPatterns.build());\r\n        showTileEntityData(player, location, 3, compoundTag);\r\n    }\r\n\r\n    @Override\r\n    public void showTabListHeaderFooter(Player player, String header, String footer) {\r\n        Component cHeader = Handler.componentToNMS(FormattedTextHelper.parse(header, ChatColor.WHITE));\r\n        Component cFooter = Handler.componentToNMS(FormattedTextHelper.parse(footer, ChatColor.WHITE));\r\n        ClientboundTabListPacket packet = new ClientboundTabListPacket(cHeader, cFooter);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void showTitle(Player player, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) {\r\n        send(player, new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks));\r\n        if (title != null) {\r\n            send(player, new ClientboundSetTitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE))));\r\n        }\r\n        if (subtitle != null) {\r\n            send(player, new ClientboundSetSubtitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(subtitle, ChatColor.WHITE))));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void showHealth(Player player, float health, int food, float saturation) {\r\n        send(player, new ClientboundSetHealthPacket(health, food, saturation));\r\n    }\r\n\r\n    @Override\r\n    public void showMobHealth(Player player, LivingEntity mob, double health, double maxHealth) {\r\n        AttributeInstance attr = new AttributeInstance(Attributes.MAX_HEALTH, (a) -> { });\r\n        attr.setBaseValue(maxHealth);\r\n        send(player, new ClientboundUpdateAttributesPacket(mob.getEntityId(), Collections.singletonList(attr)));\r\n        FriendlyByteBuf healthData = new FriendlyByteBuf(Unpooled.buffer());\r\n        healthData.writeVarInt(mob.getEntityId());\r\n        healthData.writeByte(9); // health id\r\n        healthData.writeVarInt(2); // type = float\r\n        healthData.writeFloat((float) health);\r\n        healthData.writeByte(255); // Mark end of packet\r\n        send(player, new ClientboundSetEntityDataPacket(healthData));\r\n    }\r\n\r\n    @Override\r\n    public void resetHealth(Player player) {\r\n        showHealth(player, (float) player.getHealth(), player.getFoodLevel(), player.getSaturation());\r\n    }\r\n\r\n    @Override\r\n    public void showSignEditor(Player player, Location location) {\r\n        LocationTag fakeSign = new LocationTag(player.getLocation());\r\n        fakeSign.setY(0);\r\n        FakeBlock.showFakeBlockTo(Collections.singletonList(new PlayerTag(player)), fakeSign, new MaterialTag(Material.OAK_WALL_SIGN), new DurationTag(1), true);\r\n        BlockPos pos = new BlockPos(fakeSign.getBlockX(), 0, fakeSign.getBlockZ());\r\n        DenizenNetworkManagerImpl.getNetworkManager(player).packetListener.fakeSignExpected = pos;\r\n        send(player, new ClientboundOpenSignEditorPacket(pos));\r\n    }\r\n\r\n    @Override\r\n    public void forceSpectate(Player player, Entity entity) {\r\n        send(player, new ClientboundSetCameraPacket(((CraftEntity) entity).getHandle()));\r\n    }\r\n\r\n    public static void forceRespawnPlayerEntity(Entity entity, Player viewer) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        if (entityTracker != null) {\r\n            try {\r\n                ServerEntity entry = (ServerEntity) ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n                if (entry != null) {\r\n                    entry.removePairing(((CraftPlayer) viewer).getHandle());\r\n                    entry.addPairing(((CraftPlayer) viewer).getHandle());\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendRename(Player player, Entity entity, String name, boolean listMode) {\r\n        try {\r\n            if (entity.getType() == EntityType.PLAYER) {\r\n                if (listMode) {\r\n                    send(player, new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, ((CraftPlayer) player).getHandle()));\r\n                }\r\n                else {\r\n                    // For player entities, force a respawn packet and let the dynamic intercept correct the details\r\n                    forceRespawnPlayerEntity(entity, player);\r\n                }\r\n                return;\r\n            }\r\n            SynchedEntityData fakeData = new SynchedEntityData(((CraftEntity) entity).getHandle());\r\n            List<SynchedEntityData.DataValue<?>> list = new ArrayList<>();\r\n            list.add(new SynchedEntityData.DataValue<>(ENTITY_DATA_ACCESSOR_CUSTOM_NAME.getId(), ENTITY_DATA_ACCESSOR_CUSTOM_NAME.getSerializer(), Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)))));\r\n            list.add(new SynchedEntityData.DataValue<>(ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE.getId(), ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE.getSerializer(), true));\r\n            send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), list));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static HashMap<UUID, HashMap<UUID, PlayerTeam>> noCollideTeamMap = new HashMap<>();\r\n\r\n    @Override\r\n    public void generateNoCollideTeam(Player player, UUID noCollide) {\r\n        removeNoCollideTeam(player, noCollide);\r\n        PlayerTeam team = new PlayerTeam(SidebarImpl.dummyScoreboard, Utilities.generateRandomColors(8));\r\n        team.getPlayers().add(noCollide.toString());\r\n        team.setCollisionRule(Team.CollisionRule.NEVER);\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>());\r\n        map.put(noCollide, team);\r\n        send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n    }\r\n\r\n    @Override\r\n    public void removeNoCollideTeam(Player player, UUID noCollide) {\r\n        if (noCollide == null || !player.isOnline()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n            return;\r\n        }\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.get(player.getUniqueId());\r\n        if (map == null) {\r\n            return;\r\n        }\r\n        PlayerTeam team = map.remove(noCollide);\r\n        if (team != null) {\r\n            send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        if (map.isEmpty()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityMetadataFlagsUpdate(Player player, Entity entity) {\r\n        List<SynchedEntityData.DataValue<?>> data = new ArrayList<>();\r\n        byte flags = ((CraftEntity) entity).getHandle().getEntityData().get(ENTITY_DATA_ACCESSOR_FLAGS);\r\n        data.add(SynchedEntityData.DataValue.create(ENTITY_DATA_ACCESSOR_FLAGS, flags));\r\n        send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), data));\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityEffect(Player player, Entity entity, EntityEffect effect) {\r\n        send(player, new ClientboundEntityEventPacket(((CraftEntity) entity).getHandle(), effect.getData()));\r\n    }\r\n\r\n    @Override\r\n    public int getPacketStats(Player player, boolean sent) {\r\n        DenizenNetworkManagerImpl netMan = DenizenNetworkManagerImpl.getNetworkManager(player);\r\n        return sent ? netMan.packetsSent : netMan.packetsReceived;\r\n    }\r\n\r\n    @Override\r\n    public void setMapData(MapCanvas canvas, byte[] bytes, int x, int y, MapImage image) {\r\n        if (x > 127 || y > 127) {\r\n            return;\r\n        }\r\n        int width = Math.min(image.width, 128 - x),\r\n                height = Math.min(image.height, 128 - y);\r\n        if (x + width <= 0 || y + height <= 0) {\r\n            return;\r\n        }\r\n        try {\r\n            boolean anyChanged = false;\r\n            byte[] buffer = (byte[]) CANVAS_GET_BUFFER.invoke(canvas);\r\n            for (int x2 = x < 0 ? -x : 0; x2 < width; ++x2) {\r\n                for (int y2 = y < 0 ? -y : 0; y2 < height; ++y2) {\r\n                    byte p = bytes[y2 * image.width + x2];\r\n                    if (p != MapPalette.TRANSPARENT) {\r\n                        int index = (y2 + y) * 128 + (x2 + x);\r\n                        if (buffer[index] != p) {\r\n                            buffer[index] = p;\r\n                            anyChanged = true;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            if (anyChanged) {\r\n                // Flag the whole image as dirty\r\n                MapItemSavedData map = (MapItemSavedData) MAPVIEW_WORLDMAP.get(canvas.getMapView());\r\n                map.setColorsDirty(Math.max(x, 0), Math.max(y, 0));\r\n                map.setColorsDirty(width + x - 1, height + y - 1);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setNetworkManagerFor(Player player) {\r\n        DenizenNetworkManagerImpl.setNetworkManager(player);\r\n    }\r\n\r\n    @Override\r\n    public void enableNetworkManager() {\r\n        DenizenNetworkManagerImpl.enableNetworkManager();\r\n    }\r\n\r\n    @Override\r\n    public void showDebugTestMarker(Player player, Location location, ColorTag color, String name, int time) {\r\n        ResourceLocation packetKey = new ResourceLocation(\"minecraft\", \"debug/game_test_add_marker\");\r\n        FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());\r\n        buf.writeBlockPos(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()));\r\n        int colorInt = color.blue | (color.green << 8) | (color.red << 16) | (color.alpha << 24);\r\n        buf.writeInt(colorInt);\r\n        buf.writeByteArray(name.getBytes(StandardCharsets.UTF_8));\r\n        buf.writeInt(time);\r\n        ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(packetKey, buf);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void clearDebugTestMarker(Player player) {\r\n        ResourceLocation packetKey = new ResourceLocation(\"minecraft\", \"debug/game_test_clear\");\r\n        FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());\r\n        ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(packetKey, buf);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendBrand(Player player, String brand) {\r\n        ResourceLocation packetKey = new ResourceLocation(\"minecraft\", \"brand\");\r\n        FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());\r\n        buf.writeUtf(brand);\r\n        ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(packetKey, buf);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendCollectItemEntity(Player player, Entity taker, Entity item, int amount) {\r\n        ClientboundTakeItemEntityPacket packet = new ClientboundTakeItemEntityPacket(item.getEntityId(), taker.getEntityId(), amount);\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendRelativeLookPacket(Player player, float yaw, float pitch) {\r\n        ClientboundPlayerPositionPacket packet = new ClientboundPlayerPositionPacket(0, 0, 0, yaw, pitch, RelativeMovement.ALL, 0);\r\n        DenizenNetworkManagerImpl.getNetworkManager(player).oldManager.channel.writeAndFlush(packet);\r\n    }\r\n\r\n    public static void send(Player player, Packet<?> packet) {\r\n        ((CraftPlayer) player).getHandle().connection.send(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/PlayerHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.enums.CustomEntityType;\r\nimport com.denizenscript.denizen.nms.interfaces.PlayerHelper;\r\nimport com.denizenscript.denizen.nms.v1_19.Handler;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.ImprovedOfflinePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.network.handlers.AbstractListenerPlayInImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.entity.DenizenEntityType;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport it.unimi.dsi.fastutil.ints.IntList;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.players.ServerOpList;\r\nimport net.minecraft.server.players.ServerOpListEntry;\r\nimport net.minecraft.stats.ServerRecipeBook;\r\nimport net.minecraft.tags.BlockTags;\r\nimport net.minecraft.tags.TagNetworkSerialization;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.entity.Mob;\r\nimport net.minecraft.world.item.Item;\r\nimport net.minecraft.world.item.ItemCooldowns;\r\nimport net.minecraft.world.item.crafting.Recipe;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.GameType;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.biome.BiomeManager;\r\nimport net.minecraft.world.phys.AABB;\r\nimport org.bukkit.*;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.boss.CraftBossBar;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class PlayerHelperImpl extends PlayerHelper {\r\n\r\n    public static final Field ATTACK_COOLDOWN_TICKS = ReflectionHelper.getFields(LivingEntity.class).get(ReflectionMappingsInfo.LivingEntity_attackStrengthTicker, int.class);\r\n\r\n    public static final Field FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundTickCount, int.class);\r\n    public static final Field VEHICLE_FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundVehicleTickCount, int.class);\r\n    public static final MethodHandle PLAYER_RESPAWNFORCED_SETTER = ReflectionHelper.getFinalSetter(ServerPlayer.class, ReflectionMappingsInfo.ServerPlayer_respawnForced, boolean.class);\r\n\r\n    public static final EntityDataAccessor<Byte> ENTITY_HUMAN_SKINLAYERS_DATAWATCHER;\r\n\r\n    static {\r\n        EntityDataAccessor<Byte> skinlayers = null;\r\n        try {\r\n            skinlayers = (EntityDataAccessor<Byte>) ReflectionHelper.getFields(net.minecraft.world.entity.player.Player.class).get(ReflectionMappingsInfo.Player_DATA_PLAYER_MODE_CUSTOMISATION).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n        ENTITY_HUMAN_SKINLAYERS_DATAWATCHER = skinlayers;\r\n    }\r\n\r\n    @Override\r\n    public void stopSound(Player player, NamespacedKey sound, SoundCategory category) {\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundStopSoundPacket(sound == null ? null : CraftNamespacedKey.toMinecraft(sound), null));\r\n    }\r\n\r\n    @Override\r\n    public void deTrackEntity(Player player, Entity entity) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerLevel world = (ServerLevel) nmsPlayer.level;\r\n        ChunkMap.TrackedEntity tracker = world.getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n        if (tracker == null) {\r\n            if (NMSHandler.debugPackets) {\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Failed to de-track entity \" + entity.getEntityId() + \" for \" + player.getName() + \": tracker null\");\r\n            }\r\n            return;\r\n        }\r\n        sendEntityDestroy(player, entity);\r\n        tracker.removePlayer(nmsPlayer);\r\n    }\r\n\r\n    public static class TrackerData {\r\n        public PlayerTag player;\r\n        public ServerEntity tracker;\r\n    }\r\n\r\n    @Override\r\n    public FakeEntity sendEntitySpawn(List<PlayerTag> players, DenizenEntityType entityType, LocationTag location, ArrayList<Mechanism> mechanisms, int customId, UUID customUUID, boolean autoTrack) {\r\n        CraftWorld world = ((CraftWorld) location.getWorld());\r\n        net.minecraft.world.entity.Entity nmsEntity;\r\n        if (entityType.isCustom()) {\r\n            if (entityType.customEntityType == CustomEntityType.ITEM_PROJECTILE) {\r\n                ItemStack itemStack = new ItemStack(Material.STONE);\r\n                for (Mechanism mechanism : mechanisms) {\r\n                    if (mechanism.matches(\"item\") && mechanism.requireObject(ItemTag.class)) {\r\n                        itemStack = mechanism.valueAsType(ItemTag.class).getItemStack();\r\n                    }\r\n                }\r\n                nmsEntity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n            }\r\n            else if (entityType.customEntityType == CustomEntityType.FAKE_PLAYER) {\r\n                String name = null;\r\n                String skin = null;\r\n                String blob = null;\r\n                for (Mechanism mechanism : new ArrayList<>(mechanisms)) {\r\n                    if (mechanism.matches(\"name\")) {\r\n                        name = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin\")) {\r\n                        skin = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin_blob\")) {\r\n                        blob = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    if (name != null && (skin != null || blob != null)) {\r\n                        break;\r\n                    }\r\n                }\r\n                nmsEntity = ((CraftFakePlayerImpl) NMSHandler.customEntityHelper.spawnFakePlayer(location, name, skin, blob, false)).getHandle();\r\n            }\r\n            else {\r\n                throw new IllegalArgumentException(\"entityType\");\r\n            }\r\n        }\r\n        else {\r\n            nmsEntity = world.createEntity(location, entityType.getBukkitEntityType().getEntityClass());\r\n        }\r\n        if (customUUID != null) {\r\n            nmsEntity.setId(customId);\r\n            nmsEntity.setUUID(customUUID);\r\n        }\r\n        EntityTag entity = new EntityTag(nmsEntity.getBukkitEntity());\r\n        entity.isFake = true;\r\n        entity.isFakeValid = true;\r\n        for (Mechanism mechanism : mechanisms) {\r\n            entity.safeAdjustDuplicate(mechanism);\r\n        }\r\n        nmsEntity.unsetRemoved();\r\n        FakeEntity fake = new FakeEntity(players, location, entity.getBukkitEntity().getEntityId());\r\n        fake.entity = new EntityTag(entity.getBukkitEntity());\r\n        fake.entity.isFake = true;\r\n        fake.entity.isFakeValid = true;\r\n        List<TrackerData> trackers = new ArrayList<>();\r\n        fake.triggerSpawnPacket = (player) -> {\r\n            ServerPlayer nmsPlayer = ((CraftPlayer) player.getPlayerEntity()).getHandle();\r\n            ServerGamePacketListenerImpl conn = nmsPlayer.connection;\r\n            final ServerEntity tracker = new ServerEntity(world.getHandle(), nmsEntity, 1, true, conn::send, Collections.singleton(nmsPlayer.connection));\r\n            tracker.addPairing(nmsPlayer);\r\n            final TrackerData data = new TrackerData();\r\n            data.player = player;\r\n            data.tracker = tracker;\r\n            trackers.add(data);\r\n            if (autoTrack) {\r\n                new BukkitRunnable() {\r\n                    boolean wasOnline = true;\r\n                    @Override\r\n                    public void run() {\r\n                        if (!fake.entity.isFakeValid) {\r\n                            cancel();\r\n                            return;\r\n                        }\r\n                        if (player.isOnline()) {\r\n                            if (!wasOnline) {\r\n                                tracker.addPairing(((CraftPlayer) player.getPlayerEntity()).getHandle());\r\n                                wasOnline = true;\r\n                            }\r\n                            tracker.sendChanges();\r\n                        }\r\n                        else if (wasOnline) {\r\n                            wasOnline = false;\r\n                        }\r\n                    }\r\n                }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n            }\r\n        };\r\n        for (PlayerTag player : players) {\r\n            fake.triggerSpawnPacket.accept(player);\r\n        }\r\n        fake.triggerUpdatePacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.sendChanges();\r\n                }\r\n            }\r\n        };\r\n        fake.triggerDestroyPacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.removePairing(((CraftPlayer) tracker.player.getPlayerEntity()).getHandle());\r\n                }\r\n            }\r\n            trackers.clear();\r\n        };\r\n        return fake;\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityDestroy(Player player, Entity entity) {\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId()));\r\n    }\r\n\r\n    @Override\r\n    public int getFlyKickCooldown(Player player) {\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl) {\r\n            conn = ((AbstractListenerPlayInImpl) conn).oldListener;\r\n        }\r\n        try {\r\n            return Math.max(80 - Math.max(FLY_TICKS.getInt(conn), VEHICLE_FLY_TICKS.getInt(conn)), 0);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return 80;\r\n    }\r\n\r\n    @Override\r\n    public void setFlyKickCooldown(Player player, int ticks) {\r\n        ticks = 80 - ticks;\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl) {\r\n            conn = ((AbstractListenerPlayInImpl) conn).oldListener;\r\n        }\r\n        try {\r\n            FLY_TICKS.setInt(conn, ticks);\r\n            VEHICLE_FLY_TICKS.setInt(conn, ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int ticksPassedDuringCooldown(Player player) {\r\n        try {\r\n            return ATTACK_COOLDOWN_TICKS.getInt(((CraftPlayer) player).getHandle());\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public float getMaxAttackCooldownTicks(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getCurrentItemAttackStrengthDelay() + 3;\r\n    }\r\n\r\n    @Override\r\n    public void setAttackCooldown(Player player, int ticks) {\r\n        try {\r\n            ATTACK_COOLDOWN_TICKS.setInt(((CraftPlayer) player).getHandle(), ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean hasChunkLoaded(Player player, Chunk chunk) {\r\n        return ((CraftWorld) chunk.getWorld()).getHandle().getChunkSource().chunkMap\r\n                .getPlayers(new ChunkPos(chunk.getX(), chunk.getZ()), false).stream()\r\n                .anyMatch(entityPlayer -> entityPlayer.getUUID().equals(player.getUniqueId()));\r\n    }\r\n\r\n    @Override\r\n    public void setTemporaryOp(Player player, boolean op) {\r\n        MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();\r\n        GameProfile profile = ((CraftPlayer) player).getProfile();\r\n        ServerOpList opList = server.getPlayerList().getOps();\r\n        if (op) {\r\n            int permLevel = server.getOperatorUserPermissionLevel();\r\n            opList.add(new ServerOpListEntry(profile, permLevel, opList.canBypassPlayerLimit(profile)));\r\n        }\r\n        else {\r\n            opList.remove(profile);\r\n        }\r\n        player.recalculatePermissions();\r\n    }\r\n\r\n    @Override\r\n    public void showEndCredits(Player player) {\r\n        ((CraftPlayer) player).getHandle().wonGame = true;\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1f));\r\n    }\r\n\r\n    @Override\r\n    public ImprovedOfflinePlayer getOfflineData(UUID uuid) {\r\n        return new ImprovedOfflinePlayerImpl(uuid);\r\n    }\r\n\r\n    @Override\r\n    public void resendRecipeDetails(Player player) {\r\n        Collection<Recipe<?>> recipes = ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().getRecipes();\r\n        ClientboundUpdateRecipesPacket updatePacket = new ClientboundUpdateRecipesPacket(recipes);\r\n        ((CraftPlayer) player).getHandle().connection.send(updatePacket);\r\n    }\r\n\r\n    @Override\r\n    public void resendDiscoveredRecipes(Player player) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        recipeBook.sendInitialRecipeBook(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    @Override\r\n    public void quietlyAddRecipe(Player player, NamespacedKey key) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        Recipe<?> recipe = ItemHelperImpl.getNMSRecipe(key);\r\n        if (recipe == null) {\r\n            Debug.echoError(\"Cannot add recipe '\" + key + \"': it does not exist.\");\r\n            return;\r\n        }\r\n        recipeBook.add(recipe);\r\n        recipeBook.addHighlight(recipe);\r\n    }\r\n\r\n    @Override\r\n    public String getClientBrand(Player player) {\r\n        return DenizenNetworkManagerImpl.getNetworkManager(player).packetListener.brand;\r\n    }\r\n\r\n    @Override\r\n    public byte getSkinLayers(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getEntityData().get(ENTITY_HUMAN_SKINLAYERS_DATAWATCHER);\r\n    }\r\n\r\n    @Override\r\n    public void setSkinLayers(Player player, byte flags) {\r\n        ((CraftPlayer) player).getHandle().getEntityData().set(ENTITY_HUMAN_SKINLAYERS_DATAWATCHER, flags);\r\n    }\r\n\r\n    @Override\r\n    public void setBossBarTitle(BossBar bar, String title) {\r\n        ((CraftBossBar) bar).getHandle().name = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        ((CraftBossBar) bar).getHandle().broadcast(ClientboundBossEventPacket::createUpdateNamePacket);\r\n    }\r\n\r\n    @Override\r\n    public boolean getSpawnForced(Player player) {\r\n        return ((CraftPlayer) player).getHandle().isRespawnForced();\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnForced(Player player, boolean forced) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        try {\r\n            PLAYER_RESPAWNFORCED_SETTER.invoke(nmsPlayer, forced);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Location getBedSpawnLocation(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        BlockPos spawnPosition = nmsPlayer.getRespawnPosition();\r\n        if (spawnPosition == null) {\r\n            return null;\r\n        }\r\n        Level nmsWorld = MinecraftServer.getServer().getLevel(nmsPlayer.getRespawnDimension());\r\n        if (nmsWorld == null) {\r\n            return null;\r\n        }\r\n        return new Location(nmsWorld.getWorld(), spawnPosition.getX(), spawnPosition.getY(), spawnPosition.getZ(), nmsPlayer.getRespawnAngle(), 0);\r\n    }\r\n\r\n    @Override\r\n    public long getLastActionTime(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getLastActionTime();\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerInfoAddPacket(Player player, EnumSet<ProfileEditMode> editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) {\r\n        EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action.class);\r\n        for (ProfileEditMode editMode : editModes) {\r\n            actions.add(switch (editMode) {\r\n                case ADD -> ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER;\r\n                case UPDATE_DISPLAY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME;\r\n                case UPDATE_LATENCY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY;\r\n                case UPDATE_GAME_MODE -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE;\r\n                case UPDATE_LISTED -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED;\r\n            });\r\n        }\r\n        GameProfile profile = new GameProfile(id, name);\r\n        if (texture != null) {\r\n            profile.getProperties().put(\"textures\", new Property(\"textures\", texture, signature));\r\n        }\r\n        ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(id, profile, listed, latency, gameMode == null ? null : GameType.byId(gameMode.getValue()), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)), null);\r\n        PacketHelperImpl.send(player, ProfileEditorImpl.createInfoPacket(actions, List.of(entry)));\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerInfoRemovePacket(Player player, UUID id) {\r\n        PacketHelperImpl.send(player, new ClientboundPlayerInfoRemovePacket(List.of(id)));\r\n    }\r\n\r\n    @Override\r\n    public void sendClimbableMaterials(Player player, List<Material> materials) {\r\n        Map<ResourceKey<? extends Registry<?>>, TagNetworkSerialization.NetworkPayload> packetInput = TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registries());\r\n        Map<ResourceLocation, IntList> tags = ReflectionHelper.getFieldValue(TagNetworkSerialization.NetworkPayload.class, ReflectionMappingsInfo.TagNetworkSerializationNetworkPayload_tags, packetInput.get(BuiltInRegistries.BLOCK.key()));\r\n        IntList intList = tags.get(BlockTags.CLIMBABLE.location());\r\n        intList.clear();\r\n        for (Material material : materials) {\r\n            intList.add(BuiltInRegistries.BLOCK.getId(CraftMagicNumbers.getBlock(material)));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundUpdateTagsPacket(packetInput));\r\n    }\r\n\r\n    @Override\r\n    public void refreshPlayer(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerLevel nmsWorld = (ServerLevel) nmsPlayer.level;\r\n        nmsPlayer.connection.send(new ClientboundRespawnPacket(\r\n                nmsWorld.dimensionTypeId(),\r\n                nmsWorld.dimension(),\r\n                BiomeManager.obfuscateSeed(nmsWorld.getSeed()),\r\n                nmsPlayer.gameMode.getGameModeForPlayer(),\r\n                nmsPlayer.gameMode.getPreviousGameModeForPlayer(),\r\n                nmsWorld.isDebug(),\r\n                nmsWorld.isFlat(),\r\n                ClientboundRespawnPacket.KEEP_ALL_DATA,\r\n                nmsPlayer.getLastDeathLocation()));\r\n        nmsPlayer.connection.teleport(player.getLocation());\r\n        if (nmsPlayer.isPassenger()) {\r\n           nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer.getVehicle()));\r\n        }\r\n        if (nmsPlayer.isVehicle()) {\r\n            nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer));\r\n        }\r\n        AABB boundingBox = new AABB(nmsPlayer.position(), nmsPlayer.position()).inflate(10);\r\n        for (Mob nmsMob : nmsWorld.getEntitiesOfClass(Mob.class, boundingBox, nmsMob -> nmsPlayer.equals(nmsMob.getLeashHolder()))) {\r\n            nmsPlayer.connection.send(new ClientboundSetEntityLinkPacket(nmsMob, nmsPlayer));\r\n        }\r\n        if (!nmsPlayer.getCooldowns().cooldowns.isEmpty()) {\r\n            int tickCount = nmsPlayer.getCooldowns().tickCount;\r\n            for (Map.Entry<Item, ItemCooldowns.CooldownInstance> entry : nmsPlayer.getCooldowns().cooldowns.entrySet()) {\r\n                nmsPlayer.connection.send(new ClientboundCooldownPacket(entry.getKey(), entry.getValue().endTime - tickCount));\r\n            }\r\n        }\r\n        nmsPlayer.onUpdateAbilities();\r\n        nmsPlayer.server.getPlayerList().sendAllPlayerInfo(nmsPlayer);\r\n        nmsPlayer.server.getPlayerList().sendPlayerPermissionLevel(nmsPlayer);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/WorldHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.WorldHelper;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.objects.BiomeTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.mojang.datafixers.util.Pair;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.players.SleepStatus;\r\nimport net.minecraft.world.DifficultyInstance;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.storage.PrimaryLevelData;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\n\r\npublic class WorldHelperImpl implements WorldHelper {\r\n\r\n    @Override\r\n    public boolean isStatic(World world) {\r\n        return ((CraftWorld) world).getHandle().isClientSide;\r\n    }\r\n\r\n    @Override\r\n    public void setStatic(World world, boolean isStatic) {\r\n        ServerLevel worldServer = ((CraftWorld) world).getHandle();\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.Level.class, ReflectionMappingsInfo.Level_isClientSide, worldServer, isStatic);\r\n    }\r\n\r\n    @Override\r\n    public float getLocalDifficulty(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        DifficultyInstance scaler = ((CraftWorld) location.getWorld()).getHandle().getCurrentDifficultyAt(pos);\r\n        return scaler.getEffectiveDifficulty();\r\n    }\r\n\r\n    @Override\r\n    public Location getNearestBiomeLocation(Location start, BiomeTag biome) {\r\n        Pair<BlockPos, Holder<Biome>> result = ((CraftWorld) start.getWorld()).getHandle()\r\n                .findClosestBiome3d(b -> b.is(((BiomeNMSImpl) biome.getBiome()).biomeHolder.unwrapKey().get()), new BlockPos(start.getBlockX(), start.getBlockY(), start.getBlockZ()), 6400, 32, 64);\r\n        if (result == null || result.getFirst() == null) {\r\n            return null;\r\n        }\r\n        return new Location(start.getWorld(), result.getFirst().getX(), result.getFirst().getY(), result.getFirst().getZ());\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughSleeping(World world, int percentage) {\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, ((CraftWorld) world).getHandle());\r\n        return status.areEnoughSleeping(percentage);\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughDeepSleeping(World world, int percentage) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, level);\r\n        return status.areEnoughDeepSleeping(percentage, level.players());\r\n    }\r\n\r\n    @Override\r\n    public int getSkyDarken(World world) {\r\n        return ((CraftWorld) world).getHandle().getSkyDarken();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDay(World world) {\r\n        return ((CraftWorld) world).getHandle().isDay();\r\n    }\r\n\r\n    @Override\r\n    public boolean isNight(World world) {\r\n        return ((CraftWorld) world).getHandle().isNight();\r\n    }\r\n\r\n    @Override\r\n    public void setDayTime(World world, long time) {\r\n        ((CraftWorld) world).getHandle().setDayTime(time);\r\n    }\r\n\r\n    @Override\r\n    public void setGameTime(World world, long time) {\r\n        ((PrimaryLevelData) ((CraftWorld) world).getHandle().levelData).setGameTime(time);\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#wakeUpAllPlayers()\r\n    @Override\r\n    public void wakeUpAllPlayers(World world) {\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, nmsWorld);\r\n        status.removeAllSleepers();\r\n        nmsWorld.getPlayers(LivingEntity::isSleeping).forEach((player) -> player.stopSleepInBed(false, false));\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#resetWeatherCycle()\r\n    @Override\r\n    public void clearWeather(World world) {\r\n        PrimaryLevelData data = ((CraftWorld) world).getHandle().J;\r\n        data.setRaining(false);\r\n        if (!data.isRaining()) {\r\n            data.setRainTime(0);\r\n        }\r\n        data.setThundering(false);\r\n        if (!data.isThundering()) {\r\n            data.setThunderTime(0);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/BiomeNMSImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.random.WeightedRandomList;\r\nimport net.minecraft.world.entity.MobCategory;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.BiomeSpecialEffects;\r\nimport net.minecraft.world.level.biome.MobSpawnSettings;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftLocation;\r\nimport org.bukkit.craftbukkit.v1_19_R3.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Locale;\r\nimport java.util.Optional;\r\n\r\npublic class BiomeNMSImpl extends BiomeNMS {\r\n\r\n    public static final MethodHandle BIOME_CLIMATESETTINGS_CONSTRUCTOR = ReflectionHelper.getConstructor(Biome.ClimateSettings.class, boolean.class, float.class, Biome.TemperatureModifier.class, float.class);\r\n\r\n    public Holder<Biome> biomeHolder;\r\n    public ServerLevel world;\r\n\r\n    public BiomeNMSImpl(ServerLevel world, NamespacedKey key) {\r\n        super(world.getWorld(), key);\r\n        this.world = world;\r\n        biomeHolder = world.registryAccess().registryOrThrow(Registries.BIOME).getHolder(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(key))).orElse(null);\r\n    }\r\n\r\n    @Override\r\n    public DownfallType getDownfallTypeAt(Location location) {\r\n        Biome.Precipitation precipitation = biomeHolder.value().getPrecipitationAt(CraftLocation.toBlockPosition(location));\r\n        return switch (precipitation) {\r\n            case RAIN -> DownfallType.RAIN;\r\n            case SNOW -> DownfallType.SNOW;\r\n            case NONE -> DownfallType.NONE;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public float getHumidity() {\r\n        return biomeHolder.value().climateSettings.downfall();\r\n    }\r\n\r\n    @Override\r\n    public float getBaseTemperature() {\r\n        return biomeHolder.value().getBaseTemperature();\r\n    }\r\n\r\n    @Override\r\n    public float getTemperatureAt(Location location) {\r\n        return biomeHolder.value().getTemperature(CraftLocation.toBlockPosition(location));\r\n    }\r\n\r\n    @Override\r\n    public boolean hasDownfall() {\r\n        return biomeHolder.value().hasPrecipitation();\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getAmbientEntities() {\r\n        return getSpawnableEntities(MobCategory.AMBIENT);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getCreatureEntities() {\r\n        return getSpawnableEntities(MobCategory.CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getMonsterEntities() {\r\n        return getSpawnableEntities(MobCategory.MONSTER);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getWaterEntities() {\r\n        return getSpawnableEntities(MobCategory.WATER_CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public int getFoliageColor() {\r\n        // Check if the biome already has a default color\r\n        if (biomeHolder.value().getFoliageColor() != 0) {\r\n            return biomeHolder.value().getFoliageColor();\r\n        }\r\n        // Based on net.minecraft.world.level.biome.Biome#getFoliageColorFromTexture()\r\n        float temperature = clampColor(getBaseTemperature());\r\n        float humidity = clampColor(getHumidity());\r\n        // Based on net.minecraft.world.level.FoliageColor#get()\r\n        humidity *= temperature;\r\n        int humidityValue = (int)((1.0f - humidity) * 255.0f);\r\n        int temperatureValue = (int)((1.0f - temperature) * 255.0f);\r\n        int index = temperatureValue << 8 | humidityValue;\r\n        return index >= 65536 ? 4764952 : getColor(index / 256, index % 256).asRGB();\r\n    }\r\n\r\n    public void setClimate(boolean hasPrecipitation, float temperature, Biome.TemperatureModifier temperatureModifier, float downfall) {\r\n        try {\r\n            Object newClimate = BIOME_CLIMATESETTINGS_CONSTRUCTOR.invoke(hasPrecipitation, temperature, temperatureModifier, downfall);\r\n            ReflectionHelper.setFieldValue(Biome.class, ReflectionMappingsInfo.Biome_climateSettings, biomeHolder.value(), newClimate);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHumidity(float humidity) {\r\n        setClimate(hasDownfall(), getBaseTemperature(), getTemperatureModifier(), humidity);\r\n    }\r\n\r\n    @Override\r\n    public void setBaseTemperature(float baseTemperature) {\r\n        setClimate(hasDownfall(), baseTemperature, getTemperatureModifier(), getHumidity());\r\n    }\r\n\r\n    @Override\r\n    public void setHasDownfall(boolean hasDownfall) {\r\n        setClimate(hasDownfall, getBaseTemperature(), getTemperatureModifier(), getHumidity());\r\n    }\r\n\r\n    @Override\r\n    public void setFoliageColor(int color) {\r\n        ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_foliageColorOverride, biomeHolder.value().getSpecialEffects(), Optional.of(color));\r\n    }\r\n\r\n    @Override\r\n    public int getFogColor() {\r\n        return biomeHolder.value().getFogColor();\r\n    }\r\n\r\n    @Override\r\n    public void setFogColor(int color) {\r\n        ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_fogColor, biomeHolder.value().getSpecialEffects(), color);\r\n    }\r\n\r\n    @Override\r\n    public int getWaterFogColor() {\r\n        return biomeHolder.value().getWaterFogColor();\r\n    }\r\n\r\n    @Override\r\n    public void setWaterFogColor(int color) {\r\n        ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_waterFogColor, biomeHolder.value().getSpecialEffects(), color);\r\n    }\r\n\r\n    private List<EntityType> getSpawnableEntities(MobCategory creatureType) {\r\n        MobSpawnSettings mobs = biomeHolder.value().getMobSettings();\r\n        WeightedRandomList<MobSpawnSettings.SpawnerData> typeSettingList = mobs.getMobs(creatureType);\r\n        List<EntityType> entityTypes = new ArrayList<>();\r\n        if (typeSettingList == null) {\r\n            return entityTypes;\r\n        }\r\n        for (MobSpawnSettings.SpawnerData meta : typeSettingList.unwrap()) {\r\n            try {\r\n                String n = net.minecraft.world.entity.EntityType.getKey(meta.type).getPath();\r\n                EntityType et = EntityType.fromName(n);\r\n                if (et == null) {\r\n                    et = EntityType.valueOf(n.toUpperCase(Locale.ENGLISH));\r\n                }\r\n                entityTypes.add(et);\r\n            }\r\n            catch (Throwable e) {\r\n                // Ignore the error. Likely from invalid entity type name output.\r\n            }\r\n        }\r\n        return entityTypes;\r\n    }\r\n\r\n    @Override\r\n    public void setTo(Block block) {\r\n        if (((CraftWorld) block.getWorld()).getHandle() != this.world) {\r\n            NMSHandler.instance.getBiomeNMS(block.getWorld(), getKey()).setTo(block);\r\n            return;\r\n        }\r\n        // Based on CraftWorld source\r\n        BlockPos pos = new BlockPos(block.getX(), 0, block.getZ());\r\n        if (world.hasChunkAt(pos)) {\r\n            LevelChunk chunk = world.getChunkAt(pos);\r\n            if (chunk != null) {\r\n                chunk.setBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2, biomeHolder);\r\n                chunk.setUnsaved(true);\r\n            }\r\n        }\r\n    }\r\n\r\n    public Biome.TemperatureModifier getTemperatureModifier() {\r\n        return biomeHolder.value().climateSettings.temperatureModifier();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/ImprovedOfflinePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.v1_19.helpers.NBTAdapter;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.kyori.adventure.nbt.BinaryTagTypes;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.nbt.ListTag;\r\nimport net.minecraft.nbt.NbtIo;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeMap;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.attributes.DefaultAttributes;\r\nimport net.minecraft.world.inventory.PlayerEnderChestContainer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryPlayer;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryHolder;\r\n\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.FileOutputStream;\r\nimport java.util.UUID;\r\n\r\npublic class ImprovedOfflinePlayerImpl extends ImprovedOfflinePlayer {\r\n\r\n    public ImprovedOfflinePlayerImpl(UUID playeruuid) {\r\n        super(playeruuid);\r\n    }\r\n\r\n    public static class OfflinePlayerInventory extends net.minecraft.world.entity.player.Inventory {\r\n\r\n        public OfflinePlayerInventory(net.minecraft.world.entity.player.Player entityhuman) {\r\n            super(entityhuman);\r\n        }\r\n\r\n        @Override\r\n        public InventoryHolder getOwner() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static class OfflineCraftInventoryPlayer extends CraftInventoryPlayer {\r\n\r\n        public OfflineCraftInventoryPlayer(net.minecraft.world.entity.player.Inventory inventory) {\r\n            super(inventory);\r\n        }\r\n\r\n        @Override\r\n        public HumanEntity getHolder() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public org.bukkit.inventory.PlayerInventory getInventory() {\r\n        if (inventory == null) {\r\n            net.minecraft.world.entity.player.Inventory newInv = new OfflinePlayerInventory(null);\r\n            newInv.load(NBTAdapter.toNMS(this.compound.getList(\"Inventory\", BinaryTagTypes.COMPOUND)));\r\n            inventory = new OfflineCraftInventoryPlayer(newInv);\r\n        }\r\n        return inventory;\r\n    }\r\n\r\n    @Override\r\n    public void setInventory(org.bukkit.inventory.PlayerInventory inventory) {\r\n        CraftInventoryPlayer inv = (CraftInventoryPlayer) inventory;\r\n        this.compound = compound.put(\"Inventory\", NBTAdapter.toAPI(inv.getInventory().save(new ListTag())));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    public Inventory getEnderChest() {\r\n        if (enderchest == null) {\r\n            PlayerEnderChestContainer endchest = new PlayerEnderChestContainer(null);\r\n            endchest.fromTag(NBTAdapter.toNMS(this.compound.getList(\"EnderItems\", BinaryTagTypes.COMPOUND)));\r\n            enderchest = new CraftInventory(endchest);\r\n        }\r\n        return enderchest;\r\n    }\r\n\r\n    @Override\r\n    public void setEnderChest(Inventory inventory) {\r\n        this.compound = compound.put(\"EnderItems\", NBTAdapter.toAPI(((PlayerEnderChestContainer) ((CraftInventory) inventory).getInventory()).createTag()));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    public double getMaxHealth() {\r\n        AttributeInstance maxHealth = getAttributes().getInstance(Attributes.MAX_HEALTH);\r\n        return maxHealth == null ? Attributes.MAX_HEALTH.getDefaultValue() : maxHealth.getValue();\r\n    }\r\n\r\n    @Override\r\n    public void setMaxHealth(double input) {\r\n        AttributeMap attributes = getAttributes();\r\n        AttributeInstance maxHealth = attributes.getInstance(Attributes.MAX_HEALTH);\r\n        maxHealth.setBaseValue(input);\r\n        setAttributes(attributes);\r\n    }\r\n\r\n    private AttributeMap getAttributes() {\r\n        AttributeMap amb = new AttributeMap(DefaultAttributes.getSupplier(net.minecraft.world.entity.EntityType.PLAYER));\r\n        amb.load(NBTAdapter.toNMS(this.compound.getList(\"Attributes\", BinaryTagTypes.COMPOUND)));\r\n        return amb;\r\n    }\r\n\r\n    public void setAttributes(AttributeMap attributes) {\r\n        this.compound = compound.put(\"Attributes\", NBTAdapter.toAPI(attributes.save()));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    protected boolean loadPlayerData(UUID uuid) {\r\n        try {\r\n            this.player = uuid;\r\n            for (org.bukkit.World w : Bukkit.getWorlds()) {\r\n                this.file = new File(w.getWorldFolder(), \"playerdata\" + File.separator + this.player + \".dat\");\r\n                if (this.file.exists()) {\r\n                    this.compound = NBTAdapter.toAPI(NbtIo.readCompressed(new FileInputStream(this.file)));\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void saveInternal(CompoundBinaryTag compound) {\r\n        try {\r\n            NbtIo.writeCompressed(NBTAdapter.toNMS(compound), new FileOutputStream(this.file));\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/ProfileEditorImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_19.Handler;\r\nimport com.denizenscript.denizen.nms.v1_19.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.player.PlayerRespawnEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.EnumSet;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic class ProfileEditorImpl extends ProfileEditor {\r\n\r\n    @Override\r\n    protected void updatePlayer(final Player player, final boolean isSkinChanging) {\r\n        final ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        final UUID uuid = player.getUniqueId();\r\n        ClientboundPlayerInfoRemovePacket removePlayerInfoPacket = new ClientboundPlayerInfoRemovePacket(List.of(uuid));\r\n        ClientboundPlayerInfoUpdatePacket addPlayerInfoPacket = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(nmsPlayer));\r\n        for (Player otherPlayer : Bukkit.getServer().getOnlinePlayers()) {\r\n            PacketHelperImpl.send(otherPlayer, removePlayerInfoPacket);\r\n            PacketHelperImpl.send(otherPlayer, addPlayerInfoPacket);\r\n        }\r\n        for (Player otherPlayer : NMSHandler.entityHelper.getPlayersThatSee(player)) {\r\n            if (!otherPlayer.getUniqueId().equals(uuid)) {\r\n                PacketHelperImpl.forceRespawnPlayerEntity(player, otherPlayer);\r\n            }\r\n        }\r\n        if (isSkinChanging) {\r\n            ((CraftServer) Bukkit.getServer()).getHandle().respawn(nmsPlayer, (ServerLevel) nmsPlayer.level, true, player.getLocation(), false, PlayerRespawnEvent.RespawnReason.PLUGIN);\r\n        }\r\n        player.updateInventory();\r\n    }\r\n\r\n    public static boolean handleAlteredProfiles(ClientboundPlayerInfoUpdatePacket packet, DenizenNetworkManagerImpl manager) {\r\n        if (ProfileEditor.mirrorUUIDs.isEmpty() && !RenameCommand.hasAnyDynamicRenames() && fakeProfiles.isEmpty()) {\r\n            return true;\r\n        }\r\n        EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = packet.actions();\r\n        if (!actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) && !actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {\r\n            return true;\r\n        }\r\n        try {\r\n            boolean any = false;\r\n            for (ClientboundPlayerInfoUpdatePacket.Entry entry : packet.entries()) {\r\n                if (ProfileEditor.mirrorUUIDs.contains(entry.profileId()) || RenameCommand.customNames.containsKey(entry.profileId()) || fakeProfiles.containsKey(entry.profileId())) {\r\n                    any = true;\r\n                    break;\r\n                }\r\n            }\r\n            if (!any) {\r\n                return true;\r\n            }\r\n            GameProfile ownProfile = manager.player.getGameProfile();\r\n            for (ClientboundPlayerInfoUpdatePacket.Entry data : packet.entries()) {\r\n                if (!ProfileEditor.mirrorUUIDs.contains(data.profileId()) && !RenameCommand.customNames.containsKey(data.profileId()) && !fakeProfiles.containsKey(data.profileId())) {\r\n                    manager.oldManager.send(createInfoPacket(actions, List.of(data)));\r\n                }\r\n                else {\r\n                    String rename = RenameCommand.getCustomNameFor(data.profileId(), manager.player.getBukkitEntity(), false);\r\n                    GameProfile baseProfile = fakeProfiles.containsKey(data.profileId()) ? getGameProfile(fakeProfiles.get(data.profileId())) : data.profile();\r\n                    GameProfile patchedProfile = new GameProfile(baseProfile.getId(), rename != null ? (rename.length() > 16 ? rename.substring(0, 16) : rename) : baseProfile.getName());\r\n                    if (ProfileEditor.mirrorUUIDs.contains(data.profileId())) {\r\n                        patchedProfile.getProperties().putAll(ownProfile.getProperties());\r\n                    }\r\n                    else {\r\n                        // On Paper 1.19+, we use Paper's PlayerProfile API instead of this system\r\n                        patchedProfile.getProperties().putAll(Denizen.supportsPaper ? data.profile().getProperties() : baseProfile.getProperties());\r\n                    }\r\n                    String listRename = RenameCommand.getCustomNameFor(data.profileId(), manager.player.getBukkitEntity(), true);\r\n                    Component displayName = listRename != null ? Handler.componentToNMS(FormattedTextHelper.parse(listRename, ChatColor.WHITE)) : data.displayName();\r\n                    ClientboundPlayerInfoUpdatePacket.Entry newData = new ClientboundPlayerInfoUpdatePacket.Entry(data.profileId(), patchedProfile, data.listed(), data.latency(), data.gameMode(), displayName, data.chatSession());\r\n                    manager.oldManager.send(createInfoPacket(actions, List.of(newData)));\r\n                }\r\n            }\r\n            return false;\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n            return true;\r\n        }\r\n    }\r\n\r\n    public static final Field ClientboundPlayerInfoUpdatePacket_entries = ReflectionHelper.getFields(ClientboundPlayerInfoUpdatePacket.class).getFirstOfType(List.class);\r\n\r\n    public static ClientboundPlayerInfoUpdatePacket createInfoPacket(EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, List<ClientboundPlayerInfoUpdatePacket.Entry> entries) {\r\n        ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket = new ClientboundPlayerInfoUpdatePacket(actions, List.of());\r\n        try {\r\n            ClientboundPlayerInfoUpdatePacket_entries.set(playerInfoUpdatePacket, entries);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return playerInfoUpdatePacket;\r\n    }\r\n\r\n    private static GameProfile getGameProfile(PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = new GameProfile(playerProfile.getUniqueId(), playerProfile.getName());\r\n        if (playerProfile.hasTexture()) {\r\n            gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n        }\r\n        return gameProfile;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/SidebarImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl;\r\n\r\nimport com.denizenscript.denizen.nms.v1_19.Handler;\r\nimport com.denizenscript.denizen.nms.v1_19.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.MutableComponent;\r\nimport net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetScorePacket;\r\nimport net.minecraft.server.ServerScoreboard;\r\nimport net.minecraft.world.scores.Objective;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Scoreboard;\r\nimport net.minecraft.world.scores.criteria.ObjectiveCriteria;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.reflect.Constructor;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class SidebarImpl extends Sidebar {\r\n\r\n    public static Scoreboard dummyScoreboard = new Scoreboard();\r\n    public static ObjectiveCriteria dummyCriteria;\r\n\r\n    static {\r\n        try {\r\n            Constructor<ObjectiveCriteria> constructor = ObjectiveCriteria.class.getDeclaredConstructor(String.class);\r\n            constructor.setAccessible(true);\r\n            dummyCriteria = constructor.newInstance(\"dummy\");\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public Objective obj1;\r\n    public Objective obj2;\r\n\r\n    public SidebarImpl(Player player) {\r\n        super(player);\r\n        MutableComponent chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        this.obj1 = new Objective(dummyScoreboard, \"dummy_1\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER);\r\n        this.obj2 = new Objective(dummyScoreboard, \"dummy_2\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER);\r\n    }\r\n\r\n    @Override\r\n    protected void setDisplayName(String title) {\r\n        if (this.obj1 != null) {\r\n            MutableComponent chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n            this.obj1.setDisplayName(chatComponentTitle);\r\n            this.obj2.setDisplayName(chatComponentTitle);\r\n        }\r\n    }\r\n\r\n    public List<PlayerTeam> generatedTeams = new ArrayList<>();\r\n\r\n    @Override\r\n    public void sendUpdate() {\r\n        List<PlayerTeam> oldTeams = generatedTeams;\r\n        generatedTeams = new ArrayList<>();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj1, 0));\r\n        for (int i = 0; i < this.lines.length; i++) {\r\n            String line = this.lines[i];\r\n            if (line == null) {\r\n                break;\r\n            }\r\n            String lineId = Utilities.generateRandomColors(8);\r\n            PlayerTeam team = new PlayerTeam(dummyScoreboard, lineId);\r\n            team.getPlayers().add(lineId);\r\n            team.setPlayerPrefix(Handler.componentToNMS(FormattedTextHelper.parse(line, ChatColor.WHITE)));\r\n            generatedTeams.add(team);\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n            PacketHelperImpl.send(player, new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, obj1.getName(), lineId, this.scores[i]));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundSetDisplayObjectivePacket(1, this.obj1));\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n        Objective temp = this.obj2;\r\n        this.obj2 = this.obj1;\r\n        this.obj1 = temp;\r\n        for (PlayerTeam team : oldTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        for (PlayerTeam team : generatedTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        generatedTeams.clear();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/blocks/BlockLightImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.blocks;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;\r\nimport net.minecraft.server.level.ServerChunkCache;\r\nimport net.minecraft.server.level.ThreadedLevelLightEngine;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.LightLayer;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.ChunkStatus;\r\nimport net.minecraft.world.level.chunk.DataLayer;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.lighting.LayerLightEventListener;\r\nimport net.minecraft.world.level.lighting.LevelLightEngine;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.BitSet;\r\nimport java.util.List;\r\n\r\npublic class BlockLightImpl extends BlockLight {\r\n\r\n    public static final Class LIGHTENGINETHREADED_TASKTYPE = Arrays.stream(ThreadedLevelLightEngine.class.getDeclaredClasses()).filter(c -> c.isEnum()).findFirst().get(); // TaskType\r\n    public static final Object LIGHTENGINETHREADED_TASKTYPE_PRE;\r\n\r\n    static {\r\n        Object preObj = null;\r\n        try {\r\n            preObj = ReflectionHelper.getFields(LIGHTENGINETHREADED_TASKTYPE).get(ReflectionMappingsInfo.ThreadedLevelLightEngineTaskType_PRE_UPDATE).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n        LIGHTENGINETHREADED_TASKTYPE_PRE = preObj;\r\n    }\r\n\r\n    public static final MethodHandle LIGHTENGINETHREADED_QUEUERUNNABLE = ReflectionHelper.getMethodHandle(ThreadedLevelLightEngine.class, ReflectionMappingsInfo.ThreadedLevelLightEngine_addTask_method,\r\n            int.class, int.class,  LIGHTENGINETHREADED_TASKTYPE, Runnable.class);\r\n\r\n    public static void enqueueRunnable(LevelChunk chunk, Runnable runnable) {\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        if (lightEngine instanceof ThreadedLevelLightEngine) {\r\n            ChunkPos coord = chunk.getPos();\r\n            try {\r\n                LIGHTENGINETHREADED_QUEUERUNNABLE.invoke(lightEngine, coord.x, coord.z, LIGHTENGINETHREADED_TASKTYPE_PRE, runnable);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        else {\r\n            runnable.run();\r\n        }\r\n    }\r\n\r\n    private BlockLightImpl(Location location, long ticks) {\r\n        super(location, ticks);\r\n    }\r\n\r\n    public static BlockLight createLight(Location location, int lightLevel, long ticks) {\r\n        location = location.getBlock().getLocation();\r\n        BlockLight blockLight;\r\n        if (lightsByLocation.containsKey(location)) {\r\n            blockLight = lightsByLocation.get(location);\r\n            if (blockLight.removeTask != null) {\r\n                blockLight.removeTask.cancel();\r\n                blockLight.removeTask = null;\r\n            }\r\n            if (blockLight.updateTask != null) {\r\n                blockLight.updateTask.cancel();\r\n                blockLight.updateTask = null;\r\n            }\r\n            blockLight.removeLater(ticks);\r\n        }\r\n        else {\r\n            blockLight = new BlockLightImpl(location, ticks);\r\n            lightsByLocation.put(location, blockLight);\r\n            if (!lightsByChunk.containsKey(blockLight.chunkCoord)) {\r\n                lightsByChunk.put(blockLight.chunkCoord, new ArrayList<>());\r\n            }\r\n            lightsByChunk.get(blockLight.chunkCoord).add(blockLight);\r\n        }\r\n        blockLight.intendedLevel = lightLevel;\r\n        blockLight.update(lightLevel, true);\r\n        return blockLight;\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundBlockUpdatePacket packet, Level world) {\r\n        try {\r\n            BlockPos pos = packet.getPos();\r\n            int chunkX = pos.getX() >> 4;\r\n            int chunkZ = pos.getZ() >> 4;\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                LevelChunk chunk = world.getChunk(chunkX, chunkZ);\r\n                boolean any = false;\r\n                for (Vector vec : RELATIVE_CHUNKS) {\r\n                    ChunkAccess other = world.getChunk(chunkX + vec.getBlockX(), chunkZ + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n                    if (other instanceof LevelChunk) {\r\n                        List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) other)));\r\n                        if (lights != null) {\r\n                            any = true;\r\n                            for (BlockLight light : lights) {\r\n                                Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates(chunk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundLightUpdatePacket packet, Level world) {\r\n        if (doNotCheck) {\r\n            return;\r\n        }\r\n        try {\r\n            int cX = packet.getX();\r\n            int cZ = packet.getZ();\r\n            BitSet bitMask = packet.getLightData().getBlockYMask();\r\n            List<byte[]> blockData = packet.getLightData().getBlockUpdates();\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                ChunkAccess chk = world.getChunk(cX, cZ, ChunkStatus.FULL, false);\r\n                if (!(chk instanceof LevelChunk)) {\r\n                    return;\r\n                }\r\n                List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) chk)));\r\n                if (lights == null) {\r\n                    return;\r\n                }\r\n                boolean any = false;\r\n                for (BlockLight light : lights) {\r\n                    if (((BlockLightImpl) light).checkIfChangedBy(bitMask, blockData)) {\r\n                        Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                        any = true;\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates((LevelChunk) chk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static boolean doNotCheck = false;\r\n\r\n    public boolean checkIfChangedBy(BitSet bitmask, List<byte[]> data) {\r\n        Location blockLoc = block.getLocation();\r\n        int layer = (blockLoc.getBlockY() >> 4) + 1;\r\n        if (!bitmask.get(layer)) {\r\n            return false;\r\n        }\r\n        int found = 0;\r\n        for (int i = 0; i < 16; i++) {\r\n            if (bitmask.get(i)) {\r\n                if (i == layer) {\r\n                    byte[] blocks = data.get(found);\r\n                    DataLayer arr = new DataLayer(blocks);\r\n                    int x = blockLoc.getBlockX() - (chunkCoord.x << 4);\r\n                    int y = blockLoc.getBlockY() % 16;\r\n                    int z = blockLoc.getBlockZ() - (chunkCoord.z << 4);\r\n                    int level = arr.get(x, y, z);\r\n                    return intendedLevel != level;\r\n                }\r\n                found++;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static void runResetFor(final LevelChunk chunk, final BlockPos pos) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            engineBlock.checkBlock(pos);\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    public static void runSetFor(final LevelChunk chunk, final BlockPos pos, final int level) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            engineBlock.onBlockEmissionIncrease(pos, level);\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    @Override\r\n    public void reset(boolean updateChunk) {\r\n        runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition());\r\n        if (updateChunk) {\r\n            // This runnable cast is necessary despite what your IDE may claim\r\n            updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(int lightLevel, boolean updateChunk) {\r\n        runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition());\r\n        updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), () -> {\r\n            updateTask = null;\r\n            runSetFor((LevelChunk) ((CraftChunk) chunk).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition(), lightLevel);\r\n            if (updateChunk) {\r\n                // This runnable cast is necessary despite what your IDE may claim\r\n                updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n            }\r\n        }, 1);\r\n    }\r\n\r\n    public static final Vector[] RELATIVE_CHUNKS = new Vector[] {\r\n            new Vector(0, 0, 0),\r\n            new Vector(-1, 0, 0), new Vector(1, 0, 0), new Vector(0, 0, -1), new Vector(0, 0, 1),\r\n            new Vector(-1, 0, -1), new Vector(-1, 0, 1), new Vector(1, 0, -1), new Vector(1, 0, 1)\r\n    };\r\n\r\n    public void sendNearbyChunkUpdates() {\r\n        sendNearbyChunkUpdates((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL));\r\n    }\r\n\r\n    public static void sendNearbyChunkUpdates(LevelChunk chunk) {\r\n        ChunkPos pos = chunk.getPos();\r\n        for (Vector vec : RELATIVE_CHUNKS) {\r\n            ChunkAccess other = chunk.getLevel().getChunk(pos.x + vec.getBlockX(), pos.z + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n            if (other instanceof LevelChunk) {\r\n                sendSingleChunkUpdate((LevelChunk) other);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void sendSingleChunkUpdate(LevelChunk chunk) {\r\n        doNotCheck = true;\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        ChunkPos pos = chunk.getPos();\r\n        ClientboundLightUpdatePacket packet = new ClientboundLightUpdatePacket(pos, lightEngine, null, null, true); // TODO: 1.16: should 'trust edges' be true here?\r\n        ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(pos, false).forEach((player) -> {\r\n            player.connection.send(packet);\r\n        });\r\n        doNotCheck = false;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/entities/CraftFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport net.minecraft.world.entity.projectile.AbstractArrow;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftArrow;\r\n\r\npublic class CraftFakeArrowImpl extends CraftArrow implements FakeArrow {\r\n\r\n    public CraftFakeArrowImpl(CraftServer craftServer, AbstractArrow entityArrow) {\r\n        super(craftServer, entityArrow);\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        if (getPassenger() != null) {\r\n            return;\r\n        }\r\n        super.remove();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_ARROW\";\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/entities/CraftFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;\r\nimport org.bukkit.metadata.FixedMetadataValue;\r\nimport org.bukkit.metadata.MetadataValue;\r\nimport org.bukkit.plugin.Plugin;\r\n\r\nimport java.util.List;\r\nimport java.util.Set;\r\n\r\npublic class CraftFakePlayerImpl extends CraftPlayer implements FakePlayer {\r\n\r\n    private final CraftServer server;\r\n    public String fullName;\r\n\r\n    public CraftFakePlayerImpl(CraftServer server, EntityFakePlayerImpl entity) {\r\n        super(server, entity);\r\n        this.server = server;\r\n        setMetadata(\"NPC\", new FixedMetadataValue(NMSHandler.getJavaPlugin(), true));\r\n    }\r\n\r\n    @Override\r\n    public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {\r\n        this.server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue);\r\n    }\r\n\r\n    @Override\r\n    public List<MetadataValue> getMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().getMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().hasMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public void removeMetadata(String metadataKey, Plugin owningPlugin) {\r\n        this.server.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin);\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_PLAYER\";\r\n    }\r\n\r\n    @Override\r\n    public String getFullName() {\r\n        return fullName;\r\n    }\r\n\r\n    @Override\r\n    public Block getTargetBlock(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public List<Block> getLastTwoTargetBlocks(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/entities/CraftItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\n\r\nimport java.util.UUID;\r\n\r\npublic class CraftItemProjectileImpl extends CraftEntity implements ItemProjectile {\r\n\r\n    private boolean doesBounce;\r\n\r\n    public CraftItemProjectileImpl(CraftServer server, EntityItemProjectileImpl entity) {\r\n        super(server, entity);\r\n    }\r\n\r\n    @Override\r\n    public EntityItemProjectileImpl getHandle() {\r\n        return (EntityItemProjectileImpl) super.getHandle();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return getType().name();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack getItemStack() {\r\n        return CraftItemStack.asBukkitCopy(getHandle().getItemStack());\r\n    }\r\n\r\n    @Override\r\n    public void setItemStack(ItemStack itemStack) {\r\n        getHandle().setItemStack(CraftItemStack.asNMSCopy(itemStack));\r\n    }\r\n\r\n    @Override\r\n    public int getPickupDelay() {\r\n        return 0;\r\n    }\r\n\r\n    @Override\r\n    public void setPickupDelay(int i) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public void setUnlimitedLifetime(boolean b) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnlimitedLifetime() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void setOwner(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getOwner() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setThrower(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getThrower() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ProjectileSource getShooter() {\r\n        return getHandle().projectileSource;\r\n    }\r\n\r\n    @Override\r\n    public void setShooter(ProjectileSource projectileSource) {\r\n        if (projectileSource instanceof CraftEntity) {\r\n            getHandle().setOwner(((CraftEntity) projectileSource).getHandle());\r\n        }\r\n        else {\r\n            getHandle().projectileSource = projectileSource;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean doesBounce() {\r\n        return doesBounce;\r\n    }\r\n\r\n    @Override\r\n    public void setBounce(boolean doesBounce) {\r\n        this.doesBounce = doesBounce;\r\n    }\r\n\r\n    @Override\r\n    public EntityType getType() {\r\n        return EntityType.DROPPED_ITEM;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/entities/EntityFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_19.Handler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.world.entity.projectile.SpectralArrow;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakeArrowImpl extends SpectralArrow {\r\n\r\n    public EntityFakeArrowImpl(CraftWorld craftWorld, Location location) {\r\n        super(net.minecraft.world.entity.EntityType.SPECTRAL_ARROW, craftWorld.getHandle());\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakeArrowImpl((CraftServer) Bukkit.getServer(), this));\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        level.addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    protected ItemStack getPickupItem() {\r\n        return new ItemStack(Items.ARROW);\r\n    }\r\n\r\n    @Override\r\n    public CraftFakeArrowImpl getBukkitEntity() {\r\n        return (CraftFakeArrowImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/entities/EntityFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_19.Handler;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.network.fakes.FakeNetworkManagerImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.network.fakes.FakePlayerConnectionImpl;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.player.Player;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftServer;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakePlayerImpl extends ServerPlayer {\r\n\r\n    public EntityFakePlayerImpl(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile, boolean doAdd) {\r\n        super(minecraftserver, worldserver, gameprofile);\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakePlayerImpl((CraftServer) Bukkit.getServer(), this));\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        Connection networkManager = new FakeNetworkManagerImpl(PacketFlow.CLIENTBOUND);\r\n        connection = new FakePlayerConnectionImpl(minecraftserver, networkManager, this);\r\n        networkManager.setListener(connection);\r\n        getEntityData().set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) 127);\r\n        if (doAdd) {\r\n            worldserver.addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public CraftFakePlayerImpl getBukkitEntity() {\r\n        return (CraftFakePlayerImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/entities/EntityItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.base.Preconditions;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.projectile.ThrowableProjectile;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport org.bukkit.Location;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\n\r\npublic class EntityItemProjectileImpl extends ThrowableProjectile {\r\n\r\n    public static MethodHandle setBukkitEntityMethod = ReflectionHelper.getFinalSetter(Entity.class, \"bukkitEntity\");\r\n\r\n    public static final EntityDataAccessor<ItemStack> ITEM;\r\n\r\n    static {\r\n        EntityDataAccessor<ItemStack> watcher = null;\r\n        try {\r\n            watcher = (EntityDataAccessor<ItemStack>) ReflectionHelper.getFields(ItemEntity.class).get(ReflectionMappingsInfo.ItemEntity_DATA_ITEM).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        ITEM = watcher;\r\n    }\r\n\r\n    public EntityItemProjectileImpl(Level world, Location location, ItemStack item) {\r\n        super((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.ITEM, world);\r\n        try {\r\n            setBukkitEntityMethod.invoke(this, new CraftItemProjectileImpl(((ServerLevel) world).getServer().server, this));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        setItemStack(item);\r\n    }\r\n\r\n    @Override\r\n    protected void defineSynchedData() {\r\n        this.getEntityData().define(ITEM, ItemStack.EMPTY);\r\n    }\r\n\r\n    public ItemStack getItemStack() {\r\n        return this.getEntityData().get(ITEM);\r\n    }\r\n\r\n    public void setItemStack(ItemStack itemstack) {\r\n        Preconditions.checkArgument(!itemstack.isEmpty(), \"Cannot drop air\");\r\n        this.getEntityData().set(ITEM, itemstack);\r\n        this.getEntityData().markDirty(ITEM);\r\n    }\r\n\r\n    @Override\r\n    protected void onHitBlock(BlockHitResult movingobjectpositionblock) {\r\n        super.onHitBlock(movingobjectpositionblock);\r\n        remove(RemovalReason.KILLED);\r\n    }\r\n\r\n    @Override\r\n    public void onSyncedDataUpdated(EntityDataAccessor<?> datawatcherobject) {\r\n        super.onSyncedDataUpdated(datawatcherobject);\r\n        if (ITEM.equals(datawatcherobject)) {\r\n            this.getItemStack().setEntityRepresentation(this);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean save(net.minecraft.nbt.CompoundTag nbttagcompound) {\r\n        if (!this.getItemStack().isEmpty()) {\r\n            nbttagcompound.put(\"Item\", this.getItemStack().save(new net.minecraft.nbt.CompoundTag()));\r\n        }\r\n        super.save(nbttagcompound);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public void load(net.minecraft.nbt.CompoundTag nbttagcompound) {\r\n        net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttagcompound.getCompound(\"Item\");\r\n        this.setItemStack(ItemStack.of(nbttagcompound1));\r\n        if (this.getItemStack().isEmpty()) {\r\n            this.remove(RemovalReason.KILLED);\r\n        }\r\n        super.load(nbttagcompound);\r\n    }\r\n\r\n    @Override\r\n    public CraftItemProjectileImpl getBukkitEntity() {\r\n        return (CraftItemProjectileImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/network/fakes/FakeChannelImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.network.fakes;\r\n\r\nimport io.netty.channel.*;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeChannelImpl extends AbstractChannel {\r\n\r\n    private final ChannelConfig config = new DefaultChannelConfig(this);\r\n\r\n    protected FakeChannelImpl(Channel parent) {\r\n        super(parent);\r\n    }\r\n\r\n    @Override\r\n    public ChannelConfig config() {\r\n        config.setAutoRead(true);\r\n        return config;\r\n    }\r\n\r\n    @Override\r\n    protected AbstractUnsafe newUnsafe() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected boolean isCompatible(EventLoop eventLoop) {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress localAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress remoteAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected void doBind(SocketAddress socketAddress) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doDisconnect() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doClose() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doBeginRead() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doWrite(ChannelOutboundBuffer channelOutboundBuffer) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean isOpen() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActive() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public ChannelMetadata metadata() {\r\n        return new ChannelMetadata(true);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/network/fakes/FakeNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeNetworkManagerImpl extends Connection {\r\n\r\n    public FakeNetworkManagerImpl(PacketFlow enumprotocoldirection) {\r\n        super(enumprotocoldirection);\r\n        channel = new FakeChannelImpl(null);\r\n        address = new SocketAddress() {\r\n        };\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/network/fakes/FakePlayerConnectionImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\n\r\npublic class FakePlayerConnectionImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public FakePlayerConnectionImpl(MinecraftServer minecraftserver, Connection networkmanager, ServerPlayer entityplayer) {\r\n        super(minecraftserver, networkmanager, entityplayer);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet packet) {\r\n        // Do nothing\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/network/handlers/AbstractListenerPlayInImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerSendPacketScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.network.PacketSendListener;\r\nimport net.minecraft.network.chat.ChatType;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.chat.PlayerChatMessage;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.world.entity.RelativeMovement;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.net.SocketAddress;\r\nimport java.util.Set;\r\n\r\npublic class AbstractListenerPlayInImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public final ServerGamePacketListenerImpl oldListener;\r\n    public final DenizenNetworkManagerImpl denizenNetworkManager;\r\n\r\n    public AbstractListenerPlayInImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer, ServerGamePacketListenerImpl oldListener) {\r\n        super(MinecraftServer.getServer(), networkManager, entityPlayer);\r\n        this.oldListener = oldListener;\r\n        this.denizenNetworkManager = networkManager;\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        oldListener.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(String s) {\r\n        oldListener.disconnect(s);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1) {\r\n        oldListener.teleport(d0, d1, d2, f, f1);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) {\r\n        oldListener.teleport(d0, d1, d2, f, f1, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set) {\r\n        oldListener.teleport(d0, d1, d2, f, f1, set);\r\n    }\r\n\r\n    @Override\r\n    public boolean teleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set, PlayerTeleportEvent.TeleportCause cause) {\r\n        return oldListener.teleport(d0, d1, d2, f, f1, set, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Location dest) {\r\n        oldListener.teleport(dest);\r\n    }\r\n\r\n    @Override\r\n    public CraftPlayer getCraftPlayer() {\r\n        return oldListener.getCraftPlayer();\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldListener.tick();\r\n    }\r\n\r\n    @Override\r\n    public void resetPosition() {\r\n        oldListener.resetPosition();\r\n    }\r\n\r\n    @Override\r\n    public boolean isAcceptingMessages() {\r\n        return oldListener.isAcceptingMessages();\r\n    }\r\n\r\n    @Override\r\n    public void onDisconnect(Component ichatbasecomponent) {\r\n        oldListener.onDisconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void ackBlockChangesUpTo(int i) {\r\n        oldListener.ackBlockChangesUpTo(i);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        oldListener.send(packet);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, PacketSendListener listener) {\r\n        oldListener.send(packet, listener);\r\n    }\r\n\r\n    public static Field AWAITING_POS_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_awaitingPositionFromClient, Vec3.class);\r\n    public static Field AWAITING_TELEPORT_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_awaitingTeleport, int.class);\r\n\r\n    public void debugPacketOutput(Packet<ServerGamePacketListener> packet) {\r\n        try {\r\n            if (packet instanceof ServerboundMovePlayerPacket) {\r\n                ServerboundMovePlayerPacket movePacket = (ServerboundMovePlayerPacket) packet;\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet ServerboundMovePlayerPacket sent from \" + player.getScoreboardName() + \" with XYZ=\"\r\n                        + movePacket.x + \", \" + movePacket.y + \", \" + movePacket.z + \", yRot=\" + movePacket.yRot + \", xRot=\" + movePacket.xRot\r\n                        + \", onGround=\" + movePacket.isOnGround() + \", hasPos=\" + movePacket.hasPos + \", hasRot=\" + movePacket.hasRot);\r\n            }\r\n            else if (packet instanceof ServerboundAcceptTeleportationPacket) {\r\n                Vec3 awaitPos = (Vec3) AWAITING_POS_FIELD.get(oldListener);\r\n                int awaitTeleportId = AWAITING_TELEPORT_FIELD.getInt(oldListener);\r\n                ServerboundAcceptTeleportationPacket acceptPacket = (ServerboundAcceptTeleportationPacket) packet;\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet ServerboundAcceptTeleportationPacket sent from \" + player.getScoreboardName()\r\n                        + \" with ID=\" + acceptPacket.getId() + \", awaitingTeleport=\" + awaitTeleportId + \", awaitPos=\" + awaitPos);\r\n            }\r\n            else {\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent from \" + player.getScoreboardName());\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public boolean handlePacketIn(Packet<ServerGamePacketListener> packet) {\r\n        denizenNetworkManager.packetsReceived++;\r\n        if (NMSHandler.debugPackets) {\r\n            debugPacketOutput(packet);\r\n        }\r\n        if (PlayerSendPacketScriptEvent.instance.eventData.isEnabled) {\r\n            if (PlayerSendPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) {\r\n                if (NMSHandler.debugPackets) {\r\n                    DenizenNetworkManagerImpl.doPacketOutput(\"Denied packet-in \" + packet.getClass().getCanonicalName() + \" from \" + player.getScoreboardName() + \" due to event\");\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void handleChatAck(ServerboundChatAckPacket serverboundchatackpacket) {\r\n        oldListener.handleChatAck(serverboundchatackpacket);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(ServerboundPlayerInputPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerInput(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleMoveVehicle(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleAcceptTeleportPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookSeenRecipePacket(ServerboundRecipeBookSeenRecipePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRecipeBookSeenRecipePacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRecipeBookChangeSettingsPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSeenAdvancements(ServerboundSeenAdvancementsPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSeenAdvancements(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCustomCommandSuggestions(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandBlock(ServerboundSetCommandBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCommandBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandMinecart(ServerboundSetCommandMinecartPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCommandMinecart(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePickItem(ServerboundPickItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePickItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRenameItem(ServerboundRenameItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRenameItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetBeaconPacket(ServerboundSetBeaconPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetBeaconPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetStructureBlock(ServerboundSetStructureBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetStructureBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetJigsawBlock(ServerboundSetJigsawBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetJigsawBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleJigsawGenerate(ServerboundJigsawGeneratePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleJigsawGenerate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSelectTrade(ServerboundSelectTradePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSelectTrade(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEditBook(ServerboundEditBookPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleEditBook(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEntityTagQuery(ServerboundEntityTagQuery packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleEntityTagQuery(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleBlockEntityTagQuery(ServerboundBlockEntityTagQuery packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleBlockEntityTagQuery(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleMovePlayer(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItemOn(ServerboundUseItemOnPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleUseItemOn(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleTeleportToEntityPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePaddleBoat(ServerboundPaddleBoatPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePaddleBoat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePong(ServerboundPongPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePong(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChat(ServerboundChatPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChatCommand(ServerboundChatCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChatCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void chat(String s, PlayerChatMessage original, boolean async) {\r\n        oldListener.chat(s, original, async);\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void addPendingMessage(PlayerChatMessage playerchatmessage) {\r\n        oldListener.addPendingMessage(playerchatmessage);\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerChatMessage(PlayerChatMessage playerchatmessage, ChatType.Bound chatmessagetype_a) {\r\n        oldListener.sendPlayerChatMessage(playerchatmessage, chatmessagetype_a);\r\n    }\r\n\r\n    @Override\r\n    public void sendDisguisedChatMessage(Component ichatbasecomponent, ChatType.Bound chatmessagetype_a) {\r\n        oldListener.sendDisguisedChatMessage(ichatbasecomponent, chatmessagetype_a);\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRemoteAddress() {\r\n        return oldListener.getRemoteAddress();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRawAddress() {\r\n        return oldListener.getRawAddress();\r\n    }\r\n\r\n    @Override\r\n    public void handleInteract(ServerboundInteractPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleInteract(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientCommand(ServerboundClientCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleClientCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClose(ServerboundContainerClosePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerClose(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlaceRecipe(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerButtonClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCreativeModeSlot(ServerboundSetCreativeModeSlotPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCreativeModeSlot(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleKeepAlive(ServerboundKeepAlivePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleKeepAlive(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerAbilities(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientInformation(ServerboundClientInformationPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleClientInformation(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChangeDifficulty(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleLockDifficulty(ServerboundLockDifficultyPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleLockDifficulty(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChatSessionUpdate(ServerboundChatSessionUpdatePacket serverboundchatsessionupdatepacket) {\r\n        oldListener.handleChatSessionUpdate(serverboundchatsessionupdatepacket);\r\n    }\r\n\r\n    @Override\r\n    public ServerPlayer getPlayer() {\r\n        return oldListener.getPlayer();\r\n    }\r\n\r\n    @Override\r\n    public boolean shouldPropagateHandlingExceptions() {\r\n        return oldListener.shouldPropagateHandlingExceptions();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/network/handlers/DenizenNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.*;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.v1_19.Handler;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_19.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.blocks.BlockLightImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.entities.EntityFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.network.packets.PacketOutChatImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.*;\r\nimport com.denizenscript.denizen.scripts.commands.player.DisguiseCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizen.utilities.entity.HideEntitiesHelper;\r\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\r\nimport com.denizenscript.denizen.utilities.packets.HideParticles;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptCodeGen;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.base.Joiner;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.mojang.datafixers.util.Pair;\r\nimport io.netty.buffer.Unpooled;\r\nimport io.netty.channel.ChannelHandlerContext;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.NonNullList;\r\nimport net.minecraft.core.SectionPos;\r\nimport net.minecraft.core.particles.ParticleOptions;\r\nimport net.minecraft.network.*;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.network.ServerPlayerConnection;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.GameType;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Particle;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftParticle;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport javax.crypto.Cipher;\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.net.SocketAddress;\r\nimport java.util.*;\r\nimport java.util.stream.Collectors;\r\n\r\npublic class DenizenNetworkManagerImpl extends Connection {\r\n\r\n    public static FriendlyByteBuf copyPacket(Packet<?> original) {\r\n        try {\r\n            FriendlyByteBuf copier = new FriendlyByteBuf(Unpooled.buffer());\r\n            original.write(copier);\r\n            return copier;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public final Connection oldManager;\r\n    public final DenizenPacketListenerImpl packetListener;\r\n    public final ServerPlayer player;\r\n    public int packetsSent, packetsReceived;\r\n\r\n    public DenizenNetworkManagerImpl(ServerPlayer entityPlayer, Connection oldManager) {\r\n        super(getProtocolDirection(oldManager));\r\n        this.oldManager = oldManager;\r\n        this.channel = oldManager.channel;\r\n        this.packetListener = (DenizenPacketListenerImpl) NetworkInterceptCodeGen.generateAppropriateInterceptor(this, entityPlayer, DenizenPacketListenerImpl.class, AbstractListenerPlayInImpl.class, ServerGamePacketListenerImpl.class);\r\n        oldManager.setListener(packetListener);\r\n        this.player = this.packetListener.player;\r\n    }\r\n\r\n    public static Connection getConnection(ServerPlayer player) {\r\n        try {\r\n            return (Connection) ServerGamePacketListener_ConnectionField.get(player.connection);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            throw new RuntimeException(\"Failed to get connection from player due to reflection error\", ex);\r\n        }\r\n    }\r\n\r\n    public static DenizenNetworkManagerImpl getNetworkManager(ServerPlayer player) {\r\n        return (DenizenNetworkManagerImpl) getConnection(player);\r\n    }\r\n\r\n    public static DenizenNetworkManagerImpl getNetworkManager(Player player) {\r\n        return getNetworkManager(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    public static void setNetworkManager(Player player) {\r\n        ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerGamePacketListenerImpl playerConnection = entityPlayer.connection;\r\n        setNetworkManager(playerConnection, new DenizenNetworkManagerImpl(entityPlayer, getConnection(entityPlayer)));\r\n    }\r\n\r\n    public static void enableNetworkManager() {\r\n        for (World w : Bukkit.getWorlds()) {\r\n            for (ChunkMap.TrackedEntity tracker : ((CraftWorld) w).getHandle().getChunkSource().chunkMap.entityMap.values()) {\r\n                ArrayList<ServerPlayerConnection> connections = new ArrayList<>(tracker.seenBy);\r\n                tracker.seenBy.clear();\r\n                for (ServerPlayerConnection connection : connections) {\r\n                    tracker.seenBy.add(connection.getPlayer().connection);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int hashCode() {\r\n        return oldManager.hashCode();\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object c2) {\r\n        return oldManager.equals(c2);\r\n    }\r\n\r\n    @Override\r\n    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelRegistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelUnregistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception {\r\n        oldManager.channelActive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public void setProtocol(ConnectionProtocol enumprotocol) {\r\n        oldManager.setProtocol(enumprotocol);\r\n    }\r\n\r\n    @Override\r\n    public void channelInactive(ChannelHandlerContext channelhandlercontext) {\r\n        oldManager.channelInactive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public boolean isSharable() {\r\n        return oldManager.isSharable();\r\n    }\r\n\r\n    @Override\r\n    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerAdded(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerRemoved(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) {\r\n        oldManager.exceptionCaught(channelhandlercontext, throwable);\r\n    }\r\n\r\n    @Override\r\n    protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) {\r\n        if (oldManager.channel.isOpen()) {\r\n            try {\r\n                packet.handle(this.packetListener);\r\n            }\r\n            catch (Exception e) {\r\n                // Do nothing\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setListener(PacketListener packetlistener) {\r\n        oldManager.setListener(packetlistener);\r\n    }\r\n\r\n    public static Field ENTITY_ID_PACKVELENT = ReflectionHelper.getFields(ClientboundSetEntityMotionPacket.class).get(ReflectionMappingsInfo.ClientboundSetEntityMotionPacket_id, int.class);\r\n    public static Field ENTITY_ID_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_id, int.class);\r\n    public static Field POS_X_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_x, double.class);\r\n    public static Field POS_Y_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_y, double.class);\r\n    public static Field POS_Z_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_z, double.class);\r\n    public static Field YAW_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_yRot, byte.class);\r\n    public static Field PITCH_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_xRot, byte.class);\r\n    public static Field POS_X_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_xa, short.class);\r\n    public static Field POS_Y_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_ya, short.class);\r\n    public static Field POS_Z_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_za, short.class);\r\n    public static Field YAW_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_yRot, byte.class);\r\n    public static Field PITCH_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_xRot, byte.class);\r\n    public static Field SECTIONPOS_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_sectionPos, SectionPos.class);\r\n    public static Field OFFSETARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_positions, short[].class);\r\n    public static Field BLOCKARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_states, BlockState[].class);\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        send(packet, null);\r\n    }\r\n\r\n    public static void doPacketOutput(String text) {\r\n        if (!NMSHandler.debugPackets) {\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPacketFilter == null || NMSHandler.debugPacketFilter.trim().isEmpty()\r\n                || CoreUtilities.toLowerCase(text).contains(NMSHandler.debugPacketFilter)) {\r\n            Debug.log(text);\r\n        }\r\n    }\r\n\r\n    public void debugOutputPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundSetEntityDataPacket) {\r\n            StringBuilder output = new StringBuilder(128);\r\n            output.append(\"Packet: ClientboundSetEntityDataPacket sent to \").append(player.getScoreboardName()).append(\" for entity ID: \").append(((ClientboundSetEntityDataPacket) packet).id()).append(\": \");\r\n            List<SynchedEntityData.DataValue<?>> list = ((ClientboundSetEntityDataPacket) packet).packedItems();\r\n            if (list == null) {\r\n                output.append(\"None\");\r\n            }\r\n            else {\r\n                for (SynchedEntityData.DataValue<?> data : list) {\r\n                    output.append('[').append(data.id()).append(\": \").append(data.value()).append(\"], \");\r\n                }\r\n            }\r\n            doPacketOutput(output.toString());\r\n        }\r\n        else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n            ClientboundSetEntityMotionPacket velPacket = (ClientboundSetEntityMotionPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundSetEntityMotionPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + velPacket.getId() + \": \" + velPacket.getXa() + \",\" + velPacket.getYa() + \",\" + velPacket.getZa());\r\n        }\r\n        else if (packet instanceof ClientboundAddEntityPacket) {\r\n            ClientboundAddEntityPacket addEntityPacket = (ClientboundAddEntityPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundAddEntityPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + addEntityPacket.getId() + \": \" + \"uuid: \" + addEntityPacket.getUUID()\r\n                    + \", type: \" + addEntityPacket.getType() + \", at: \" + addEntityPacket.getX() + \",\" + addEntityPacket.getY() + \",\" + addEntityPacket.getZ() + \", data: \" + addEntityPacket.getData());\r\n        }\r\n        else if (packet instanceof ClientboundMapItemDataPacket) {\r\n            ClientboundMapItemDataPacket mapPacket = (ClientboundMapItemDataPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundMapItemDataPacket sent to \" + player.getScoreboardName() + \" for map ID: \" + mapPacket.getMapId() + \", scale: \" + mapPacket.getScale() + \", locked: \" + mapPacket.isLocked());\r\n        }\r\n        else if (packet instanceof ClientboundRemoveEntitiesPacket) {\r\n            ClientboundRemoveEntitiesPacket removePacket = (ClientboundRemoveEntitiesPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundRemoveEntitiesPacket sent to \" + player.getScoreboardName() + \" for entities: \" + removePacket.getEntityIds().stream().map(Object::toString).collect(Collectors.joining(\", \")));\r\n        }\r\n        else if (packet instanceof ClientboundPlayerInfoUpdatePacket) {\r\n            ClientboundPlayerInfoUpdatePacket playerInfoPacket = (ClientboundPlayerInfoUpdatePacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundPlayerInfoPacket sent to \" + player.getScoreboardName() + \" of types \" + playerInfoPacket.actions() + \" for player profiles: \" +\r\n                    playerInfoPacket.entries().stream().map(p -> \"mode=\" + p.gameMode() + \"/latency=\" + p.latency() + \"/display=\" + p.displayName() + \"/name=\" + p.profile().getName() + \"/id=\" + p.profile().getId() + \"/\"\r\n                            + p.profile().getProperties().asMap().entrySet().stream().map(e -> e.getKey() + \"=\" + e.getValue().stream().map(v -> v.getValue() + \";\" + v.getSignature()).collect(Collectors.joining(\";;;\"))).collect(Collectors.joining(\"/\"))).collect(Collectors.joining(\", \")));\r\n        }\r\n        else {\r\n            doPacketOutput(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, PacketSendListener genericfuturelistener) {\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            if (Settings.cache_warnOnAsyncPackets\r\n                    && !(packet instanceof ClientboundSystemChatPacket) && !(packet instanceof ClientboundPlayerChatPacket) // Vanilla supports an async chat system, though it's normally disabled, some plugins use this as justification for sending messages async\r\n                    && !(packet instanceof ClientboundCommandSuggestionsPacket)) { // Async tab complete is wholly unsupported in Spigot (and will cause an exception), however Paper explicitly adds async support (for unclear reasons), so let it through too\r\n                Debug.echoError(\"Warning: packet sent off main thread! This is completely unsupported behavior! Denizen network interceptor ignoring packet to avoid crash. Packet class: \"\r\n                        + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName() + \" identify the sender of the packet from the stack trace:\");\r\n                try {\r\n                    throw new RuntimeException(\"Trace\");\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(ex);\r\n                }\r\n            }\r\n            oldManager.send(packet, genericfuturelistener);\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPackets) {\r\n            debugOutputPacket(packet);\r\n        }\r\n        packetsSent++;\r\n        if (packet instanceof ClientboundBundlePacket bundlePacket) {\r\n            Iterator<Packet<ClientGamePacketListener>> iter = bundlePacket.subPackets().iterator();\r\n            int count = 0;\r\n            while (iter.hasNext()) {\r\n                count++;\r\n                if (processPacket(iter.next(), genericfuturelistener)) {\r\n                    iter.remove();\r\n                    count--;\r\n                }\r\n            }\r\n            if (count == 0) {\r\n                return;\r\n            }\r\n        }\r\n        else if (processPacket(packet, genericfuturelistener)) {\r\n            return;\r\n        }\r\n        oldManager.send(packet, genericfuturelistener);\r\n    }\r\n\r\n    public boolean processPacket(Packet<?> packet, PacketSendListener genericfuturelistener) {\r\n        if (processAttachToForPacket(packet)\r\n            || processHiddenEntitiesForPacket(packet)\r\n            || processMirrorForPacket(packet)\r\n            || processParticlesForPacket(packet)\r\n            || processSoundPacket(packet)\r\n            || processPacketHandlerForPacket(packet, genericfuturelistener)\r\n            || processTablistPacket(packet, genericfuturelistener)\r\n            || processActionbarPacket(packet, genericfuturelistener)\r\n            || processDisguiseForPacket(packet, genericfuturelistener)\r\n            || processMetadataChangesForPacket(packet, genericfuturelistener)\r\n            || processEquipmentForPacket(packet, genericfuturelistener)\r\n            || processShowFakeForPacket(packet, genericfuturelistener)) {\r\n            if (NMSHandler.debugPackets) {\r\n                doPacketOutput(\"DENIED PACKET \" + packet.getClass().getCanonicalName() + \" DENIED FROM SEND TO \" + player.getScoreboardName());\r\n            }\r\n            return true;\r\n        }\r\n        if (PlayerReceivesPacketScriptEvent.instance.eventData.isEnabled) {\r\n            if (PlayerReceivesPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) {\r\n                if (NMSHandler.debugPackets) {\r\n                    doPacketOutput(\"DENIED PACKET \" + packet.getClass().getCanonicalName() + \" DENIED FROM SEND TO \" + player.getScoreboardName() + \" due to event\");\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        processBlockLightForPacket(packet);\r\n        processFakePlayerSpawnForPacket(packet);\r\n        return false;\r\n    }\r\n\r\n    public static boolean tablistBreakOnlyOnce = false;\r\n\r\n    public boolean processTablistPacket(Packet<?> packet, PacketSendListener genericfuturelistener) {\r\n        if (!PlayerReceivesTablistUpdateScriptEvent.instance.eventData.isEnabled) {\r\n            return false;\r\n        }\r\n        if (packet instanceof ClientboundPlayerInfoUpdatePacket) {\r\n            ClientboundPlayerInfoUpdatePacket infoPacket = (ClientboundPlayerInfoUpdatePacket) packet;\r\n            String mode = \"\";\r\n            for (ClientboundPlayerInfoUpdatePacket.Action action : infoPacket.actions()) {\r\n                switch (action) {\r\n                    case ADD_PLAYER:\r\n                        mode = \"add\";\r\n                        break;\r\n                    case UPDATE_LATENCY:\r\n                        mode = mode.isEmpty() ? \"update_latency\" : mode + \"|update_latency\";\r\n                        break;\r\n                    case UPDATE_GAME_MODE:\r\n                        mode = mode.isEmpty() ? \"update_gamemode\" : mode + \"|update_gamemode\";\r\n                        break;\r\n                    case UPDATE_DISPLAY_NAME:\r\n                        mode = mode.isEmpty() ? \"update_display\" : mode + \"|update_display\";\r\n                        break;\r\n                    case UPDATE_LISTED:\r\n                        mode = mode.isEmpty() ? \"update_listed\" : mode + \"|update_listed\";\r\n                        break;\r\n                    case INITIALIZE_CHAT:\r\n                        mode = mode.isEmpty() ? \"initialize_chat\" : mode + \"|initialize_chat\";\r\n                    default:\r\n                        break;\r\n                }\r\n            }\r\n            if (mode.isEmpty()) {\r\n                if (!tablistBreakOnlyOnce) {\r\n                    tablistBreakOnlyOnce = true;\r\n                    Debug.echoError(\"Tablist packet processing failed: unknown action \" + Joiner.on(\", \").join(infoPacket.actions()));\r\n                }\r\n                return false;\r\n            }\r\n            boolean isOverriding = false;\r\n            for (ClientboundPlayerInfoUpdatePacket.Entry update : infoPacket.entries()) {\r\n                GameProfile profile = update.profile();\r\n                String texture = null, signature = null;\r\n                if (profile.getProperties().containsKey(\"textures\")) {\r\n                    Property property = profile.getProperties().get(\"textures\").stream().findFirst().get();\r\n                    texture = property.getValue();\r\n                    signature = property.getSignature();\r\n                }\r\n                String modeText = update.gameMode() == null ? null : update.gameMode().name();\r\n                PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(mode, profile.getId(), update.listed(), profile.getName(),\r\n                        update.displayName() == null ? null : FormattedTextHelper.stringify(Handler.componentToSpigot(update.displayName())), modeText, texture, signature, update.latency());\r\n                PlayerReceivesTablistUpdateScriptEvent.fire(player.getBukkitEntity(), data);\r\n                if (data.modified) {\r\n                    if (!isOverriding) {\r\n                        isOverriding = true;\r\n                        for (ClientboundPlayerInfoUpdatePacket.Entry priorUpdate : infoPacket.entries()) {\r\n                            if (priorUpdate == update) {\r\n                                break;\r\n                            }\r\n                            oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(priorUpdate)));\r\n                        }\r\n                    }\r\n                    if (!data.cancelled) {\r\n                        GameProfile newProfile = new GameProfile(data.id, data.name);\r\n                        if (data.texture != null) {\r\n                            newProfile.getProperties().put(\"textures\", new Property(\"textures\", data.texture, data.signature));\r\n                        }\r\n                        ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(newProfile.getId(), newProfile, data.isListed, data.latency, data.gamemode == null ? null : GameType.byName(CoreUtilities.toLowerCase(data.gamemode)),\r\n                                data.display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(data.display, ChatColor.WHITE)), update.chatSession());\r\n                        oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(entry)), genericfuturelistener);\r\n                    }\r\n                }\r\n                else if (isOverriding) {\r\n                    oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(update)), genericfuturelistener);\r\n                }\r\n            }\r\n            return isOverriding;\r\n        }\r\n        else if (packet instanceof ClientboundPlayerInfoRemovePacket) {\r\n            ClientboundPlayerInfoRemovePacket removePacket = (ClientboundPlayerInfoRemovePacket) packet;\r\n            boolean modified = false;\r\n            List<UUID> altIds = new ArrayList<>(((ClientboundPlayerInfoRemovePacket) packet).profileIds());\r\n            for (UUID id : ((ClientboundPlayerInfoRemovePacket) packet).profileIds()) {\r\n                PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(\"remove\", id, false, null, null, null, null, null, 0);\r\n                PlayerReceivesTablistUpdateScriptEvent.fire(player.getBukkitEntity(), data);\r\n                if (data.modified && data.cancelled) {\r\n                    modified = true;\r\n                    altIds.remove(id);\r\n                }\r\n            }\r\n            if (modified) {\r\n                oldManager.send(new ClientboundPlayerInfoRemovePacket(altIds), genericfuturelistener);\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processActionbarPacket(Packet<?> packet, PacketSendListener genericfuturelistener) {\r\n        if (!PlayerReceivesActionbarScriptEvent.instance.loaded) {\r\n            return false;\r\n        }\r\n        if (packet instanceof ClientboundSetActionBarTextPacket) {\r\n            ClientboundSetActionBarTextPacket actionbarPacket = (ClientboundSetActionBarTextPacket) packet;\r\n            PlayerReceivesActionbarScriptEvent event = PlayerReceivesActionbarScriptEvent.instance;\r\n            Component baseComponent = actionbarPacket.getText();\r\n            event.reset();\r\n            event.message = new ElementTag(FormattedTextHelper.stringify(Handler.componentToSpigot(baseComponent)));\r\n            event.rawJson = new ElementTag(Component.Serializer.toJson(baseComponent));\r\n            event.system = new ElementTag(false);\r\n            event.player = PlayerTag.mirrorBukkitPlayer(player.getBukkitEntity());\r\n            event = (PlayerReceivesActionbarScriptEvent) event.triggerNow();\r\n            if (event.cancelled) {\r\n                return true;\r\n            }\r\n            if (event.modified) {\r\n                Component component = Handler.componentToNMS(event.altMessageDetermination);\r\n                ClientboundSetActionBarTextPacket newPacket = new ClientboundSetActionBarTextPacket(component);\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processSoundPacket(Packet<?> packet) {\r\n        if (!PlayerHearsSoundScriptEvent.instance.eventData.isEnabled) {\r\n            return false;\r\n        }\r\n        if (packet instanceof ClientboundSoundPacket) {\r\n            ClientboundSoundPacket spacket = (ClientboundSoundPacket) packet;\r\n            return PlayerHearsSoundScriptEvent.instance.run(player.getBukkitEntity(), spacket.getSound().value().getLocation().getPath(), spacket.getSource().name(),\r\n                    false, null, new Location(player.getBukkitEntity().getWorld(), spacket.getX(), spacket.getY(), spacket.getZ()), spacket.getVolume(), spacket.getPitch());\r\n        }\r\n        else if (packet instanceof ClientboundSoundEntityPacket) {\r\n            ClientboundSoundEntityPacket spacket = (ClientboundSoundEntityPacket) packet;\r\n            Entity entity = player.getLevel().getEntity(spacket.getId());\r\n            if (entity == null) {\r\n                return false;\r\n            }\r\n            return PlayerHearsSoundScriptEvent.instance.run(player.getBukkitEntity(), spacket.getSound().value().getLocation().getPath(), spacket.getSource().name(),\r\n                    false, entity.getBukkitEntity(), null, spacket.getVolume(), spacket.getPitch());\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processEquipmentForPacket(Packet<?> packet, PacketSendListener genericfuturelistener) {\r\n        if (FakeEquipCommand.overrides.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundSetEquipmentPacket) {\r\n                int eid = ((ClientboundSetEquipmentPacket) packet).getEntity();\r\n                Entity ent = player.level.getEntity(eid);\r\n                if (ent == null) {\r\n                    return false;\r\n                }\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(ent.getUUID(), player.getBukkitEntity());\r\n                if (override == null) {\r\n                    return false;\r\n                }\r\n                List<Pair<net.minecraft.world.entity.EquipmentSlot, ItemStack>> equipment = new ArrayList<>(((ClientboundSetEquipmentPacket) packet).getSlots());\r\n                ClientboundSetEquipmentPacket newPacket = new ClientboundSetEquipmentPacket(eid, equipment);\r\n                for (int i = 0; i < equipment.size(); i++) {\r\n                    Pair<net.minecraft.world.entity.EquipmentSlot, ItemStack> pair =  equipment.get(i);\r\n                    ItemStack use = pair.getSecond();\r\n                    switch (pair.getFirst()) {\r\n                        case MAINHAND:\r\n                            use = override.hand == null ? use : CraftItemStack.asNMSCopy(override.hand.getItemStack());\r\n                            break;\r\n                        case OFFHAND:\r\n                            use = override.offhand == null ? use : CraftItemStack.asNMSCopy(override.offhand.getItemStack());\r\n                            break;\r\n                        case CHEST:\r\n                            use = override.chest == null ? use : CraftItemStack.asNMSCopy(override.chest.getItemStack());\r\n                            break;\r\n                        case HEAD:\r\n                            use = override.head == null ? use : CraftItemStack.asNMSCopy(override.head.getItemStack());\r\n                            break;\r\n                        case LEGS:\r\n                            use = override.legs == null ? use : CraftItemStack.asNMSCopy(override.legs.getItemStack());\r\n                            break;\r\n                        case FEET:\r\n                            use = override.boots == null ? use : CraftItemStack.asNMSCopy(override.boots.getItemStack());\r\n                            break;\r\n                    }\r\n                    equipment.set(i, new Pair<>(pair.getFirst(), use));\r\n                }\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundEntityEventPacket) {\r\n                Entity ent = ((ClientboundEntityEventPacket) packet).getEntity(player.level);\r\n                if (!(ent instanceof net.minecraft.world.entity.LivingEntity)) {\r\n                    return false;\r\n                }\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(ent.getUUID(), player.getBukkitEntity());\r\n                if (override == null || (override.hand == null && override.offhand == null)) {\r\n                    return false;\r\n                }\r\n                if (((ClientboundEntityEventPacket) packet).getEventId() != (byte) 55) {\r\n                    return false;\r\n                }\r\n                List<Pair<net.minecraft.world.entity.EquipmentSlot, ItemStack>> equipment = new ArrayList<>();\r\n                ItemStack hand = override.hand != null ? CraftItemStack.asNMSCopy(override.hand.getItemStack()) : ((net.minecraft.world.entity.LivingEntity) ent).getMainHandItem();\r\n                ItemStack offhand = override.offhand != null ? CraftItemStack.asNMSCopy(override.offhand.getItemStack()) : ((net.minecraft.world.entity.LivingEntity) ent).getOffhandItem();\r\n                equipment.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.MAINHAND, hand));\r\n                equipment.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.OFFHAND, offhand));\r\n                ClientboundSetEquipmentPacket newPacket = new ClientboundSetEquipmentPacket(ent.getId(), equipment);\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundContainerSetContentPacket) {\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), player.getBukkitEntity());\r\n                if (override == null) {\r\n                    return false;\r\n                }\r\n                int window = ((ClientboundContainerSetContentPacket) packet).getContainerId();\r\n                if (window != 0) {\r\n                    return false;\r\n                }\r\n                NonNullList<ItemStack> items = (NonNullList<ItemStack>) ((ClientboundContainerSetContentPacket) packet).getItems();\r\n                if (override.head != null) {\r\n                    items.set(5, CraftItemStack.asNMSCopy(override.head.getItemStack()));\r\n                }\r\n                if (override.chest != null) {\r\n                    items.set(6, CraftItemStack.asNMSCopy(override.chest.getItemStack()));\r\n                }\r\n                if (override.legs != null) {\r\n                    items.set(7, CraftItemStack.asNMSCopy(override.legs.getItemStack()));\r\n                }\r\n                if (override.boots != null) {\r\n                    items.set(8, CraftItemStack.asNMSCopy(override.boots.getItemStack()));\r\n                }\r\n                if (override.offhand != null) {\r\n                    items.set(45, CraftItemStack.asNMSCopy(override.offhand.getItemStack()));\r\n                }\r\n                if (override.hand != null) {\r\n                    items.set(player.getInventory().selected + 36, CraftItemStack.asNMSCopy(override.hand.getItemStack()));\r\n                }\r\n                ClientboundContainerSetContentPacket newPacket = new ClientboundContainerSetContentPacket(window, ((ClientboundContainerSetContentPacket) packet).getStateId(), items, ((ClientboundContainerSetContentPacket) packet).getCarriedItem());\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundContainerSetSlotPacket) {\r\n                FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), player.getBukkitEntity());\r\n                if (override == null) {\r\n                    return false;\r\n                }\r\n                int window = ((ClientboundContainerSetSlotPacket) packet).getContainerId();\r\n                if (window != 0) {\r\n                    return false;\r\n                }\r\n                int slot = ((ClientboundContainerSetSlotPacket) packet).getSlot();\r\n                org.bukkit.inventory.ItemStack item = null;\r\n                if (slot == 5 && override.head != null) {\r\n                    item = override.head.getItemStack();\r\n                }\r\n                else if (slot == 6 && override.chest != null) {\r\n                    item = override.chest.getItemStack();\r\n                }\r\n                else if (slot == 7 && override.legs != null) {\r\n                    item = override.legs.getItemStack();\r\n                }\r\n                else if (slot == 8 && override.boots != null) {\r\n                    item = override.boots.getItemStack();\r\n                }\r\n                else if (slot == 45 && override.offhand != null) {\r\n                    item = override.offhand.getItemStack();\r\n                }\r\n                else if (slot == player.getInventory().selected + 36 && override.hand != null) {\r\n                    item = override.hand.getItemStack();\r\n                }\r\n                if (item == null) {\r\n                    return false;\r\n                }\r\n                ClientboundContainerSetSlotPacket newPacket = new ClientboundContainerSetSlotPacket(window, ((ClientboundContainerSetSlotPacket) packet).getStateId(), slot, CraftItemStack.asNMSCopy(item));\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processParticlesForPacket(Packet<?> packet) {\r\n        if (HideParticles.hidden.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundLevelParticlesPacket) {\r\n                HashSet<Particle> hidden = HideParticles.hidden.get(player.getUUID());\r\n                if (hidden == null) {\r\n                    return false;\r\n                }\r\n                ParticleOptions particle = ((ClientboundLevelParticlesPacket) packet).getParticle();\r\n                Particle bukkitParticle = CraftParticle.toBukkit(particle);\r\n                if (hidden.contains(bukkitParticle)) {\r\n                    return true;\r\n                }\r\n                return false;\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    private boolean antiDuplicate = false;\r\n\r\n    public boolean processDisguiseForPacket(Packet<?> packet, PacketSendListener genericfuturelistener) {\r\n        if (DisguiseCommand.disguises.isEmpty() || antiDuplicate) {\r\n            return false;\r\n        }\r\n        try {\r\n            int entityID = -1;\r\n            if (packet instanceof ClientboundSetEntityDataPacket entityDataPacket) {\r\n                entityID = entityDataPacket.id();\r\n            }\r\n            if (packet instanceof ClientboundUpdateAttributesPacket updateAttributesPacket) {\r\n                entityID = updateAttributesPacket.getEntityId();\r\n            }\r\n            if (packet instanceof ClientboundAddPlayerPacket addPlayerPacket) {\r\n                entityID = addPlayerPacket.getEntityId();\r\n            }\r\n            else if (packet instanceof ClientboundAddEntityPacket addEntityPacket) {\r\n                entityID = addEntityPacket.getId();\r\n            }\r\n            else if (packet instanceof ClientboundTeleportEntityPacket teleportEntityPacket) {\r\n                entityID = teleportEntityPacket.getId();\r\n            }\r\n            else if (packet instanceof ClientboundMoveEntityPacket moveEntityPacket) {\r\n                Entity e = moveEntityPacket.getEntity(player.level);\r\n                if (e != null) {\r\n                    entityID = e.getId();\r\n                }\r\n            }\r\n            if (entityID == -1) {\r\n                return false;\r\n            }\r\n            Entity entity = player.getLevel().getEntity(entityID);\r\n            if (entity == null) {\r\n                return false;\r\n            }\r\n            HashMap<UUID, DisguiseCommand.TrackedDisguise> playerMap = DisguiseCommand.disguises.get(entity.getUUID());\r\n            if (playerMap == null) {\r\n                return false;\r\n            }\r\n            DisguiseCommand.TrackedDisguise disguise = playerMap.get(player.getUUID());\r\n            if (disguise == null) {\r\n                disguise = playerMap.get(null);\r\n                if (disguise == null) {\r\n                    return false;\r\n                }\r\n            }\r\n            if (!disguise.isActive) {\r\n                return false;\r\n            }\r\n            if (NMSHandler.debugPackets) {\r\n                doPacketOutput(\"DISGUISED packet \" + packet.getClass().getName() + \" for entity \" + entityID + \" to player \" + player.getScoreboardName());\r\n            }\r\n            if (packet instanceof ClientboundSetEntityDataPacket metadataPacket) {\r\n                if (entityID == player.getId()) {\r\n                    if (!disguise.shouldFake) {\r\n                        return false;\r\n                    }\r\n                    List<SynchedEntityData.DataValue<?>> data = metadataPacket.packedItems();\r\n                    for (SynchedEntityData.DataValue<?> dataValue : data) {\r\n                        if (dataValue.id() == 0) { // Entity flags\r\n                            data = new ArrayList<>(data);\r\n                            data.remove(dataValue);\r\n                            byte flags = (byte) dataValue.value();\r\n                            flags |= 0x20; // Invisible flag\r\n                            data.add(new SynchedEntityData.DataValue(dataValue.id(), dataValue.serializer(), flags));\r\n                            ClientboundSetEntityDataPacket altPacket = new ClientboundSetEntityDataPacket(metadataPacket.id(), data);\r\n                            ClientboundSetEntityDataPacket updatedPacket = getModifiedMetadataFor(altPacket);\r\n                            oldManager.send(updatedPacket == null ? altPacket : updatedPacket, genericfuturelistener);\r\n                            return true;\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    List<SynchedEntityData.DataValue<?>> data = ((CraftEntity) disguise.toOthers.entity.entity).getHandle().getEntityData().getNonDefaultValues();\r\n                    if (data != null) {\r\n                        oldManager.send(new ClientboundSetEntityDataPacket(entityID, data), genericfuturelistener);\r\n                    }\r\n                    return true;\r\n                }\r\n                return false;\r\n            }\r\n            else if (packet instanceof ClientboundUpdateAttributesPacket) {\r\n                FakeEntity fake = entityID == player.getId() ? disguise.fakeToSelf : disguise.toOthers;\r\n                if (fake == null) {\r\n                    return false;\r\n                }\r\n                if (fake.entity.entity instanceof LivingEntity) {\r\n                    return false;\r\n                }\r\n                return true; // Non-living don't have attributes\r\n            }\r\n            else if (packet instanceof ClientboundTeleportEntityPacket) {\r\n                if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\r\n                    ClientboundTeleportEntityPacket pOld = (ClientboundTeleportEntityPacket) packet;\r\n                    ClientboundTeleportEntityPacket pNew = new ClientboundTeleportEntityPacket(entity);\r\n                    ENTITY_ID_PACKTELENT.setInt(pNew, pOld.getId());\r\n                    POS_X_PACKTELENT.setDouble(pNew, pOld.getX());\r\n                    POS_Y_PACKTELENT.setDouble(pNew, pOld.getY());\r\n                    POS_Z_PACKTELENT.setDouble(pNew, pOld.getZ());\r\n                    YAW_PACKTELENT.setByte(pNew, EntityAttachmentHelper.adaptedCompressedAngle(pOld.getyRot(), 180));\r\n                    PITCH_PACKTELENT.setByte(pNew, pOld.getxRot());\r\n                    oldManager.send(pNew, genericfuturelistener);\r\n                    return true;\r\n                }\r\n            }\r\n            else if (packet instanceof ClientboundMoveEntityPacket) {\r\n                if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\r\n                    ClientboundMoveEntityPacket pOld = (ClientboundMoveEntityPacket) packet;\r\n                    ClientboundMoveEntityPacket pNew = null;\r\n                    if (packet instanceof ClientboundMoveEntityPacket.Rot) {\r\n                        pNew = new ClientboundMoveEntityPacket.Rot(entityID, EntityAttachmentHelper.adaptedCompressedAngle(pOld.getyRot(), 180), pOld.getxRot(), pOld.isOnGround());\r\n                    }\r\n                    else if (packet instanceof ClientboundMoveEntityPacket.PosRot) {\r\n                        pNew = new ClientboundMoveEntityPacket.PosRot(entityID, pOld.getXa(), pOld.getYa(), pOld.getZa(), EntityAttachmentHelper.adaptedCompressedAngle(pOld.getyRot(), 180), pOld.getxRot(), pOld.isOnGround());\r\n                    }\r\n                    if (pNew != null) {\r\n                        oldManager.send(pNew, genericfuturelistener);\r\n                        return true;\r\n                    }\r\n                    return false;\r\n                }\r\n            }\r\n            antiDuplicate = true;\r\n            disguise.sendTo(List.of(new PlayerTag(player.getUUID())));\r\n            antiDuplicate = false;\r\n            return true;\r\n        }\r\n        catch (Throwable ex) {\r\n            antiDuplicate = false;\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public ClientboundSetEntityDataPacket getModifiedMetadataFor(ClientboundSetEntityDataPacket metadataPacket) {\r\n        if (!RenameCommand.hasAnyDynamicRenames() && SneakCommand.forceSetSneak.isEmpty() && InvisibleCommand.helper.noOverrides() && GlowCommand.helper.noOverrides()) {\r\n            return null;\r\n        }\r\n        try {\r\n            Entity entity = player.level.getEntity(metadataPacket.id());\r\n            if (entity == null) {\r\n                return null; // If it doesn't exist on-server, it's definitely not relevant, so move on\r\n            }\r\n            String nameToApply = RenameCommand.getCustomNameFor(entity.getUUID(), player.getBukkitEntity(), false);\r\n            Boolean forceSneak = SneakCommand.shouldSneak(entity.getUUID(), player.getUUID());\r\n            Boolean isInvisible = InvisibleCommand.helper.getState(entity.getBukkitEntity(), player.getUUID(), true);\r\n            Boolean isGlowing = GlowCommand.helper.getState(entity.getBukkitEntity(), player.getUUID(), true);\r\n            boolean shouldModifyFlags = isInvisible != null || forceSneak != null || isGlowing != null;\r\n            if (nameToApply == null && !shouldModifyFlags) {\r\n                return null;\r\n            }\r\n            List<SynchedEntityData.DataValue<?>> data = new ArrayList<>(metadataPacket.packedItems().size());\r\n            Byte currentFlags = null;\r\n            for (SynchedEntityData.DataValue<?> dataValue : metadataPacket.packedItems()) {\r\n                if (dataValue.id() == 0 && shouldModifyFlags) { // 0: Entity Flags\r\n                    currentFlags = (Byte) dataValue.value();\r\n                }\r\n                else if (nameToApply == null || (dataValue.id() != 2 && dataValue.id() != 3)) { // 2 and 3: Custom name and custom name visible\r\n                    data.add(dataValue);\r\n                }\r\n            }\r\n            if (shouldModifyFlags) {\r\n                byte flags = currentFlags == null ? entity.getEntityData().get(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS) : currentFlags;\r\n                flags = applyEntityDataFlag(flags, forceSneak, 0x02);\r\n                flags = applyEntityDataFlag(flags, isInvisible, 0x20);\r\n                flags = applyEntityDataFlag(flags, isGlowing, 0x40);\r\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags));\r\n            }\r\n            if (nameToApply != null) {\r\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(nameToApply, ChatColor.WHITE)))));\r\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true));\r\n            }\r\n            return new ClientboundSetEntityDataPacket(metadataPacket.id(), data);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public byte applyEntityDataFlag(byte currentFlags, Boolean value, int flag) {\r\n        if (value == null) {\r\n            return currentFlags;\r\n        }\r\n        return (byte) (value ? currentFlags | flag : currentFlags & ~flag);\r\n    }\r\n\r\n    public boolean processMetadataChangesForPacket(Packet<?> packet, PacketSendListener genericfuturelistener) {\r\n        if (!(packet instanceof ClientboundSetEntityDataPacket entityDataPacket)) {\r\n            return false;\r\n        }\r\n        ClientboundSetEntityDataPacket altPacket = getModifiedMetadataFor(entityDataPacket);\r\n        if (altPacket == null) {\r\n            return false;\r\n        }\r\n        oldManager.send(altPacket, genericfuturelistener);\r\n        return true;\r\n    }\r\n\r\n    public void tryProcessMovePacketForAttach(ClientboundMoveEntityPacket packet, Entity e) throws IllegalAccessException {\r\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n        if (attList != null) {\r\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                if (attMap.attached.isValid() && att != null) {\r\n                    ClientboundMoveEntityPacket pNew;\r\n                    int newId = att.attached.getBukkitEntity().getEntityId();\r\n                    if (packet instanceof ClientboundMoveEntityPacket.Pos) {\r\n                        pNew = new ClientboundMoveEntityPacket.Pos(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.isOnGround());\r\n                    }\r\n                    else if (packet instanceof ClientboundMoveEntityPacket.Rot) {\r\n                        pNew = new ClientboundMoveEntityPacket.Rot(newId, packet.getyRot(), packet.getxRot(), packet.isOnGround());\r\n                    }\r\n                    else if (packet instanceof ClientboundMoveEntityPacket.PosRot) {\r\n                        pNew = new ClientboundMoveEntityPacket.PosRot(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.getyRot(), packet.getxRot(), packet.isOnGround());\r\n                    }\r\n                    else {\r\n                        if (CoreConfiguration.debugVerbose) {\r\n                            Debug.echoError(\"Impossible move-entity packet class: \" + packet.getClass().getCanonicalName());\r\n                        }\r\n                        return;\r\n                    }\r\n                    if (att.positionalOffset != null) {\r\n                        boolean isRotate = packet instanceof ClientboundMoveEntityPacket.PosRot || packet instanceof ClientboundMoveEntityPacket.Rot;\r\n                        byte yaw, pitch;\r\n                        if (att.noRotate) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            yaw = EntityAttachmentHelper.compressAngle(attachedEntity.getYRot());\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        else if (isRotate) {\r\n                            yaw = packet.getyRot();\r\n                            pitch = packet.getxRot();\r\n                        }\r\n                        else {\r\n                            yaw = EntityAttachmentHelper.compressAngle(e.getYRot());\r\n                            pitch = EntityAttachmentHelper.compressAngle(e.getXRot());\r\n                        }\r\n                        if (att.noPitch) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        byte newYaw = yaw;\r\n                        if (isRotate) {\r\n                            newYaw = EntityAttachmentHelper.adaptedCompressedAngle(newYaw, att.positionalOffset.getYaw());\r\n                            pitch = EntityAttachmentHelper.adaptedCompressedAngle(pitch, att.positionalOffset.getPitch());\r\n                        }\r\n                        Vector goalPosition = att.fixedForOffset(new Vector(e.getX(), e.getY(), e.getZ()), e.getYRot(), e.getXRot());\r\n                        Vector oldPos = att.visiblePositions.get(player.getUUID());\r\n                        boolean forceTele = false;\r\n                        if (oldPos == null) {\r\n                            oldPos = att.attached.getLocation().toVector();\r\n                            forceTele = true;\r\n                        }\r\n                        Vector moveNeeded = goalPosition.clone().subtract(oldPos);\r\n                        att.visiblePositions.put(player.getUUID(), goalPosition.clone());\r\n                        int offX = (int) (moveNeeded.getX() * (32 * 128));\r\n                        int offY = (int) (moveNeeded.getY() * (32 * 128));\r\n                        int offZ = (int) (moveNeeded.getZ() * (32 * 128));\r\n                        if ((isRotate && att.offsetRelative) || forceTele || offX < Short.MIN_VALUE || offX > Short.MAX_VALUE\r\n                                || offY < Short.MIN_VALUE || offY > Short.MAX_VALUE\r\n                                || offZ < Short.MIN_VALUE || offZ > Short.MAX_VALUE) {\r\n                            ClientboundTeleportEntityPacket newTeleportPacket = new ClientboundTeleportEntityPacket(e);\r\n                            ENTITY_ID_PACKTELENT.setInt(newTeleportPacket, att.attached.getBukkitEntity().getEntityId());\r\n                            POS_X_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getX());\r\n                            POS_Y_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getY());\r\n                            POS_Z_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getZ());\r\n                            YAW_PACKTELENT.setByte(newTeleportPacket, newYaw);\r\n                            PITCH_PACKTELENT.setByte(newTeleportPacket, pitch);\r\n                            if (NMSHandler.debugPackets) {\r\n                                doPacketOutput(\"Attach Move-Tele Packet: \" + newTeleportPacket.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getScoreboardName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\r\n                            }\r\n                            oldManager.send(newTeleportPacket);\r\n                        }\r\n                        else {\r\n                            POS_X_PACKENT.setShort(pNew, (short) Mth.clamp(offX, Short.MIN_VALUE, Short.MAX_VALUE));\r\n                            POS_Y_PACKENT.setShort(pNew, (short) Mth.clamp(offY, Short.MIN_VALUE, Short.MAX_VALUE));\r\n                            POS_Z_PACKENT.setShort(pNew, (short) Mth.clamp(offZ, Short.MIN_VALUE, Short.MAX_VALUE));\r\n                            if (isRotate) {\r\n                                YAW_PACKENT.setByte(pNew, yaw);\r\n                                PITCH_PACKENT.setByte(pNew, pitch);\r\n                            }\r\n                            if (NMSHandler.debugPackets) {\r\n                                doPacketOutput(\"Attach Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getScoreboardName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\r\n                            }\r\n                            oldManager.send(pNew);\r\n                        }\r\n                    }\r\n                    else {\r\n                        if (NMSHandler.debugPackets) {\r\n                            doPacketOutput(\"Attach Replica-Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getScoreboardName());\r\n                        }\r\n                        oldManager.send(pNew);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        if (e.passengers != null && !e.passengers.isEmpty()) {\r\n            for (Entity ent : e.passengers) {\r\n                tryProcessMovePacketForAttach(packet, ent);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void tryProcessRotateHeadPacketForAttach(ClientboundRotateHeadPacket packet, Entity e) throws IllegalAccessException {\r\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n        if (attList != null) {\r\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                if (attMap.attached.isValid() && att != null) {\r\n                    byte yaw = packet.getYHeadRot();\r\n                    Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                    if (att.positionalOffset != null) {\r\n                        if (att.noRotate) {\r\n                            yaw = EntityAttachmentHelper.compressAngle(attachedEntity.getYRot());\r\n                        }\r\n                        yaw = EntityAttachmentHelper.adaptedCompressedAngle(yaw, att.positionalOffset.getYaw());\r\n                    }\r\n                    ClientboundRotateHeadPacket pNew = new ClientboundRotateHeadPacket(attachedEntity, yaw);\r\n                    if (NMSHandler.debugPackets) {\r\n                        doPacketOutput(\"Head Rotation Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getScoreboardName());\r\n                    }\r\n                    oldManager.send(pNew);\r\n                }\r\n            }\r\n        }\r\n        if (e.passengers != null && !e.passengers.isEmpty()) {\r\n            for (Entity ent : e.passengers) {\r\n                tryProcessRotateHeadPacketForAttach(packet, ent);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void tryProcessVelocityPacketForAttach(ClientboundSetEntityMotionPacket packet, Entity e) throws IllegalAccessException {\r\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n        if (attList != null) {\r\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                if (attMap.attached.isValid() && att != null) {\r\n                    ClientboundSetEntityMotionPacket pNew = new ClientboundSetEntityMotionPacket(copyPacket(packet));\r\n                    ENTITY_ID_PACKVELENT.setInt(pNew, att.attached.getBukkitEntity().getEntityId());\r\n                    if (NMSHandler.debugPackets) {\r\n                        doPacketOutput(\"Attach Velocity Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + player.getScoreboardName());\r\n                    }\r\n                    oldManager.send(pNew);\r\n                }\r\n            }\r\n        }\r\n        if (e.passengers != null && !e.passengers.isEmpty()) {\r\n            for (Entity ent : e.passengers) {\r\n                tryProcessVelocityPacketForAttach(packet, ent);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void tryProcessTeleportPacketForAttach(ClientboundTeleportEntityPacket packet, Entity e, Vector relative) throws IllegalAccessException {\r\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n        if (attList != null) {\r\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                if (attMap.attached.isValid() && att != null) {\r\n                    ClientboundTeleportEntityPacket pNew = new ClientboundTeleportEntityPacket(copyPacket(packet));\r\n                    ENTITY_ID_PACKTELENT.setInt(pNew, att.attached.getBukkitEntity().getEntityId());\r\n                    Vector resultPos = new Vector(POS_X_PACKTELENT.getDouble(pNew), POS_Y_PACKTELENT.getDouble(pNew), POS_Z_PACKTELENT.getDouble(pNew)).add(relative);\r\n                    if (att.positionalOffset != null) {\r\n                        resultPos = att.fixedForOffset(resultPos, e.getYRot(), e.getXRot());\r\n                        byte yaw, pitch;\r\n                        if (att.noRotate) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            yaw = EntityAttachmentHelper.compressAngle(attachedEntity.getYRot());\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        else {\r\n                            yaw = packet.getyRot();\r\n                            pitch = packet.getxRot();\r\n                        }\r\n                        if (att.noPitch) {\r\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\r\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\r\n                        }\r\n                        byte newYaw = EntityAttachmentHelper.adaptedCompressedAngle(yaw, att.positionalOffset.getYaw());\r\n                        pitch = EntityAttachmentHelper.adaptedCompressedAngle(pitch, att.positionalOffset.getPitch());\r\n                        POS_X_PACKTELENT.setDouble(pNew, resultPos.getX());\r\n                        POS_Y_PACKTELENT.setDouble(pNew, resultPos.getY());\r\n                        POS_Z_PACKTELENT.setDouble(pNew, resultPos.getZ());\r\n                        YAW_PACKTELENT.setByte(pNew, newYaw);\r\n                        PITCH_PACKTELENT.setByte(pNew, pitch);\r\n                        if (NMSHandler.debugPackets) {\r\n                            doPacketOutput(\"Attach Teleport Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID()\r\n                                    + \" sent to \" + player.getScoreboardName() + \" with raw yaw \" + yaw + \" adapted to \" + newYaw);\r\n                        }\r\n                    }\r\n                    att.visiblePositions.put(player.getUUID(), resultPos.clone());\r\n                    oldManager.send(pNew);\r\n                }\r\n            }\r\n        }\r\n        if (e.passengers != null && !e.passengers.isEmpty()) {\r\n            for (Entity ent : e.passengers) {\r\n                tryProcessTeleportPacketForAttach(packet, ent, new Vector(ent.getX() - e.getX(), ent.getY() - e.getY(), ent.getZ() - e.getZ()));\r\n            }\r\n        }\r\n    }\r\n\r\n    public static Vector VECTOR_ZERO = new Vector(0, 0, 0);\r\n\r\n    public boolean processAttachToForPacket(Packet<?> packet) {\r\n        if (EntityAttachmentHelper.toEntityToData.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundMoveEntityPacket moveEntityPacket) {\r\n                Entity e = moveEntityPacket.getEntity(player.getLevel());\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                if (!e.isPassenger()) {\r\n                    tryProcessMovePacketForAttach(moveEntityPacket, e);\r\n                }\r\n                return EntityAttachmentHelper.denyOriginalPacketSend(player.getUUID(), e.getUUID());\r\n            }\r\n            else if (packet instanceof ClientboundRotateHeadPacket rotateHeadPacket) {\r\n                Entity e = rotateHeadPacket.getEntity(player.getLevel());\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                tryProcessRotateHeadPacketForAttach(rotateHeadPacket, e);\r\n                return EntityAttachmentHelper.denyOriginalPacketSend(player.getUUID(), e.getUUID());\r\n            }\r\n            else if (packet instanceof ClientboundSetEntityMotionPacket setEntityMotionPacket) {\r\n                int ider = setEntityMotionPacket.getId();\r\n                Entity e = player.getLevel().getEntity(ider);\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                tryProcessVelocityPacketForAttach(setEntityMotionPacket, e);\r\n                return EntityAttachmentHelper.denyOriginalPacketSend(player.getUUID(), e.getUUID());\r\n            }\r\n            else if (packet instanceof ClientboundTeleportEntityPacket teleportEntityPacket) {\r\n                int ider = teleportEntityPacket.getId();\r\n                Entity e = player.getLevel().getEntity(ider);\r\n                if (e == null) {\r\n                    return false;\r\n                }\r\n                tryProcessTeleportPacketForAttach(teleportEntityPacket, e, VECTOR_ZERO);\r\n                return EntityAttachmentHelper.denyOriginalPacketSend(player.getUUID(), e.getUUID());\r\n            }\r\n            else if (packet instanceof ClientboundRemoveEntitiesPacket removeEntitiesPacket) {\r\n                for (int id : removeEntitiesPacket.getEntityIds()) {\r\n                    Entity e = player.getLevel().getEntity(id);\r\n                    if (e != null) {\r\n                        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\r\n                        if (attList != null) {\r\n                            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\r\n                                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(player.getUUID());\r\n                                if (attMap.attached.isValid() && att != null) {\r\n                                    att.visiblePositions.remove(player.getUUID());\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean isHidden(Entity entity) {\r\n        return entity != null && HideEntitiesHelper.playerShouldHide(player.getBukkitEntity().getUniqueId(), entity.getBukkitEntity());\r\n    }\r\n\r\n    public boolean processHiddenEntitiesForPacket(Packet<?> packet) {\r\n        if (!HideEntitiesHelper.hasAnyHides()) {\r\n            return false;\r\n        }\r\n        try {\r\n            int ider = -1;\r\n            Entity e = null;\r\n            if (packet instanceof ClientboundAddPlayerPacket) {\r\n                ider = ((ClientboundAddPlayerPacket) packet).getEntityId();\r\n            }\r\n            else if (packet instanceof ClientboundAddEntityPacket) {\r\n                ider = ((ClientboundAddEntityPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundAddExperienceOrbPacket) {\r\n                ider = ((ClientboundAddExperienceOrbPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundMoveEntityPacket) {\r\n                e = ((ClientboundMoveEntityPacket) packet).getEntity(player.getLevel());\r\n            }\r\n            else if (packet instanceof ClientboundSetEntityDataPacket) {\r\n                ider = ((ClientboundSetEntityDataPacket) packet).id();\r\n            }\r\n            else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n                ider = ((ClientboundSetEntityMotionPacket) packet).getId();\r\n            }\r\n            else if (packet instanceof ClientboundTeleportEntityPacket) {\r\n                ider = ((ClientboundTeleportEntityPacket) packet).getId();\r\n            }\r\n            if (e == null && ider != -1) {\r\n                e = player.getLevel().getEntity(ider);\r\n            }\r\n            if (e != null) {\r\n                if (isHidden(e)) {\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void processFakePlayerSpawnForPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundAddPlayerPacket) {\r\n            int id = ((ClientboundAddPlayerPacket) packet).getEntityId();\r\n            if (id != -1) {\r\n                Entity e = player.getLevel().getEntity(id);\r\n                processFakePlayerSpawn(e);\r\n            }\r\n        }\r\n    }\r\n\r\n    public void processFakePlayerSpawn(Entity entity) {\r\n        if (entity instanceof EntityFakePlayerImpl) {\r\n            final EntityFakePlayerImpl fakePlayer = (EntityFakePlayerImpl) entity;\r\n            send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, fakePlayer));\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(),\r\n                    () -> send(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(fakePlayer.getUUID()))), 5);\r\n        }\r\n    }\r\n\r\n    public boolean processMirrorForPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket) {\r\n            if (!ProfileEditorImpl.handleAlteredProfiles(playerInfoUpdatePacket, this)) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processPacketHandlerForPacket(Packet<?> packet, PacketSendListener genericfuturelistener) {\r\n        if (DenizenPacketHandler.instance.shouldInterceptChatPacket()) {\r\n            PacketOutChatImpl packetHelper = null;\r\n            boolean isActionbar = false;\r\n            if (packet instanceof ClientboundSystemChatPacket chatPacket) {\r\n                isActionbar = chatPacket.overlay();\r\n                packetHelper = new PacketOutChatImpl(chatPacket);\r\n                if (packetHelper.rawJson == null) { // Makes no sense but this can be null in weird edge cases\r\n                    return false;\r\n                }\r\n            }\r\n            else if (packet instanceof ClientboundPlayerChatPacket playerChatPacket) {\r\n                packetHelper = new PacketOutChatImpl(playerChatPacket);\r\n            }\r\n            if (packetHelper != null) {\r\n                PlayerReceivesMessageScriptEvent result = DenizenPacketHandler.instance.sendPacket(player.getBukkitEntity(), packetHelper);\r\n                if (result != null) {\r\n                    if (result.cancelled) {\r\n                        return true;\r\n                    }\r\n                    if (result.modified) {\r\n                        oldManager.send(new ClientboundSystemChatPacket(result.altMessageDetermination, isActionbar), genericfuturelistener);\r\n                        return true;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public boolean processShowFakeForPacket(Packet<?> packet, PacketSendListener genericfuturelistener) {\r\n        if (FakeBlock.blocks.isEmpty()) {\r\n            return false;\r\n        }\r\n        try {\r\n            if (packet instanceof ClientboundLevelChunkWithLightPacket) {\r\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(player.getUUID());\r\n                if (map == null) {\r\n                    return false;\r\n                }\r\n                int chunkX = ((ClientboundLevelChunkWithLightPacket) packet).getX();\r\n                int chunkZ = ((ClientboundLevelChunkWithLightPacket) packet).getZ();\r\n                ChunkCoordinate chunkCoord = new ChunkCoordinate(chunkX, chunkZ, player.getLevel().getWorld().getName());\r\n                List<FakeBlock> blocks = FakeBlock.getFakeBlocksFor(player.getUUID(), chunkCoord);\r\n                if (blocks == null || blocks.isEmpty()) {\r\n                    return false;\r\n                }\r\n                ClientboundLevelChunkWithLightPacket newPacket = FakeBlockHelper.handleMapChunkPacket(player.getBukkitEntity().getWorld(), (ClientboundLevelChunkWithLightPacket) packet, chunkX, chunkZ, blocks);\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundSectionBlocksUpdatePacket) {\r\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(player.getUUID());\r\n                if (map == null) {\r\n                    return false;\r\n                }\r\n                SectionPos coord = (SectionPos) SECTIONPOS_MULTIBLOCKCHANGE.get(packet);\r\n                ChunkCoordinate coordinateDenizen = new ChunkCoordinate(coord.getX(), coord.getZ(), player.getLevel().getWorld().getName());\r\n                if (!map.byChunk.containsKey(coordinateDenizen)) {\r\n                    return false;\r\n                }\r\n                ClientboundSectionBlocksUpdatePacket newPacket = new ClientboundSectionBlocksUpdatePacket(copyPacket(packet));\r\n                LocationTag location = new LocationTag(player.getLevel().getWorld(), 0, 0, 0);\r\n                short[] originalOffsetArray = (short[])OFFSETARRAY_MULTIBLOCKCHANGE.get(newPacket);\r\n                BlockState[] originalDataArray = (BlockState[])BLOCKARRAY_MULTIBLOCKCHANGE.get(newPacket);\r\n                short[] offsetArray = Arrays.copyOf(originalOffsetArray, originalOffsetArray.length);\r\n                BlockState[] dataArray = Arrays.copyOf(originalDataArray, originalDataArray.length);\r\n                OFFSETARRAY_MULTIBLOCKCHANGE.set(newPacket, offsetArray);\r\n                BLOCKARRAY_MULTIBLOCKCHANGE.set(newPacket, dataArray);\r\n                for (int i = 0; i < offsetArray.length; i++) {\r\n                    short offset = offsetArray[i];\r\n                    BlockPos pos = coord.relativeToBlockPos(offset);\r\n                    location.setX(pos.getX());\r\n                    location.setY(pos.getY());\r\n                    location.setZ(pos.getZ());\r\n                    FakeBlock block = map.byLocation.get(location);\r\n                    if (block != null) {\r\n                        dataArray[i] = FakeBlockHelper.getNMSState(block);\r\n                    }\r\n                }\r\n                oldManager.send(newPacket, genericfuturelistener);\r\n                return true;\r\n            }\r\n            else if (packet instanceof ClientboundBlockUpdatePacket) {\r\n                BlockPos pos = ((ClientboundBlockUpdatePacket) packet).getPos();\r\n                LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ());\r\n                FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc);\r\n                if (block != null) {\r\n                    ClientboundBlockUpdatePacket newPacket = new ClientboundBlockUpdatePacket(((ClientboundBlockUpdatePacket) packet).getPos(), FakeBlockHelper.getNMSState(block));\r\n                    oldManager.send(newPacket, genericfuturelistener);\r\n                    return true;\r\n                }\r\n            }\r\n            else if (packet instanceof ClientboundBlockChangedAckPacket) {\r\n                // TODO: 1.19: Can no longer determine what block this packet is for. Would have to track separately? Possibly from the inbound packet rather than the outbound one.\r\n                /*\r\n                ClientboundBlockChangedAckPacket origPack = (ClientboundBlockChangedAckPacket) packet;\r\n                BlockPos pos = origPack.pos();\r\n                LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ());\r\n                FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc);\r\n                if (block != null) {\r\n                    ClientboundBlockChangedAckPacket newPacket = new ClientboundBlockChangedAckPacket(origPack.pos(), FakeBlockHelper.getNMSState(block), origPack.action(), false);\r\n                    oldManager.send(newPacket, genericfuturelistener);\r\n                    return true;\r\n                }*/\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void processBlockLightForPacket(Packet<?> packet) {\r\n        if (BlockLight.lightsByChunk.isEmpty()) {\r\n            return;\r\n        }\r\n        if (packet instanceof ClientboundLightUpdatePacket) {\r\n            BlockLightImpl.checkIfLightsBrokenByPacket((ClientboundLightUpdatePacket) packet, player.level);\r\n        }\r\n        else if (packet instanceof ClientboundBlockUpdatePacket) {\r\n            BlockLightImpl.checkIfLightsBrokenByPacket((ClientboundBlockUpdatePacket) packet, player.level);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldManager.tick();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRemoteAddress() {\r\n        return oldManager.getRemoteAddress();\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        if (!player.getBukkitEntity().isOnline()) { // Workaround Paper duplicate quit event issue\r\n            return;\r\n        }\r\n        oldManager.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public boolean isMemoryConnection() {\r\n        return oldManager.isMemoryConnection();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getReceiving() {\r\n        return oldManager.getReceiving();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getSending() {\r\n        return oldManager.getSending();\r\n    }\r\n\r\n    @Override\r\n    public void setEncryptionKey(Cipher cipher, Cipher cipher1) {\r\n        oldManager.setEncryptionKey(cipher, cipher1);\r\n    }\r\n\r\n    @Override\r\n    public boolean isEncrypted() {\r\n        return oldManager.isEncrypted();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnected() {\r\n        return oldManager.isConnected();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnecting() {\r\n        return oldManager.isConnecting();\r\n    }\r\n\r\n    @Override\r\n    public PacketListener getPacketListener() {\r\n        return oldManager.getPacketListener();\r\n    }\r\n\r\n    @Override\r\n    public Component getDisconnectedReason() {\r\n        return oldManager.getDisconnectedReason();\r\n    }\r\n\r\n    @Override\r\n    public void setReadOnly() {\r\n        oldManager.setReadOnly();\r\n    }\r\n\r\n    @Override\r\n    public void setupCompression(int i, boolean b) {\r\n        oldManager.setupCompression(i, b);\r\n    }\r\n\r\n    @Override\r\n    public void handleDisconnection() {\r\n        oldManager.handleDisconnection();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageReceivedPackets() {\r\n        return oldManager.getAverageReceivedPackets();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageSentPackets() {\r\n        return oldManager.getAverageSentPackets();\r\n    }\r\n\r\n    //////////////////////////////////\r\n    //// Reflection Methods/Fields\r\n    ///////////\r\n\r\n    private static final Field protocolDirectionField = ReflectionHelper.getFields(Connection.class).get(ReflectionMappingsInfo.Connection_receiving, PacketFlow.class);\r\n    private static final Field ServerGamePacketListener_ConnectionField = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_connection);\r\n    private static final MethodHandle ServerGamePacketListener_ConnectionSetter = ReflectionHelper.getFinalSetter(ServerGamePacketListenerImpl.class, ReflectionMappingsInfo.ServerGamePacketListenerImpl_connection);\r\n\r\n    private static PacketFlow getProtocolDirection(Connection networkManager) {\r\n        PacketFlow direction = null;\r\n        try {\r\n            direction = (PacketFlow) protocolDirectionField.get(networkManager);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return direction;\r\n    }\r\n\r\n    private static void setNetworkManager(ServerGamePacketListenerImpl playerConnection, Connection networkManager) {\r\n        try {\r\n            ServerGamePacketListener_ConnectionSetter.invoke(playerConnection, networkManager);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean acceptInboundMessage(Object msg) throws Exception {\r\n        return oldManager.acceptInboundMessage(msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\r\n        oldManager.channelRead(ctx, msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelReadComplete(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\r\n        oldManager.userEventTriggered(ctx, evt);\r\n    }\r\n\r\n    @Override\r\n    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelWritabilityChanged(ctx);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/network/handlers/DenizenPacketListenerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerChangesSignScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerSteersEntityScriptEvent;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.network.packets.PacketInResourcePackStatusImpl;\r\nimport com.denizenscript.denizen.nms.v1_19.impl.network.packets.PacketInSteerVehicleImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\r\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.event.block.SignChangeEvent;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\n\r\npublic class DenizenPacketListenerImpl extends AbstractListenerPlayInImpl {\r\n\r\n    public String brand = \"unknown\";\r\n\r\n    public BlockPos fakeSignExpected;\r\n\r\n    public DenizenPacketListenerImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer) {\r\n        super(networkManager, entityPlayer, entityPlayer.connection);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(final ServerboundPlayerInputPacket packet) {\r\n        if (!PlayerSteersEntityScriptEvent.instance.eventData.isEnabled) {\r\n            super.handlePlayerInput(packet);\r\n            return;\r\n        }\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInSteerVehicleImpl(packet), () -> super.handlePlayerInput(packet));\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInResourcePackStatusImpl(packet));\r\n        super.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        DenizenPacketHandler.instance.receivePlacePacket(player.getBukkitEntity());\r\n        super.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        DenizenPacketHandler.instance.receiveDigPacket(player.getBukkitEntity());\r\n        super.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && (override.hand != null || override.offhand != null)) {\r\n            player.getBukkitEntity().updateInventory();\r\n        }\r\n        super.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && override.hand != null) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 2);\r\n        }\r\n        super.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && packet.getContainerId() == 0) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 1);\r\n        }\r\n        super.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        if (NMSHandler.debugPackets) {\r\n            Debug.log(\"Custom packet payload: \" + packet.identifier.toString() + \" sent from \" + player.getScoreboardName());\r\n        }\r\n        if (packet.identifier.getNamespace().equals(\"minecraft\") && packet.identifier.getPath().equals(\"brand\")) {\r\n            FriendlyByteBuf newData = new FriendlyByteBuf(packet.data.copy());\r\n            int i = newData.readVarInt(); // read off the varInt of length to get rid of it\r\n            brand = StandardCharsets.UTF_8.decode(newData.nioBuffer()).toString();\r\n        }\r\n        super.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        if (fakeSignExpected != null && packet.getPos().equals(fakeSignExpected)) {\r\n            fakeSignExpected = null;\r\n            PlayerChangesSignScriptEvent evt = (PlayerChangesSignScriptEvent) PlayerChangesSignScriptEvent.instance.clone();\r\n            evt.material = new MaterialTag(org.bukkit.Material.OAK_WALL_SIGN);\r\n            evt.location = new LocationTag(player.getBukkitEntity().getLocation());\r\n            LocationTag loc = evt.location.clone();\r\n            loc.setY(0);\r\n            evt.event = new SignChangeEvent(loc.getBlock(), player.getBukkitEntity(), packet.getLines());\r\n            evt.fire(evt.event);\r\n        }\r\n        super.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        if (DenizenPacketHandler.forceNoclip.contains(player.getUUID())) {\r\n            player.noPhysics = true;\r\n        }\r\n        super.handleMovePlayer(packet);\r\n    }\r\n\r\n    // For compatibility with other plugins using Reflection weirdly...\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        super.send(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/network/handlers/FakeBlockHelper.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.Biomes;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.PalettedContainer;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_19_R3.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Constructor;\r\nimport java.lang.reflect.Field;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\nimport java.util.ListIterator;\r\n\r\npublic class FakeBlockHelper {\r\n\r\n    public static Field CHUNKDATA_BLOCK_ENTITIES = ReflectionHelper.getFields(ClientboundLevelChunkPacketData.class).getFirstOfType(List.class);\r\n    public static MethodHandle CHUNKDATA_BUFFER_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkPacketData.class, byte[].class);\r\n    public static Class CHUNKDATA_BLOCKENTITYINFO_CLASS = ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0];\r\n    public static Field CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get(ReflectionMappingsInfo.ClientboundLevelChunkPacketDataBlockEntityInfo_packedXZ);\r\n    public static Field CHUNKDATA_BLOCKENTITYINFO_Y = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get(ReflectionMappingsInfo.ClientboundLevelChunkPacketDataBlockEntityInfo_y);\r\n    public static MethodHandle CHUNKPACKET_CHUNKDATA_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class);\r\n    public static Constructor<?> PALETTEDCONTAINER_CTOR = Arrays.stream(PalettedContainer.class.getConstructors()).filter(c -> c.getParameterCount() == 3).findFirst().get();\r\n\r\n    public static BlockState getNMSState(FakeBlock block) {\r\n        return ((CraftBlockData) block.material.getModernData()).getState();\r\n    }\r\n\r\n    public static boolean anyBlocksInSection(List<FakeBlock> blocks, int y) {\r\n        int minY = y << 4;\r\n        int maxY = (y << 4) + 16;\r\n        for (FakeBlock block : blocks) {\r\n            int blockY = block.location.getBlockY();\r\n            if (blockY >= minY && blockY < maxY) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static Field PAPER_CHUNK_READY;\r\n    public static boolean tryPaperPatch = true;\r\n\r\n    public static void copyPacketPaperPatch(ClientboundLevelChunkWithLightPacket newPacket, ClientboundLevelChunkWithLightPacket oldPacket) {\r\n        if (!Denizen.supportsPaper || !tryPaperPatch) {\r\n            return;\r\n        }\r\n        try {\r\n            if (PAPER_CHUNK_READY == null) {\r\n                PAPER_CHUNK_READY = ReflectionHelper.getFields(ClientboundLevelChunkWithLightPacket.class).get(\"ready\");\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            tryPaperPatch = false;\r\n            Debug.echoError(\"Paper packet patch failed:\");\r\n            Debug.echoError(ex);\r\n            return;\r\n        }\r\n        try {\r\n            PAPER_CHUNK_READY.setBoolean(newPacket, true);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static ClientboundLevelChunkWithLightPacket handleMapChunkPacket(World world, ClientboundLevelChunkWithLightPacket originalPacket, int chunkX, int chunkZ, List<FakeBlock> blocks) {\r\n        try {\r\n            ClientboundLevelChunkWithLightPacket duplicateCorePacket = new ClientboundLevelChunkWithLightPacket(DenizenNetworkManagerImpl.copyPacket(originalPacket));\r\n            copyPacketPaperPatch(duplicateCorePacket, originalPacket);\r\n            FriendlyByteBuf copier = new FriendlyByteBuf(Unpooled.buffer());\r\n            originalPacket.getChunkData().write(copier);\r\n            ClientboundLevelChunkPacketData packet = new ClientboundLevelChunkPacketData(copier, chunkX, chunkZ);\r\n            FriendlyByteBuf serial = originalPacket.getChunkData().getReadBuffer();\r\n            FriendlyByteBuf outputSerial = new FriendlyByteBuf(Unpooled.buffer(serial.readableBytes()));\r\n            List blockEntities = new ArrayList((List) CHUNKDATA_BLOCK_ENTITIES.get(originalPacket.getChunkData()));\r\n            CHUNKDATA_BLOCK_ENTITIES.set(packet, blockEntities);\r\n            ListIterator iterator = blockEntities.listIterator();\r\n            while (iterator.hasNext()) {\r\n                Object blockEnt = iterator.next();\r\n                int xz = CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ.getInt(blockEnt);\r\n                int y = CHUNKDATA_BLOCKENTITYINFO_Y.getInt(blockEnt);\r\n                int x = (chunkX << 4) + ((xz >> 4) & 15);\r\n                int z = (chunkZ << 4) + (xz & 15);\r\n                for (FakeBlock block : blocks) {\r\n                    LocationTag loc = block.location;\r\n                    if (loc.getBlockX() == x && loc.getBlockY() == y && loc.getBlockZ() == z && block.material != null) {\r\n                        iterator.remove();\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            int worldMinY = world.getMinHeight();\r\n            int worldMaxY = world.getMaxHeight();\r\n            int minChunkY = worldMinY >> 4;\r\n            int maxChunkY = worldMaxY >> 4;\r\n            Registry<Biome> biomeRegistry = ((CraftWorld) world).getHandle().registryAccess().registryOrThrow(Registries.BIOME);\r\n            for (int y = minChunkY; y < maxChunkY; y++) {\r\n                int blockCount = serial.readShort();\r\n                // reflected constructors as workaround for spigot remapper bug - Mojang \"IdMap\" became Spigot \"IRegistry\" but should be \"Registry\"\r\n                PalettedContainer<BlockState> states = (PalettedContainer<BlockState>) PALETTEDCONTAINER_CTOR.newInstance(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);\r\n                states.read(serial);\r\n                PalettedContainer<Biome> biomes = (PalettedContainer<Biome>) PALETTEDCONTAINER_CTOR.newInstance(biomeRegistry, biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);\r\n                biomes.read(serial);\r\n                if (anyBlocksInSection(blocks, y)) {\r\n                    int minY = y << 4;\r\n                    int maxY = (y << 4) + 16;\r\n                    for (FakeBlock block : blocks) {\r\n                        int blockY = block.location.getBlockY();\r\n                        if (blockY >= minY && blockY < maxY && block.material != null) {\r\n                            int blockX = block.location.getBlockX();\r\n                            int blockZ = block.location.getBlockZ();\r\n                            blockX -= (blockX >> 4) * 16;\r\n                            blockY -= (blockY >> 4) * 16;\r\n                            blockZ -= (blockZ >> 4) * 16;\r\n                            BlockState oldState = states.get(blockX, blockY, blockZ);\r\n                            BlockState newState = getNMSState(block);\r\n                            if (oldState.isAir() && !newState.isAir()) {\r\n                                blockCount++;\r\n                            }\r\n                            else if (newState.isAir() && !oldState.isAir()) {\r\n                                blockCount--;\r\n                            }\r\n                            states.set(blockX, blockY, blockZ, newState);\r\n                        }\r\n                    }\r\n                }\r\n                outputSerial.writeShort(blockCount);\r\n                states.write(outputSerial);\r\n                biomes.write(outputSerial);\r\n            }\r\n            byte[] outputBytes = outputSerial.array();\r\n            CHUNKDATA_BUFFER_SETTER.invoke(packet, outputBytes);\r\n            CHUNKPACKET_CHUNKDATA_SETTER.invoke(duplicateCorePacket, packet);\r\n            return duplicateCorePacket;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/network/packets/PacketInResourcePackStatusImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInResourcePackStatus;\r\nimport net.minecraft.network.protocol.game.ServerboundResourcePackPacket;\r\n\r\npublic class PacketInResourcePackStatusImpl implements PacketInResourcePackStatus {\r\n\r\n    private ServerboundResourcePackPacket internal;\r\n\r\n    public PacketInResourcePackStatusImpl(ServerboundResourcePackPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public String getStatus() {\r\n        return internal.action.name();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/network/packets/PacketInSteerVehicleImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInSteerVehicle;\r\nimport net.minecraft.network.protocol.game.ServerboundPlayerInputPacket;\r\n\r\npublic class PacketInSteerVehicleImpl implements PacketInSteerVehicle {\r\n\r\n    private ServerboundPlayerInputPacket internal;\r\n\r\n    public PacketInSteerVehicleImpl(ServerboundPlayerInputPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public float getLeftwardInput() {\r\n        return internal.getXxa();\r\n    }\r\n\r\n    @Override\r\n    public float getForwardInput() {\r\n        return internal.getZza();\r\n    }\r\n\r\n    @Override\r\n    public boolean getJumpInput() {\r\n        return internal.isJumping();\r\n    }\r\n\r\n    @Override\r\n    public boolean getDismountInput() {\r\n        return internal.isShiftKeyDown();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/impl/network/packets/PacketOutChatImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_19.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSystemChatPacket;\r\n\r\nimport java.lang.reflect.Field;\r\n\r\npublic class PacketOutChatImpl extends PacketOutChat {\r\n\r\n    public ClientboundPlayerChatPacket playerPacket;\r\n    public ClientboundSystemChatPacket systemPacket;\r\n    public String message;\r\n    public String rawJson;\r\n    public boolean isOverlayActionbar;\r\n\r\n    public static Field paperTextField;\r\n\r\n    public PacketOutChatImpl(ClientboundSystemChatPacket internal) {\r\n        systemPacket = internal;\r\n        rawJson = internal.content();\r\n        if (rawJson == null && convertComponentToJsonString != null) {\r\n            try {\r\n                if (paperTextField == null) {\r\n                    paperTextField = ReflectionHelper.getFields(ClientboundSystemChatPacket.class).get(\"adventure$content\");\r\n                }\r\n                if (paperTextField != null) {\r\n                    rawJson = convertComponentToJsonString.apply(paperTextField.get(internal));\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        message = FormattedTextHelper.stringify(ComponentSerializer.parse(rawJson));\r\n        isOverlayActionbar = internal.overlay();\r\n    }\r\n\r\n    public PacketOutChatImpl(ClientboundPlayerChatPacket internal) {\r\n        playerPacket = internal;\r\n        rawJson = ComponentSerializer.toString(internal.body().content());\r\n        message = FormattedTextHelper.stringify(ComponentSerializer.parse(rawJson));\r\n    }\r\n\r\n    @Override\r\n    public boolean isSystem() {\r\n        return systemPacket != null;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActionbar() {\r\n        return isOverlayActionbar;\r\n    }\r\n\r\n    @Override\r\n    public String getMessage() {\r\n        return message;\r\n    }\r\n\r\n    @Override\r\n    public String getRawJson() {\r\n        return rawJson;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.denizenscript</groupId>\n    <artifactId>denizen-v1_20</artifactId>\n    <version>1.3.2-SNAPSHOT</version>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen</artifactId>\n            <version>${project.version}</version>\n            <scope>compile</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>1.20.6-R0.1-SNAPSHOT</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot</artifactId>\n            <version>1.20.6-R0.1-SNAPSHOT</version>\n            <classifier>remapped-mojang</classifier>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>net.md-5</groupId>\n                <artifactId>specialsource-maven-plugin</artifactId>\n                <version>2.0.2</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>remap</goal>\n                        </goals>\n                        <id>remap-obf</id>\n                        <configuration>\n                            <srgIn>org.spigotmc:minecraft-server:1.20.6-R0.1-SNAPSHOT:txt:maps-mojang</srgIn>\n                            <reverse>true</reverse>\n                            <remappedDependencies>org.spigotmc:spigot:1.20.6-R0.1-SNAPSHOT:jar:remapped-mojang</remappedDependencies>\n                            <remappedArtifactAttached>true</remappedArtifactAttached>\n                            <remappedClassifierName>remapped-obf</remappedClassifierName>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>remap</goal>\n                        </goals>\n                        <id>remap-spigot</id>\n                        <configuration>\n                            <inputFile>${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar</inputFile>\n                            <srgIn>org.spigotmc:minecraft-server:1.20.6-R0.1-SNAPSHOT:csrg:maps-spigot</srgIn>\n                            <remappedDependencies>org.spigotmc:spigot:1.20.6-R0.1-SNAPSHOT:jar:remapped-obf</remappedDependencies>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.10.1</version>\n                <configuration>\n                    <source>16</source>\n                    <target>16</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/Handler.java",
    "content": "package com.denizenscript.denizen.nms.v1_20;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_20.helpers.*;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.blocks.BlockLightImpl;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.core.QuaternionTag;\r\nimport com.denizenscript.denizencore.scripts.commands.core.ReflectionSetCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.mojang.authlib.yggdrasil.ProfileResult;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.Rotations;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.nbt.ByteArrayTag;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.nbt.StringTag;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.BossEvent;\r\nimport net.minecraft.world.Container;\r\nimport net.minecraft.world.Nameable;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.World;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_20_R4.boss.CraftBossBar;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryCustom;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_20_R4.legacy.FieldRename;\r\nimport org.bukkit.craftbukkit.v1_20_R4.persistence.CraftPersistentDataContainer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.*;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryView;\r\nimport org.bukkit.persistence.PersistentDataContainer;\r\nimport org.joml.Quaternionf;\r\nimport org.joml.Vector3f;\r\nimport org.spigotmc.AsyncCatcher;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\nimport java.util.function.Function;\r\n\r\npublic class Handler extends NMSHandler {\r\n\r\n    public Handler() {\r\n        advancementHelper = new AdvancementHelperImpl();\r\n        animationHelper = new AnimationHelperImpl();\r\n        blockHelper = new BlockHelperImpl();\r\n        chunkHelper = new ChunkHelperImpl();\r\n        customEntityHelper = new CustomEntityHelperImpl();\r\n        entityHelper = new EntityHelperImpl();\r\n        fishingHelper = new FishingHelperImpl();\r\n        itemHelper = new ItemHelperImpl();\r\n        packetHelper = new PacketHelperImpl();\r\n        playerHelper = new PlayerHelperImpl();\r\n        worldHelper = new WorldHelperImpl();\r\n        enchantmentHelper = new EnchantmentHelperImpl();\r\n\r\n        registerConversion(ItemTag.class, ItemStack.class, item -> CraftItemStack.asNMSCopy(item.getItemStack()));\r\n        registerConversion(ElementTag.class, Component.class, element -> componentToNMS(FormattedTextHelper.parse(element.asString(), ChatColor.WHITE)));\r\n        registerConversion(MaterialTag.class, BlockState.class, material -> ((CraftBlockData) material.getModernData()).getState());\r\n        registerConversion(LocationTag.class, Rotations.class, location -> new Rotations((float) location.getX(), (float) location.getY(), (float) location.getZ()));\r\n        registerConversion(LocationTag.class, BlockPos.class, CraftLocation::toBlockPosition);\r\n        registerConversion(MapTag.class, CompoundTag.class, map -> {\r\n            CompoundBinaryTag compoundTag = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(map, CoreUtilities.noDebugContext, \"(item).\");\r\n            return compoundTag != null ? NBTAdapter.toNMS(compoundTag) : null;\r\n        });\r\n        registerConversion(LocationTag.class, Vector3f.class, location -> new Vector3f((float) location.getX(), (float) location.getY(), (float) location.getZ()));\r\n        registerConversion(QuaternionTag.class, Quaternionf.class, quaternion -> new Quaternionf(quaternion.x, quaternion.y, quaternion.z, quaternion.w));\r\n    }\r\n\r\n    public static <DT extends ObjectTag, JT> void registerConversion(Class<DT> denizenType, Class<JT> javaType, Function<DT, JT> convertor) {\r\n        ReflectionSetCommand.typeConverters.put(javaType, objectTag -> {\r\n            DT denizenObject = objectTag.asType(denizenType, CoreUtilities.noDebugContext);\r\n            return denizenObject != null ? convertor.apply(denizenObject) : null;\r\n        });\r\n    }\r\n\r\n    private final ProfileEditor profileEditor = new ProfileEditorImpl();\r\n\r\n    private boolean wasAsyncCatcherEnabled;\r\n\r\n    @Override\r\n    public void disableAsyncCatcher() {\r\n        wasAsyncCatcherEnabled = AsyncCatcher.enabled;\r\n        AsyncCatcher.enabled = false;\r\n    }\r\n\r\n    @Override\r\n    public void undisableAsyncCatcher() {\r\n        AsyncCatcher.enabled = wasAsyncCatcherEnabled;\r\n    }\r\n\r\n    @Override\r\n    public boolean isExactServerVersionMatch() {\r\n        return ((CraftMagicNumbers) CraftMagicNumbers.INSTANCE).getMappingsVersion().equals(\"ee13f98a43b9c5abffdcc0bb24154460\");\r\n    }\r\n\r\n    @Override\r\n    public double[] getRecentTps() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().recentTps;\r\n    }\r\n\r\n    @Override\r\n    public Sidebar createSidebar(Player player) {\r\n        return new SidebarImpl(player);\r\n    }\r\n\r\n    @Override\r\n    public BlockLight createBlockLight(Location location, int lightLevel, long ticks) {\r\n        return BlockLightImpl.createLight(location, lightLevel, ticks);\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile fillPlayerProfile(PlayerProfile playerProfile) {\r\n        if (playerProfile == null) {\r\n            return null;\r\n        }\r\n        if (playerProfile.getName() == null && playerProfile.getUniqueId() == null) {\r\n            return playerProfile; // Cannot fill without lookup data\r\n        }\r\n        if (playerProfile.hasTexture() && playerProfile.hasTextureSignature() && playerProfile.getName() != null && playerProfile.getUniqueId() != null) {\r\n            return playerProfile; // Already filled\r\n        }\r\n        try {\r\n            GameProfile profile = null;\r\n            MinecraftServer minecraftServer = ((CraftServer) Bukkit.getServer()).getServer();\r\n            if (playerProfile.getUniqueId() != null) {\r\n                profile = minecraftServer.getProfileCache().get(playerProfile.getUniqueId()).orElse(null);\r\n            }\r\n            if (profile == null && playerProfile.getName() != null) {\r\n                profile = minecraftServer.getProfileCache().get(playerProfile.getName()).orElse(null);\r\n            }\r\n            if (profile == null) {\r\n                profile = ProfileEditorImpl.getGameProfileNoProperties(playerProfile);\r\n            }\r\n            Property textures = profile.getProperties().containsKey(\"textures\") ? Iterables.getFirst(profile.getProperties().get(\"textures\"), null) : null;\r\n            if (textures == null || !textures.hasSignature() || profile.getName() == null || profile.getId() == null) {\r\n                ProfileResult actualProfile = minecraftServer.getSessionService().fetchProfile(profile.getId(), true);\r\n                if (actualProfile == null) {\r\n                    return null;\r\n                }\r\n                profile = actualProfile.profile();\r\n                textures = profile.getProperties().containsKey(\"textures\") ? Iterables.getFirst(profile.getProperties().get(\"textures\"), null) : null;\r\n            }\r\n            return new PlayerProfile(profile.getName(), profile.getId(), textures == null ? null : textures.value(), textures == null ? null : textures.signature());\r\n        }\r\n        catch (Exception e) {\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static MethodHandle PAPER_INVENTORY_TITLE_GETTER;\r\n\r\n    @Override\r\n    public String getTitle(Inventory inventory) {\r\n        Container nms = ((CraftInventory) inventory).getInventory();\r\n        if (inventory instanceof CraftInventoryCustom && Denizen.supportsPaper) {\r\n            try {\r\n                if (PAPER_INVENTORY_TITLE_GETTER == null) {\r\n                    PAPER_INVENTORY_TITLE_GETTER = ReflectionHelper.getMethodHandle(nms.getClass(), \"title\");\r\n                }\r\n                return PaperAPITools.instance.parseComponent(PAPER_INVENTORY_TITLE_GETTER.invoke(nms));\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        if (nms instanceof Nameable) {\r\n            return CraftChatMessage.fromComponent(((Nameable) nms).getDisplayName());\r\n        }\r\n        else if (MINECRAFT_INVENTORY.isInstance(nms)) {\r\n            try {\r\n                return (String) INVENTORY_TITLE.get(nms);\r\n            }\r\n            catch (IllegalAccessException e) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return \"Chest\";\r\n    }\r\n\r\n    public static MethodHandle AbstractContainerMenu_title_SETTER = ReflectionHelper.getFinalSetter(AbstractContainerMenu.class, \"title\");\r\n\r\n    @Override\r\n    public void setInventoryTitle(InventoryView view, String title) {\r\n        AbstractContainerMenu menu = ((CraftInventoryView) view).getHandle();\r\n        try {\r\n            AbstractContainerMenu_title_SETTER.invoke(menu, componentToNMS(FormattedTextHelper.parse(title, ChatColor.DARK_GRAY)));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final Class MINECRAFT_INVENTORY;\r\n    public static final Field INVENTORY_TITLE;\r\n    public static final Field ENTITY_BUKKITYENTITY = ReflectionHelper.getFields(Entity.class).get(\"bukkitEntity\");\r\n\r\n    static {\r\n        Class minecraftInv = null;\r\n        Field title = null;\r\n        try {\r\n            for (Class clzz : CraftInventoryCustom.class.getDeclaredClasses()) {\r\n                if (CoreUtilities.toLowerCase(clzz.getName()).contains(\"minecraftinventory\")) { // MinecraftInventory.\r\n                    minecraftInv = clzz;\r\n                    title = clzz.getDeclaredField(\"title\");\r\n                    title.setAccessible(true);\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            e.printStackTrace();\r\n        }\r\n        MINECRAFT_INVENTORY = minecraftInv;\r\n        INVENTORY_TITLE = title;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Player player) {\r\n        GameProfile gameProfile = ((CraftPlayer) player).getProfile();\r\n        Property property = Iterables.getFirst(gameProfile.getProperties().get(\"textures\"), null);\r\n        return new PlayerProfile(gameProfile.getName(), gameProfile.getId(),\r\n                property != null ? property.value() : null,\r\n                property != null ? property.signature() : null);\r\n    }\r\n\r\n    @Override\r\n    public ProfileEditor getProfileEditor() {\r\n        return profileEditor;\r\n    }\r\n\r\n    @Override\r\n    public List<BiomeNMS> getBiomes(World world) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        ArrayList<BiomeNMS> output = new ArrayList<>();\r\n        for (Map.Entry<ResourceKey<Biome>, Biome> pair : level.registryAccess().registryOrThrow(Registries.BIOME).entrySet()) {\r\n            output.add(new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(pair.getKey().location())));\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeNMS(World world, NamespacedKey key) {\r\n        BiomeNMSImpl impl = new BiomeNMSImpl(((CraftWorld) world).getHandle(), key);\r\n        if (impl.biomeHolder == null) {\r\n            return null;\r\n        }\r\n        return impl;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeAt(Block block) {\r\n        // Based on CraftWorld source\r\n        ServerLevel level = ((CraftWorld) block.getWorld()).getHandle();\r\n        Holder<Biome> biome = level.getNoiseBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2);\r\n        ResourceLocation key = level.registryAccess().registryOrThrow(Registries.BIOME).getKey(biome.value());\r\n        return new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key));\r\n    }\r\n\r\n    @Override\r\n    public ArrayList<String> containerListFlags(PersistentDataContainer container, String prefix) {\r\n        prefix = \"denizen:\" + prefix;\r\n        ArrayList<String> output = new ArrayList<>();\r\n        for (String key : ((CraftPersistentDataContainer) container).getRaw().keySet()) {\r\n            if (key.startsWith(prefix)) {\r\n                output.add(key.substring(prefix.length()));\r\n            }\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public boolean containerHas(PersistentDataContainer container, String key) {\r\n        return ((CraftPersistentDataContainer) container).getRaw().containsKey(key);\r\n    }\r\n\r\n    @Override\r\n    public String containerGetString(PersistentDataContainer container, String key) {\r\n        net.minecraft.nbt.Tag base = ((CraftPersistentDataContainer) container).getRaw().get(key);\r\n        if (base instanceof StringTag) {\r\n            return base.getAsString();\r\n        }\r\n        else if (base instanceof ByteArrayTag) {\r\n            return new String(((ByteArrayTag) base).getAsByteArray(), StandardCharsets.UTF_8);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public UUID getBossbarUUID(BossBar bar) {\r\n        return ((CraftBossBar) bar).getHandle().getId();\r\n    }\r\n\r\n    public static MethodHandle BOSSBAR_ID_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BossEvent.class, UUID.class);\r\n\r\n    @Override\r\n    public void setBossbarUUID(BossBar bar, UUID id) {\r\n        try {\r\n            BOSSBAR_ID_SETTER.invoke(((CraftBossBar) bar).getHandle(), id);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static BaseComponent[] componentToSpigot(Component nms) {\r\n        if (nms == null) {\r\n            return null;\r\n        }\r\n        return ComponentSerializer.parse(CraftChatMessage.toJSON(nms));\r\n    }\r\n\r\n    public static Component componentToNMS(BaseComponent[] spigot) {\r\n        if (spigot == null) {\r\n            return null;\r\n        }\r\n        return CraftChatMessage.fromJSONOrNull(FormattedTextHelper.componentToJson(spigot));\r\n    }\r\n\r\n    @Override\r\n    public String updateLegacyName(Class<?> type, String legacyName) {\r\n        return FieldRename.rename(ApiVersion.FIELD_NAME_PARITY, DebugInternals.getFullClassNameOpti(type).replace('.', '/'), legacyName);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/ReflectionMappingsInfo.java",
    "content": "package com.denizenscript.denizen.nms.v1_20;\r\n\r\npublic class ReflectionMappingsInfo {\r\n\r\n    // Content generated by ReflectionMappingsGenerator - https://github.com/DenizenScript/ReflectionMappingsGenerator\r\n\r\n    // net.minecraft.world.level.block.state.BlockBehaviour\r\n    public static String BlockBehaviour_explosionResistance = \"aH\";\r\n\r\n    // net.minecraft.core.MappedRegistry\r\n    public static String MappedRegistry_frozen = \"l\";\r\n    public static String MappedRegistry_unregisteredIntrusiveHolders = \"m\";\r\n\r\n    // net.minecraft.world.item.crafting.RecipeManager\r\n    public static String RecipeManager_byName = \"e\";\r\n\r\n    // net.minecraft.world.entity.Entity\r\n    public static String Entity_onGround = \"aI\";\r\n    public static String Entity_DATA_SHARED_FLAGS_ID = \"ap\";\r\n    public static String Entity_DATA_CUSTOM_NAME = \"aS\";\r\n    public static String Entity_DATA_CUSTOM_NAME_VISIBLE = \"aT\";\r\n\r\n    // net.minecraft.world.entity.LivingEntity\r\n    public static String LivingEntity_attackStrengthTicker = \"aT\";\r\n    public static String LivingEntity_autoSpinAttackTicks = \"bC\";\r\n    public static String LivingEntity_setLivingEntityFlag_method = \"c\";\r\n\r\n    // net.minecraft.world.entity.player.Player\r\n    public static String Player_DATA_PLAYER_ABSORPTION_ID = \"d\";\r\n    public static String Player_DATA_PLAYER_MODE_CUSTOMISATION = \"bV\";\r\n\r\n    // net.minecraft.server.level.ServerPlayer\r\n    public static String ServerPlayer_respawnForced = \"dl\";\r\n\r\n    // net.minecraft.world.entity.monster.EnderMan\r\n    public static String EnderMan_DATA_CREEPY = \"ca\";\r\n\r\n    // net.minecraft.world.entity.monster.Zombie\r\n    public static String Zombie_inWaterTime = \"ci\";\r\n\r\n    // net.minecraft.world.item.Item\r\n    public static String Item_components = \"c\";\r\n\r\n    // net.minecraft.world.level.Level\r\n    public static String Level_isClientSide = \"B\";\r\n\r\n    // net.minecraft.server.level.ThreadedLevelLightEngine\r\n    public static String ThreadedLevelLightEngine_addTask_method = \"a\";\r\n\r\n    // net.minecraft.server.level.ThreadedLevelLightEngine$TaskType\r\n    public static String ThreadedLevelLightEngineTaskType_PRE_UPDATE = \"a\";\r\n\r\n    // net.minecraft.world.entity.ExperienceOrb\r\n    public static String ExperienceOrb_age = \"g\";\r\n\r\n    // net.minecraft.world.entity.item.ItemEntity\r\n    public static String ItemEntity_DATA_ITEM = \"d\";\r\n\r\n    // net.minecraft.world.level.biome.Biome\r\n    public static String Biome_climateSettings = \"i\";\r\n\r\n    // net.minecraft.world.level.biome.BiomeSpecialEffects\r\n    public static String BiomeSpecialEffects_foliageColorOverride = \"f\";\r\n    public static String BiomeSpecialEffects_fogColor = \"b\";\r\n    public static String BiomeSpecialEffects_waterFogColor = \"d\";\r\n\r\n    // net.minecraft.network.Connection\r\n    public static String Connection_receiving = \"k\";\r\n    public static String Connection_packetListener = \"q\";\r\n\r\n    // net.minecraft.server.network.ServerGamePacketListenerImpl\r\n    public static String ServerGamePacketListenerImpl_aboveGroundTickCount = \"J\";\r\n    public static String ServerGamePacketListenerImpl_aboveGroundVehicleTickCount = \"L\";\r\n    public static String ServerGamePacketListenerImpl_awaitingPositionFromClient = \"F\";\r\n    public static String ServerGamePacketListenerImpl_awaitingTeleport = \"G\";\r\n    public static String ServerGamePacketListenerImpl_chunkSender = \"g\";\r\n\r\n    // net.minecraft.server.network.ServerCommonPacketListenerImpl\r\n    public static String ServerCommonPacketListenerImpl_connection = \"e\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket\r\n    public static String ClientboundPlayerAbilitiesPacket_walkingSpeed = \"k\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket\r\n    public static String ClientboundSectionBlocksUpdatePacket_sectionPos = \"c\";\r\n    public static String ClientboundSectionBlocksUpdatePacket_positions = \"d\";\r\n    public static String ClientboundSectionBlocksUpdatePacket_states = \"e\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundMoveEntityPacket\r\n    public static String ClientboundMoveEntityPacket_xa = \"b\";\r\n    public static String ClientboundMoveEntityPacket_ya = \"c\";\r\n    public static String ClientboundMoveEntityPacket_za = \"d\";\r\n    public static String ClientboundMoveEntityPacket_yRot = \"e\";\r\n    public static String ClientboundMoveEntityPacket_xRot = \"f\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket\r\n    public static String ClientboundSetEntityMotionPacket_id = \"b\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSetPassengersPacket\r\n    public static String ClientboundSetPassengersPacket_passengers = \"c\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket\r\n    public static String ClientboundTeleportEntityPacket_id = \"b\";\r\n    public static String ClientboundTeleportEntityPacket_x = \"c\";\r\n    public static String ClientboundTeleportEntityPacket_y = \"d\";\r\n    public static String ClientboundTeleportEntityPacket_z = \"e\";\r\n    public static String ClientboundTeleportEntityPacket_yRot = \"f\";\r\n    public static String ClientboundTeleportEntityPacket_xRot = \"g\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo\r\n    public static String ClientboundLevelChunkPacketDataBlockEntityInfo_packedXZ = \"c\";\r\n    public static String ClientboundLevelChunkPacketDataBlockEntityInfo_y = \"d\";\r\n\r\n    // net.minecraft.network.syncher.SynchedEntityData\r\n    public static String SynchedEntityData_itemsById = \"e\";\r\n\r\n    // net.minecraft.world.entity.projectile.FishingHook\r\n    public static String FishingHook_nibble = \"j\";\r\n    public static String FishingHook_timeUntilLured = \"k\";\r\n    public static String FishingHook_timeUntilHooked = \"l\";\r\n\r\n    // net.minecraft.tags.TagNetworkSerialization$NetworkPayload\r\n    public static String TagNetworkSerializationNetworkPayload_tags = \"a\";\r\n\r\n    // net.minecraft.core.HolderSet$Named\r\n    public static String HolderSetNamed_bind_method = \"b\";\r\n\r\n    // net.minecraft.core.Holder$Reference\r\n    public static String HolderReference_bindTags_method = \"a\";\r\n\r\n    // net.minecraft.server.level.ServerLevel\r\n    public static String ServerLevel_sleepStatus = \"P\";\r\n\r\n    // net.minecraft.world.item.AdventureModePredicate\r\n    public static String AdventureModePredicate_predicates = \"h\";\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/AdvancementHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.AdvancementHelper;\r\nimport com.denizenscript.denizen.nms.v1_20.Handler;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.google.common.collect.ImmutableMap;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.advancements.*;\r\nimport net.minecraft.advancements.critereon.ImpossibleTrigger;\r\nimport net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.PlayerAdvancements;\r\nimport net.minecraft.server.ServerAdvancementManager;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.*;\r\n\r\npublic class AdvancementHelperImpl extends AdvancementHelper {\r\n\r\n    private static final String IMPOSSIBLE_KEY = \"impossible\";\r\n    private static final Map<String, Criterion<?>> IMPOSSIBLE_CRITERIA = Map.of(IMPOSSIBLE_KEY, new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance()));\r\n    private static final List<List<String>> IMPOSSIBLE_REQUIREMENTS = List.of(List.of(IMPOSSIBLE_KEY));\r\n\r\n    public static ServerAdvancementManager getNMSAdvancementManager() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getAdvancements();\r\n    }\r\n\r\n    @Override\r\n    public void register(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || advancement.registered) {\r\n            return;\r\n        }\r\n        AdvancementHolder nmsAdvancementHolder = asNMSCopy(advancement);\r\n        Map<ResourceLocation, AdvancementHolder> nmsAdvancements = getNMSAdvancementManager().advancements;\r\n        ImmutableMap.Builder<ResourceLocation, AdvancementHolder> mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() + 1);\r\n        mapBuilder.putAll(nmsAdvancements);\r\n        mapBuilder.put(nmsAdvancementHolder.id(), nmsAdvancementHolder);\r\n        getNMSAdvancementManager().advancements = mapBuilder.build();\r\n\r\n        AdvancementTree tree = getNMSAdvancementManager().tree();\r\n        tree.addAll(List.of(nmsAdvancementHolder));\r\n        // recalculate advancement tree from this advancement's root\r\n        AdvancementNode node = tree.get(nmsAdvancementHolder.id());\r\n        if (node != null) {\r\n            AdvancementNode root = node.root();\r\n            if (root.holder().value().display().isPresent()) {\r\n                TreeNodePosition.run(root);\r\n            }\r\n        }\r\n        advancement.registered = true;\r\n        if (!advancement.hidden && advancement.parent != null) {\r\n            PacketHelperImpl.broadcast(new ClientboundUpdateAdvancementsPacket(false,\r\n                    List.of(nmsAdvancementHolder), Set.of(), Map.of()));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void unregister(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || !advancement.registered) {\r\n            return;\r\n        }\r\n        ResourceLocation nmsKey = CraftNamespacedKey.toMinecraft(advancement.key);\r\n        Map<ResourceLocation, AdvancementHolder> nmsAdvancements = getNMSAdvancementManager().advancements;\r\n        ImmutableMap.Builder<ResourceLocation, AdvancementHolder> mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() - 1);\r\n        for (Map.Entry<ResourceLocation, AdvancementHolder> entry : nmsAdvancements.entrySet()) {\r\n            if (!entry.getKey().equals(nmsKey)) {\r\n                mapBuilder.put(entry);\r\n            }\r\n        }\r\n        getNMSAdvancementManager().advancements = mapBuilder.build();\r\n        getNMSAdvancementManager().tree().remove(Set.of(nmsKey));\r\n        advancement.registered = false;\r\n        PacketHelperImpl.broadcast(new ClientboundUpdateAdvancementsPacket(false, List.of(), Set.of(nmsKey), Map.of()));\r\n    }\r\n\r\n    @Override\r\n    public void grantPartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) {\r\n        if (advancement.length <= 1) {\r\n            grant(advancement, player);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            AdvancementHolder nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS));\r\n            for (int i = 0; i < len; i++) {\r\n                progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria\r\n            }\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nmsAdvancement),\r\n                    Collections.emptySet(),\r\n                    Collections.singletonMap(nmsAdvancement.id(), progress)));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            for (int i = 0; i < len; i++) {\r\n                ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY + i);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void grant(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.length > 1) {\r\n            grantPartial(advancement, player, advancement.length);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            AdvancementHolder nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS));\r\n            progress.grantProgress(IMPOSSIBLE_KEY); // complete impossible criteria\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.singleton(nmsAdvancement),\r\n                    Collections.emptySet(),\r\n                    Collections.singletonMap(nmsAdvancement.id(), progress)));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void revoke(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.temporary) {\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false,\r\n                    Collections.emptySet(),\r\n                    Collections.singleton(CraftNamespacedKey.toMinecraft(advancement.key)),\r\n                    Collections.emptyMap()));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            ((CraftPlayer) player).getHandle().getAdvancements().revoke(nmsAdvancement, IMPOSSIBLE_KEY);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        nmsPlayer.connection.send(new ClientboundUpdateAdvancementsPacket(true,\r\n                Collections.emptySet(),\r\n                Collections.emptySet(),\r\n                Collections.emptyMap()));\r\n        PlayerAdvancements data = nmsPlayer.getAdvancements();\r\n        data.save(); // save progress\r\n        data.reload(getNMSAdvancementManager()); // clear progress\r\n        data.flushDirty(nmsPlayer); // load progress and update client\r\n    }\r\n\r\n    private static AdvancementHolder asNMSCopy(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        AdvancementHolder parent = advancement.parent != null\r\n                ? getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.parent))\r\n                : null;\r\n        DisplayInfo display = new DisplayInfo(CraftItemStack.asNMSCopy(advancement.icon),\r\n                Handler.componentToNMS(FormattedTextHelper.parse(advancement.title, ChatColor.WHITE)), Handler.componentToNMS(FormattedTextHelper.parse(advancement.description, ChatColor.WHITE)),\r\n                Optional.ofNullable(advancement.background).map(CraftNamespacedKey::toMinecraft), AdvancementType.valueOf(advancement.frame.name()),\r\n                advancement.toast, advancement.announceToChat, advancement.hidden);\r\n        display.setLocation(advancement.xOffset, advancement.yOffset);\r\n        Map<String, Criterion<?>> criteria = IMPOSSIBLE_CRITERIA;\r\n        List<List<String>> requirements = IMPOSSIBLE_REQUIREMENTS;\r\n        if (advancement.length > 1) {\r\n            criteria = new HashMap<>();\r\n            requirements = new ArrayList<>(advancement.length);\r\n            for (int i = 0; i < advancement.length; i++) {\r\n                criteria.put(IMPOSSIBLE_KEY + i, new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance()));\r\n                requirements.add(List.of(IMPOSSIBLE_KEY + i));\r\n            }\r\n        }\r\n        AdvancementRequirements reqs = new AdvancementRequirements(requirements);\r\n        Advancement adv = new Advancement(parent == null ? Optional.empty() : Optional.of(parent.id()), Optional.of(display), AdvancementRewards.EMPTY, criteria, reqs, false); // TODO: 1.20: do we want to ever enable telemetry?\r\n        return new AdvancementHolder(CraftNamespacedKey.toMinecraft(advancement.key), adv);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/AnimationHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.AnimationHelper;\r\nimport net.minecraft.world.entity.Entity;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftHorse;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPolarBear;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Horse;\r\nimport org.bukkit.entity.IronGolem;\r\n\r\npublic class AnimationHelperImpl extends AnimationHelper {\r\n\r\n    public AnimationHelperImpl() {\r\n        register(\"POLAR_BEAR_START_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(true);\r\n            }\r\n        });\r\n        register(\"POLAR_BEAR_STOP_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(false);\r\n            }\r\n        });\r\n        register(\"HORSE_START_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().setStanding(true);\r\n            }\r\n        });\r\n        register(\"HORSE_STOP_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().setStanding(false);\r\n            }\r\n        });\r\n        register(\"HORSE_BUCK\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().makeMad();\r\n            }\r\n        });\r\n        register(\"IRON_GOLEM_ATTACK\", entity -> {\r\n            if (entity instanceof IronGolem) {\r\n                Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n                nmsEntity.level().broadcastEntityEvent(nmsEntity, (byte) 4);\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/BlockHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.BlockHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.utilities.VanillaTagHelper;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.HolderSet;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.tags.TagKey;\r\nimport net.minecraft.tags.TagNetworkSerialization;\r\nimport net.minecraft.util.InclusiveRange;\r\nimport net.minecraft.util.random.SimpleWeightedRandomList;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.item.component.ResolvableProfile;\r\nimport net.minecraft.world.level.BaseSpawner;\r\nimport net.minecraft.world.level.SpawnData;\r\nimport net.minecraft.world.level.block.entity.BlockEntity;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockBehaviour;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.status.ChunkStatus;\r\nimport net.minecraft.world.level.material.FluidState;\r\nimport net.minecraft.world.level.material.PushReaction;\r\nimport org.bukkit.*;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.Skull;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftRegistry;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockEntityState;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.CraftSkull;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_20_R4.tag.CraftBlockTag;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftLocation;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.stream.Collectors;\r\n\r\npublic class BlockHelperImpl implements BlockHelper {\r\n\r\n    public static final Field craftBlockEntityState_tileEntity = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"tileEntity\");\r\n    public static final Field craftBlockEntityState_snapshot = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"snapshot\");\r\n    public static final Field craftSkull_profile = ReflectionHelper.getFields(CraftSkull.class).get(\"profile\");\r\n\r\n    @Override\r\n    public void applyPhysics(Location location) {\r\n        ((CraftWorld) location.getWorld()).getHandle().updateNeighborsAt(CraftLocation.toBlockPosition(location), CraftMagicNumbers.getBlock(location.getBlock().getType()));\r\n    }\r\n\r\n    public static <T extends BlockEntity> T getTE(CraftBlockEntityState<T> cbs) {\r\n        try {\r\n            return (T) craftBlockEntityState_tileEntity.get(cbs);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Skull skull) {\r\n        ResolvableProfile profile = getTE(((CraftSkull) skull)).owner;\r\n        if (profile == null) {\r\n            return null;\r\n        }\r\n        com.mojang.authlib.properties.Property property = Iterables.getFirst(profile.properties().get(\"textures\"), null);\r\n        return new PlayerProfile(profile.name().orElse(null), profile.id().orElse(null), property != null ? property.value() : null);\r\n    }\r\n\r\n    @Override\r\n    public void setPlayerProfile(Skull skull, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile);\r\n        try {\r\n            craftSkull_profile.set(skull, gameProfile);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        skull.update();\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Block block) {\r\n        BlockEntity te = ((CraftWorld) block.getWorld()).getHandle().getBlockEntity(new BlockPos(block.getX(), block.getY(), block.getZ()), true);\r\n        if (te != null) {\r\n            CompoundTag nmsData = te.saveWithFullMetadata(CraftRegistry.getMinecraftRegistry());\r\n            return NBTAdapter.toAPI(nmsData);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Block block, CompoundBinaryTag ctag) {\r\n        CompoundTag nmsData = NBTAdapter.toNMS(ctag);\r\n        nmsData.putInt(\"x\", block.getX());\r\n        nmsData.putInt(\"y\", block.getY());\r\n        nmsData.putInt(\"z\", block.getZ());\r\n        BlockPos blockPos = new BlockPos(block.getX(), block.getY(), block.getZ());\r\n        BlockEntity te = ((CraftWorld) block.getWorld()).getHandle().getBlockEntity(blockPos, true);\r\n        te.loadWithComponents(nmsData, CraftRegistry.getMinecraftRegistry());\r\n    }\r\n\r\n    @Override\r\n    public boolean setBlockResistance(Material material, float resistance) {\r\n        net.minecraft.world.level.block.Block block = CraftMagicNumbers.getBlock(material);\r\n        if (block == null) {\r\n            return false;\r\n        }\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, ReflectionMappingsInfo.BlockBehaviour_explosionResistance, block, resistance);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public float getBlockResistance(Material material) {\r\n        net.minecraft.world.level.block.Block block = CraftMagicNumbers.getBlock(material);\r\n        if (block == null) {\r\n            return 0;\r\n        }\r\n        return ReflectionHelper.getFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, ReflectionMappingsInfo.BlockBehaviour_explosionResistance, block);\r\n    }\r\n\r\n    public static final MethodHandle MATERIAL_PUSH_REACTION_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BlockBehaviour.BlockStateBase.class, PushReaction.class);\r\n\r\n    public static final MethodHandle BLOCK_STRENGTH_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase.class, float.class); // destroySpeed\r\n\r\n    public net.minecraft.world.level.block.state.BlockState getMaterialBlockState(Material bukkitMaterial) {\r\n        net.minecraft.world.level.block.Block nmsBlock = CraftMagicNumbers.getBlock(bukkitMaterial);\r\n        return nmsBlock != null ? nmsBlock.defaultBlockState() : null;\r\n    }\r\n\r\n    @Override\r\n    public void setPushReaction(Material mat, PistonPushReaction reaction) {\r\n        try {\r\n            MATERIAL_PUSH_REACTION_SETTER.invoke(getMaterialBlockState(mat), PushReaction.values()[reaction.ordinal()]);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBlockStrength(Material mat) {\r\n        return getMaterialBlockState(mat).destroySpeed;\r\n    }\r\n\r\n    @Override\r\n    public void setBlockStrength(Material mat, float strength) {\r\n        try {\r\n            BLOCK_STRENGTH_SETTER.invoke(getMaterialBlockState(mat), strength);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void doRandomTick(Location location) {\r\n        BlockPos pos = CraftLocation.toBlockPosition(location);\r\n        ChunkAccess nmsChunk = ((CraftChunk) location.getChunk()).getHandle(ChunkStatus.FULL);\r\n        net.minecraft.world.level.block.state.BlockState nmsBlock = nmsChunk.getBlockState(pos);\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        if (nmsBlock.isRandomlyTicking()) {\r\n            nmsBlock.randomTick(nmsWorld, pos, nmsWorld.random);\r\n        }\r\n        FluidState fluid = nmsBlock.getFluidState();\r\n        if (fluid.isRandomlyTicking()) {\r\n            fluid.animateTick(nmsWorld, pos, nmsWorld.random);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Instrument getInstrumentFor(Material mat) {\r\n        return Instrument.values()[getMaterialBlockState(mat).instrument().ordinal()];\r\n    }\r\n\r\n    @Override\r\n    public int getExpDrop(Block block, org.bukkit.inventory.ItemStack item) {\r\n        net.minecraft.world.level.block.Block blockType = CraftMagicNumbers.getBlock(block.getType());\r\n        if (blockType == null) {\r\n            return 0;\r\n        }\r\n        return blockType.getExpDrop(((CraftBlock) block).getNMS(), ((CraftBlock) block).getCraftWorld().getHandle(), ((CraftBlock) block).getPosition(),\r\n                item == null ? null : CraftItemStack.asNMSCopy(item), true);\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnerSpawnedType(CreatureSpawner spawner, EntityTag entity) {\r\n        spawner.setSpawnedType(entity.getBukkitEntityType());\r\n        if (entity.getWaitingMechanisms() == null || entity.getWaitingMechanisms().size() == 0) {\r\n            return;\r\n        }\r\n        try {\r\n            // Wrangle a fake entity\r\n            org.bukkit.entity.Entity bukkitEntity = ((CraftWorld) spawner.getWorld()).createEntity(spawner.getLocation(), entity.getBukkitEntityType().getEntityClass());\r\n            Entity nmsEntity = ((CraftEntity) bukkitEntity).getHandle();\r\n            EntityTag entityTag = new EntityTag(bukkitEntity);\r\n            entityTag.isFake = true;\r\n            entityTag.isFakeValid = true;\r\n            for (Mechanism mechanism : entity.getWaitingMechanisms()) {\r\n                entityTag.safeAdjustDuplicate(mechanism);\r\n            }\r\n            nmsEntity.unsetRemoved();\r\n            // Store it into the spawner\r\n            CraftCreatureSpawner bukkitSpawner = (CraftCreatureSpawner) spawner;\r\n            SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(bukkitSpawner);\r\n            BaseSpawner nmsSpawner = nmsSnapshot.getSpawner();\r\n            SpawnData toSpawn = nmsSpawner.nextSpawnData;\r\n            CompoundTag tag = toSpawn.getEntityToSpawn();\r\n            nmsEntity.saveWithoutId(tag);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnerCustomRules(CreatureSpawner spawner, int skyMin, int skyMax, int blockMin, int blockMax) {\r\n        try {\r\n            CraftCreatureSpawner bukkitSpawner = (CraftCreatureSpawner) spawner;\r\n            SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(bukkitSpawner);\r\n            BaseSpawner nmsSpawner = nmsSnapshot.getSpawner();\r\n            SpawnData toSpawn = nmsSpawner.nextSpawnData;\r\n            SpawnData.CustomSpawnRules rules = skyMin == -1 ? null : new SpawnData.CustomSpawnRules(new InclusiveRange<>(skyMin, skyMax), new InclusiveRange<>(blockMin, blockMax));\r\n            nmsSpawner.nextSpawnData = new SpawnData(toSpawn.entityToSpawn(), Optional.ofNullable(rules), toSpawn.equipment());\r\n            nmsSpawner.spawnPotentials = SimpleWeightedRandomList.empty();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final MethodHandle HOLDERSET_NAMED_BIND = ReflectionHelper.getMethodHandle(HolderSet.Named.class, ReflectionMappingsInfo.HolderSetNamed_bind_method, List.class);\r\n    public static final MethodHandle HOLDER_REFERENCE_BINDTAGS = ReflectionHelper.getMethodHandle(Holder.Reference.class, ReflectionMappingsInfo.HolderReference_bindTags_method, Collection.class);\r\n\r\n    @Override\r\n    public void setVanillaTags(Material material, Set<NamespacedKey> tags) {\r\n        Holder<net.minecraft.world.level.block.Block> nmsHolder = CraftMagicNumbers.getBlock(material).builtInRegistryHolder();\r\n        nmsHolder.tags().forEach(nmsTag -> {\r\n            HolderSet.Named<net.minecraft.world.level.block.Block> nmsHolderSet = BuiltInRegistries.BLOCK.getTag(nmsTag).orElse(null);\r\n            if (nmsHolderSet == null) {\r\n                return;\r\n            }\r\n            List<Holder<net.minecraft.world.level.block.Block>> nmsHolders = nmsHolderSet.stream().collect(Collectors.toCollection(ArrayList::new));\r\n            nmsHolders.remove(nmsHolder);\r\n            try {\r\n                HOLDERSET_NAMED_BIND.invoke(nmsHolderSet, nmsHolders);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            VanillaTagHelper.updateMaterialTag(new CraftBlockTag(BuiltInRegistries.BLOCK, nmsTag));\r\n        });\r\n        List<TagKey<net.minecraft.world.level.block.Block>> newNmsTags = new ArrayList<>();\r\n        for (NamespacedKey tag : tags) {\r\n            TagKey<net.minecraft.world.level.block.Block> newNmsTag = TagKey.create(BuiltInRegistries.BLOCK.key(), CraftNamespacedKey.toMinecraft(tag));\r\n            HolderSet.Named<net.minecraft.world.level.block.Block> nmsHolderSet = BuiltInRegistries.BLOCK.getOrCreateTag(newNmsTag);\r\n            List<Holder<net.minecraft.world.level.block.Block>> nmsHolders = nmsHolderSet.stream().collect(Collectors.toCollection(ArrayList::new));\r\n            nmsHolders.add(nmsHolder);\r\n            try {\r\n                HOLDERSET_NAMED_BIND.invoke(nmsHolderSet, nmsHolders);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n            newNmsTags.add(newNmsTag);\r\n            VanillaTagHelper.addOrUpdateMaterialTag(new CraftBlockTag(BuiltInRegistries.BLOCK, newNmsTag));\r\n        }\r\n        try {\r\n            HOLDER_REFERENCE_BINDTAGS.invoke(nmsHolder, newNmsTags);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        PacketHelperImpl.broadcast(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registries())));\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/ChunkHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.nms.interfaces.ChunkHelper;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.QuartPos;\r\nimport net.minecraft.server.level.ServerChunkCache;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.LevelHeightAccessor;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.LevelChunkSection;\r\nimport net.minecraft.world.level.chunk.PalettedContainer;\r\nimport net.minecraft.world.level.chunk.status.ChunkStatus;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport org.bukkit.World;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\n\r\npublic class ChunkHelperImpl implements ChunkHelper {\r\n\r\n    public final static Field chunkProviderServerThreadField;\r\n    public final static MethodHandle chunkProviderServerThreadFieldSetter;\r\n    public final static Field worldThreadField;\r\n    public final static MethodHandle worldThreadFieldSetter;\r\n\r\n    static {\r\n        chunkProviderServerThreadField = ReflectionHelper.getFields(ServerChunkCache.class).getFirstOfType(Thread.class);\r\n        chunkProviderServerThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(ServerChunkCache.class, Thread.class);\r\n        worldThreadField = ReflectionHelper.getFields(net.minecraft.world.level.Level.class).getFirstOfType(Thread.class);\r\n        worldThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.Level.class, Thread.class);\r\n    }\r\n\r\n    public Thread resetServerThread;\r\n\r\n    @Override\r\n    public void changeChunkServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread != null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkSource();\r\n        try {\r\n            resetServerThread = (Thread) chunkProviderServerThreadField.get(provider);\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, Thread.currentThread());\r\n            worldThreadFieldSetter.invoke(nmsWorld, Thread.currentThread());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void restoreServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread == null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkSource();\r\n        try {\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, resetServerThread);\r\n            worldThreadFieldSetter.invoke(nmsWorld, resetServerThread);\r\n            resetServerThread = null;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int[] getHeightMap(Chunk chunk) {\r\n        Heightmap map = ((CraftChunk) chunk).getHandle(ChunkStatus.FEATURES).heightmaps.get(Heightmap.Types.MOTION_BLOCKING);\r\n        int[] outputMap = new int[256];\r\n        for (int x = 0; x < 16; x++) {\r\n            for (int y = 0; y < 16; y++) {\r\n                outputMap[x * 16 + y] = map.getFirstAvailable(x, y);\r\n            }\r\n        }\r\n        return outputMap;\r\n    }\r\n\r\n    @Override\r\n    public void setAllBiomes(Chunk chunk, BiomeNMS biome) {\r\n        Holder<Biome> nmsBiome = ((BiomeNMSImpl) biome).biomeHolder;\r\n        ChunkAccess nmsChunk = ((CraftChunk) chunk).getHandle(ChunkStatus.BIOMES);\r\n        ChunkPos chunkcoordintpair = nmsChunk.getPos();\r\n        int i = QuartPos.fromBlock(chunkcoordintpair.getMinBlockX());\r\n        int j = QuartPos.fromBlock(chunkcoordintpair.getMinBlockZ());\r\n        LevelHeightAccessor levelheightaccessor = nmsChunk.getHeightAccessorForGeneration();\r\n        for(int k = levelheightaccessor.getMinSection(); k < levelheightaccessor.getMaxSection(); ++k) {\r\n            LevelChunkSection chunksection = nmsChunk.getSection(nmsChunk.getSectionIndexFromSectionY(k));\r\n            PalettedContainer<Holder<Biome>> datapaletteblock = (PalettedContainer<Holder<Biome>>) chunksection.getBiomes();\r\n            datapaletteblock.acquire();\r\n            for(int l = 0; l < 4; ++l) {\r\n                for(int i1 = 0; i1 < 4; ++i1) {\r\n                    for(int j1 = 0; j1 < 4; ++j1) {\r\n                        datapaletteblock.getAndSetUnchecked(l, i1, j1, nmsBiome);\r\n                    }\r\n                }\r\n            }\r\n            datapaletteblock.release();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/CustomEntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.CustomEntityHelper;\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.entities.EntityFakeArrowImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.entities.EntityFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.minecraft.server.level.ClientInformation;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scoreboard.Scoreboard;\r\nimport org.bukkit.scoreboard.Team;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.UUID;\r\n\r\npublic class CustomEntityHelperImpl implements CustomEntityHelper {\r\n\r\n    @Override\r\n    public FakeArrow spawnFakeArrow(Location location) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityFakeArrowImpl arrow = new EntityFakeArrowImpl(world, location);\r\n        return arrow.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public ItemProjectile spawnItemProjectile(Location location, ItemStack itemStack) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityItemProjectileImpl entity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n        world.getHandle().addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return entity.getBukkitEntity();\r\n    }\r\n\r\n    public FakePlayer spawnFakePlayer(Location location, String name, String skin, String blob, boolean doAdd) throws IllegalArgumentException {\r\n        BukkitImplDeprecations.fakePlayer.warn();\r\n        String fullName = name;\r\n        String prefix = null;\r\n        String suffix = null;\r\n        if (name == null) {\r\n            Debug.echoError(\"FAKE_PLAYER: null name, cannot spawn\");\r\n            return null;\r\n        }\r\n        else if (fullName.length() > 16) {\r\n            prefix = fullName.substring(0, 16);\r\n            if (fullName.length() > 30) {\r\n                int len = 30;\r\n                name = fullName.substring(16, 30);\r\n                if (name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    if (fullName.length() >= 32) {\r\n                        len = 32;\r\n                        name = fullName.substring(16, 32);\r\n                    }\r\n                    else if (fullName.length() == 31) {\r\n                        len = 31;\r\n                        name = fullName.substring(16, 31);\r\n                    }\r\n                }\r\n                else if (name.length() > 46) {\r\n                    throw new IllegalArgumentException(\"You must specify a name with no more than 46 characters for FAKE_PLAYER entities!\");\r\n                }\r\n                else {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                suffix = fullName.substring(len);\r\n            }\r\n            else {\r\n                name = fullName.substring(16);\r\n                if (!name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                if (name.length() > 16) {\r\n                    suffix = name.substring(16);\r\n                    name = name.substring(0, 16);\r\n                }\r\n            }\r\n        }\r\n        if (skin != null && skin.length() > 16) {\r\n            throw new IllegalArgumentException(\"You must specify a name with no more than 16 characters for FAKE_PLAYER entity skins!\");\r\n        }\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        ServerLevel worldServer = world.getHandle();\r\n        PlayerProfile playerProfile = new PlayerProfile(name, null);\r\n        if (blob != null) {\r\n            int sc = blob.indexOf(';');\r\n            if (sc != -1) {\r\n                playerProfile.setTexture(blob.substring(0, sc));\r\n                playerProfile.setTextureSignature(blob.substring(sc + 1));\r\n            }\r\n        }\r\n        else if (skin == null && !name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n            playerProfile = NMSHandler.instance.fillPlayerProfile(playerProfile);\r\n        }\r\n        if (skin != null) {\r\n            PlayerProfile skinProfile = new PlayerProfile(skin, null);\r\n            skinProfile = NMSHandler.instance.fillPlayerProfile(skinProfile);\r\n            playerProfile.setTexture(skinProfile.getTexture());\r\n            playerProfile.setTextureSignature(skinProfile.getTextureSignature());\r\n        }\r\n        UUID uuid = UUID.randomUUID();\r\n        playerProfile.setUniqueId(uuid);\r\n\r\n        GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile);\r\n        final EntityFakePlayerImpl fakePlayer = new EntityFakePlayerImpl(worldServer.getServer(), worldServer, gameProfile, ClientInformation.createDefault(), doAdd);\r\n\r\n        fakePlayer.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(),\r\n                location.getYaw(), location.getPitch());\r\n        CraftFakePlayerImpl craftFakePlayer = fakePlayer.getBukkitEntity();\r\n        craftFakePlayer.fullName = fullName;\r\n        if (prefix != null) {\r\n            Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();\r\n            String teamName = \"FAKE_PLAYER_TEAM_\" + fullName;\r\n            String hash = null;\r\n            try {\r\n                hash = CoreUtilities.hash_md5(teamName.getBytes(StandardCharsets.UTF_8)).substring(0, 16);\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(e);\r\n            }\r\n            if (hash != null) {\r\n                Team team = scoreboard.getTeam(hash);\r\n                if (team == null) {\r\n                    team = scoreboard.registerNewTeam(hash);\r\n                    team.setPrefix(prefix);\r\n                    if (suffix != null) {\r\n                        team.setSuffix(suffix);\r\n                    }\r\n                }\r\n                team.addPlayer(craftFakePlayer);\r\n            }\r\n        }\r\n        return craftFakePlayer;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/EnchantmentHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.interfaces.EnchantmentHelper;\r\nimport com.denizenscript.denizen.nms.v1_20.Handler;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.scripts.containers.core.EnchantmentScriptContainer;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.MappedRegistry;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.world.damagesource.DamageSource;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.entity.EquipmentSlot;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.craftbukkit.v1_20_R4.enchantments.CraftEnchantment;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey;\r\nimport org.bukkit.enchantments.Enchantment;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.IdentityHashMap;\r\nimport java.util.Map;\r\n\r\npublic class EnchantmentHelperImpl extends EnchantmentHelper {\r\n    public static final Field REGISTRY_FROZEN = ReflectionHelper.getFields(MappedRegistry.class).get(ReflectionMappingsInfo.MappedRegistry_frozen, boolean.class);\r\n    public static final Field REGISTRY_INTRUSIVE_HOLDERS = ReflectionHelper.getFields(MappedRegistry.class).get(ReflectionMappingsInfo.MappedRegistry_unregisteredIntrusiveHolders, Map.class);\r\n\r\n    @Override\r\n    public org.bukkit.enchantments.Enchantment registerFakeEnchantment(EnchantmentScriptContainer.EnchantmentReference script) {\r\n        try {\r\n            Map holders = (Map) REGISTRY_INTRUSIVE_HOLDERS.get(BuiltInRegistries.ENCHANTMENT);\r\n            if (holders == null) {\r\n                REGISTRY_INTRUSIVE_HOLDERS.set(BuiltInRegistries.ENCHANTMENT, new IdentityHashMap());\r\n            }\r\n            boolean wasFrozen = REGISTRY_FROZEN.getBoolean(BuiltInRegistries.ENCHANTMENT);\r\n            REGISTRY_FROZEN.setBoolean(BuiltInRegistries.ENCHANTMENT, false);\r\n            EquipmentSlot[] slots = new EquipmentSlot[script.script.slots.size()];\r\n            for (int i = 0; i < slots.length; i++) {\r\n                slots[i] = EquipmentSlot.valueOf(CoreUtilities.toUpperCase(script.script.slots.get(i)));\r\n            }\r\n            // TODO: 1.20.6: rarity is provided as an int, can make our own mirror enum; categories seemed to only over control #canEnchant(ItemStack), so can probably safely phase them out?\r\n            // net.minecraft.world.item.enchantment.Enchantment.Rarity.valueOf(script.script.rarity), EnchantmentCategory.valueOf(script.script.category), slots\r\n            net.minecraft.world.item.enchantment.Enchantment nmsEnchant = new net.minecraft.world.item.enchantment.Enchantment(null) {\r\n                // TODO: 1.20.6: methods are final now and the values are provided by EnchantmentDefinition - would probably need to create a new one on reload and modify the existing enchantment\r\n//                @Override\r\n//                public int getMinLevel() {\r\n//                    return script.script.minLevel;\r\n//                }\r\n//                @Override\r\n//                public int getMaxLevel() {\r\n//                    return script.script.maxLevel;\r\n//                }\r\n//                @Override\r\n//                public int getMinCost(int level) {\r\n//                    return script.script.getMinCost(level);\r\n//                }\r\n//                @Override\r\n//                public int getMaxCost(int level) {\r\n//                    return script.script.getMaxCost(level);\r\n//                }\r\n                @Override\r\n                public int getDamageProtection(int level, DamageSource src) {\r\n                    return script.script.getDamageProtection(level, src.getMsgId(), src.getEntity() == null ? null : src.getEntity().getBukkitEntity());\r\n                }\r\n                // TODO: 1.20.6: Takes an EntityType now, and MobType seems to have been removed in favor of vanilla tags - can probably use these to backsupport & properly pass the entity type\r\n//                @Override\r\n//                public float getDamageBonus(int level, EntityType type) {\r\n//                    String typeName = \"UNDEFINED\";\r\n//                    if (type == MobType.ARTHROPOD) {\r\n//                        typeName = \"ARTHROPOD\";\r\n//                    }\r\n//                    else if (type == MobType.ILLAGER) {\r\n//                        typeName = \"ILLAGER\";\r\n//                    }\r\n//                    else if (type == MobType.UNDEAD) {\r\n//                        typeName = \"UNDEAD\";\r\n//                    }\r\n//                    else if (type == MobType.WATER) {\r\n//                        typeName = \"WATER\";\r\n//                    }\r\n//                    return script.script.getDamageBonus(level, typeName);\r\n//                }\r\n                @Override\r\n                protected boolean checkCompatibility(net.minecraft.world.item.enchantment.Enchantment nmsEnchantment) {\r\n                    ResourceLocation nmsKey = BuiltInRegistries.ENCHANTMENT.getKey(nmsEnchantment);\r\n                    NamespacedKey bukkitKey = CraftNamespacedKey.fromMinecraft(nmsKey);\r\n                    org.bukkit.enchantments.Enchantment bukkitEnchant = CraftEnchantment.getByKey(bukkitKey);\r\n                    return script.script.isCompatible(bukkitEnchant);\r\n                }\r\n                @Override\r\n                protected String getOrCreateDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public String getDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public Component getFullname(int level) {\r\n                    return Handler.componentToNMS(script.script.getFullName(level));\r\n                }\r\n                @Override\r\n                public boolean canEnchant(net.minecraft.world.item.ItemStack var0) {\r\n                    return super.canEnchant(var0) && script.script.canEnchant(CraftItemStack.asBukkitCopy(var0));\r\n                }\r\n                @Override\r\n                public void doPostAttack(LivingEntity attacker, Entity victim, int level) {\r\n                    script.script.doPostAttack(attacker.getBukkitEntity(), victim.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public void doPostHurt(LivingEntity victim, Entity attacker, int level) {\r\n                    script.script.doPostHurt(victim.getBukkitEntity(), attacker.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public boolean isTreasureOnly() {\r\n                    return script.script.isTreasureOnly;\r\n                }\r\n                @Override\r\n                public boolean isCurse() {\r\n                    return script.script.isCurse;\r\n                }\r\n                @Override\r\n                public boolean isTradeable() {\r\n                    return script.script.isTradable;\r\n                }\r\n                @Override\r\n                public boolean isDiscoverable() {\r\n                    return script.script.isDiscoverable;\r\n                }\r\n            };\r\n            NamespacedKey enchantmentKey = new NamespacedKey(Denizen.getInstance(), script.script.id);\r\n            Registry.register(BuiltInRegistries.ENCHANTMENT, enchantmentKey.toString(), nmsEnchant);\r\n            String enchName = CoreUtilities.toUpperCase(script.script.id);\r\n            CraftEnchantment ench = new CraftEnchantment(enchantmentKey, nmsEnchant) {\r\n                @Override\r\n                public String getName() {\r\n                    return enchName;\r\n                }\r\n            };\r\n            REGISTRY_INTRUSIVE_HOLDERS.set(BuiltInRegistries.ENCHANTMENT, holders);\r\n            if (wasFrozen) {\r\n                BuiltInRegistries.ENCHANTMENT.freeze();\r\n            }\r\n            return ench;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(\"Failed to register enchantment \" + script.script.id);\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    // TODO: 1.20.6: rarity is just an int now (weight), can deprecate & backsupport by estimating it based on the weight\r\n//    @Override\r\n//    public String getRarity(Enchantment enchantment) {\r\n//        return ((CraftEnchantment) enchantment).getHandle().getRarity().name();\r\n//    }\r\n\r\n    @Override\r\n    public boolean isDiscoverable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isDiscoverable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isTradable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isTradeable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isCurse(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isCurse();\r\n    }\r\n\r\n    @Override\r\n    public int getMinCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMinCost(level);\r\n    }\r\n\r\n    @Override\r\n    public int getMaxCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMaxCost(level);\r\n    }\r\n\r\n    @Override\r\n    public String getFullName(Enchantment enchantment, int level) {\r\n        return FormattedTextHelper.stringify(Handler.componentToSpigot(((CraftEnchantment) enchantment).getHandle().getFullname(level)));\r\n    }\r\n\r\n    // TODO: 1.20.6: MobType was removed in favor of using the entity type directly - deprecate + potentially backsupport with vanilla tags\r\n//    @Override\r\n//    public float getDamageBonus(Enchantment enchantment, int level, String type) {\r\n//        MobType mobType = switch (type) {\r\n//            case \"illager\" -> MobType.ILLAGER;\r\n//            case \"undead\" -> MobType.UNDEAD;\r\n//            case \"water\" -> MobType.WATER;\r\n//            case \"arthropod\" -> MobType.ARTHROPOD;\r\n//            default -> MobType.UNDEFINED;\r\n//        };\r\n//        return ((CraftEnchantment) enchantment).getHandle().getDamageBonus(level, mobType);\r\n//    }\r\n\r\n    @Override\r\n    public int getDamageProtection(Enchantment enchantment, int level, EntityDamageEvent.DamageCause type, org.bukkit.entity.Entity attacker) {\r\n        Entity nmsAttacker = attacker == null ? null : ((CraftEntity) attacker).getHandle();\r\n        DamageSource src = EntityHelperImpl.getSourceFor(nmsAttacker, type, nmsAttacker);\r\n        if (src instanceof EntityHelperImpl.FakeDamageSrc fakeDamageSrc) {\r\n            src = fakeDamageSrc.real;\r\n        }\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageProtection(level, src);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/EntityDataNameMapper.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\n\n\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport net.minecraft.world.entity.*;\nimport net.minecraft.world.entity.ambient.Bat;\nimport net.minecraft.world.entity.animal.*;\nimport net.minecraft.world.entity.animal.axolotl.Axolotl;\nimport net.minecraft.world.entity.animal.camel.Camel;\nimport net.minecraft.world.entity.animal.frog.Frog;\nimport net.minecraft.world.entity.animal.goat.Goat;\nimport net.minecraft.world.entity.animal.horse.AbstractChestedHorse;\nimport net.minecraft.world.entity.animal.horse.AbstractHorse;\nimport net.minecraft.world.entity.animal.horse.Horse;\nimport net.minecraft.world.entity.animal.horse.Llama;\nimport net.minecraft.world.entity.animal.sniffer.Sniffer;\nimport net.minecraft.world.entity.boss.enderdragon.EndCrystal;\nimport net.minecraft.world.entity.boss.enderdragon.EnderDragon;\nimport net.minecraft.world.entity.boss.wither.WitherBoss;\nimport net.minecraft.world.entity.decoration.ArmorStand;\nimport net.minecraft.world.entity.decoration.ItemFrame;\nimport net.minecraft.world.entity.decoration.Painting;\nimport net.minecraft.world.entity.item.PrimedTnt;\nimport net.minecraft.world.entity.monster.*;\nimport net.minecraft.world.entity.monster.hoglin.Hoglin;\nimport net.minecraft.world.entity.monster.piglin.AbstractPiglin;\nimport net.minecraft.world.entity.monster.piglin.Piglin;\nimport net.minecraft.world.entity.monster.warden.Warden;\nimport net.minecraft.world.entity.npc.AbstractVillager;\nimport net.minecraft.world.entity.npc.Villager;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.entity.projectile.*;\nimport net.minecraft.world.entity.raid.Raider;\nimport net.minecraft.world.entity.vehicle.AbstractMinecart;\nimport net.minecraft.world.entity.vehicle.Boat;\nimport net.minecraft.world.entity.vehicle.MinecartCommandBlock;\nimport net.minecraft.world.entity.vehicle.MinecartFurnace;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class EntityDataNameMapper {\n\n    public static final Map<Class<? extends Entity>, Map<String, Integer>> entityDataNames = new HashMap<>();\n\n    public static void registerDataName(Class<? extends Entity> entityClass, int id, String name) {\n        entityDataNames.computeIfAbsent(entityClass, k -> new HashMap<>()).put(name, id);\n    }\n\n    static {\n        // Entity\n        registerDataName(Entity.class, 0, \"entity_flags\");\n        registerDataName(Entity.class, 1, \"air_ticks\");\n        registerDataName(Entity.class, 2, \"custom_name\");\n        registerDataName(Entity.class, 3, \"custom_name_visible\");\n        registerDataName(Entity.class, 4, \"silent\");\n        registerDataName(Entity.class, 5, \"no_gravity\");\n        registerDataName(Entity.class, 6, \"pose\");\n        registerDataName(Entity.class, 7, \"frozen_ticks\");\n\n        // Interaction\n        registerDataName(Interaction.class, 8, \"width\");\n        registerDataName(Interaction.class, 9, \"height\");\n        registerDataName(Interaction.class, 10, \"responsive\");\n\n        // Display\n        registerDataName(Display.class, 8, \"transform_interpolation_start\");\n        registerDataName(Display.class, 9, \"transform_interpolation_duration\");\n        registerDataName(Display.class, 10, \"movement_interpolation_duration\");\n        registerDataName(Display.class, 11, \"translation\");\n        registerDataName(Display.class, 12, \"scale\");\n        registerDataName(Display.class, 13, \"left_rotation\");\n        registerDataName(Display.class, 14, \"right_rotation\");\n        registerDataName(Display.class, 15, \"billboard\");\n        registerDataName(Display.class, 16, \"brightness\");\n        registerDataName(Display.class, 17, \"view_range\");\n        registerDataName(Display.class, 18, \"shadow_radius\");\n        registerDataName(Display.class, 19, \"shadow_strength\");\n        registerDataName(Display.class, 20, \"width\");\n        registerDataName(Display.class, 21, \"height\");\n        registerDataName(Display.class, 22, \"glow_color\");\n\n        // Block display\n        registerDataName(Display.BlockDisplay.class, 23, \"material\");\n\n        // Item display\n        registerDataName(Display.ItemDisplay.class, 23, \"item\");\n        registerDataName(Display.ItemDisplay.class, 24, \"model_transform\");\n\n        // Text display\n        registerDataName(Display.TextDisplay.class, 23, \"text\");\n        registerDataName(Display.TextDisplay.class, 24, \"line_width\");\n        registerDataName(Display.TextDisplay.class, 25, \"background_color\");\n        registerDataName(Display.TextDisplay.class, 26, \"text_opacity\");\n        registerDataName(Display.TextDisplay.class, 27, \"text_display_flags\");\n\n        // Thrown item projectile\n        registerDataName(ThrowableProjectile.class, 8, \"item\");\n\n        // Eye of ender\n        registerDataName(EyeOfEnder.class, 8, \"item\");\n\n        // Falling block\n        registerDataName(FireworkRocketEntity.class, 8, \"spawn_position\");\n\n        // Area effect cloud\n        registerDataName(AreaEffectCloud.class, 8, \"radius\");\n        registerDataName(AreaEffectCloud.class, 9, \"color\");\n        registerDataName(AreaEffectCloud.class, 10, \"waiting\");\n        registerDataName(AreaEffectCloud.class, 11, \"particle\");\n\n        // Fishing hook\n        registerDataName(FishingHook.class, 8, \"hooked_entity_id\");\n        registerDataName(FishingHook.class, 9, \"catchable\");\n\n        // Abstract arrow\n        registerDataName(AbstractArrow.class, 8, \"abstract_arrow_flags\");\n        registerDataName(AbstractArrow.class, 9, \"piercing_level\");\n\n        // Arrow\n        registerDataName(Arrow.class, 10, \"color\");\n\n        // Thrown trident\n        registerDataName(ThrownTrident.class, 10, \"loyalty_level\");\n        registerDataName(ThrownTrident.class, 11, \"enchantment_glint\");\n\n        // Boat\n        registerDataName(Boat.class, 8, \"shaking_ticks\");\n        registerDataName(Boat.class, 9, \"shaking_direction\");\n        registerDataName(Boat.class, 10, \"damage_taken\");\n        registerDataName(Boat.class, 11, \"type\");\n        registerDataName(Boat.class, 12, \"left_paddle_moving\");\n        registerDataName(Boat.class, 13, \"right_paddle_moving\");\n        registerDataName(Boat.class, 14, \"bubble_shaking_ticks\");\n\n        // End crystal\n        registerDataName(EndCrystal.class, 8, \"beam_target\");\n        registerDataName(EndCrystal.class, 9, \"showing_bottom\");\n\n        // Small fireball\n        registerDataName(SmallFireball.class, 8, \"item\");\n\n        // Fireball\n        registerDataName(Fireball.class, 8, \"item\");\n\n        // Wither skull\n        registerDataName(WitherSkull.class, 8, \"invulnerable\");\n\n        // Firework rocket\n        registerDataName(FireworkRocketEntity.class, 8, \"item\");\n        registerDataName(FireworkRocketEntity.class, 9, \"shooter_id\");\n        registerDataName(FireworkRocketEntity.class, 10, \"shot_at_angle\");\n\n        // Item frame\n        registerDataName(ItemFrame.class, 8, \"item\");\n        registerDataName(ItemFrame.class, 9, \"rotation\");\n\n        // Painting\n        registerDataName(Painting.class, 8, \"painting_variant\");\n\n        // Living entity\n        registerDataName(LivingEntity.class, 8, \"living_entity_flags\");\n        registerDataName(LivingEntity.class, 9, \"health\");\n        registerDataName(LivingEntity.class, 10, \"potion_effect_color\");\n        registerDataName(LivingEntity.class, 11, \"is_potion_effect_ambient\");\n        registerDataName(LivingEntity.class, 12, \"arrows_in_body\");\n        registerDataName(LivingEntity.class, 13, \"bee_stingers_in_body\");\n        registerDataName(LivingEntity.class, 14, \"bed_location\");\n\n        // Player\n        registerDataName(Player.class, 15, \"additional_hearts\");\n        registerDataName(Player.class, 16, \"score\");\n        registerDataName(Player.class, 17, \"skin_parts\");\n        registerDataName(Player.class, 18, \"main_hand\");\n        registerDataName(Player.class, 19, \"left_shoulder_entity\");\n        registerDataName(Player.class, 20, \"right_shoulder_entity\");\n\n        // Armor stand\n        registerDataName(ArmorStand.class, 15, \"armor_stand_flags\");\n        registerDataName(ArmorStand.class, 16, \"head_rotation\");\n        registerDataName(ArmorStand.class, 17, \"body_rotation\");\n        registerDataName(ArmorStand.class, 18, \"left_arm_rotation\");\n        registerDataName(ArmorStand.class, 19, \"right_arm_rotation\");\n        registerDataName(ArmorStand.class, 20, \"left_leg_rotation\");\n        registerDataName(ArmorStand.class, 21, \"right_leg_rotation\");\n\n        // Mob\n        registerDataName(Mob.class, 15, \"mob_flags\");\n\n        // Bat flags\n        registerDataName(Bat.class, 16, \"bat_flags\");\n\n        // Dolphin\n        registerDataName(Dolphin.class, 16, \"treasure_location\");\n        registerDataName(Dolphin.class, 17, \"has_fish\");\n        registerDataName(Dolphin.class, 18, \"moisture_level\");\n\n        // Abstract Fish\n        registerDataName(AbstractFish.class, 16, \"from_bucket\");\n\n        // PufferFish\n        registerDataName(Pufferfish.class, 17, \"puff_state\");\n\n        // Tropical fish\n        registerDataName(TropicalFish.class, 17, \"variant\");\n\n        // Ageable mob\n        registerDataName(AgeableMob.class, 16, \"is_baby\");\n\n        // Sniffer\n        registerDataName(Sniffer.class, 17, \"sniffer_state\");\n        registerDataName(Sniffer.class, 18, \"finish_dig_time\");\n\n        // Abstract horse\n        registerDataName(AbstractHorse.class, 17, \"horse_flags\");\n\n        // Horse\n        registerDataName(Horse.class, 18, \"variant\");\n\n        // Camel\n        registerDataName(Camel.class, 18, \"is_dashing\");\n        registerDataName(Camel.class, 19, \"last_pose_change\");\n\n        // Chested horse\n        registerDataName(AbstractChestedHorse.class, 18, \"has_chest\");\n\n        // Llama\n        registerDataName(Llama.class, 19, \"strength\");\n        registerDataName(Llama.class, 20, \"carpet_color\");\n        registerDataName(Llama.class, 21, \"variant\");\n\n        // Axolotl\n        registerDataName(Axolotl.class, 17, \"variant\");\n        registerDataName(Axolotl.class, 18, \"playing_dead\");\n        registerDataName(Axolotl.class, 19, \"from_bucket\");\n\n        // Bee\n        registerDataName(Bee.class, 17, \"bee_flags\");\n        registerDataName(Bee.class, 18, \"anger_time\");\n\n        // Fox\n        registerDataName(Fox.class, 17, \"type\");\n        registerDataName(Fox.class, 18, \"fox_flags\");\n        registerDataName(Fox.class, 19, \"first_trusted_uuid\");\n        registerDataName(Fox.class, 20, \"second_trusted_uuid\");\n\n        // Frog\n        registerDataName(Frog.class, 17, \"variant\");\n        registerDataName(Frog.class, 18, \"target_id\");\n\n        // Ocelot\n        registerDataName(Ocelot.class, 17, \"is_trusting\");\n\n        // Panda\n        registerDataName(Panda.class, 17, \"ask_for_bamboo_timer\");\n        registerDataName(Panda.class, 18, \"sneeze_timer\");\n        registerDataName(Panda.class, 19, \"eat_timer\");\n        registerDataName(Panda.class, 20, \"main_gene\");\n        registerDataName(Panda.class, 21, \"hidden_gene\");\n        registerDataName(Panda.class, 22, \"panda_flags\");\n\n        // Pig\n        registerDataName(Pig.class, 17, \"has_saddle\");\n        registerDataName(Pig.class, 18, \"boost_ticks\");\n\n        // Rabbit\n        registerDataName(Rabbit.class, 17, \"type\");\n\n        // Turtle\n        registerDataName(Turtle.class, 17, \"home_location\");\n        registerDataName(Turtle.class, 18, \"has_egg\");\n        registerDataName(Turtle.class, 19, \"laying_egg\");\n        registerDataName(Turtle.class, 20, \"travel_location\");\n        registerDataName(Turtle.class, 21, \"going_home\");\n        registerDataName(Turtle.class, 20, \"traveling\");\n\n        // Polar bear\n        registerDataName(PolarBear.class, 17, \"standing_up\");\n\n        // Hoglin\n        registerDataName(Hoglin.class, 17, \"immune_to_zombification\");\n\n        // Mooshroom\n        registerDataName(MushroomCow.class, 17, \"variant\");\n\n        // Sheep\n        registerDataName(Sheep.class, 17, \"sheep_wool_flags\");\n\n        // Strider\n        registerDataName(Strider.class, 17, \"boost_ticks\");\n        registerDataName(Strider.class, 18, \"shaking\");\n        registerDataName(Strider.class, 19, \"has_saddle\");\n\n        // Tamable animal\n        registerDataName(TamableAnimal.class, 17, \"tamable_animal_flags\");\n        registerDataName(TamableAnimal.class, 18, \"owner\");\n\n        // Cat\n        registerDataName(Cat.class, 19, \"variant\");\n        registerDataName(Cat.class, 20, \"lying\");\n        registerDataName(Cat.class, 20, \"relaxed\");\n        registerDataName(Cat.class, 21, \"collar_color\");\n\n        // Wolf\n        registerDataName(Wolf.class, 19, \"begging\");\n        registerDataName(Wolf.class, 20, \"collar_color\");\n        registerDataName(Wolf.class, 21, \"anger_time\");\n\n        // Parrot\n        registerDataName(Parrot.class, 19, \"variant\");\n\n        // Abstract villager\n        registerDataName(AbstractVillager.class, 17, \"head_shake_ticks\");\n\n        // Villager\n        registerDataName(Villager.class, 18, \"villager_data\");\n\n        // Iron golem\n        registerDataName(IronGolem.class, 16, \"iron_golem_flags\");\n\n        // Snow golem\n        registerDataName(SnowGolem.class, 16, \"snow_golem_pumpkin_flags\");\n\n        // Shulker\n        registerDataName(Shulker.class, 16, \"attach_face\");\n        registerDataName(Shulker.class, 17, \"attachment_location\");\n        registerDataName(Shulker.class, 18, \"peek\");\n        registerDataName(Shulker.class, 19, \"color\");\n\n        // Base piglin\n        registerDataName(AbstractPiglin.class, 16, \"immune_to_zombification\");\n\n        // Piglin\n        registerDataName(Piglin.class, 17, \"is_baby\");\n        registerDataName(Piglin.class, 18, \"charging_crossbow\");\n        registerDataName(Piglin.class, 19, \"dancing\");\n\n        // Blaze\n        registerDataName(Blaze.class, 16, \"blaze_flags\");\n\n        // Creeper\n        registerDataName(Creeper.class, 16, \"state\");\n        registerDataName(Creeper.class, 17, \"charged\");\n        registerDataName(Creeper.class, 18, \"ignited\");\n\n        // Goat\n        registerDataName(Goat.class, 17, \"screaming\");\n        registerDataName(Goat.class, 18, \"has_left_horn\");\n        registerDataName(Goat.class, 19, \"has_right_horn\");\n\n        // Guardian\n        registerDataName(Guardian.class, 16, \"spikes_retracted\");\n        registerDataName(Guardian.class, 17, \"target_id\");\n\n        // Raider\n        registerDataName(Raider.class, 16, \"celebrating\");\n\n        // Pillager\n        registerDataName(Pillager.class, 17, \"charging_crossbow\");\n\n        // Spellcaster illager\n        registerDataName(SpellcasterIllager.class, 17, \"spell\");\n\n        // Witch\n        registerDataName(Witch.class, 17, \"drinking_potion\");\n\n        // Vex\n        registerDataName(Vex.class, 16, \"vex_flags\");\n\n        // Spider\n        registerDataName(Spider.class, 16, \"spider_flags\");\n\n        // Warden\n        registerDataName(Warden.class, 16, \"anger_level\");\n\n        // Wither\n        registerDataName(WitherBoss.class, 16, \"center_head_target\");\n        registerDataName(WitherBoss.class, 17, \"left_head_target\");\n        registerDataName(WitherBoss.class, 18, \"right_head_target\");\n        registerDataName(WitherBoss.class, 19, \"invulnerable_time\");\n\n        // Zoglin\n        registerDataName(Zoglin.class, 16, \"is_baby\");\n\n        // Zombie\n        registerDataName(Zombie.class, 16, \"is_baby\");\n        registerDataName(Zombie.class, 17, \"type\"); // Unused\n        registerDataName(Zombie.class, 18, \"converting_in_water\");\n\n        // Zombie villager\n        registerDataName(ZombieVillager.class, 19, \"is_converting\");\n        registerDataName(ZombieVillager.class, 20, \"villager_data\");\n\n        // Enderman\n        registerDataName(EnderMan.class, 16, \"carried_block\");\n        registerDataName(EnderMan.class, 17, \"screaming\");\n        registerDataName(EnderMan.class, 18, \"staring\");\n\n        // Ender dragon\n        registerDataName(EnderDragon.class, 16, \"phase\");\n\n        // Ghast\n        registerDataName(Ghast.class, 16, \"attacking\");\n\n        // Phantom\n        registerDataName(Phantom.class, 16, \"size\");\n\n        // Slime\n        registerDataName(Slime.class, 16, \"size\");\n\n        // Abstract minecart\n        registerDataName(AbstractMinecart.class, 8, \"shaking_ticks\");\n        registerDataName(AbstractMinecart.class, 9, \"shaking_direction\");\n        registerDataName(AbstractMinecart.class, 10, \"damage_taken\");\n        registerDataName(AbstractMinecart.class, 11, \"display_block_id\");\n        registerDataName(AbstractMinecart.class, 12, \"display_block_y\");\n        registerDataName(AbstractMinecart.class, 13, \"show_display_block\");\n\n        // Minecraft furnace\n        registerDataName(MinecartFurnace.class, 14, \"has_fuel\");\n\n        // Minecraft command block\n        registerDataName(MinecartCommandBlock.class, 14, \"command\");\n        registerDataName(MinecartCommandBlock.class, 15, \"last_output\");\n\n        // Primed TNT\n        registerDataName(PrimedTnt.class, 8, \"fuse_ticks\");\n    }\n\n    public static int getIdForName(Class<? extends Entity> entityClass, String name) {\n        Class<?> currentClass = entityClass;\n        int id = getIdFromClass(currentClass, name);\n        while (id == -1) {\n            currentClass = currentClass.getSuperclass();\n            if (currentClass == Object.class) {\n                break;\n            }\n            id = getIdFromClass(currentClass, name);\n        }\n        return id;\n    }\n\n    private static int getIdFromClass(Class<?> entityClass, String name) {\n        Map<String, Integer> nameToId = entityDataNames.get(entityClass);\n        int id = nameToId != null ? nameToId.getOrDefault(name, -1) : -1;\n        if (id == -1 && ArgumentHelper.matchesInteger(name)) {\n            id = new ElementTag(name).asInt();\n        }\n        return id;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/EntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityState;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.scripts.commands.core.ReflectionSetCommand;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport io.netty.buffer.Unpooled;\r\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.commands.arguments.EntityAnchorArgument;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.dedicated.DedicatedPlayerList;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.server.players.PlayerList;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.InteractionHand;\r\nimport net.minecraft.world.damagesource.CombatRules;\r\nimport net.minecraft.world.damagesource.DamageSource;\r\nimport net.minecraft.world.damagesource.DamageSources;\r\nimport net.minecraft.world.entity.Mob;\r\nimport net.minecraft.world.entity.MoverType;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.goal.Goal;\r\nimport net.minecraft.world.entity.ai.navigation.PathNavigation;\r\nimport net.minecraft.world.entity.animal.armadillo.Armadillo;\r\nimport net.minecraft.world.entity.item.FallingBlockEntity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.item.PrimedTnt;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.level.ClipContext;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.pathfinder.Path;\r\nimport net.minecraft.world.phys.AABB;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport net.minecraft.world.phys.HitResult;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport net.minecraft.world.phys.shapes.CollisionContext;\r\nimport org.bukkit.Art;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.attribute.Attribute;\r\nimport org.bukkit.attribute.AttributeInstance;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.*;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftLocation;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\nimport org.bukkit.util.BoundingBox;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.function.BiConsumer;\r\n\r\npublic class EntityHelperImpl extends EntityHelper {\r\n\r\n    public static final MethodHandle ENTITY_ONGROUND_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_onGround, boolean.class);\r\n\r\n    public static final EntityDataAccessor<Boolean> ENDERMAN_DATA_ACCESSOR_SCREAMING = ReflectionHelper.getFieldValue(EnderMan.class, ReflectionMappingsInfo.EnderMan_DATA_CREEPY, null);\r\n\r\n    @Override\r\n    public int getBlockHeight(Art art) {\r\n        return art.getBlockHeight();\r\n    }\r\n\r\n    @Override\r\n    public int getBlockWidth(Art art) {\r\n        return art.getBlockWidth();\r\n    }\r\n\r\n    @Override\r\n    public void setInvisible(Entity entity, boolean invisible) {\r\n        ((CraftEntity) entity).getHandle().setInvisible(invisible);\r\n    }\r\n\r\n    @Override\r\n    public boolean isInvisible(Entity entity) {\r\n        return ((CraftEntity) entity).getHandle().isInvisible();\r\n    }\r\n\r\n    @Override\r\n    public void setPose(Entity entity, Pose pose) {\r\n        ((CraftEntity) entity).getHandle().setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]);\r\n    }\r\n\r\n    @Override\r\n    public double getDamageTo(LivingEntity attacker, Entity target) {\r\n        double damage = 0;\r\n        AttributeInstance attrib = attacker.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE);\r\n        if (attrib != null) {\r\n            damage = attrib.getValue();\r\n        }\r\n        if (attacker.getEquipment() != null) {\r\n            damage += EnchantmentHelper.getDamageBonus(CraftItemStack.asNMSCopy(attacker.getEquipment().getItemInMainHand()), CraftEntityType.bukkitToMinecraft(target.getType()));\r\n        }\r\n        if (damage <= 0) {\r\n            return 0;\r\n        }\r\n        if (target != null) {\r\n            DamageSource source;\r\n            net.minecraft.world.entity.Entity nmsTarget = ((CraftEntity) target).getHandle();\r\n            if (attacker instanceof CraftPlayer playerAttacker) {\r\n                source = nmsTarget.level().damageSources().playerAttack(playerAttacker.getHandle());\r\n            }\r\n            else {\r\n                source = nmsTarget.level().damageSources().mobAttack(((CraftLivingEntity) attacker).getHandle());\r\n            }\r\n            if (nmsTarget.isInvulnerableTo(source)) {\r\n                return 0;\r\n            }\r\n            if (!(nmsTarget instanceof net.minecraft.world.entity.LivingEntity livingTarget)) {\r\n                return damage;\r\n            }\r\n            damage = CombatRules.getDamageAfterAbsorb((float) damage, source, (float) livingTarget.getArmorValue(), (float) livingTarget.getAttributeValue(Attributes.ARMOR_TOUGHNESS));\r\n            int enchantDamageModifier = EnchantmentHelper.getDamageProtection(livingTarget.getArmorSlots(), source);\r\n            if (enchantDamageModifier > 0) {\r\n                damage = CombatRules.getDamageAfterMagicAbsorb((float) damage, (float) enchantDamageModifier);\r\n            }\r\n        }\r\n        return damage;\r\n    }\r\n\r\n    public static final MethodHandle LIVINGENTITY_AUTOSPINATTACK_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.LivingEntity.class, ReflectionMappingsInfo.LivingEntity_autoSpinAttackTicks);\r\n    public static final MethodHandle LIVINGENTITY_SETLIVINGENTITYFLAG = ReflectionHelper.getMethodHandle(net.minecraft.world.entity.LivingEntity.class, ReflectionMappingsInfo.LivingEntity_setLivingEntityFlag_method, int.class, boolean.class);\r\n\r\n    @Override\r\n    public void setRiptide(Entity entity, boolean state) {\r\n        try {\r\n            net.minecraft.world.entity.LivingEntity nmsEntity = ((CraftLivingEntity) entity).getHandle();\r\n            LIVINGENTITY_AUTOSPINATTACK_SETTER.invoke(nmsEntity, state ? 0 : 1);\r\n            LIVINGENTITY_SETLIVINGENTITYFLAG.invoke(nmsEntity, 4, true);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void forceInteraction(Player player, Location location) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ((CraftBlock) location.getBlock()).getNMS().useItemOn(nmsPlayer.getMainHandItem(), ((CraftWorld) location.getWorld()).getHandle(),\r\n                nmsPlayer, InteractionHand.MAIN_HAND,\r\n                new BlockHitResult(new Vec3(0, 0, 0), null, CraftLocation.toBlockPosition(location), false));\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Entity entity) {\r\n        CompoundTag compound = new CompoundTag();\r\n        ((CraftEntity) entity).getHandle().saveAsPassenger(compound);\r\n        return NBTAdapter.toAPI(compound);\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Entity entity, CompoundBinaryTag compoundTag) {\r\n        ((CraftEntity) entity).getHandle().load(NBTAdapter.toNMS(compoundTag));\r\n    }\r\n\r\n    /*\r\n        Entity Movement\r\n     */\r\n\r\n    private final static Map<UUID, BukkitTask> followTasks = new HashMap<>();\r\n\r\n    @Override\r\n    public void stopFollowing(Entity follower) {\r\n        if (follower == null) {\r\n            return;\r\n        }\r\n        UUID uuid = follower.getUniqueId();\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void stopWalking(Entity entity) {\r\n        if (((CraftEntity) entity).getHandle() instanceof Mob nmsMob) {\r\n            nmsMob.getNavigation().stop();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void follow(final Entity target, final Entity follower, final double speed, final double lead,\r\n                       final double maxRange, final boolean allowWander, final boolean teleport) {\r\n        if (target == null || follower == null) {\r\n            return;\r\n        }\r\n\r\n        final net.minecraft.world.entity.Entity nmsEntityFollower = ((CraftEntity) follower).getHandle();\r\n        if (!(nmsEntityFollower instanceof Mob nmsFollower)) {\r\n            return;\r\n        }\r\n        final PathNavigation followerNavigation = nmsFollower.getNavigation();\r\n\r\n        UUID uuid = follower.getUniqueId();\r\n\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n\r\n        final int locationNearInt = (int) Math.floor(lead);\r\n        final boolean hasMax = maxRange > lead;\r\n\r\n        followTasks.put(follower.getUniqueId(), new BukkitRunnable() {\r\n\r\n            private boolean inRadius = false;\r\n\r\n            public void run() {\r\n                if (!target.isValid() || !follower.isValid()) {\r\n                    this.cancel();\r\n                }\r\n                followerNavigation.setSpeedModifier(2D);\r\n                Location targetLocation = target.getLocation();\r\n                Path path;\r\n\r\n                if (hasMax && !Utilities.checkLocation(targetLocation, follower.getLocation(), maxRange)\r\n                        && !target.isDead() && target.isOnGround()) {\r\n                    if (!inRadius) {\r\n                        if (teleport) {\r\n                            follower.teleport(Utilities.getWalkableLocationNear(targetLocation, locationNearInt));\r\n                        }\r\n                        else {\r\n                            cancel();\r\n                        }\r\n                    }\r\n                    else {\r\n                        inRadius = false;\r\n                        path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                        if (path != null) {\r\n                            followerNavigation.moveTo(path, 1D);\r\n                            followerNavigation.setSpeedModifier(2D);\r\n                        }\r\n                    }\r\n                }\r\n                else if (!inRadius && !Utilities.checkLocation(targetLocation, follower.getLocation(), lead)) {\r\n                    path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                    if (path != null) {\r\n                        followerNavigation.moveTo(path, 1D);\r\n                        followerNavigation.setSpeedModifier(2D);\r\n                    }\r\n                }\r\n                else {\r\n                    inRadius = true;\r\n                }\r\n                if (inRadius && !allowWander) {\r\n                    followerNavigation.stop();\r\n                }\r\n                nmsFollower.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n        }.runTaskTimer(NMSHandler.getJavaPlugin(), 0, 10));\r\n    }\r\n\r\n    @Override\r\n    public void walkTo(final LivingEntity entity, Location location, Double speed, final Runnable callback) {\r\n        if (entity == null || location == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        if (!(nmsEntity instanceof final Mob nmsMob)) {\r\n            return;\r\n        }\r\n        final PathNavigation entityNavigation = nmsMob.getNavigation();\r\n        final Path path;\r\n        final boolean aiDisabled = !entity.hasAI();\r\n        if (aiDisabled) {\r\n            entity.setAI(true);\r\n            try {\r\n                ENTITY_ONGROUND_SETTER.invoke(nmsMob, true);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        path = entityNavigation.createPath(location.getX(), location.getY(), location.getZ(), 1);\r\n        if (path != null) {\r\n            nmsMob.goalSelector.enableControlFlag(Goal.Flag.MOVE);\r\n            entityNavigation.moveTo(path, 1D);\r\n            final double oldSpeed = nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).getBaseValue();\r\n            if (speed != null) {\r\n                nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    if (!entity.isValid()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        cancel();\r\n                        return;\r\n                    }\r\n                    if (aiDisabled && entity instanceof Wolf wolf) {\r\n                        wolf.setAngry(false);\r\n                    }\r\n                    if (entityNavigation.isDone() || path.isDone()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        if (speed != null) {\r\n                            nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(oldSpeed);\r\n                        }\r\n                        if (aiDisabled) {\r\n                            entity.setAI(false);\r\n                        }\r\n                        cancel();\r\n                    }\r\n                }\r\n            }.runTaskTimer(NMSHandler.getJavaPlugin(), 1, 1);\r\n        }\r\n        //if (!Utilities.checkLocation(location, entity.getLocation(), 20)) {\r\n        // TODO: generate waypoints to the target location?\r\n        else {\r\n            entity.teleport(location);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendAllUpdatePackets(Entity entity) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level()).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        if (entityTracker == null) {\r\n            return;\r\n        }\r\n        try {\r\n            ServerEntity serverEntity = (ServerEntity) PacketHelperImpl.ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n            serverEntity.sendChanges();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    /*\r\n        Hide Entity\r\n     */\r\n\r\n    @Override\r\n    public void sendHidePacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player player) {\r\n            pl.hidePlayer(Denizen.getInstance(), player);\r\n            return;\r\n        }\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) pl).getHandle();\r\n        if (nmsPlayer.connection != null && !pl.equals(entity)) {\r\n            ChunkMap.TrackedEntity entry = nmsPlayer.serverLevel().getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                entry.removePlayer(nmsPlayer);\r\n            }\r\n            if (Denizen.supportsPaper) { // Workaround for Paper issue\r\n                nmsPlayer.connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId()));\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendShowPacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player player) {\r\n            pl.showPlayer(Denizen.getInstance(), player);\r\n            return;\r\n        }\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) pl).getHandle();\r\n        if (nmsPlayer.connection != null && !pl.equals(entity)) {\r\n            ChunkMap.TrackedEntity entry = nmsPlayer.serverLevel().getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                entry.removePlayer(nmsPlayer);\r\n                entry.updatePlayer(nmsPlayer);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void rotate(Entity entity, float yaw, float pitch) {\r\n        // If this entity is a real player instead of a player type NPC,\r\n        // it will appear to be online\r\n        if (entity instanceof Player player && player.isOnline()) {\r\n            NetworkInterceptHelper.enable();\r\n            float relYaw = (yaw - entity.getLocation().getYaw()) % 360;\r\n            if (relYaw > 180) {\r\n                relYaw -= 360;\r\n            }\r\n            final float actualRelYaw = relYaw;\r\n            float relPitch = pitch - entity.getLocation().getPitch();\r\n            NMSHandler.packetHelper.sendRelativeLookPacket(player, actualRelYaw, relPitch);\r\n        }\r\n        else if (entity instanceof LivingEntity) {\r\n            if (entity instanceof EnderDragon) {\r\n                yaw = normalizeYaw(yaw - 180);\r\n            }\r\n            look(entity, yaw, pitch);\r\n        }\r\n        else {\r\n            net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n            handle.setYRot(yaw - 360);\r\n            handle.setXRot(pitch);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBaseYaw(LivingEntity entity) {\r\n        return ((CraftLivingEntity) entity).getHandle().yBodyRot;\r\n    }\r\n\r\n    @Override\r\n    public void look(Entity entity, float yaw, float pitch) {\r\n        net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n        if (handle == null) {\r\n            Debug.echoError(\"Cannot set look direction for unspawned entity \" + entity.getUniqueId());\r\n            return;\r\n        }\r\n        handle.setYRot(yaw);\r\n        if (handle instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity) {\r\n            while (yaw < -180.0F) {\r\n                yaw += 360.0F;\r\n            }\r\n            while (yaw >= 180.0F) {\r\n                yaw -= 360.0F;\r\n            }\r\n            nmsLivingEntity.yBodyRotO = yaw;\r\n            if (!(handle instanceof net.minecraft.world.entity.player.Player)) {\r\n                nmsLivingEntity.setYBodyRot(yaw);\r\n            }\r\n            nmsLivingEntity.setYHeadRot(yaw);\r\n        }\r\n        handle.setXRot(pitch);\r\n    }\r\n\r\n    private static HitResult rayTrace(World world, Vector start, Vector end) {\r\n        try {\r\n            NMSHandler.chunkHelper.changeChunkServerThread(world);\r\n            return ((CraftWorld) world).getHandle().clip(new ClipContext(new Vec3(start.getX(), start.getY(), start.getZ()),\r\n                    new Vec3(end.getX(), end.getY(), end.getZ()),\r\n                    ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, CollisionContext.empty()));\r\n        }\r\n        finally {\r\n            NMSHandler.chunkHelper.restoreServerThread(world);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean canTrace(World world, Vector start, Vector end) {\r\n        HitResult pos = rayTrace(world, start, end);\r\n        if (pos == null) {\r\n            return true;\r\n        }\r\n        return pos.getType() == HitResult.Type.MISS;\r\n    }\r\n\r\n    @Override\r\n    public void snapPositionTo(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().setPosRaw(vector.getX(), vector.getY(), vector.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void move(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().move(MoverType.SELF, new Vec3(vector.getX(), vector.getY(), vector.getZ()));\r\n    }\r\n\r\n    @Override\r\n    public boolean internalLook(Player player, Location at) {\r\n        PacketHelperImpl.send(player, new ClientboundPlayerLookAtPacket(EntityAnchorArgument.Anchor.EYES, at.getX(), at.getY(), at.getZ()));\r\n        return true;\r\n    }\r\n\r\n    public static long entityToPacket(double x) {\r\n        return Mth.lfloor(x * 4096.0D);\r\n    }\r\n\r\n    @Override\r\n    public void fakeMove(Entity entity, Vector vector) {\r\n        long x = entityToPacket(vector.getX());\r\n        long y = entityToPacket(vector.getY());\r\n        long z = entityToPacket(vector.getZ());\r\n        ClientboundMoveEntityPacket packet = new ClientboundMoveEntityPacket.Pos(entity.getEntityId(), (short) x, (short) y, (short) z, entity.isOnGround());\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void fakeTeleport(Entity entity, Location location) {\r\n        FriendlyByteBuf packetData = new FriendlyByteBuf(Unpooled.buffer());\r\n        // Referenced from ClientboundTeleportEntityPacket source\r\n        packetData.writeVarInt(entity.getEntityId());\r\n        packetData.writeDouble(location.getX());\r\n        packetData.writeDouble(location.getY());\r\n        packetData.writeDouble(location.getZ());\r\n        packetData.writeByte((byte)((int)(location.getYaw() * 256.0F / 360.0F)));\r\n        packetData.writeByte((byte)((int)(location.getPitch() * 256.0F / 360.0F)));\r\n        packetData.writeBoolean(entity.isOnGround());\r\n        ClientboundTeleportEntityPacket packet = ClientboundTeleportEntityPacket.STREAM_CODEC.decode(packetData);\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void clientResetLoc(Entity entity) {\r\n        ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(((CraftEntity) entity).getHandle());\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Entity entity, Location loc) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        nmsEntity.setYRot(loc.getYaw());\r\n        nmsEntity.setXRot(loc.getPitch());\r\n        if (nmsEntity instanceof ServerPlayer) {\r\n            nmsEntity.teleportTo(loc.getX(), loc.getY(), loc.getZ());\r\n        }\r\n        nmsEntity.setPos(loc.getX(), loc.getY(), loc.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void setBoundingBox(Entity entity, BoundingBox box) {\r\n        ((CraftEntity) entity).getHandle().setBoundingBox(new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()));\r\n    }\r\n\r\n    public static final Field EXPERIENCE_ORB_AGE = ReflectionHelper.getFields(net.minecraft.world.entity.ExperienceOrb.class).get(ReflectionMappingsInfo.ExperienceOrb_age, int.class);\r\n\r\n    @Override\r\n    public void setTicksLived(Entity entity, int ticks) {\r\n        // Bypass Spigot's must-be-at-least-1-tick requirement, as negative tick counts are useful\r\n        ((CraftEntity) entity).getHandle().tickCount = ticks;\r\n        if (entity instanceof CraftFallingBlock craftFallingBlock) {\r\n            craftFallingBlock.getHandle().time = ticks;\r\n        }\r\n        else if (entity instanceof CraftItem craftItem) {\r\n            ((ItemEntity) craftItem.getHandle()).age = ticks;\r\n        }\r\n        else if (entity instanceof CraftExperienceOrb craftExperienceOrb) {\r\n            try {\r\n                EXPERIENCE_ORB_AGE.setInt(craftExperienceOrb.getHandle(), ticks);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHeadAngle(LivingEntity entity, float angle) {\r\n        ((CraftLivingEntity) entity).getHandle().setYHeadRot(angle);\r\n    }\r\n\r\n    @Override\r\n    public void setEndermanAngry(Enderman enderman, boolean angry) {\r\n        ((CraftEnderman) enderman).getHandle().getEntityData().set(ENDERMAN_DATA_ACCESSOR_SCREAMING, angry);\r\n    }\r\n\r\n    public static class FakeDamageSrc extends DamageSource { public DamageSource real; public FakeDamageSrc(DamageSource src) { super(null); real = src; } }\r\n\r\n    public static DamageSources backupDamageSources;\r\n\r\n    public static DamageSources getReusableDamageSources() {\r\n        if (backupDamageSources == null) {\r\n            backupDamageSources = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle().damageSources();\r\n        }\r\n        return backupDamageSources;\r\n    }\r\n\r\n    public static DamageSource getSourceFor(net.minecraft.world.entity.Entity nmsSource, EntityDamageEvent.DamageCause cause, net.minecraft.world.entity.Entity nmsSourceProvider) {\r\n        DamageSources sources = nmsSourceProvider == null ? getReusableDamageSources() : nmsSourceProvider.level().damageSources();\r\n        DamageSource src = sources.generic();\r\n        if (nmsSource != null) {\r\n            if (nmsSource instanceof net.minecraft.world.entity.player.Player nmsPlayer) {\r\n                src = nmsSource.level().damageSources().playerAttack(nmsPlayer);\r\n            }\r\n            else if (nmsSource instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity) {\r\n                src = nmsSource.level().damageSources().mobAttack(nmsLivingEntity);\r\n            }\r\n        }\r\n        if (cause == null) {\r\n            return src;\r\n        }\r\n        return switch (cause) {\r\n            case CONTACT -> sources.cactus();\r\n            case ENTITY_ATTACK -> sources.mobAttack(nmsSource instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity ? nmsLivingEntity : null);\r\n            case ENTITY_SWEEP_ATTACK -> src != sources.generic() ? src.sweep() : src;\r\n            case PROJECTILE -> sources.thrown(nmsSource, nmsSource != null && nmsSource.getBukkitEntity() instanceof Projectile projectile\r\n                        && projectile.getShooter() instanceof CraftEntity shooter ? shooter.getHandle() : null);\r\n            case SUFFOCATION -> sources.inWall();\r\n            case FALL -> sources.fall();\r\n            case FIRE -> sources.inFire();\r\n            case FIRE_TICK -> sources.onFire();\r\n            case MELTING -> sources.melting();\r\n            case LAVA -> sources.lava();\r\n            case DROWNING -> sources.drown();\r\n            case BLOCK_EXPLOSION -> nmsSource instanceof PrimedTnt primedTnt ? sources.explosion(primedTnt, primedTnt.getOwner()) : sources.explosion(null);\r\n            case ENTITY_EXPLOSION -> sources.explosion(nmsSource, null);\r\n            case VOID -> sources.fellOutOfWorld();\r\n            case LIGHTNING -> sources.lightningBolt();\r\n            case STARVATION -> sources.starve();\r\n            case POISON -> sources.poison();\r\n            case MAGIC -> sources.magic();\r\n            case WITHER -> sources.wither();\r\n            case FALLING_BLOCK -> sources.fallingBlock(nmsSource);\r\n            case THORNS -> sources.thorns(nmsSource);\r\n            case DRAGON_BREATH -> sources.dragonBreath();\r\n            case CUSTOM -> sources.generic();\r\n            case FLY_INTO_WALL -> sources.flyIntoWall();\r\n            case HOT_FLOOR -> sources.hotFloor();\r\n            case CRAMMING -> sources.cramming();\r\n            case DRYOUT -> sources.dryOut();\r\n            case FREEZE -> sources.freeze();\r\n            case SONIC_BOOM -> sources.sonicBoom(nmsSource);\r\n            case WORLD_BORDER -> sources.outOfBorder();\r\n            case KILL -> sources.genericKill();\r\n            case SUICIDE -> new FakeDamageSrc(src);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void damage(LivingEntity target, float amount, EntityTag source, Location sourceLoc, EntityDamageEvent.DamageCause cause) {\r\n        if (target == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.LivingEntity nmsTarget = ((CraftLivingEntity) target).getHandle();\r\n        net.minecraft.world.entity.Entity nmsSource = source == null ? null : ((CraftEntity) source.getBukkitEntity()).getHandle();\r\n        DamageSource src = getSourceFor(nmsSource, cause, nmsTarget);\r\n        if (src instanceof FakeDamageSrc fakeDamageSrc) {\r\n            src = fakeDamageSrc.real;\r\n            if (fireFakeDamageEvent(target, source, sourceLoc, cause, amount).isCancelled()) {\r\n                return;\r\n            }\r\n        }\r\n        nmsTarget.hurt(src, amount);\r\n    }\r\n\r\n    @Override\r\n    public void setLastHurtBy(LivingEntity mob, LivingEntity damager) {\r\n        ((CraftLivingEntity) mob).getHandle().setLastHurtByMob(((CraftLivingEntity) damager).getHandle());\r\n    }\r\n\r\n    public static final Field FALLINGBLOCK_BLOCK_STATE = ReflectionHelper.getFields(FallingBlockEntity.class).getFirstOfType(BlockState.class);\r\n\r\n    @Override\r\n    public void setFallingBlockType(FallingBlock fallingBlock, BlockData block) {\r\n        BlockState state = ((CraftBlockData) block).getState();\r\n        FallingBlockEntity nmsEntity = ((CraftFallingBlock) fallingBlock).getHandle();\r\n        try {\r\n            FALLINGBLOCK_BLOCK_STATE.set(nmsEntity, state);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public EntityTag getMobSpawnerDisplayEntity(CreatureSpawner spawner) {\r\n        SpawnerBlockEntity nmsSpawner = BlockHelperImpl.getTE((CraftCreatureSpawner) spawner);\r\n        ServerLevel level = ((CraftWorld) spawner.getWorld()).getHandle();\r\n        net.minecraft.world.entity.Entity nmsEntity = nmsSpawner.getSpawner().getOrCreateDisplayEntity(level, nmsSpawner.getBlockPos());\r\n        return new EntityTag(nmsEntity.getBukkitEntity());\r\n    }\r\n\r\n    public static final Field ZOMBIE_INWATERTIME = ReflectionHelper.getFields(net.minecraft.world.entity.monster.Zombie.class).get(ReflectionMappingsInfo.Zombie_inWaterTime, int.class);\r\n\r\n    @Override\r\n    public int getInWaterTime(Zombie zombie) {\r\n        try {\r\n            return ZOMBIE_INWATERTIME.getInt(((CraftZombie) zombie).getHandle());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return 0;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setInWaterTime(Zombie zombie, int ticks) {\r\n        try {\r\n            ZOMBIE_INWATERTIME.setInt(((CraftZombie) zombie).getHandle(), ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final MethodHandle TRACKING_RANGE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ChunkMap.TrackedEntity.class, int.class);\r\n\r\n    @Override\r\n    public void setTrackingRange(Entity entity, int range) {\r\n        try {\r\n            ChunkMap map = ((CraftWorld) entity.getWorld()).getHandle().getChunkSource().chunkMap;\r\n            ChunkMap.TrackedEntity entry = map.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                TRACKING_RANGE_SETTER.invoke(entry, range);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean isAggressive(org.bukkit.entity.Mob mob) {\r\n        return ((CraftMob) mob).getHandle().isAggressive();\r\n    }\r\n\r\n    @Override\r\n    public void setAggressive(org.bukkit.entity.Mob mob, boolean aggressive) {\r\n        ((CraftMob) mob).getHandle().setAggressive(aggressive);\r\n    }\r\n\r\n    // Use reflection because Paper changes the method return type\r\n    public static final MethodHandle PLAYERLIST_REMOVE = ReflectionHelper.getMethodHandle(PlayerList.class, \"remove\", ServerPlayer.class);\r\n\r\n    @Override\r\n    public void setUUID(Entity entity, UUID id) {\r\n        try {\r\n            net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n            nmsEntity.stopRiding();\r\n            nmsEntity.getPassengers().forEach(net.minecraft.world.entity.Entity::stopRiding);\r\n            Level level = nmsEntity.level();\r\n            DedicatedPlayerList playerList = ((CraftServer) Bukkit.getServer()).getHandle();\r\n            if (nmsEntity instanceof ServerPlayer nmsPlayer) {\r\n                PLAYERLIST_REMOVE.invoke(playerList, nmsPlayer);\r\n            }\r\n            else {\r\n                nmsEntity.remove(net.minecraft.world.entity.Entity.RemovalReason.DISCARDED);\r\n            }\r\n            nmsEntity.unsetRemoved();\r\n            nmsEntity.setUUID(id);\r\n            if (nmsEntity instanceof ServerPlayer nmsPlayer) {\r\n                playerList.placeNewPlayer(DenizenNetworkManagerImpl.getConnection(nmsPlayer), nmsPlayer, new CommonListenerCookie(nmsPlayer.getGameProfile(), nmsPlayer.connection.latency(), nmsPlayer.clientInformation(), nmsPlayer.connection.isTransferred()));\r\n            }\r\n            else {\r\n                level.addFreshEntity(nmsEntity);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final Field SynchedEntityData_itemsById = ReflectionHelper.getFields(SynchedEntityData.class).get(ReflectionMappingsInfo.SynchedEntityData_itemsById);\r\n\r\n    public static Int2ObjectMap<SynchedEntityData.DataItem<Object>> getDataItems(Entity entity) {\r\n        try {\r\n            return (Int2ObjectMap<SynchedEntityData.DataItem<Object>>) SynchedEntityData_itemsById.get(((CraftEntity) entity).getHandle().getEntityData());\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            throw new RuntimeException(e); // Stop the code here to avoid NPEs down the road\r\n        }\r\n    }\r\n\r\n    public static void convertToInternalData(Entity entity, MapTag internalData, BiConsumer<SynchedEntityData.DataItem<Object>, Object> processConverted) {\r\n        Int2ObjectMap<SynchedEntityData.DataItem<Object>> dataItemsById = getDataItems(entity);\r\n        for (Map.Entry<StringHolder, ObjectTag> entry : internalData.entrySet()) {\r\n            int id = EntityDataNameMapper.getIdForName(((CraftEntity) entity).getHandle().getClass(), entry.getKey().low);\r\n            if (id == -1) {\r\n                Debug.echoError(\"Invalid internal data key: \" + entry.getKey());\r\n                return;\r\n            }\r\n            SynchedEntityData.DataItem<Object> dataItem = dataItemsById.get(id);\r\n            if (dataItem == null) {\r\n                Debug.echoError(\"Invalid internal data id '\" + id + \"': couldn't be matched to any internal data for entity of type '\" + entity.getType() + \"'.\");\r\n                return;\r\n            }\r\n            Object converted = ReflectionSetCommand.convertObjectTypeFor(dataItem.getValue().getClass(), entry.getValue());\r\n            if (converted != null) {\r\n                processConverted.accept(dataItem, converted);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public List<Object> convertInternalEntityDataValues(Entity entity, MapTag internalData) {\r\n        List<Object> dataValues = new ArrayList<>(internalData.size());\r\n        convertToInternalData(entity, internalData, (dataItem, converted) -> dataValues.add(PacketHelperImpl.createEntityData(dataItem.getAccessor(), converted)));\r\n        return dataValues;\r\n    }\r\n\r\n    @Override\r\n    public void modifyInternalEntityData(Entity entity, MapTag internalData) {\r\n        SynchedEntityData nmsEntityData = ((CraftEntity) entity).getHandle().getEntityData();\r\n        convertToInternalData(entity, internalData, (dataItem, converted) -> nmsEntityData.set(dataItem.getAccessor(), converted));\r\n    }\r\n\r\n    @Override\r\n    public void startUsingItem(LivingEntity entity, EquipmentSlot hand) {\r\n        ((CraftLivingEntity) entity).getHandle().startUsingItem(hand == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND);\r\n    }\r\n\r\n    @Override\r\n    public void stopUsingItem(LivingEntity entity) {\r\n        ((CraftLivingEntity) entity).getHandle().stopUsingItem();\r\n    }\r\n\r\n    @Override\r\n    public void openHorseInventory(Player player, AbstractHorse horse) {\r\n        net.minecraft.world.entity.animal.horse.AbstractHorse nmsHorse = ((CraftAbstractHorse) horse).getHandle();\r\n        ((CraftPlayer) player).getHandle().openHorseInventory(nmsHorse, nmsHorse.inventory);\r\n    }\r\n\r\n    private CompoundTag getRawEntityNBT(net.minecraft.world.entity.Entity entity) {\r\n        return entity.saveWithoutId(new CompoundTag());\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getRawNBT(Entity entity) {\r\n        return NBTAdapter.toAPI(getRawEntityNBT(((CraftEntity) entity).getHandle()));\r\n    }\r\n\r\n    @Override\r\n    public void modifyRawNBT(Entity entity, CompoundBinaryTag tag) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        CompoundTag nmsTag = NBTAdapter.toNMS(tag);\r\n        CompoundTag nmsMergedTag = getRawEntityNBT(nmsEntity).merge(nmsTag);\r\n        UUID uuid = nmsEntity.getUUID();\r\n        nmsEntity.load(nmsMergedTag);\r\n        nmsEntity.setUUID(uuid);\r\n    }\r\n\r\n    @Override\r\n    public EntityState.ArmadilloState getArmadilloState(org.bukkit.entity.Armadillo entity) {\r\n        Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle();\r\n        return switch (armadillo.getState()) {\r\n            case IDLE -> EntityState.ArmadilloState.IDLE;\r\n            case ROLLING -> EntityState.ArmadilloState.ROLLING;\r\n            case SCARED -> EntityState.ArmadilloState.SCARED;\r\n            case UNROLLING -> EntityState.ArmadilloState.UNROLLING;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void setArmadilloState(org.bukkit.entity.Armadillo entity, EntityState.ArmadilloState state) {\r\n        Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle();\r\n        armadillo.switchToState(switch (state) {\r\n            case IDLE -> Armadillo.ArmadilloState.IDLE;\r\n            case ROLLING -> Armadillo.ArmadilloState.ROLLING;\r\n            case SCARED -> Armadillo.ArmadilloState.SCARED;\r\n            case UNROLLING -> Armadillo.ArmadilloState.UNROLLING;\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/FishingHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FishingHelper;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Maps;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.entity.projectile.FishingHook;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.item.enchantment.Enchantments;\r\nimport net.minecraft.world.level.storage.loot.BuiltInLootTables;\r\nimport net.minecraft.world.level.storage.loot.LootParams;\r\nimport net.minecraft.world.level.storage.loot.LootTable;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParam;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParams;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftFishHook;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.entity.FishHook;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class FishingHelperImpl implements FishingHelper {\r\n\r\n    @Override\r\n    public org.bukkit.inventory.ItemStack getResult(FishHook fishHook, CatchType catchType) {\r\n        FishingHook nmsHook = ((CraftFishHook) fishHook).getHandle();\r\n        ItemStack result = switch (catchType) {\r\n            case DEFAULT -> {\r\n                float f = ((CraftWorld) fishHook.getWorld()).getHandle().random.nextFloat();\r\n                int i = EnchantmentHelper.getMobLooting(nmsHook.getPlayerOwner());\r\n                int j = EnchantmentHelper.getEnchantmentLevel(Enchantments.LUCK_OF_THE_SEA, nmsHook.getPlayerOwner());\r\n                float f1 = 0.1F - (float) i * 0.025F - (float) j * 0.01F;\r\n                float f2 = 0.05F + (float) i * 0.01F - (float) j * 0.01F;\r\n\r\n                f1 = Mth.clamp(f1, 0.0F, 1.0F);\r\n                f2 = Mth.clamp(f2, 0.0F, 1.0F);\r\n                if (f < f1) {\r\n                    yield catchRandomJunk(nmsHook);\r\n                }\r\n                else {\r\n                    f -= f1;\r\n                    if (f < f2) {\r\n                        yield catchRandomTreasure(nmsHook);\r\n                    }\r\n                    else {\r\n                        yield catchRandomFish(nmsHook);\r\n                    }\r\n                }\r\n            }\r\n            case JUNK -> catchRandomJunk(nmsHook);\r\n            case TREASURE -> catchRandomTreasure(nmsHook);\r\n            case FISH -> catchRandomFish(nmsHook);\r\n            default -> null;\r\n        };\r\n        return result != null ? CraftItemStack.asBukkitCopy(result) : null;\r\n    }\r\n\r\n    public ItemStack getRandomReward(FishingHook nmsHook, ResourceKey<LootTable> key) {\r\n        ServerLevel nmsWorld = (ServerLevel) nmsHook.level();\r\n        Map<LootContextParam<?>, Object> params = Maps.newIdentityHashMap();\r\n        params.put(LootContextParams.ORIGIN, new Vec3(nmsHook.getX(), nmsHook.getY(), nmsHook.getZ()));\r\n        params.put(LootContextParams.TOOL, new ItemStack(Items.FISHING_ROD));\r\n        LootParams nmsLootParams = new LootParams(nmsWorld, params, Maps.newHashMap(), 0);\r\n        List<ItemStack> nmsItems = nmsHook.registryAccess().registryOrThrow(Registries.LOOT_TABLE).get(key).getRandomItems(nmsLootParams);\r\n        return nmsItems.get(nmsWorld.random.nextInt(nmsItems.size()));\r\n    }\r\n\r\n    @Override\r\n    public FishHook spawnHook(Location location, Player player) {\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        FishingHook hook = new FishingHook(((CraftPlayer) player).getHandle(), nmsWorld, 0, 0);\r\n        nmsWorld.addFreshEntity(hook, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return (FishHook) hook.getBukkitEntity();\r\n    }\r\n\r\n    private ItemStack catchRandomJunk(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_JUNK);\r\n    }\r\n\r\n    private ItemStack catchRandomTreasure(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_TREASURE);\r\n    }\r\n\r\n    private ItemStack catchRandomFish(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_FISH);\r\n    }\r\n\r\n    public static final Field FISHING_HOOK_NIBBLE = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_nibble, int.class);\r\n    public static final Field FISHING_HOOK_LURE_TIME = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_timeUntilLured, int.class);\r\n    public static final Field FISHING_HOOK_HOOK_TIME = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_timeUntilHooked, int.class);\r\n\r\n    @Override\r\n    public FishHook getHookFrom(Player player) {\r\n        FishingHook nmsHook = ((CraftPlayer) player).getHandle().fishing;\r\n        if (nmsHook == null) {\r\n            return null;\r\n        }\r\n        return (FishHook) nmsHook.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public void setNibble(FishHook hook, int ticks) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_NIBBLE.setInt(nmsHook, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHookTime(FishHook hook, int ticks) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_HOOK_TIME.setInt(nmsHook, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int getLureTime(FishHook hook) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            return FISHING_HOOK_LURE_TIME.getInt(nmsHook);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public void setLureTime(FishHook hook, int ticks) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_LURE_TIME.setInt(nmsHook, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/ItemHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.ItemHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_20.Handler;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemComponentsPatch;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.*;\r\nimport com.google.gson.JsonObject;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.mojang.serialization.Dynamic;\r\nimport com.mojang.serialization.JsonOps;\r\nimport net.kyori.adventure.nbt.BinaryTag;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.advancements.critereon.BlockPredicate;\r\nimport net.minecraft.core.*;\r\nimport net.minecraft.core.component.DataComponentMap;\r\nimport net.minecraft.core.component.DataComponentPatch;\r\nimport net.minecraft.core.component.DataComponentType;\r\nimport net.minecraft.core.component.DataComponents;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.nbt.NbtOps;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.resources.RegistryOps;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.util.datafix.fixes.References;\r\nimport net.minecraft.world.item.AdventureModePredicate;\r\nimport net.minecraft.world.item.BlockItem;\r\nimport net.minecraft.world.item.Item;\r\nimport net.minecraft.world.item.alchemy.PotionBrewing;\r\nimport net.minecraft.world.item.component.CustomData;\r\nimport net.minecraft.world.item.component.ItemLore;\r\nimport net.minecraft.world.item.component.ResolvableProfile;\r\nimport net.minecraft.world.item.crafting.*;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport net.minecraft.world.level.material.FluidState;\r\nimport net.minecraft.world.level.material.MapColor;\r\nimport net.minecraft.world.level.saveddata.maps.MapId;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.DyeColor;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftRegistry;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryPlayer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftRecipe;\r\nimport org.bukkit.craftbukkit.v1_20_R4.map.CraftMapView;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.inventory.RecipeChoice;\r\nimport org.bukkit.inventory.ShapedRecipe;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.function.Consumer;\r\nimport java.util.function.Predicate;\r\n\r\npublic class ItemHelperImpl extends ItemHelper {\r\n\r\n    public static net.minecraft.world.item.crafting.RecipeHolder<?> getNMSRecipe(NamespacedKey key) {\r\n        ResourceLocation nmsKey = CraftNamespacedKey.toMinecraft(key);\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().byKey(nmsKey).orElse(null);\r\n    }\r\n\r\n    public static final Field Item_components = ReflectionHelper.getFields(Item.class).get(ReflectionMappingsInfo.Item_components, DataComponentMap.class);\r\n\r\n    public void setMaxStackSize(Material material, int size) {\r\n        try {\r\n            ReflectionHelper.getFinalSetter(Material.class, \"maxStack\").invoke(material, size);\r\n            Item nmsItem = BuiltInRegistries.ITEM.get(CraftNamespacedKey.toMinecraft(material.getKey()));\r\n            DataComponentMap currentComponents = nmsItem.components();\r\n            Item_components.set(nmsItem, DataComponentMap.composite(currentComponents, DataComponentMap.builder().set(DataComponents.MAX_STACK_SIZE, size).build()));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Integer burnTime(Material material) {\r\n        return AbstractFurnaceBlockEntity.getFuel().get(CraftMagicNumbers.getItem(material));\r\n    }\r\n\r\n    @Override\r\n    public void setShapedRecipeIngredient(ShapedRecipe recipe, char c, ItemStack[] item, boolean exact) {\r\n        if (item.length == 1 && item[0].getType() == Material.AIR) {\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(Material.AIR));\r\n        }\r\n        else if (exact) {\r\n            recipe.setIngredient(c, new RecipeChoice.ExactChoice(item));\r\n        }\r\n        else {\r\n            Material[] mats = new Material[item.length];\r\n            for (int i = 0; i < item.length; i++) {\r\n                mats[i] = item[i].getType();\r\n            }\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(mats));\r\n        }\r\n    }\r\n\r\n    public static Ingredient itemArrayToRecipe(ItemStack[] items, boolean exact) {\r\n        Ingredient.ItemValue[] stacks = new Ingredient.ItemValue[items.length];\r\n        for (int i = 0; i < items.length; i++) {\r\n            stacks[i] = new Ingredient.ItemValue(CraftItemStack.asNMSCopy(items[i]));\r\n        }\r\n        Ingredient itemRecipe = new Ingredient(Arrays.stream(stacks));\r\n        itemRecipe.exact = exact;\r\n        return itemRecipe;\r\n    }\r\n\r\n    @Override\r\n    public void registerFurnaceRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, float exp, int time, String type, boolean exact, String category) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        AbstractCookingRecipe recipe;\r\n        CookingBookCategory categoryValue = category == null ? CookingBookCategory.MISC : CookingBookCategory.valueOf(CoreUtilities.toUpperCase(category));\r\n        if (type.equalsIgnoreCase(\"smoker\")) {\r\n            recipe = new SmokingRecipe(group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"blast\")) {\r\n            recipe = new BlastingRecipe(group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"campfire\")) {\r\n            recipe = new CampfireCookingRecipe(group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else {\r\n            recipe = new SmeltingRecipe(group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        RecipeHolder<AbstractCookingRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public void registerStonecuttingRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, boolean exact) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        StonecutterRecipe recipe = new StonecutterRecipe(group, itemRecipe, CraftItemStack.asNMSCopy(result));\r\n        RecipeHolder<StonecutterRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public void registerSmithingRecipe(String keyName, ItemStack result, ItemStack[] baseItem, boolean baseExact, ItemStack[] upgradeItem, boolean upgradeExact, ItemStack[] templateItem, boolean templateExact) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        Ingredient templateItemRecipe = itemArrayToRecipe(templateItem, templateExact);\r\n        Ingredient baseItemRecipe = itemArrayToRecipe(baseItem, baseExact);\r\n        Ingredient upgradeItemRecipe = itemArrayToRecipe(upgradeItem, upgradeExact);\r\n        SmithingTransformRecipe recipe = new SmithingTransformRecipe(templateItemRecipe, baseItemRecipe, upgradeItemRecipe, CraftItemStack.asNMSCopy(result));\r\n        RecipeHolder<SmithingTransformRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public void registerShapelessRecipe(String keyName, String group, ItemStack result, List<ItemStack[]> ingredients, boolean[] exact, String category) {\r\n        ResourceLocation key = new ResourceLocation(\"denizen\", keyName);\r\n        ArrayList<Ingredient> ingredientList = new ArrayList<>();\r\n        CraftingBookCategory categoryValue = category == null ? CraftingBookCategory.MISC : CraftingBookCategory.valueOf(CoreUtilities.toUpperCase(category));\r\n        for (int i = 0; i < ingredients.size(); i++) {\r\n            ingredientList.add(itemArrayToRecipe(ingredients.get(i), exact[i]));\r\n        }\r\n        // TODO: 1.19.3: Add support for choosing a CraftingBookCategory\r\n        ShapelessRecipe recipe = new ShapelessRecipe(group, categoryValue, CraftItemStack.asNMSCopy(result), NonNullList.of(null, ingredientList.toArray(new Ingredient[0])));\r\n        RecipeHolder<ShapelessRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public String getJsonString(ItemStack itemStack) {\r\n        String json = CraftItemStack.asNMSCopy(itemStack).getDisplayName().getStyle().toString().replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\");\r\n        return json.substring(176, json.length() - 185);\r\n    }\r\n\r\n    @Override\r\n    public JsonObject getRawHoverComponentsJson(ItemStack item) {\r\n        DataComponentPatch nmsComponents = CraftItemStack.asNMSCopy(item).getComponentsPatch();\r\n        if (nmsComponents.isEmpty()) {\r\n            return null;\r\n        }\r\n        return DataComponentPatch.CODEC.encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE), nmsComponents).getOrThrow().getAsJsonObject();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack applyRawHoverComponentsJson(ItemStack item, JsonObject components) {\r\n        return DataComponentPatch.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE), components).mapOrElse(\r\n                nmsComponents -> {\r\n                    net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item);\r\n                    nmsItem.applyComponents(nmsComponents);\r\n                    return CraftItemStack.asCraftMirror(nmsItem);\r\n                },\r\n                error -> {\r\n                    Debug.echoError(\"Invalid hover item data '\" + components + \"': \" + error.message());\r\n                    return item;\r\n                });\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getSkullSkin(ItemStack is) {\r\n        net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(is);\r\n        ResolvableProfile profile = itemStack.get(DataComponents.PROFILE);\r\n        if (profile != null) {\r\n            Property property = Iterables.getFirst(profile.properties().get(\"textures\"), null);\r\n            return new PlayerProfile(profile.name().orElse(null), profile.id().orElse(null),\r\n                    property != null ? property.value() : null,\r\n                    property != null ? property.signature() : null);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setSkullSkin(ItemStack itemStack, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile);\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.set(DataComponents.PROFILE, new ResolvableProfile(gameProfile));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack addNbtData(ItemStack itemStack, String key, BinaryTag value) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.update(DataComponents.CUSTOM_DATA, CustomData.EMPTY, customData -> customData.update(nmsCompoundTag -> nmsCompoundTag.put(key, NBTAdapter.toNMS(value))));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    // TODO: 1.20.6: this now needs to serialize components into NBT every single time, should probably only return custom NBT data with specialized methods for other usages\r\n    // TODO: 1.20.6: NBT structure is different basically everywhere, usages of this will need an update\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(ItemStack itemStack) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        if (nmsItemStack != null && !nmsItemStack.isEmpty()) {\r\n            return NBTAdapter.toAPI((CompoundTag) nmsItemStack.save(CraftRegistry.getMinecraftRegistry()));\r\n        }\r\n        return CompoundBinaryTag.empty();\r\n    }\r\n\r\n    // TODO: 1.20.6: same as getNbtData, ideally needs to only set custom NBT data and have specialized methods for other usages\r\n    @Override\r\n    public ItemStack setNbtData(ItemStack itemStack, CompoundBinaryTag compoundTag) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = net.minecraft.world.item.ItemStack.parseOptional(CraftRegistry.getMinecraftRegistry(), NBTAdapter.toNMS(compoundTag));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getCustomData(ItemStack item) {\r\n        CustomData customData = CraftItemStack.asNMSCopy(item).get(DataComponents.CUSTOM_DATA);\r\n        return customData != null ? NBTAdapter.toAPI(customData.getUnsafe()) : null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setCustomData(ItemStack item, CompoundBinaryTag data) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        if (data == null) {\r\n            nmsItemStack.remove(DataComponents.CUSTOM_DATA);\r\n        }\r\n        else {\r\n            nmsItemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(NBTAdapter.toNMS(data)));\r\n        }\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    public static final int DATA_VERSION_1_20_4 = 3700;\r\n\r\n    @Override\r\n    public ItemStack setPartialOldNbt(ItemStack item, CompoundBinaryTag oldTag) {\r\n        int currentDataVersion = CraftMagicNumbers.INSTANCE.getDataVersion();\r\n        CompoundTag nmsOldTag = new CompoundTag();\r\n        nmsOldTag.putString(\"id\", item.getType().getKey().toString());\r\n        nmsOldTag.putByte(\"Count\", (byte) item.getAmount());\r\n        nmsOldTag.put(\"tag\", NBTAdapter.toNMS(oldTag));\r\n        CompoundTag nmsUpdatedTag = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, nmsOldTag), DATA_VERSION_1_20_4, currentDataVersion).getValue();\r\n        CompoundTag nmsCurrentTag = (CompoundTag) CraftItemStack.asNMSCopy(item).save(CraftRegistry.getMinecraftRegistry());\r\n        CompoundTag nmsMergedTag = nmsCurrentTag.merge(nmsUpdatedTag);\r\n        return CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.parse(CraftRegistry.getMinecraftRegistry(), nmsMergedTag).orElseThrow());\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getEntityData(ItemStack item) {\r\n        CustomData entityData = CraftItemStack.asNMSCopy(item).get(DataComponents.ENTITY_DATA);\r\n        return entityData != null ? NBTAdapter.toAPI(entityData.getUnsafe()) : null;\r\n    }\r\n\r\n    public static final CompoundTag EMPTY_TAG = new CompoundTag();\r\n\r\n    @Override\r\n    public ItemStack setEntityData(ItemStack item, CompoundBinaryTag entityNbt, EntityType entityType) {\r\n        CompoundTag nmsEntityNbt = EMPTY_TAG;\r\n        if (entityNbt != null && !entityNbt.isEmpty() && (!entityNbt.contains(\"id\") || entityNbt.size() > 1)) {\r\n            nmsEntityNbt = NBTAdapter.toNMS(entityNbt);\r\n            nmsEntityNbt.putString(\"id\", entityType.getKey().toString());\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        CustomData.set(DataComponents.ENTITY_DATA, nmsItemStack, nmsEntityNbt);\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public MapTag getRawComponentsPatch(ItemStack item, boolean excludeHandled) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        DataComponentPatch patch = nmsItemStack.getComponentsPatch();\r\n        if (excludeHandled) {\r\n            patch = patch.forget(componentType -> {\r\n                ResourceLocation componentId = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(componentType);\r\n                return ItemComponentsPatch.propertyHandledComponents.contains(componentId.toString());\r\n            });\r\n        }\r\n        if (patch.isEmpty()) {\r\n            return new MapTag();\r\n        }\r\n        RegistryOps<net.minecraft.nbt.Tag> registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE);\r\n        CompoundTag nmsPatch = (CompoundTag) DataComponentPatch.CODEC.encodeStart(registryOps, patch).getOrThrow();\r\n        MapTag rawComponents = (MapTag) ItemRawNBT.nbtTagToObject(NBTAdapter.toAPI(nmsPatch));\r\n        rawComponents.putObject(ItemComponentsPatch.DATA_VERSION_KEY, new ElementTag(CraftMagicNumbers.INSTANCE.getDataVersion()));\r\n        return rawComponents;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setRawComponentsPatch(ItemStack item, MapTag rawComponentsMap, int dataVersion, Consumer<String> errorHandler) {\r\n        int currentDataVersion = CraftMagicNumbers.INSTANCE.getDataVersion();\r\n        CompoundBinaryTag rawComponents = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(rawComponentsMap, CoreUtilities.errorButNoDebugContext, \"\");\r\n        CompoundTag nmsRawComponents = NBTAdapter.toNMS(rawComponents);\r\n        RegistryOps<net.minecraft.nbt.Tag> registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE);\r\n        if (dataVersion < currentDataVersion) {\r\n            CompoundTag legacyItemData = new CompoundTag();\r\n            legacyItemData.putString(\"id\", item.getType().getKey().toString());\r\n            legacyItemData.putInt(\"count\", item.getAmount());\r\n            legacyItemData.put(\"components\", nmsRawComponents);\r\n            CompoundTag nmsUpdatedTag = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(registryOps, legacyItemData), dataVersion, currentDataVersion).getValue();\r\n            nmsRawComponents = nmsUpdatedTag.getCompound(\"components\");\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        DataComponentPatch.CODEC.parse(registryOps, nmsRawComponents)\r\n                .ifError(error -> errorHandler.accept(error.message()))\r\n                .ifSuccess(nmsItemStack::applyComponents);\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    public static final Field AdventureModePredicate_predicates = ReflectionHelper.getFields(AdventureModePredicate.class).get(ReflectionMappingsInfo.AdventureModePredicate_predicates);\r\n\r\n    @Override\r\n    public List<Material> getCanPlaceOn(ItemStack item) {\r\n        return getAdventureModePredicateMaterials(item, DataComponents.CAN_PLACE_ON);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setCanPlaceOn(ItemStack item, List<Material> canPlaceOn) {\r\n        return setAdventureModePredicateMaterials(item, DataComponents.CAN_PLACE_ON, canPlaceOn);\r\n    }\r\n\r\n    @Override\r\n    public List<Material> getCanBreak(ItemStack item) {\r\n        return getAdventureModePredicateMaterials(item, DataComponents.CAN_BREAK);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setCanBreak(ItemStack item, List<Material> canBreak) {\r\n        return setAdventureModePredicateMaterials(item, DataComponents.CAN_BREAK, canBreak);\r\n    }\r\n\r\n    private List<Material> getAdventureModePredicateMaterials(ItemStack item, DataComponentType<AdventureModePredicate> nmsComponent) {\r\n        AdventureModePredicate nmsAdventurePredicate = CraftItemStack.asNMSCopy(item).get(nmsComponent);\r\n        if (nmsAdventurePredicate == null) {\r\n            return null;\r\n        }\r\n        List<BlockPredicate> nmsPredicates;\r\n        try {\r\n            nmsPredicates = (List<BlockPredicate>) AdventureModePredicate_predicates.get(nmsAdventurePredicate);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n            return null;\r\n        }\r\n        List<Material> materials = new ArrayList<>();\r\n        for (BlockPredicate nmsPredicate : nmsPredicates) {\r\n            nmsPredicate.blocks().ifPresent(nmsHolderSet -> {\r\n                for (Holder<Block> nmsHolder : nmsHolderSet) {\r\n                    materials.add(CraftMagicNumbers.getMaterial(nmsHolder.value()));\r\n                }\r\n            });\r\n        }\r\n        return materials;\r\n    }\r\n\r\n    private ItemStack setAdventureModePredicateMaterials(ItemStack item, DataComponentType<AdventureModePredicate> nmsComponent, List<Material> materials) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        AdventureModePredicate nmsAdventurePredicate = nmsItemStack.get(nmsComponent);\r\n        if (materials == null) {\r\n            if (nmsAdventurePredicate == null) {\r\n                return item;\r\n            }\r\n            nmsItemStack.remove(nmsComponent);\r\n            return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n        }\r\n        BlockPredicate nmsPredicate = new BlockPredicate(Optional.of(\r\n                HolderSet.direct(material -> BuiltInRegistries.BLOCK.getHolder(CraftNamespacedKey.toMinecraft(material.getKey())).orElseThrow(), materials)\r\n        ), Optional.empty(), Optional.empty());\r\n        nmsItemStack.set(nmsComponent, new AdventureModePredicate(List.of(nmsPredicate), nmsAdventurePredicate == null || nmsAdventurePredicate.showInTooltip()));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public void setInventoryItem(Inventory inventory, ItemStack item, int slot) {\r\n        if (inventory instanceof CraftInventoryPlayer && ((CraftInventoryPlayer) inventory).getInventory().player == null) {\r\n            ((CraftInventoryPlayer) inventory).getInventory().setItem(slot, CraftItemStack.asNMSCopy(item));\r\n        }\r\n        else {\r\n            inventory.setItem(slot, item);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getDisplayName(ItemTag item) {\r\n        if (!item.getItemMeta().hasDisplayName()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        Component nmsDisplayName = nmsItemStack.get(DataComponents.CUSTOM_NAME);\r\n        return FormattedTextHelper.stringify(Handler.componentToSpigot(nmsDisplayName));\r\n    }\r\n\r\n    @Override\r\n    public List<String> getLore(ItemTag item) {\r\n        if (!item.getItemMeta().hasLore()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        ItemLore nmsLore = nmsItemStack.get(DataComponents.LORE);\r\n        List<String> outList = new ArrayList<>(nmsLore.lines().size());\r\n        for (Component nmsLoreLine : nmsLore.lines()) {\r\n            outList.add(FormattedTextHelper.stringify(Handler.componentToSpigot(nmsLoreLine)));\r\n        }\r\n        return outList;\r\n    }\r\n\r\n    @Override\r\n    public void setDisplayName(ItemTag item, String name) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        if (name == null || name.isEmpty()) {\r\n            nmsItemStack.remove(DataComponents.CUSTOM_NAME);\r\n        }\r\n        else {\r\n            nmsItemStack.set(DataComponents.CUSTOM_NAME, Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)));\r\n        }\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    @Override\r\n    public void setLore(ItemTag item, List<String> lore) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        if (lore == null || lore.isEmpty()) {\r\n            nmsItemStack.remove(DataComponents.LORE);\r\n        }\r\n        else {\r\n            List<Component> nmsLore = new ArrayList<>(lore.size());\r\n            for (String loreLine : lore) {\r\n                nmsLore.add(Handler.componentToNMS(FormattedTextHelper.parse(loreLine, ChatColor.WHITE)));\r\n            }\r\n            nmsItemStack.set(DataComponents.LORE, new ItemLore(nmsLore));\r\n        }\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.getCorrectStateForFluidBlock.\r\n     */\r\n    public static BlockState getCorrectStateForFluidBlock(Level world, BlockState blockState, BlockPos blockPos) {\r\n        FluidState fluid = blockState.getFluidState();\r\n        return !fluid.isEmpty() && !blockState.isFaceSturdy(world, blockPos, Direction.UP) ? fluid.createLegacyBlock() : blockState;\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.update, redesigned slightly to render totally rather than just relative to a player.\r\n     * Some variables manually renamed for readability.\r\n     */\r\n    public static void renderFullMap(MapItemSavedData worldmap, int xMin, int zMin, int xMax, int zMax) {\r\n        Level world = ((CraftWorld) worldmap.mapView.getWorld()).getHandle();\r\n        int scale = 1 << worldmap.scale;\r\n        int mapX = worldmap.centerX;\r\n        int mapZ = worldmap.centerZ;\r\n        for (int x = xMin; x < xMax; x++) {\r\n            double d0 = 0.0D;\r\n            for (int z = zMin; z < zMax; z++) {\r\n                int k2 = (mapX / scale + x - 64) * scale;\r\n                int l2 = (mapZ / scale + z - 64) * scale;\r\n                Multiset<MapColor> multiset = LinkedHashMultiset.create();\r\n                LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2));\r\n                if (!chunk.isEmpty()) {\r\n                    ChunkPos chunkcoordintpair = chunk.getPos();\r\n                    int i3 = k2 & 15;\r\n                    int j3 = l2 & 15;\r\n                    int k3 = 0;\r\n                    double d1 = 0.0D;\r\n                    if (world.dimensionType().hasCeiling()) {\r\n                        int l3 = k2 + l2 * 231871;\r\n                        l3 = l3 * l3 * 31287121 + l3 * 11;\r\n                        if ((l3 >> 20 & 1) == 0) {\r\n                            multiset.add(Blocks.DIRT.defaultBlockState().getMapColor(world, BlockPos.ZERO), 10);\r\n                        }\r\n                        else {\r\n                            multiset.add(Blocks.STONE.defaultBlockState().getMapColor(world, BlockPos.ZERO), 100);\r\n                        }\r\n\r\n                        d1 = 100.0D;\r\n                    }\r\n                    else {\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition1 = new BlockPos.MutableBlockPos();\r\n                        for (int i4 = 0; i4 < scale; ++i4) {\r\n                            for (int j4 = 0; j4 < scale; ++j4) {\r\n                                int k4 = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i4 + i3, j4 + j3) + 1;\r\n                                BlockState iblockdata;\r\n                                if (k4 <= world.getMinBuildHeight() + 1) {\r\n                                    iblockdata = Blocks.BEDROCK.defaultBlockState();\r\n                                }\r\n                                else {\r\n                                    do {\r\n                                        --k4;\r\n                                        blockposition_mutableblockposition.set(chunkcoordintpair.getMinBlockX() + i4 + i3, k4, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                        iblockdata = chunk.getBlockState(blockposition_mutableblockposition);\r\n                                    } while (iblockdata.getMapColor(world, blockposition_mutableblockposition) == MapColor.NONE && k4 > world.getMinBuildHeight());\r\n                                    if (k4 > world.getMinBuildHeight() && !iblockdata.getFluidState().isEmpty()) {\r\n                                        int l4 = k4 - 1;\r\n                                        blockposition_mutableblockposition1.set(blockposition_mutableblockposition);\r\n\r\n                                        BlockState iblockdata1;\r\n                                        do {\r\n                                            blockposition_mutableblockposition1.setY(l4--);\r\n                                            iblockdata1 = chunk.getBlockState(blockposition_mutableblockposition1);\r\n                                            k3++;\r\n                                        } while (l4 > world.getMinBuildHeight() && !iblockdata1.getFluidState().isEmpty());\r\n                                        iblockdata = getCorrectStateForFluidBlock(world, iblockdata, blockposition_mutableblockposition);\r\n                                    }\r\n                                }\r\n                                worldmap.checkBanners(world, chunkcoordintpair.getMinBlockX() + i4 + i3, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                d1 += (double) k4 / (double) (scale * scale);\r\n                                multiset.add(iblockdata.getMapColor(world, blockposition_mutableblockposition));\r\n                            }\r\n                        }\r\n                    }\r\n                    k3 /= scale * scale;\r\n                    double d2 = (d1 - d0) * 4.0D / (double) (scale + 4) + ((double) (x + z & 1) - 0.5D) * 0.4D;\r\n                    byte b0 = 1;\r\n                    if (d2 > 0.6D) {\r\n                        b0 = 2;\r\n                    }\r\n                    if (d2 < -0.6D) {\r\n                        b0 = 0;\r\n                    }\r\n                    MapColor materialmapcolor = Iterables.getFirst(Multisets.copyHighestCountFirst(multiset), MapColor.NONE);\r\n                    if (materialmapcolor == MapColor.WATER) {\r\n                        d2 = (double) k3 * 0.1D + (double) (x + z & 1) * 0.2D;\r\n                        b0 = 1;\r\n                        if (d2 < 0.5D) {\r\n                            b0 = 2;\r\n                        }\r\n                        if (d2 > 0.9D) {\r\n                            b0 = 0;\r\n                        }\r\n                    }\r\n                    d0 = d1;\r\n                    worldmap.updateColor(x, z, (byte) (materialmapcolor.id * 4 + b0));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean renderEntireMap(int mapId, int xMin, int zMin, int xMax, int zMax) {\r\n        MapItemSavedData worldmap = ((CraftServer) Bukkit.getServer()).getServer().getLevel(net.minecraft.world.level.Level.OVERWORLD).getMapData(new MapId(mapId));\r\n        if (worldmap == null) {\r\n            return false;\r\n        }\r\n        renderFullMap(worldmap, xMin, zMin, xMax, zMax);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public BlockData getPlacedBlock(Material material) {\r\n        Item nmsItem = BuiltInRegistries.ITEM.getOptional(CraftNamespacedKey.toMinecraft(material.getKey())).orElse(null);\r\n        if (nmsItem instanceof BlockItem) {\r\n            Block block = ((BlockItem) nmsItem).getBlock();\r\n            return CraftBlockData.fromData(block.defaultBlockState());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public boolean isValidMix(ItemStack input, ItemStack ingredient) {\r\n        net.minecraft.world.item.ItemStack nmsInput = CraftItemStack.asNMSCopy(input);\r\n        net.minecraft.world.item.ItemStack nmsIngredient = CraftItemStack.asNMSCopy(ingredient);\r\n        return MinecraftServer.getServer().potionBrewing().hasMix(nmsInput, nmsIngredient);\r\n    }\r\n\r\n    public static Class<?> PaperPotionMix_CLASS = null;\r\n    public static Map<NamespacedKey, BrewingRecipe> customBrewingRecipes = null;\r\n\r\n    @Override\r\n    public Map<NamespacedKey, BrewingRecipe> getCustomBrewingRecipes() {\r\n        if (customBrewingRecipes == null) {\r\n            customBrewingRecipes = Maps.transformValues((Map<NamespacedKey, ?>) ReflectionHelper.getFieldValue(PotionBrewing.class, \"customMixes\", MinecraftServer.getServer().potionBrewing()), paperMix -> {\r\n                if (PaperPotionMix_CLASS == null) {\r\n                    PaperPotionMix_CLASS = paperMix.getClass();\r\n                }\r\n                RecipeChoice ingredient = convertChoice(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"ingredient\", paperMix));\r\n                RecipeChoice input = convertChoice(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"input\", paperMix));\r\n                ItemStack result = CraftItemStack.asBukkitCopy(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"result\", paperMix));\r\n                return new BrewingRecipe(input, ingredient, result);\r\n            });\r\n        }\r\n        return customBrewingRecipes;\r\n    }\r\n\r\n    private RecipeChoice convertChoice(Predicate<net.minecraft.world.item.ItemStack> nmsPredicate) {\r\n        // Not an instance of net.minecraft.world.item.crafting.Ingredient = a predicate recipe choice\r\n        if (nmsPredicate instanceof Ingredient ingredient) {\r\n            return CraftRecipe.toBukkit(ingredient);\r\n        }\r\n        return PaperAPITools.instance.createPredicateRecipeChoice(item -> nmsPredicate.test(CraftItemStack.asNMSCopy(item)));\r\n    }\r\n\r\n    @Override\r\n    public byte[] renderMap(MapView mapView, Player player) {\r\n        return ((CraftMapView) mapView).render((CraftPlayer) player).buffer;\r\n    }\r\n\r\n    @Override\r\n    public int getFoodPoints(Material itemType) {\r\n        return CraftMagicNumbers.getItem(itemType).components().get(DataComponents.FOOD).nutrition();\r\n    }\r\n\r\n    @Override\r\n    public DyeColor getShieldColor(ItemStack item) {\r\n        net.minecraft.world.item.DyeColor nmsColor = CraftItemStack.asNMSCopy(item).get(DataComponents.BASE_COLOR);\r\n        return nmsColor != null ? DyeColor.getByWoolData((byte) nmsColor.getId()) : null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setShieldColor(ItemStack item, DyeColor color) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        if (color != null) {\r\n            nmsItemStack.set(DataComponents.BASE_COLOR, net.minecraft.world.item.DyeColor.byId(color.getWoolData()));\r\n        }\r\n        else {\r\n            nmsItemStack.remove(DataComponents.BASE_COLOR);\r\n        }\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/NBTAdapter.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\n\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport net.kyori.adventure.nbt.*;\nimport net.minecraft.nbt.*;\n\nimport java.lang.invoke.MethodHandle;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class NBTAdapter {\n\n    public static final MethodHandle COMPOUND_TAG_MAP_CONSTRUCTOR = ReflectionHelper.getConstructor(CompoundTag.class, Map.class);\n\n    public static Tag toNMS(BinaryTag tag) {\n        if (tag instanceof ByteBinaryTag byteTag) {\n            return switch (byteTag.value()) {\n                case 0 -> ByteTag.ZERO;\n                case 1 -> ByteTag.ONE;\n                default -> ByteTag.valueOf(byteTag.value());\n            };\n        }\n        else if (tag instanceof ShortBinaryTag shortTag) {\n            return ShortTag.valueOf(shortTag.value());\n        }\n        else if (tag instanceof IntBinaryTag intTag) {\n            return IntTag.valueOf(intTag.value());\n        }\n        else if (tag instanceof LongBinaryTag longTag) {\n            return LongTag.valueOf(longTag.value());\n        }\n        else if (tag instanceof FloatBinaryTag floatTag) {\n            return FloatTag.valueOf(floatTag.value());\n        }\n        else if (tag instanceof DoubleBinaryTag doubleTag) {\n            return DoubleTag.valueOf(doubleTag.value());\n        }\n        else if (tag instanceof ByteArrayBinaryTag byteArrayTag) {\n            return new ByteArrayTag(byteArrayTag.value());\n        }\n        else if (tag instanceof IntArrayBinaryTag intArrayTag) {\n            return new IntArrayTag(intArrayTag.value());\n        }\n        else if (tag instanceof LongArrayBinaryTag longArrayTag) {\n            return new LongArrayTag(longArrayTag.value());\n        }\n        else if (tag instanceof StringBinaryTag stringTag) {\n            return StringTag.valueOf(stringTag.value());\n        }\n        else if (tag instanceof ListBinaryTag listTag) {\n            return toNMS(listTag);\n        }\n        else if (tag instanceof CompoundBinaryTag compoundTag) {\n            return toNMS(compoundTag);\n        }\n        else if (tag instanceof EndBinaryTag) {\n            return EndTag.INSTANCE;\n        }\n        throw new IllegalStateException(\"Unrecognized API tag of type '\" + tag.type() + \"': \" + tag);\n    }\n\n    public static BinaryTag toAPI(Tag nmsTag) {\n        if (nmsTag instanceof ByteTag nmsByteTag) {\n            return ByteBinaryTag.byteBinaryTag(nmsByteTag.getAsByte());\n        }\n        else if (nmsTag instanceof ShortTag nmsShortTag) {\n            return ShortBinaryTag.shortBinaryTag(nmsShortTag.getAsShort());\n        }\n        else if (nmsTag instanceof IntTag nmsIntTag) {\n            return IntBinaryTag.intBinaryTag(nmsIntTag.getAsInt());\n        }\n        else if (nmsTag instanceof LongTag nmsLongTag) {\n            return LongBinaryTag.longBinaryTag(nmsLongTag.getAsLong());\n        }\n        else if (nmsTag instanceof FloatTag nmsFloatTag) {\n            return FloatBinaryTag.floatBinaryTag(nmsFloatTag.getAsFloat());\n        }\n        else if (nmsTag instanceof DoubleTag nmsDoubleTag) {\n            return DoubleBinaryTag.doubleBinaryTag(nmsDoubleTag.getAsDouble());\n        }\n        else if (nmsTag instanceof ByteArrayTag nmsByteArrayTag) {\n            return ByteArrayBinaryTag.byteArrayBinaryTag(nmsByteArrayTag.getAsByteArray());\n        }\n        else if (nmsTag instanceof IntArrayTag nmsIntArrayTag) {\n            return IntArrayBinaryTag.intArrayBinaryTag(nmsIntArrayTag.getAsIntArray());\n        }\n        else if (nmsTag instanceof LongArrayTag nmsLongArrayTag) {\n            return LongArrayBinaryTag.longArrayBinaryTag(nmsLongArrayTag.getAsLongArray());\n        }\n        else if (nmsTag instanceof StringTag nmsStringTag) {\n            return StringBinaryTag.stringBinaryTag(nmsStringTag.getAsString());\n        }\n        else if (nmsTag instanceof ListTag nmsListTag) {\n            return toAPI(nmsListTag);\n        }\n        else if (nmsTag instanceof CompoundTag nmsCompoundTag) {\n            return toAPI(nmsCompoundTag);\n        }\n        else if (nmsTag instanceof EndTag) {\n            return EndBinaryTag.endBinaryTag();\n        }\n        throw new IllegalStateException(\"Unrecognized NMS tag of type '\" + nmsTag.getClass().getName() + '/' + nmsTag.getType().getName() + \"': \" + nmsTag);\n    }\n\n    public static ListBinaryTag toAPI(ListTag nmsListTag) {\n        ListBinaryTag.Builder<BinaryTag> builder = ListBinaryTag.builder(nmsListTag.size());\n        for (Tag nmsValue : nmsListTag) {\n            builder.add(toAPI(nmsValue));\n        }\n        return builder.build();\n    }\n\n    public static ListTag toNMS(ListBinaryTag listTag) {\n        List<Tag> nmsTags = new ArrayList<>(listTag.size());\n        for (BinaryTag value : listTag) {\n            nmsTags.add(toNMS(value));\n        }\n        return new ListTag(nmsTags, listTag.elementType().id());\n    }\n\n    public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) {\n        CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size());\n        for (String key : nmsCompoundTag.getAllKeys()) {\n            builder.put(key, toAPI(nmsCompoundTag.get(key)));\n        }\n        return builder.build();\n    }\n\n    public static CompoundTag toNMS(CompoundBinaryTag compoundTag) {\n        Map<String, Tag> nmsTags = new HashMap<>(compoundTag.size());\n        for (Map.Entry<String, ? extends BinaryTag> entry : compoundTag) {\n            nmsTags.put(entry.getKey(), toNMS(entry.getValue()));\n        }\n        try {\n            return (CompoundTag) COMPOUND_TAG_MAP_CONSTRUCTOR.invokeExact(nmsTags);\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/PacketHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.PacketHelper;\r\nimport com.denizenscript.denizen.nms.v1_20.Handler;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.scripts.commands.entity.TeleportCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.maps.MapImage;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;\r\nimport net.minecraft.network.protocol.common.custom.BrandPayload;\r\nimport net.minecraft.network.protocol.common.custom.GameTestAddMarkerDebugPayload;\r\nimport net.minecraft.network.protocol.common.custom.GameTestClearMarkersDebugPayload;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.RelativeMovement;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.monster.CaveSpider;\r\nimport net.minecraft.world.entity.monster.Creeper;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.entity.monster.Spider;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Team;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.EntityEffect;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.block.Sign;\r\nimport org.bukkit.block.sign.Side;\r\nimport org.bukkit.block.sign.SignSide;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_20_R4.map.CraftMapCanvas;\r\nimport org.bukkit.craftbukkit.v1_20_R4.map.CraftMapView;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftLocation;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapPalette;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class PacketHelperImpl implements PacketHelper {\r\n\r\n    public static final EntityDataAccessor<Float> PLAYER_DATA_ACCESSOR_ABSORPTION = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, ReflectionMappingsInfo.Player_DATA_PLAYER_ABSORPTION_ID, null);\r\n\r\n    public static final EntityDataAccessor<Byte> ENTITY_DATA_ACCESSOR_FLAGS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_SHARED_FLAGS_ID, null);\r\n\r\n    public static final MethodHandle ABILITIES_PACKET_FOV_SETTER = ReflectionHelper.getFinalSetter(ClientboundPlayerAbilitiesPacket.class, ReflectionMappingsInfo.ClientboundPlayerAbilitiesPacket_walkingSpeed);\r\n\r\n    public static final Field ENTITY_TRACKER_ENTRY_GETTER = ReflectionHelper.getFields(ChunkMap.TrackedEntity.class).getFirstOfType(ServerEntity.class);\r\n\r\n    public static final MethodHandle CANVAS_GET_BUFFER = ReflectionHelper.getMethodHandle(CraftMapCanvas.class, \"getBuffer\");\r\n    public static final Field MAPVIEW_WORLDMAP = ReflectionHelper.getFields(CraftMapView.class).get(\"worldMap\");\r\n\r\n    public static final EntityDataAccessor<Optional<Component>> ENTITY_DATA_ACCESSOR_CUSTOM_NAME = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_CUSTOM_NAME, null);\r\n    public static final EntityDataAccessor<Boolean> ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_CUSTOM_NAME_VISIBLE, null);\r\n\r\n    @Override\r\n    public void setFakeAbsorption(Player player, float value) {\r\n        send(player, new ClientboundSetEntityDataPacket(player.getEntityId(), List.of(createEntityData(PLAYER_DATA_ACCESSOR_ABSORPTION, value))));\r\n    }\r\n\r\n    @Override\r\n    public void setSlot(Player player, int slot, ItemStack itemStack, boolean playerOnly) {\r\n        AbstractContainerMenu menu = ((CraftPlayer) player).getHandle().containerMenu;\r\n        int windowId = playerOnly ? 0 : menu.containerId;\r\n        send(player, new ClientboundContainerSetSlotPacket(windowId, menu.incrementStateId(), slot, CraftItemStack.asNMSCopy(itemStack)));\r\n    }\r\n\r\n    @Override\r\n    public void setFieldOfView(Player player, float fov) {\r\n        ClientboundPlayerAbilitiesPacket packet = new ClientboundPlayerAbilitiesPacket(((CraftPlayer) player).getHandle().getAbilities());\r\n        if (!Float.isNaN(fov)) {\r\n            try {\r\n                ABILITIES_PACKET_FOV_SETTER.invoke(packet, fov);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void respawn(Player player) {\r\n        ((CraftPlayer) player).getHandle().connection.handleClientCommand(new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN));\r\n    }\r\n\r\n    @Override\r\n    public void setVision(Player player, EntityType entityType) {\r\n        final net.minecraft.world.entity.LivingEntity entity;\r\n        if (entityType == EntityType.CREEPER) {\r\n            entity = new Creeper(net.minecraft.world.entity.EntityType.CREEPER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.SPIDER) {\r\n            entity = new Spider(net.minecraft.world.entity.EntityType.SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.CAVE_SPIDER) {\r\n            entity = new CaveSpider(net.minecraft.world.entity.EntityType.CAVE_SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.ENDERMAN) {\r\n            entity = new EnderMan(net.minecraft.world.entity.EntityType.ENDERMAN, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n\r\n        // Spectating an entity then immediately respawning the player prevents a client shader update,\r\n        // allowing the player to retain whatever vision the mob they spectated had.\r\n        send(player, new ClientboundAddEntityPacket(entity));\r\n        send(player, new ClientboundSetCameraPacket(entity));\r\n        NMSHandler.playerHelper.refreshPlayer(player);\r\n    }\r\n\r\n    @Override\r\n    public void showBlockAction(Player player, Location location, int action, int state) {\r\n        BlockPos position = CraftLocation.toBlockPosition(location);\r\n        Block block = ((CraftWorld) location.getWorld()).getHandle().getBlockState(position).getBlock();\r\n        send(player, new ClientboundBlockEventPacket(position, block, action, state));\r\n    }\r\n\r\n    @Override\r\n    public void showTabListHeaderFooter(Player player, String header, String footer) {\r\n        Component cHeader = Handler.componentToNMS(FormattedTextHelper.parse(header, ChatColor.WHITE));\r\n        Component cFooter = Handler.componentToNMS(FormattedTextHelper.parse(footer, ChatColor.WHITE));\r\n        send(player, new ClientboundTabListPacket(cHeader, cFooter));\r\n    }\r\n\r\n    @Override\r\n    public void showTitle(Player player, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) {\r\n        send(player, new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks));\r\n        if (title != null) {\r\n            send(player, new ClientboundSetTitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE))));\r\n        }\r\n        if (subtitle != null) {\r\n            send(player, new ClientboundSetSubtitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(subtitle, ChatColor.WHITE))));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void showMobHealth(Player player, LivingEntity mob, double health, double maxHealth) {\r\n        AttributeInstance attr = new AttributeInstance(Attributes.MAX_HEALTH, (a) -> {});\r\n        attr.setBaseValue(maxHealth);\r\n        send(player, new ClientboundUpdateAttributesPacket(mob.getEntityId(), List.of(attr)));\r\n        send(player, new ClientboundSetEntityDataPacket(mob.getEntityId(), List.of(createEntityData(net.minecraft.world.entity.LivingEntity.DATA_HEALTH_ID, (float) health))));\r\n    }\r\n\r\n    @Override\r\n    public void showSignEditor(Player player, Location location) {\r\n        NetworkInterceptHelper.enable();\r\n        Sign sign = null;\r\n        BlockPos toOpen = null;\r\n        // It actually allows 8 blocks of distance, but we limit to 7 because the client doesn't properly round down\r\n        for (int i = 0; i < 8; i++) {\r\n            Location toCheck = player.getLocation();\r\n            toCheck.setY(toCheck.getY() - i);\r\n            if (toCheck.getBlock().getState() instanceof Sign foundSign) {\r\n                sign = foundSign;\r\n            }\r\n            else {\r\n                sign = null;\r\n                toOpen = CraftLocation.toBlockPosition(toCheck);\r\n                break;\r\n            }\r\n        }\r\n        if (sign != null) {\r\n            toOpen = CraftLocation.toBlockPosition(sign.getLocation());\r\n            SignSide front = sign.getSide(Side.FRONT);\r\n            for (int line = 0; line < 4; line++) {\r\n                front.setLine(line, \"\");\r\n            }\r\n            player.sendBlockUpdate(sign.getLocation(), sign);\r\n        }\r\n        DenizenNetworkManagerImpl.getNetworkManager(player).packetListener.fakeSignExpected = toOpen;\r\n        send(player, new ClientboundOpenSignEditorPacket(toOpen, true));\r\n    }\r\n\r\n    @Override\r\n    public void forceSpectate(Player player, Entity entity) {\r\n        send(player, new ClientboundSetCameraPacket(((CraftEntity) entity).getHandle()));\r\n    }\r\n\r\n    public static void forceRespawnPlayerEntity(Entity entity, Player viewer) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level()).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        if (entityTracker != null) {\r\n            try {\r\n                ServerEntity entry = (ServerEntity) ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n                if (entry != null) {\r\n                    entry.removePairing(((CraftPlayer) viewer).getHandle());\r\n                    entry.addPairing(((CraftPlayer) viewer).getHandle());\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendRename(Player player, Entity entity, String name, boolean listMode) {\r\n        try {\r\n            if (entity.getType() == EntityType.PLAYER) {\r\n                if (listMode) {\r\n                    send(player, new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, ((CraftPlayer) player).getHandle()));\r\n                }\r\n                else {\r\n                    // For player entities, force a respawn packet and let the dynamic intercept correct the details\r\n                    forceRespawnPlayerEntity(entity, player);\r\n                }\r\n                return;\r\n            }\r\n            List<SynchedEntityData.DataValue<?>> list = List.of(\r\n                    createEntityData(ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)))),\r\n                    createEntityData(ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true)\r\n            );\r\n            send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), list));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static HashMap<UUID, HashMap<UUID, PlayerTeam>> noCollideTeamMap = new HashMap<>();\r\n\r\n    @Override\r\n    public void generateNoCollideTeam(Player player, UUID noCollide) {\r\n        removeNoCollideTeam(player, noCollide);\r\n        PlayerTeam team = new PlayerTeam(SidebarImpl.dummyScoreboard, Utilities.generateRandomColors(8));\r\n        team.getPlayers().add(noCollide.toString());\r\n        team.setCollisionRule(Team.CollisionRule.NEVER);\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>());\r\n        map.put(noCollide, team);\r\n        send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n    }\r\n\r\n    @Override\r\n    public void removeNoCollideTeam(Player player, UUID noCollide) {\r\n        if (noCollide == null || !player.isOnline()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n            return;\r\n        }\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.get(player.getUniqueId());\r\n        if (map == null) {\r\n            return;\r\n        }\r\n        PlayerTeam team = map.remove(noCollide);\r\n        if (team != null) {\r\n            send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        if (map.isEmpty()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityMetadataFlagsUpdate(Player player, Entity entity) {\r\n        byte flags = ((CraftEntity) entity).getHandle().getEntityData().get(ENTITY_DATA_ACCESSOR_FLAGS);\r\n        send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), List.of(createEntityData(ENTITY_DATA_ACCESSOR_FLAGS, flags))));\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityEffect(Player player, Entity entity, EntityEffect effect) {\r\n        send(player, new ClientboundEntityEventPacket(((CraftEntity) entity).getHandle(), effect.getData()));\r\n    }\r\n\r\n    @Override\r\n    public int getPacketStats(Player player, boolean sent) {\r\n        DenizenNetworkManagerImpl netMan = DenizenNetworkManagerImpl.getNetworkManager(player);\r\n        return sent ? netMan.packetsSent : netMan.packetsReceived;\r\n    }\r\n\r\n    @Override\r\n    public void setMapData(MapCanvas canvas, byte[] bytes, int x, int y, MapImage image) {\r\n        if (x > 127 || y > 127) {\r\n            return;\r\n        }\r\n        int width = Math.min(image.width, 128 - x),\r\n                height = Math.min(image.height, 128 - y);\r\n        if (x + width <= 0 || y + height <= 0) {\r\n            return;\r\n        }\r\n        try {\r\n            boolean anyChanged = false;\r\n            byte[] buffer = (byte[]) CANVAS_GET_BUFFER.invoke(canvas);\r\n            for (int x2 = x < 0 ? -x : 0; x2 < width; ++x2) {\r\n                for (int y2 = y < 0 ? -y : 0; y2 < height; ++y2) {\r\n                    byte p = bytes[y2 * image.width + x2];\r\n                    if (p != MapPalette.TRANSPARENT) {\r\n                        int index = (y2 + y) * 128 + (x2 + x);\r\n                        if (buffer[index] != p) {\r\n                            buffer[index] = p;\r\n                            anyChanged = true;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            if (anyChanged) {\r\n                // Flag the whole image as dirty\r\n                MapItemSavedData map = (MapItemSavedData) MAPVIEW_WORLDMAP.get(canvas.getMapView());\r\n                map.setColorsDirty(Math.max(x, 0), Math.max(y, 0));\r\n                map.setColorsDirty(width + x - 1, height + y - 1);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setNetworkManagerFor(Player player) {\r\n        DenizenNetworkManagerImpl.setNetworkManager(player);\r\n    }\r\n\r\n    @Override\r\n    public void enableNetworkManager() {\r\n        DenizenNetworkManagerImpl.enableNetworkManager();\r\n    }\r\n\r\n    @Override\r\n    public void showDebugTestMarker(Player player, Location location, ColorTag color, String name, int time) {\r\n        int colorInt = color.blue | (color.green << 8) | (color.red << 16) | (color.alpha << 24);\r\n        GameTestAddMarkerDebugPayload payload = new GameTestAddMarkerDebugPayload(CraftLocation.toBlockPosition(location), colorInt, name, time);\r\n        send(player, new ClientboundCustomPayloadPacket(payload));\r\n    }\r\n\r\n    @Override\r\n    public void clearDebugTestMarker(Player player) {\r\n        GameTestClearMarkersDebugPayload payload = new GameTestClearMarkersDebugPayload();\r\n        send(player, new ClientboundCustomPayloadPacket(payload));\r\n    }\r\n\r\n    @Override\r\n    public void sendBrand(Player player, String brand) {\r\n        BrandPayload payload = new BrandPayload(brand);\r\n        send(player, new ClientboundCustomPayloadPacket(payload));\r\n    }\r\n\r\n    @Override\r\n    public void sendCollectItemEntity(Player player, Entity taker, Entity item, int amount) {\r\n        send(player, new ClientboundTakeItemEntityPacket(item.getEntityId(), taker.getEntityId(), amount));\r\n    }\r\n\r\n    public RelativeMovement toNmsRelativeMovement(TeleportCommand.Relative relative) {\r\n        return switch (relative) {\r\n            case X -> RelativeMovement.X;\r\n            case Y -> RelativeMovement.Y;\r\n            case Z -> RelativeMovement.Z;\r\n            case YAW -> RelativeMovement.Y_ROT;\r\n            case PITCH -> RelativeMovement.X_ROT;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void sendRelativePositionPacket(Player player, double x, double y, double z, float yaw, float pitch, List<TeleportCommand.Relative> relativeAxis) {\r\n        Set<RelativeMovement> relativeMovements;\r\n        if (relativeAxis == null) {\r\n            relativeMovements = RelativeMovement.ALL;\r\n        }\r\n        else {\r\n            relativeMovements = EnumSet.noneOf(RelativeMovement.class);\r\n            for (TeleportCommand.Relative relative : relativeAxis) {\r\n                relativeMovements.add(toNmsRelativeMovement(relative));\r\n            }\r\n        }\r\n        ClientboundPlayerPositionPacket packet = new ClientboundPlayerPositionPacket(x, y, z, yaw, pitch, relativeMovements, 0);\r\n        sendAsyncSafe(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendRelativeLookPacket(Player player, float yaw, float pitch) {\r\n        sendRelativePositionPacket(player, 0, 0, 0, yaw, pitch, null);\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityDataPacket(List<Player> players, Entity entity, List<Object> data) {\r\n        ClientboundSetEntityDataPacket setEntityDataPacket = new ClientboundSetEntityDataPacket(entity.getEntityId(), (List<SynchedEntityData.DataValue<?>>) (Object) data);\r\n        Iterator<Player> playerIterator = players.iterator();\r\n        while (playerIterator.hasNext()) {\r\n            Player player = playerIterator.next();\r\n            if (!DenizenNetworkManagerImpl.getConnection(player).isConnected()) {\r\n                playerIterator.remove();\r\n                continue;\r\n            }\r\n            sendAsyncSafe(player, setEntityDataPacket);\r\n        }\r\n    }\r\n\r\n    public static void send(Player player, Packet<?> packet) {\r\n        ((CraftPlayer) player).getHandle().connection.send(packet);\r\n    }\r\n\r\n    public static void broadcast(Packet<?> packet) {\r\n        ((CraftServer) Bukkit.getServer()).getHandle().broadcastAll(packet);\r\n    }\r\n\r\n    public static void sendAsyncSafe(Player player, Packet<?> packet) {\r\n        DenizenNetworkManagerImpl.getConnection(player).channel.writeAndFlush(packet);\r\n    }\r\n\r\n    public static <T> SynchedEntityData.DataValue<T> createEntityData(EntityDataAccessor<T> accessor, T value) {\r\n        return new SynchedEntityData.DataValue<>(accessor.id(), accessor.serializer(), value);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/PlayerHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.enums.CustomEntityType;\r\nimport com.denizenscript.denizen.nms.interfaces.PlayerHelper;\r\nimport com.denizenscript.denizen.nms.v1_20.Handler;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.ImprovedOfflinePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.AbstractListenerPlayInImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.entity.DenizenEntityType;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport it.unimi.dsi.fastutil.ints.IntList;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.resources.ResourceLocation;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.players.ServerOpList;\r\nimport net.minecraft.server.players.ServerOpListEntry;\r\nimport net.minecraft.stats.ServerRecipeBook;\r\nimport net.minecraft.tags.BlockTags;\r\nimport net.minecraft.tags.TagNetworkSerialization;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.entity.Mob;\r\nimport net.minecraft.world.item.Item;\r\nimport net.minecraft.world.item.ItemCooldowns;\r\nimport net.minecraft.world.item.crafting.RecipeHolder;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.GameType;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.phys.AABB;\r\nimport org.bukkit.*;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.boss.CraftBossBar;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class PlayerHelperImpl extends PlayerHelper {\r\n\r\n    public static final Field ATTACK_COOLDOWN_TICKS = ReflectionHelper.getFields(LivingEntity.class).get(ReflectionMappingsInfo.LivingEntity_attackStrengthTicker, int.class);\r\n\r\n    public static final Field FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundTickCount, int.class);\r\n    public static final Field VEHICLE_FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundVehicleTickCount, int.class);\r\n    public static final Field PASSENGERS_PACKET_PASSENGERS = ReflectionHelper.getFields(ClientboundSetPassengersPacket.class).get(ReflectionMappingsInfo.ClientboundSetPassengersPacket_passengers, int[].class);\r\n    public static final MethodHandle PLAYER_RESPAWNFORCED_SETTER = ReflectionHelper.getFinalSetter(ServerPlayer.class, ReflectionMappingsInfo.ServerPlayer_respawnForced, boolean.class);\r\n\r\n    public static final EntityDataAccessor<Byte> PLAYER_DATA_ACCESSOR_SKINLAYERS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, ReflectionMappingsInfo.Player_DATA_PLAYER_MODE_CUSTOMISATION, null);\r\n\r\n    @Override\r\n    public void stopSound(Player player, NamespacedKey sound, SoundCategory category) {\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundStopSoundPacket(sound == null ? null : CraftNamespacedKey.toMinecraft(sound), null));\r\n    }\r\n\r\n    @Override\r\n    public void deTrackEntity(Player player, Entity entity) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ChunkMap.TrackedEntity tracker = nmsPlayer.serverLevel().getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n        if (tracker == null) {\r\n            if (NMSHandler.debugPackets) {\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Failed to de-track entity \" + entity.getEntityId() + \" for \" + player.getName() + \": tracker null\");\r\n            }\r\n            return;\r\n        }\r\n        sendEntityDestroy(player, entity);\r\n        tracker.removePlayer(nmsPlayer);\r\n    }\r\n\r\n    public record TrackerData(PlayerTag player, ServerEntity tracker) {}\r\n\r\n    @Override\r\n    public void addFakePassenger(List<PlayerTag> players, Entity vehicle, FakeEntity fakePassenger) {\r\n        ClientboundSetPassengersPacket packet = new ClientboundSetPassengersPacket(((CraftEntity) vehicle).getHandle());\r\n        int[] newPassengers = Arrays.copyOf(packet.getPassengers(), packet.getPassengers().length + 1);\r\n        newPassengers[packet.getPassengers().length] = fakePassenger.id;\r\n        try {\r\n            PASSENGERS_PACKET_PASSENGERS.set(packet, newPassengers);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        for (PlayerTag player : players) {\r\n            PacketHelperImpl.send(player.getPlayerEntity(), packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public FakeEntity sendEntitySpawn(List<PlayerTag> players, DenizenEntityType entityType, LocationTag location, ArrayList<Mechanism> mechanisms, int customId, UUID customUUID, boolean autoTrack) {\r\n        CraftWorld world = ((CraftWorld) location.getWorld());\r\n        net.minecraft.world.entity.Entity nmsEntity;\r\n        if (entityType.isCustom()) {\r\n            if (entityType.customEntityType == CustomEntityType.ITEM_PROJECTILE) {\r\n                ItemStack itemStack = new ItemStack(Material.STONE);\r\n                for (Mechanism mechanism : mechanisms) {\r\n                    if (mechanism.matches(\"item\") && mechanism.requireObject(ItemTag.class)) {\r\n                        itemStack = mechanism.valueAsType(ItemTag.class).getItemStack();\r\n                    }\r\n                }\r\n                nmsEntity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n            }\r\n            else if (entityType.customEntityType == CustomEntityType.FAKE_PLAYER) {\r\n                String name = null;\r\n                String skin = null;\r\n                String blob = null;\r\n                for (Mechanism mechanism : new ArrayList<>(mechanisms)) {\r\n                    if (mechanism.matches(\"name\")) {\r\n                        name = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin\")) {\r\n                        skin = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin_blob\")) {\r\n                        blob = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    if (name != null && (skin != null || blob != null)) {\r\n                        break;\r\n                    }\r\n                }\r\n                nmsEntity = ((CraftFakePlayerImpl) NMSHandler.customEntityHelper.spawnFakePlayer(location, name, skin, blob, false)).getHandle();\r\n            }\r\n            else {\r\n                throw new IllegalArgumentException(\"entityType\");\r\n            }\r\n        }\r\n        else {\r\n            org.bukkit.entity.Entity entity = world.createEntity(location, entityType.getBukkitEntityType().getEntityClass());\r\n            nmsEntity = ((CraftEntity) entity).getHandle();\r\n        }\r\n        if (customUUID != null) {\r\n            nmsEntity.setId(customId);\r\n            nmsEntity.setUUID(customUUID);\r\n        }\r\n        EntityTag entity = new EntityTag(nmsEntity.getBukkitEntity());\r\n        entity.isFake = true;\r\n        entity.isFakeValid = true;\r\n        for (Mechanism mechanism : mechanisms) {\r\n            entity.safeAdjustDuplicate(mechanism);\r\n        }\r\n        nmsEntity.unsetRemoved();\r\n        FakeEntity fake = new FakeEntity(players, location, entity.getBukkitEntity().getEntityId());\r\n        fake.entity = new EntityTag(entity.getBukkitEntity());\r\n        fake.entity.isFake = true;\r\n        fake.entity.isFakeValid = true;\r\n        List<TrackerData> trackers = new ArrayList<>();\r\n        fake.triggerSpawnPacket = (player) -> {\r\n            ServerPlayer nmsPlayer = ((CraftPlayer) player.getPlayerEntity()).getHandle();\r\n            ServerGamePacketListenerImpl conn = nmsPlayer.connection;\r\n            final ServerEntity tracker = new ServerEntity(world.getHandle(), nmsEntity, 1, true, conn::send, Collections.singleton(nmsPlayer.connection));\r\n            tracker.addPairing(nmsPlayer);\r\n            final TrackerData data = new TrackerData(player, tracker);\r\n            trackers.add(data);\r\n            if (autoTrack) {\r\n                new BukkitRunnable() {\r\n                    boolean wasOnline = true;\r\n                    @Override\r\n                    public void run() {\r\n                        if (!fake.entity.isFakeValid) {\r\n                            cancel();\r\n                            return;\r\n                        }\r\n                        if (player.isOnline()) {\r\n                            if (!wasOnline) {\r\n                                tracker.addPairing(((CraftPlayer) player.getPlayerEntity()).getHandle());\r\n                                wasOnline = true;\r\n                            }\r\n                            tracker.sendChanges();\r\n                        }\r\n                        else if (wasOnline) {\r\n                            wasOnline = false;\r\n                        }\r\n                    }\r\n                }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n            }\r\n        };\r\n        for (PlayerTag player : players) {\r\n            fake.triggerSpawnPacket.accept(player);\r\n        }\r\n        fake.triggerUpdatePacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.sendChanges();\r\n                }\r\n            }\r\n        };\r\n        fake.triggerDestroyPacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.removePairing(((CraftPlayer) tracker.player.getPlayerEntity()).getHandle());\r\n                }\r\n            }\r\n            trackers.clear();\r\n        };\r\n        return fake;\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityDestroy(Player player, Entity entity) {\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId()));\r\n    }\r\n\r\n    @Override\r\n    public int getFlyKickCooldown(Player player) {\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl denizenListener) {\r\n            conn = denizenListener.oldListener;\r\n        }\r\n        try {\r\n            return Math.max(80 - Math.max(FLY_TICKS.getInt(conn), VEHICLE_FLY_TICKS.getInt(conn)), 0);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return 80;\r\n    }\r\n\r\n    @Override\r\n    public void setFlyKickCooldown(Player player, int ticks) {\r\n        ticks = 80 - ticks;\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl denizenListener) {\r\n            conn = denizenListener.oldListener;\r\n        }\r\n        try {\r\n            FLY_TICKS.setInt(conn, ticks);\r\n            VEHICLE_FLY_TICKS.setInt(conn, ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int ticksPassedDuringCooldown(Player player) {\r\n        try {\r\n            return ATTACK_COOLDOWN_TICKS.getInt(((CraftPlayer) player).getHandle());\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public float getMaxAttackCooldownTicks(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getCurrentItemAttackStrengthDelay() + 3;\r\n    }\r\n\r\n    @Override\r\n    public void setAttackCooldown(Player player, int ticks) {\r\n        try {\r\n            ATTACK_COOLDOWN_TICKS.setInt(((CraftPlayer) player).getHandle(), ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean hasChunkLoaded(Player player, Chunk chunk) {\r\n        return ((CraftWorld) chunk.getWorld()).getHandle().getChunkSource().chunkMap\r\n                .getPlayers(new ChunkPos(chunk.getX(), chunk.getZ()), false).stream()\r\n                .anyMatch(entityPlayer -> entityPlayer.getUUID().equals(player.getUniqueId()));\r\n    }\r\n\r\n    @Override\r\n    public void setTemporaryOp(Player player, boolean op) {\r\n        MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();\r\n        GameProfile profile = ((CraftPlayer) player).getProfile();\r\n        ServerOpList opList = server.getPlayerList().getOps();\r\n        if (op) {\r\n            opList.add(new ServerOpListEntry(profile, server.getOperatorUserPermissionLevel(), opList.canBypassPlayerLimit(profile)));\r\n        }\r\n        else {\r\n            opList.remove(profile);\r\n        }\r\n        player.recalculatePermissions();\r\n    }\r\n\r\n    @Override\r\n    public void showEndCredits(Player player) {\r\n        ((CraftPlayer) player).getHandle().wonGame = true;\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1f));\r\n    }\r\n\r\n    @Override\r\n    public ImprovedOfflinePlayer getOfflineData(UUID uuid) {\r\n        return new ImprovedOfflinePlayerImpl(uuid);\r\n    }\r\n\r\n    @Override\r\n    public void resendRecipeDetails(Player player) {\r\n        Collection<RecipeHolder<?>> recipes = ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().getRecipes();\r\n        ClientboundUpdateRecipesPacket updatePacket = new ClientboundUpdateRecipesPacket(recipes);\r\n        ((CraftPlayer) player).getHandle().connection.send(updatePacket);\r\n    }\r\n\r\n    @Override\r\n    public void resendDiscoveredRecipes(Player player) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        recipeBook.sendInitialRecipeBook(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    @Override\r\n    public void quietlyAddRecipe(Player player, NamespacedKey key) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        RecipeHolder<?> recipe = ItemHelperImpl.getNMSRecipe(key);\r\n        if (recipe == null) {\r\n            Debug.echoError(\"Cannot add recipe '\" + key + \"': it does not exist.\");\r\n            return;\r\n        }\r\n        recipeBook.add(recipe);\r\n        recipeBook.addHighlight(recipe);\r\n    }\r\n\r\n    @Override\r\n    public byte getSkinLayers(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getEntityData().get(PLAYER_DATA_ACCESSOR_SKINLAYERS);\r\n    }\r\n\r\n    @Override\r\n    public void setSkinLayers(Player player, byte flags) {\r\n        ((CraftPlayer) player).getHandle().getEntityData().set(PLAYER_DATA_ACCESSOR_SKINLAYERS, flags);\r\n    }\r\n\r\n    @Override\r\n    public void setBossBarTitle(BossBar bar, String title) {\r\n        ((CraftBossBar) bar).getHandle().name = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        ((CraftBossBar) bar).getHandle().broadcast(ClientboundBossEventPacket::createUpdateNamePacket);\r\n    }\r\n\r\n    @Override\r\n    public boolean getSpawnForced(Player player) {\r\n        return ((CraftPlayer) player).getHandle().isRespawnForced();\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnForced(Player player, boolean forced) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        try {\r\n            PLAYER_RESPAWNFORCED_SETTER.invoke(nmsPlayer, forced);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Location getBedSpawnLocation(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        BlockPos spawnPosition = nmsPlayer.getRespawnPosition();\r\n        if (spawnPosition == null) {\r\n            return null;\r\n        }\r\n        Level nmsWorld = MinecraftServer.getServer().getLevel(nmsPlayer.getRespawnDimension());\r\n        if (nmsWorld == null) {\r\n            return null;\r\n        }\r\n        return new Location(nmsWorld.getWorld(), spawnPosition.getX(), spawnPosition.getY(), spawnPosition.getZ(), nmsPlayer.getRespawnAngle(), 0);\r\n    }\r\n\r\n    @Override\r\n    public long getLastActionTime(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getLastActionTime();\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerInfoAddPacket(Player player, EnumSet<ProfileEditMode> editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) {\r\n        EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action.class);\r\n        for (ProfileEditMode editMode : editModes) {\r\n            actions.add(switch (editMode) {\r\n                case ADD -> ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER;\r\n                case UPDATE_DISPLAY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME;\r\n                case UPDATE_LATENCY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY;\r\n                case UPDATE_GAME_MODE -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE;\r\n                case UPDATE_LISTED -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED;\r\n            });\r\n        }\r\n        GameProfile profile = new GameProfile(id, name != null ? name : ProfileEditorImpl.EMPTY_NAME);\r\n        if (texture != null) {\r\n            profile.getProperties().put(\"textures\", new Property(\"textures\", texture, signature));\r\n        }\r\n        ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(id, profile, listed, latency, gameMode == null ? null : GameType.byId(gameMode.getValue()), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)), null);\r\n        PacketHelperImpl.send(player, ProfileEditorImpl.createInfoPacket(actions, List.of(entry)));\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerInfoRemovePacket(Player player, UUID id) {\r\n        PacketHelperImpl.send(player, new ClientboundPlayerInfoRemovePacket(List.of(id)));\r\n    }\r\n\r\n    @Override\r\n    public void sendClimbableMaterials(Player player, List<Material> materials) {\r\n        Map<ResourceKey<? extends Registry<?>>, TagNetworkSerialization.NetworkPayload> packetInput = TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registries());\r\n        Map<ResourceLocation, IntList> tags = ReflectionHelper.getFieldValue(TagNetworkSerialization.NetworkPayload.class, ReflectionMappingsInfo.TagNetworkSerializationNetworkPayload_tags, packetInput.get(BuiltInRegistries.BLOCK.key()));\r\n        IntList climbableBlocks = tags.get(BlockTags.CLIMBABLE.location());\r\n        climbableBlocks.clear();\r\n        for (Material material : materials) {\r\n            climbableBlocks.add(BuiltInRegistries.BLOCK.getId(CraftMagicNumbers.getBlock(material)));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundUpdateTagsPacket(packetInput));\r\n    }\r\n\r\n    @Override\r\n    public void refreshPlayer(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerLevel nmsWorld = (ServerLevel) nmsPlayer.level();\r\n        nmsPlayer.connection.send(new ClientboundRespawnPacket(nmsPlayer.createCommonSpawnInfo(nmsWorld), ClientboundRespawnPacket.KEEP_ALL_DATA));\r\n        nmsPlayer.connection.teleport(player.getLocation());\r\n        if (nmsPlayer.isPassenger()) {\r\n           nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer.getVehicle()));\r\n        }\r\n        if (nmsPlayer.isVehicle()) {\r\n            nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer));\r\n        }\r\n        AABB boundingBox = new AABB(nmsPlayer.position(), nmsPlayer.position()).inflate(10);\r\n        for (Mob nmsMob : nmsWorld.getEntitiesOfClass(Mob.class, boundingBox, nmsMob -> nmsPlayer.equals(nmsMob.getLeashHolder()))) {\r\n            nmsPlayer.connection.send(new ClientboundSetEntityLinkPacket(nmsMob, nmsPlayer));\r\n        }\r\n        if (!nmsPlayer.getCooldowns().cooldowns.isEmpty()) {\r\n            int tickCount = nmsPlayer.getCooldowns().tickCount;\r\n            for (Map.Entry<Item, ItemCooldowns.CooldownInstance> entry : nmsPlayer.getCooldowns().cooldowns.entrySet()) {\r\n                nmsPlayer.connection.send(new ClientboundCooldownPacket(entry.getKey(), entry.getValue().endTime - tickCount));\r\n            }\r\n        }\r\n        nmsPlayer.onUpdateAbilities();\r\n        nmsPlayer.server.getPlayerList().sendAllPlayerInfo(nmsPlayer);\r\n        nmsPlayer.server.getPlayerList().sendPlayerPermissionLevel(nmsPlayer);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/helpers/WorldHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.WorldHelper;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.objects.BiomeTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.mojang.datafixers.util.Pair;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.players.SleepStatus;\r\nimport net.minecraft.world.DifficultyInstance;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.storage.PrimaryLevelData;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\n\r\npublic class WorldHelperImpl implements WorldHelper {\r\n\r\n    @Override\r\n    public boolean isStatic(World world) {\r\n        return ((CraftWorld) world).getHandle().isClientSide;\r\n    }\r\n\r\n    @Override\r\n    public void setStatic(World world, boolean isStatic) {\r\n        ServerLevel worldServer = ((CraftWorld) world).getHandle();\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.Level.class, ReflectionMappingsInfo.Level_isClientSide, worldServer, isStatic);\r\n    }\r\n\r\n    @Override\r\n    public float getLocalDifficulty(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        DifficultyInstance scaler = ((CraftWorld) location.getWorld()).getHandle().getCurrentDifficultyAt(pos);\r\n        return scaler.getEffectiveDifficulty();\r\n    }\r\n\r\n    @Override\r\n    public Location getNearestBiomeLocation(Location start, BiomeTag biome) {\r\n        Pair<BlockPos, Holder<Biome>> result = ((CraftWorld) start.getWorld()).getHandle()\r\n                .findClosestBiome3d(b -> b.is(((BiomeNMSImpl) biome.getBiome()).biomeHolder.unwrapKey().get()), new BlockPos(start.getBlockX(), start.getBlockY(), start.getBlockZ()), 6400, 32, 64);\r\n        if (result == null || result.getFirst() == null) {\r\n            return null;\r\n        }\r\n        return new Location(start.getWorld(), result.getFirst().getX(), result.getFirst().getY(), result.getFirst().getZ());\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughSleeping(World world, int percentage) {\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, ((CraftWorld) world).getHandle());\r\n        return status.areEnoughSleeping(percentage);\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughDeepSleeping(World world, int percentage) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, level);\r\n        return status.areEnoughDeepSleeping(percentage, level.players());\r\n    }\r\n\r\n    @Override\r\n    public int getSkyDarken(World world) {\r\n        return ((CraftWorld) world).getHandle().getSkyDarken();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDay(World world) {\r\n        return ((CraftWorld) world).getHandle().isDay();\r\n    }\r\n\r\n    @Override\r\n    public boolean isNight(World world) {\r\n        return ((CraftWorld) world).getHandle().isNight();\r\n    }\r\n\r\n    @Override\r\n    public void setDayTime(World world, long time) {\r\n        ((CraftWorld) world).getHandle().setDayTime(time);\r\n    }\r\n\r\n    @Override\r\n    public void setGameTime(World world, long time) {\r\n        ((PrimaryLevelData) ((CraftWorld) world).getHandle().levelData).setGameTime(time);\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#wakeUpAllPlayers()\r\n    @Override\r\n    public void wakeUpAllPlayers(World world) {\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, nmsWorld);\r\n        status.removeAllSleepers();\r\n        nmsWorld.getPlayers(LivingEntity::isSleeping).forEach((player) -> player.stopSleepInBed(false, false));\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#resetWeatherCycle()\r\n    @Override\r\n    public void clearWeather(World world) {\r\n        PrimaryLevelData data = ((CraftWorld) world).getHandle().K;\r\n        data.setRaining(false);\r\n        if (!data.isRaining()) {\r\n            data.setRainTime(0);\r\n        }\r\n        data.setThundering(false);\r\n        if (!data.isThundering()) {\r\n            data.setThunderTime(0);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/BiomeNMSImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.random.WeightedRandomList;\r\nimport net.minecraft.world.entity.MobCategory;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.BiomeSpecialEffects;\r\nimport net.minecraft.world.level.biome.MobSpawnSettings;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntityType;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftLocation;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Optional;\r\n\r\npublic class BiomeNMSImpl extends BiomeNMS {\r\n\r\n    public static final MethodHandle BIOME_CLIMATESETTINGS_CONSTRUCTOR = ReflectionHelper.getConstructor(Biome.ClimateSettings.class, boolean.class, float.class, Biome.TemperatureModifier.class, float.class);\r\n\r\n    public Holder<Biome> biomeHolder;\r\n    public ServerLevel world;\r\n\r\n    public BiomeNMSImpl(ServerLevel world, NamespacedKey key) {\r\n        super(world.getWorld(), key);\r\n        this.world = world;\r\n        biomeHolder = world.registryAccess().registryOrThrow(Registries.BIOME).getHolder(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(key))).orElse(null);\r\n    }\r\n\r\n    @Override\r\n    public DownfallType getDownfallTypeAt(Location location) {\r\n        Biome.Precipitation precipitation = biomeHolder.value().getPrecipitationAt(CraftLocation.toBlockPosition(location));\r\n        return switch (precipitation) {\r\n            case RAIN -> DownfallType.RAIN;\r\n            case SNOW -> DownfallType.SNOW;\r\n            case NONE -> DownfallType.NONE;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public float getHumidity() {\r\n        return biomeHolder.value().climateSettings.downfall();\r\n    }\r\n\r\n    @Override\r\n    public float getBaseTemperature() {\r\n        return biomeHolder.value().getBaseTemperature();\r\n    }\r\n\r\n    @Override\r\n    public float getTemperatureAt(Location location) {\r\n        return biomeHolder.value().getTemperature(CraftLocation.toBlockPosition(location));\r\n    }\r\n\r\n    @Override\r\n    public boolean hasDownfall() {\r\n        return biomeHolder.value().hasPrecipitation();\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getAmbientEntities() {\r\n        return getSpawnableEntities(MobCategory.AMBIENT);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getCreatureEntities() {\r\n        return getSpawnableEntities(MobCategory.CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getMonsterEntities() {\r\n        return getSpawnableEntities(MobCategory.MONSTER);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getWaterEntities() {\r\n        return getSpawnableEntities(MobCategory.WATER_CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public int getFoliageColor() {\r\n        // Check if the biome already has a default color\r\n        if (biomeHolder.value().getFoliageColor() != 0) {\r\n            return biomeHolder.value().getFoliageColor();\r\n        }\r\n        // Based on net.minecraft.world.level.biome.Biome#getFoliageColorFromTexture()\r\n        float temperature = clampColor(getBaseTemperature());\r\n        float humidity = clampColor(getHumidity());\r\n        // Based on net.minecraft.world.level.FoliageColor#get()\r\n        humidity *= temperature;\r\n        int humidityValue = (int)((1.0f - humidity) * 255.0f);\r\n        int temperatureValue = (int)((1.0f - temperature) * 255.0f);\r\n        int index = temperatureValue << 8 | humidityValue;\r\n        return index >= 65536 ? 4764952 : getColor(index / 256, index % 256).asRGB();\r\n    }\r\n\r\n    public void setClimate(boolean hasPrecipitation, float temperature, Biome.TemperatureModifier temperatureModifier, float downfall) {\r\n        try {\r\n            Object newClimate = BIOME_CLIMATESETTINGS_CONSTRUCTOR.invoke(hasPrecipitation, temperature, temperatureModifier, downfall);\r\n            ReflectionHelper.setFieldValue(Biome.class, ReflectionMappingsInfo.Biome_climateSettings, biomeHolder.value(), newClimate);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHumidity(float humidity) {\r\n        setClimate(hasDownfall(), getBaseTemperature(), getTemperatureModifier(), humidity);\r\n    }\r\n\r\n    @Override\r\n    public void setBaseTemperature(float baseTemperature) {\r\n        setClimate(hasDownfall(), baseTemperature, getTemperatureModifier(), getHumidity());\r\n    }\r\n\r\n    @Override\r\n    public void setHasDownfall(boolean hasDownfall) {\r\n        setClimate(hasDownfall, getBaseTemperature(), getTemperatureModifier(), getHumidity());\r\n    }\r\n\r\n    @Override\r\n    public void setFoliageColor(int color) {\r\n        ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_foliageColorOverride, biomeHolder.value().getSpecialEffects(), Optional.of(color));\r\n    }\r\n\r\n    @Override\r\n    public int getFogColor() {\r\n        return biomeHolder.value().getFogColor();\r\n    }\r\n\r\n    @Override\r\n    public void setFogColor(int color) {\r\n        ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_fogColor, biomeHolder.value().getSpecialEffects(), color);\r\n    }\r\n\r\n    @Override\r\n    public int getWaterFogColor() {\r\n        return biomeHolder.value().getWaterFogColor();\r\n    }\r\n\r\n    @Override\r\n    public void setWaterFogColor(int color) {\r\n        ReflectionHelper.setFieldValue(BiomeSpecialEffects.class, ReflectionMappingsInfo.BiomeSpecialEffects_waterFogColor, biomeHolder.value().getSpecialEffects(), color);\r\n    }\r\n\r\n    private List<EntityType> getSpawnableEntities(MobCategory creatureType) {\r\n        MobSpawnSettings mobs = biomeHolder.value().getMobSettings();\r\n        WeightedRandomList<MobSpawnSettings.SpawnerData> typeSettingList = mobs.getMobs(creatureType);\r\n        List<EntityType> entityTypes = new ArrayList<>();\r\n        if (typeSettingList == null) {\r\n            return entityTypes;\r\n        }\r\n        for (MobSpawnSettings.SpawnerData meta : typeSettingList.unwrap()) {\r\n            entityTypes.add(CraftEntityType.minecraftToBukkit(meta.type));\r\n        }\r\n        return entityTypes;\r\n    }\r\n\r\n    @Override\r\n    public void setTo(Block block) {\r\n        if (((CraftWorld) block.getWorld()).getHandle() != this.world) {\r\n            NMSHandler.instance.getBiomeNMS(block.getWorld(), getKey()).setTo(block);\r\n            return;\r\n        }\r\n        // Based on CraftWorld source\r\n        BlockPos pos = new BlockPos(block.getX(), 0, block.getZ());\r\n        if (world.hasChunkAt(pos)) {\r\n            LevelChunk chunk = world.getChunkAt(pos);\r\n            if (chunk != null) {\r\n                chunk.setBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2, biomeHolder);\r\n                chunk.setUnsaved(true);\r\n            }\r\n        }\r\n    }\r\n\r\n    public Biome.TemperatureModifier getTemperatureModifier() {\r\n        return biomeHolder.value().climateSettings.temperatureModifier();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/ImprovedOfflinePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.v1_20.helpers.NBTAdapter;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.kyori.adventure.nbt.BinaryTagTypes;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.nbt.ListTag;\r\nimport net.minecraft.nbt.NbtAccounter;\r\nimport net.minecraft.nbt.NbtIo;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeMap;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.attributes.DefaultAttributes;\r\nimport net.minecraft.world.inventory.PlayerEnderChestContainer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftRegistry;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryPlayer;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryHolder;\r\n\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.FileOutputStream;\r\nimport java.util.UUID;\r\n\r\npublic class ImprovedOfflinePlayerImpl extends ImprovedOfflinePlayer {\r\n\r\n    public ImprovedOfflinePlayerImpl(UUID playeruuid) {\r\n        super(playeruuid);\r\n    }\r\n\r\n    public static class OfflinePlayerInventory extends net.minecraft.world.entity.player.Inventory {\r\n\r\n        public OfflinePlayerInventory(net.minecraft.world.entity.player.Player entityhuman) {\r\n            super(entityhuman);\r\n        }\r\n\r\n        @Override\r\n        public InventoryHolder getOwner() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static class OfflineCraftInventoryPlayer extends CraftInventoryPlayer {\r\n\r\n        public OfflineCraftInventoryPlayer(net.minecraft.world.entity.player.Inventory inventory) {\r\n            super(inventory);\r\n        }\r\n\r\n        @Override\r\n        public HumanEntity getHolder() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public org.bukkit.inventory.PlayerInventory getInventory() {\r\n        if (inventory == null) {\r\n            net.minecraft.world.entity.player.Inventory newInv = new OfflinePlayerInventory(null);\r\n            newInv.load(NBTAdapter.toNMS(this.compound.getList(\"Inventory\", BinaryTagTypes.COMPOUND)));\r\n            inventory = new OfflineCraftInventoryPlayer(newInv);\r\n        }\r\n        return inventory;\r\n    }\r\n\r\n    @Override\r\n    public void setInventory(org.bukkit.inventory.PlayerInventory inventory) {\r\n        CraftInventoryPlayer inv = (CraftInventoryPlayer) inventory;\r\n        this.compound = compound.put(\"Inventory\", NBTAdapter.toAPI(inv.getInventory().save(new ListTag())));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    public Inventory getEnderChest() {\r\n        if (enderchest == null) {\r\n            PlayerEnderChestContainer endchest = new PlayerEnderChestContainer(null);\r\n            endchest.fromTag(NBTAdapter.toNMS(this.compound.getList(\"EnderItems\", BinaryTagTypes.COMPOUND)), CraftRegistry.getMinecraftRegistry());\r\n            enderchest = new CraftInventory(endchest);\r\n        }\r\n        return enderchest;\r\n    }\r\n\r\n    @Override\r\n    public void setEnderChest(Inventory inventory) {\r\n        this.compound = compound.put(\"EnderItems\", NBTAdapter.toAPI(((PlayerEnderChestContainer) ((CraftInventory) inventory).getInventory()).createTag(CraftRegistry.getMinecraftRegistry())));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    public double getMaxHealth() {\r\n        AttributeInstance maxHealth = getAttributes().getInstance(Attributes.MAX_HEALTH);\r\n        return maxHealth == null ? Attributes.MAX_HEALTH.value().getDefaultValue() : maxHealth.getValue();\r\n    }\r\n\r\n    @Override\r\n    public void setMaxHealth(double input) {\r\n        AttributeMap attributes = getAttributes();\r\n        AttributeInstance maxHealth = attributes.getInstance(Attributes.MAX_HEALTH);\r\n        maxHealth.setBaseValue(input);\r\n        setAttributes(attributes);\r\n    }\r\n\r\n    private AttributeMap getAttributes() {\r\n        AttributeMap amb = new AttributeMap(DefaultAttributes.getSupplier(net.minecraft.world.entity.EntityType.PLAYER));\r\n        amb.load(NBTAdapter.toNMS(this.compound.getList(\"Attributes\", BinaryTagTypes.COMPOUND)));\r\n        return amb;\r\n    }\r\n\r\n    public void setAttributes(AttributeMap attributes) {\r\n        this.compound = compound.put(\"Attributes\", NBTAdapter.toAPI(attributes.save()));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    protected boolean loadPlayerData(UUID uuid) {\r\n        try {\r\n            this.player = uuid;\r\n            for (org.bukkit.World w : Bukkit.getWorlds()) {\r\n                this.file = new File(w.getWorldFolder(), \"playerdata\" + File.separator + this.player + \".dat\");\r\n                if (this.file.exists()) {\r\n                    this.compound = NBTAdapter.toAPI(NbtIo.readCompressed(new FileInputStream(this.file), NbtAccounter.unlimitedHeap()));\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void saveInternal(CompoundBinaryTag compound) {\r\n        try {\r\n            NbtIo.writeCompressed(NBTAdapter.toNMS(compound), new FileOutputStream(this.file));\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/ProfileEditorImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_20.Handler;\r\nimport com.denizenscript.denizen.nms.v1_20.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.player.PlayerRespawnEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.ArrayList;\r\nimport java.util.EnumSet;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\npublic class ProfileEditorImpl extends ProfileEditor {\r\n\r\n    @Override\r\n    protected void updatePlayer(final Player player, final boolean isSkinChanging) {\r\n        final ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        final UUID uuid = player.getUniqueId();\r\n        ClientboundPlayerInfoRemovePacket removePlayerInfoPacket = new ClientboundPlayerInfoRemovePacket(List.of(uuid));\r\n        ClientboundPlayerInfoUpdatePacket addPlayerInfoPacket = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(nmsPlayer));\r\n        for (Player otherPlayer : Bukkit.getServer().getOnlinePlayers()) {\r\n            PacketHelperImpl.send(otherPlayer, removePlayerInfoPacket);\r\n            PacketHelperImpl.send(otherPlayer, addPlayerInfoPacket);\r\n        }\r\n        for (Player otherPlayer : NMSHandler.entityHelper.getPlayersThatSee(player)) {\r\n            if (!otherPlayer.getUniqueId().equals(uuid)) {\r\n                PacketHelperImpl.forceRespawnPlayerEntity(player, otherPlayer);\r\n            }\r\n        }\r\n        if (isSkinChanging) {\r\n            ((CraftServer) Bukkit.getServer()).getHandle().respawn(nmsPlayer, (ServerLevel) nmsPlayer.level(), true, player.getLocation(), false, PlayerRespawnEvent.RespawnReason.PLUGIN);\r\n        }\r\n        player.updateInventory();\r\n    }\r\n\r\n    public static void registerHandlers() {\r\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoUpdatePacket.class, ProfileEditorImpl::processPlayerInfoUpdatePacket);\r\n    }\r\n\r\n    public static ClientboundPlayerInfoUpdatePacket processPlayerInfoUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket) {\r\n        if (ProfileEditor.mirrorUUIDs.isEmpty() && !RenameCommand.hasAnyDynamicRenames() && fakeProfiles.isEmpty()) {\r\n            return playerInfoUpdatePacket;\r\n        }\r\n        EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = playerInfoUpdatePacket.actions();\r\n        if (!actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) && !actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {\r\n            return playerInfoUpdatePacket;\r\n        }\r\n        boolean any = false;\r\n        for (ClientboundPlayerInfoUpdatePacket.Entry entry : playerInfoUpdatePacket.entries()) {\r\n            if (shouldChange(entry)) {\r\n                any = true;\r\n                break;\r\n            }\r\n        }\r\n        if (!any) {\r\n            return playerInfoUpdatePacket;\r\n        }\r\n        GameProfile ownProfile = networkManager.player.getGameProfile();\r\n        List<ClientboundPlayerInfoUpdatePacket.Entry> modifiedEntries = new ArrayList<>(playerInfoUpdatePacket.entries().size());\r\n        for (ClientboundPlayerInfoUpdatePacket.Entry entry : playerInfoUpdatePacket.entries()) {\r\n            if (!shouldChange(entry)) {\r\n                modifiedEntries.add(entry);\r\n                continue;\r\n            }\r\n            String rename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), false);\r\n            GameProfile baseProfile = fakeProfiles.containsKey(entry.profileId()) ? getGameProfile(fakeProfiles.get(entry.profileId())) : entry.profile();\r\n            GameProfile modifiedProfile = new GameProfile(baseProfile.getId(), rename != null ? (rename.length() > 16 ? rename.substring(0, 16) : rename) : baseProfile.getName());\r\n            if (ProfileEditor.mirrorUUIDs.contains(entry.profileId())) {\r\n                modifiedProfile.getProperties().putAll(ownProfile.getProperties());\r\n            }\r\n            else {\r\n                // On Paper 1.19+, we use Paper's PlayerProfile API instead of this system\r\n                modifiedProfile.getProperties().putAll(Denizen.supportsPaper ? entry.profile().getProperties() : baseProfile.getProperties());\r\n            }\r\n            String listRename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), true);\r\n            Component displayName = listRename != null ? Handler.componentToNMS(FormattedTextHelper.parse(listRename, ChatColor.WHITE)) : entry.displayName();\r\n            ClientboundPlayerInfoUpdatePacket.Entry modifiedEntry = new ClientboundPlayerInfoUpdatePacket.Entry(entry.profileId(), modifiedProfile, entry.listed(), entry.latency(), entry.gameMode(), displayName, entry.chatSession());\r\n            modifiedEntries.add(modifiedEntry);\r\n        }\r\n        return createInfoPacket(actions, modifiedEntries);\r\n    }\r\n\r\n    public static boolean shouldChange(ClientboundPlayerInfoUpdatePacket.Entry entry) {\r\n        return ProfileEditor.mirrorUUIDs.contains(entry.profileId()) || RenameCommand.customNames.containsKey(entry.profileId()) || fakeProfiles.containsKey(entry.profileId());\r\n    }\r\n\r\n    public static final Field ClientboundPlayerInfoUpdatePacket_entries = ReflectionHelper.getFields(ClientboundPlayerInfoUpdatePacket.class).getFirstOfType(List.class);\r\n\r\n    public static ClientboundPlayerInfoUpdatePacket createInfoPacket(EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, List<ClientboundPlayerInfoUpdatePacket.Entry> entries) {\r\n        ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket = new ClientboundPlayerInfoUpdatePacket(actions, List.of());\r\n        try {\r\n            ClientboundPlayerInfoUpdatePacket_entries.set(playerInfoUpdatePacket, entries);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return playerInfoUpdatePacket;\r\n    }\r\n\r\n    public static GameProfile getGameProfileNoProperties(PlayerProfile playerProfile) {\r\n        UUID uuid = playerProfile.getUniqueId();\r\n        String name = playerProfile.getName();\r\n        return new GameProfile(uuid != null ? uuid : NIL_UUID, name != null ? name : EMPTY_NAME);\r\n    }\r\n\r\n    public static GameProfile getGameProfile(PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = getGameProfileNoProperties(playerProfile);\r\n        if (playerProfile.hasTexture()) {\r\n            gameProfile.getProperties().put(\"textures\", new Property(\"textures\", playerProfile.getTexture(), playerProfile.getTextureSignature()));\r\n        }\r\n        return gameProfile;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/SidebarImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.nms.v1_20.Handler;\r\nimport com.denizenscript.denizen.nms.v1_20.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.chat.numbers.StyledFormat;\r\nimport net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetScorePacket;\r\nimport net.minecraft.world.scores.DisplaySlot;\r\nimport net.minecraft.world.scores.Objective;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Scoreboard;\r\nimport net.minecraft.world.scores.criteria.ObjectiveCriteria;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.reflect.Constructor;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Optional;\r\n\r\npublic class SidebarImpl extends Sidebar {\r\n\r\n    // TODO: 1.20.3: the scoreboard system saw significant changes, there is likely a better way to do this now\r\n\r\n    public static Scoreboard dummyScoreboard = new Scoreboard();\r\n    public static ObjectiveCriteria dummyCriteria;\r\n\r\n    static {\r\n        try {\r\n            Constructor<ObjectiveCriteria> constructor = ObjectiveCriteria.class.getDeclaredConstructor(String.class);\r\n            constructor.setAccessible(true);\r\n            dummyCriteria = constructor.newInstance(\"dummy\");\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public Objective obj1;\r\n    public Objective obj2;\r\n\r\n    public SidebarImpl(Player player) {\r\n        super(player);\r\n        Component chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        this.obj1 = new Objective(dummyScoreboard, \"dummy_1\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER, false, StyledFormat.SIDEBAR_DEFAULT);\r\n        this.obj2 = new Objective(dummyScoreboard, \"dummy_2\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER, false, StyledFormat.SIDEBAR_DEFAULT);\r\n    }\r\n\r\n    @Override\r\n    protected void setDisplayName(String title) {\r\n        if (this.obj1 != null) {\r\n            Component chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n            this.obj1.setDisplayName(chatComponentTitle);\r\n            this.obj2.setDisplayName(chatComponentTitle);\r\n        }\r\n    }\r\n\r\n    public List<PlayerTeam> generatedTeams = new ArrayList<>();\r\n\r\n    @Override\r\n    public void sendUpdate() {\r\n        List<PlayerTeam> oldTeams = generatedTeams;\r\n        generatedTeams = new ArrayList<>();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj1, 0));\r\n        String[] ids = getIds();\r\n        for (int i = 0; i < this.lines.length; i++) {\r\n            String line = this.lines[i];\r\n            if (line == null) {\r\n                break;\r\n            }\r\n            String lineId = ids[i];\r\n            PlayerTeam team = new PlayerTeam(dummyScoreboard, lineId);\r\n            team.getPlayers().add(lineId);\r\n            team.setPlayerPrefix(Handler.componentToNMS(FormattedTextHelper.parse(line, ChatColor.WHITE)));\r\n            generatedTeams.add(team);\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n            PacketHelperImpl.send(player, new ClientboundSetScorePacket(lineId, obj1.getName(), this.scores[i], Optional.empty(), Optional.of(StyledFormat.SIDEBAR_DEFAULT)));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundSetDisplayObjectivePacket(DisplaySlot.SIDEBAR, this.obj1));\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n        Objective temp = this.obj2;\r\n        this.obj2 = this.obj1;\r\n        this.obj1 = temp;\r\n        for (PlayerTeam team : oldTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        for (PlayerTeam team : generatedTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        generatedTeams.clear();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/blocks/BlockLightImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.blocks;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;\r\nimport net.minecraft.server.level.ThreadedLevelLightEngine;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.LightLayer;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.DataLayer;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.chunk.status.ChunkStatus;\r\nimport net.minecraft.world.level.lighting.LayerLightEventListener;\r\nimport net.minecraft.world.level.lighting.LevelLightEngine;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.CraftBlock;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.BitSet;\r\nimport java.util.List;\r\n\r\npublic class BlockLightImpl extends BlockLight {\r\n\r\n    public static final Class LIGHTENGINETHREADED_TASKTYPE = Arrays.stream(ThreadedLevelLightEngine.class.getDeclaredClasses()).filter(c -> c.isEnum()).findFirst().get(); // TaskType\r\n    public static final Object LIGHTENGINETHREADED_TASKTYPE_PRE;\r\n\r\n    static {\r\n        Object preObj = null;\r\n        try {\r\n            preObj = ReflectionHelper.getFields(LIGHTENGINETHREADED_TASKTYPE).get(ReflectionMappingsInfo.ThreadedLevelLightEngineTaskType_PRE_UPDATE).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n        LIGHTENGINETHREADED_TASKTYPE_PRE = preObj;\r\n    }\r\n\r\n    public static final MethodHandle LIGHTENGINETHREADED_QUEUERUNNABLE = ReflectionHelper.getMethodHandle(ThreadedLevelLightEngine.class, ReflectionMappingsInfo.ThreadedLevelLightEngine_addTask_method,\r\n            int.class, int.class,  LIGHTENGINETHREADED_TASKTYPE, Runnable.class);\r\n\r\n    public static void enqueueRunnable(LevelChunk chunk, Runnable runnable) {\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        if (lightEngine instanceof ThreadedLevelLightEngine) {\r\n            ChunkPos coord = chunk.getPos();\r\n            try {\r\n                LIGHTENGINETHREADED_QUEUERUNNABLE.invoke(lightEngine, coord.x, coord.z, LIGHTENGINETHREADED_TASKTYPE_PRE, runnable);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        else {\r\n            runnable.run();\r\n        }\r\n    }\r\n\r\n    private BlockLightImpl(Location location, long ticks) {\r\n        super(location, ticks);\r\n    }\r\n\r\n    public static BlockLight createLight(Location location, int lightLevel, long ticks) {\r\n        location = location.getBlock().getLocation();\r\n        BlockLight blockLight;\r\n        if (lightsByLocation.containsKey(location)) {\r\n            blockLight = lightsByLocation.get(location);\r\n            if (blockLight.removeTask != null) {\r\n                blockLight.removeTask.cancel();\r\n                blockLight.removeTask = null;\r\n            }\r\n            if (blockLight.updateTask != null) {\r\n                blockLight.updateTask.cancel();\r\n                blockLight.updateTask = null;\r\n            }\r\n            blockLight.removeLater(ticks);\r\n        }\r\n        else {\r\n            blockLight = new BlockLightImpl(location, ticks);\r\n            lightsByLocation.put(location, blockLight);\r\n            if (!lightsByChunk.containsKey(blockLight.chunkCoord)) {\r\n                lightsByChunk.put(blockLight.chunkCoord, new ArrayList<>());\r\n            }\r\n            lightsByChunk.get(blockLight.chunkCoord).add(blockLight);\r\n        }\r\n        blockLight.intendedLevel = lightLevel;\r\n        blockLight.update(lightLevel, true);\r\n        return blockLight;\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundBlockUpdatePacket packet, Level world) {\r\n        try {\r\n            BlockPos pos = packet.getPos();\r\n            int chunkX = pos.getX() >> 4;\r\n            int chunkZ = pos.getZ() >> 4;\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                LevelChunk chunk = world.getChunk(chunkX, chunkZ);\r\n                boolean any = false;\r\n                for (Vector vec : RELATIVE_CHUNKS) {\r\n                    ChunkAccess other = world.getChunk(chunkX + vec.getBlockX(), chunkZ + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n                    if (other instanceof LevelChunk) {\r\n                        List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) other)));\r\n                        if (lights != null) {\r\n                            any = true;\r\n                            for (BlockLight light : lights) {\r\n                                Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates(chunk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundLightUpdatePacket packet, Level world) {\r\n        if (doNotCheck) {\r\n            return;\r\n        }\r\n        try {\r\n            int cX = packet.getX();\r\n            int cZ = packet.getZ();\r\n            BitSet bitMask = packet.getLightData().getBlockYMask();\r\n            List<byte[]> blockData = packet.getLightData().getBlockUpdates();\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                ChunkAccess chk = world.getChunk(cX, cZ, ChunkStatus.FULL, false);\r\n                if (!(chk instanceof LevelChunk)) {\r\n                    return;\r\n                }\r\n                List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) chk)));\r\n                if (lights == null) {\r\n                    return;\r\n                }\r\n                boolean any = false;\r\n                for (BlockLight light : lights) {\r\n                    if (((BlockLightImpl) light).checkIfChangedBy(bitMask, blockData)) {\r\n                        Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                        any = true;\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates((LevelChunk) chk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static boolean doNotCheck = false;\r\n\r\n    public boolean checkIfChangedBy(BitSet bitmask, List<byte[]> data) {\r\n        Location blockLoc = block.getLocation();\r\n        int layer = (blockLoc.getBlockY() >> 4) + 1;\r\n        if (!bitmask.get(layer)) {\r\n            return false;\r\n        }\r\n        int found = 0;\r\n        for (int i = 0; i < 16; i++) {\r\n            if (bitmask.get(i)) {\r\n                if (i == layer) {\r\n                    byte[] blocks = data.get(found);\r\n                    DataLayer arr = new DataLayer(blocks);\r\n                    int x = blockLoc.getBlockX() - (chunkCoord.x << 4);\r\n                    int y = blockLoc.getBlockY() % 16;\r\n                    int z = blockLoc.getBlockZ() - (chunkCoord.z << 4);\r\n                    int level = arr.get(x, y, z);\r\n                    return intendedLevel != level;\r\n                }\r\n                found++;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static void runResetFor(final LevelChunk chunk, final BlockPos pos) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            engineBlock.checkBlock(pos);\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    public static void runSetFor(final LevelChunk chunk, final BlockPos pos, final int level) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            // engineBlock.onBlockEmissionIncrease(pos, level); // TODO: 1.20: ?\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    @Override\r\n    public void reset(boolean updateChunk) {\r\n        runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition());\r\n        if (updateChunk) {\r\n            // This runnable cast is necessary despite what your IDE may claim\r\n            updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(int lightLevel, boolean updateChunk) {\r\n        runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition());\r\n        updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), () -> {\r\n            updateTask = null;\r\n            runSetFor((LevelChunk) ((CraftChunk) chunk).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition(), lightLevel);\r\n            if (updateChunk) {\r\n                // This runnable cast is necessary despite what your IDE may claim\r\n                updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n            }\r\n        }, 1);\r\n    }\r\n\r\n    public static final Vector[] RELATIVE_CHUNKS = new Vector[] {\r\n            new Vector(0, 0, 0),\r\n            new Vector(-1, 0, 0), new Vector(1, 0, 0), new Vector(0, 0, -1), new Vector(0, 0, 1),\r\n            new Vector(-1, 0, -1), new Vector(-1, 0, 1), new Vector(1, 0, -1), new Vector(1, 0, 1)\r\n    };\r\n\r\n    public void sendNearbyChunkUpdates() {\r\n        sendNearbyChunkUpdates((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL));\r\n    }\r\n\r\n    public static void sendNearbyChunkUpdates(LevelChunk chunk) {\r\n        ChunkPos pos = chunk.getPos();\r\n        for (Vector vec : RELATIVE_CHUNKS) {\r\n            ChunkAccess other = chunk.getLevel().getChunk(pos.x + vec.getBlockX(), pos.z + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n            if (other instanceof LevelChunk) {\r\n                sendSingleChunkUpdate((LevelChunk) other);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void sendSingleChunkUpdate(LevelChunk chunk) {\r\n        // TODO: 1.20: ?\r\n        /*\r\n        doNotCheck = true;\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        ChunkPos pos = chunk.getPos();\r\n        //ClientboundLightUpdatePacket packet = new ClientboundLightUpdatePacket(pos, lightEngine, null, null, true); // TODO: 1.16: should 'trust edges' be true here?\r\n        ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(pos, false).forEach((player) -> {\r\n            player.connection.send(packet);\r\n        });\r\n        doNotCheck = false;*/\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/entities/CraftFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport net.minecraft.world.entity.projectile.AbstractArrow;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftAbstractArrow;\r\n\r\npublic class CraftFakeArrowImpl extends CraftAbstractArrow implements FakeArrow {\r\n\r\n    public CraftFakeArrowImpl(CraftServer craftServer, AbstractArrow entityArrow) {\r\n        super(craftServer, entityArrow);\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        if (getPassenger() != null) {\r\n            return;\r\n        }\r\n        super.remove();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_ARROW\";\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/entities/CraftFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;\r\nimport org.bukkit.metadata.FixedMetadataValue;\r\nimport org.bukkit.metadata.MetadataValue;\r\nimport org.bukkit.plugin.Plugin;\r\n\r\nimport java.util.List;\r\nimport java.util.Set;\r\n\r\npublic class CraftFakePlayerImpl extends CraftPlayer implements FakePlayer {\r\n\r\n    private final CraftServer server;\r\n    public String fullName;\r\n\r\n    public CraftFakePlayerImpl(CraftServer server, EntityFakePlayerImpl entity) {\r\n        super(server, entity);\r\n        this.server = server;\r\n        setMetadata(\"NPC\", new FixedMetadataValue(NMSHandler.getJavaPlugin(), true));\r\n    }\r\n\r\n    @Override\r\n    public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {\r\n        this.server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue);\r\n    }\r\n\r\n    @Override\r\n    public List<MetadataValue> getMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().getMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().hasMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public void removeMetadata(String metadataKey, Plugin owningPlugin) {\r\n        this.server.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin);\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_PLAYER\";\r\n    }\r\n\r\n    @Override\r\n    public String getFullName() {\r\n        return fullName;\r\n    }\r\n\r\n    @Override\r\n    public Block getTargetBlock(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public List<Block> getLastTwoTargetBlocks(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/entities/CraftItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.UUID;\r\n\r\npublic class CraftItemProjectileImpl extends CraftEntity implements ItemProjectile {\r\n\r\n    private boolean doesBounce;\r\n\r\n    public CraftItemProjectileImpl(CraftServer server, EntityItemProjectileImpl entity) {\r\n        super(server, entity);\r\n        MethodHandle handle = ReflectionHelper.getFinalSetterForFirstOfType(CraftEntity.class, EntityType.class);\r\n        if (handle != null) {\r\n            try {\r\n                handle.invoke(this, EntityType.ITEM);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public EntityItemProjectileImpl getHandle() {\r\n        return (EntityItemProjectileImpl) super.getHandle();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return getType().name();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack getItemStack() {\r\n        return CraftItemStack.asBukkitCopy(getHandle().getItemStack());\r\n    }\r\n\r\n    @Override\r\n    public void setItemStack(ItemStack itemStack) {\r\n        getHandle().setItemStack(CraftItemStack.asNMSCopy(itemStack));\r\n    }\r\n\r\n    @Override\r\n    public int getPickupDelay() {\r\n        return 0;\r\n    }\r\n\r\n    @Override\r\n    public void setPickupDelay(int i) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public void setUnlimitedLifetime(boolean b) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnlimitedLifetime() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void setOwner(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getOwner() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setThrower(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getThrower() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ProjectileSource getShooter() {\r\n        return getHandle().projectileSource;\r\n    }\r\n\r\n    @Override\r\n    public void setShooter(ProjectileSource projectileSource) {\r\n        if (projectileSource instanceof CraftEntity) {\r\n            getHandle().setOwner(((CraftEntity) projectileSource).getHandle());\r\n        }\r\n        else {\r\n            getHandle().projectileSource = projectileSource;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean doesBounce() {\r\n        return doesBounce;\r\n    }\r\n\r\n    @Override\r\n    public void setBounce(boolean doesBounce) {\r\n        this.doesBounce = doesBounce;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/entities/EntityFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_20.Handler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.world.entity.projectile.SpectralArrow;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakeArrowImpl extends SpectralArrow {\r\n\r\n    public EntityFakeArrowImpl(CraftWorld craftWorld, Location location) {\r\n        super(net.minecraft.world.entity.EntityType.SPECTRAL_ARROW, craftWorld.getHandle());\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakeArrowImpl((CraftServer) Bukkit.getServer(), this));\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    protected ItemStack getPickupItem() {\r\n        return new ItemStack(Items.ARROW);\r\n    }\r\n\r\n    @Override\r\n    public CraftFakeArrowImpl getBukkitEntity() {\r\n        return (CraftFakeArrowImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/entities/EntityFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_20.Handler;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.fakes.FakeNetworkManagerImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.fakes.FakePlayerConnectionImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ClientInformation;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.world.entity.player.Player;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftServer;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakePlayerImpl extends ServerPlayer {\r\n\r\n    public EntityFakePlayerImpl(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile, ClientInformation clientInfo, boolean doAdd) {\r\n        super(minecraftserver, worldserver, gameprofile, clientInfo);\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakePlayerImpl((CraftServer) Bukkit.getServer(), this));\r\n            Connection networkManager = new FakeNetworkManagerImpl(PacketFlow.CLIENTBOUND);\r\n            connection = new FakePlayerConnectionImpl(minecraftserver, networkManager, this, new CommonListenerCookie(gameprofile, 0, clientInfo, false));\r\n            DenizenNetworkManagerImpl.Connection_packetListener.set(networkManager, connection);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        getEntityData().set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) 127);\r\n        if (doAdd) {\r\n            worldserver.addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public CraftFakePlayerImpl getBukkitEntity() {\r\n        return (CraftFakePlayerImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/entities/EntityItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.base.Preconditions;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.projectile.ThrowableProjectile;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftRegistry;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\n\r\npublic class EntityItemProjectileImpl extends ThrowableProjectile {\r\n\r\n    public static MethodHandle setBukkitEntityMethod = ReflectionHelper.getFinalSetter(Entity.class, \"bukkitEntity\");\r\n\r\n    public static final EntityDataAccessor<ItemStack> ITEM;\r\n\r\n    static {\r\n        EntityDataAccessor<ItemStack> watcher = null;\r\n        try {\r\n            watcher = (EntityDataAccessor<ItemStack>) ReflectionHelper.getFields(ItemEntity.class).get(ReflectionMappingsInfo.ItemEntity_DATA_ITEM).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        ITEM = watcher;\r\n    }\r\n\r\n    public EntityItemProjectileImpl(Level world, Location location, ItemStack item) {\r\n        super((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.ITEM, world);\r\n        try {\r\n            setBukkitEntityMethod.invoke(this, new CraftItemProjectileImpl(((ServerLevel) world).getServer().server, this));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        setItemStack(item);\r\n    }\r\n\r\n    @Override\r\n    protected void defineSynchedData(SynchedEntityData.Builder builder) {\r\n        builder.define(ITEM, ItemStack.EMPTY);\r\n    }\r\n\r\n    public ItemStack getItemStack() {\r\n        return this.getEntityData().get(ITEM);\r\n    }\r\n\r\n    public void setItemStack(ItemStack itemstack) {\r\n        Preconditions.checkArgument(!itemstack.isEmpty(), \"Cannot drop air\");\r\n        this.getEntityData().set(ITEM, itemstack);\r\n        this.getEntityData().markDirty(ITEM);\r\n    }\r\n\r\n    @Override\r\n    protected void onHitBlock(BlockHitResult movingobjectpositionblock) {\r\n        super.onHitBlock(movingobjectpositionblock);\r\n        remove(RemovalReason.KILLED);\r\n    }\r\n\r\n    @Override\r\n    public void onSyncedDataUpdated(EntityDataAccessor<?> datawatcherobject) {\r\n        super.onSyncedDataUpdated(datawatcherobject);\r\n        if (ITEM.equals(datawatcherobject)) {\r\n            this.getItemStack().setEntityRepresentation(this);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean save(net.minecraft.nbt.CompoundTag nbttagcompound) {\r\n        if (!this.getItemStack().isEmpty()) {\r\n            nbttagcompound.put(\"Item\", this.getItemStack().save(CraftRegistry.getMinecraftRegistry()));\r\n        }\r\n        super.save(nbttagcompound);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public void load(net.minecraft.nbt.CompoundTag nbttagcompound) {\r\n        net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttagcompound.getCompound(\"Item\");\r\n        this.setItemStack(ItemStack.parseOptional(CraftRegistry.getMinecraftRegistry(), nbttagcompound1));\r\n        if (this.getItemStack().isEmpty()) {\r\n            this.remove(RemovalReason.KILLED);\r\n        }\r\n        super.load(nbttagcompound);\r\n    }\r\n\r\n    @Override\r\n    public CraftItemProjectileImpl getBukkitEntity() {\r\n        return (CraftItemProjectileImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/fakes/FakeChannelImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.fakes;\r\n\r\nimport io.netty.channel.*;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeChannelImpl extends AbstractChannel {\r\n\r\n    private final ChannelConfig config = new DefaultChannelConfig(this);\r\n\r\n    protected FakeChannelImpl(Channel parent) {\r\n        super(parent);\r\n    }\r\n\r\n    @Override\r\n    public ChannelConfig config() {\r\n        config.setAutoRead(true);\r\n        return config;\r\n    }\r\n\r\n    @Override\r\n    protected AbstractUnsafe newUnsafe() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected boolean isCompatible(EventLoop eventLoop) {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress localAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress remoteAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected void doBind(SocketAddress socketAddress) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doDisconnect() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doClose() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doBeginRead() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doWrite(ChannelOutboundBuffer channelOutboundBuffer) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean isOpen() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActive() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public ChannelMetadata metadata() {\r\n        return new ChannelMetadata(true);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/fakes/FakeNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeNetworkManagerImpl extends Connection {\r\n\r\n    public FakeNetworkManagerImpl(PacketFlow enumprotocoldirection) {\r\n        super(enumprotocoldirection);\r\n        channel = new FakeChannelImpl(null);\r\n        address = new SocketAddress() {\r\n        };\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/fakes/FakePlayerConnectionImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\n\r\npublic class FakePlayerConnectionImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public FakePlayerConnectionImpl(MinecraftServer minecraftserver, Connection networkmanager, ServerPlayer entityplayer, CommonListenerCookie cookie) {\r\n        super(minecraftserver, networkmanager, entityplayer, cookie);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet packet) {\r\n        // Do nothing\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/AbstractListenerPlayInImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerSendPacketScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.minecraft.network.ConnectionProtocol;\r\nimport net.minecraft.network.PacketSendListener;\r\nimport net.minecraft.network.chat.ChatType;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.chat.PlayerChatMessage;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.network.protocol.common.*;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.world.entity.RelativeMovement;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.net.SocketAddress;\r\nimport java.util.Set;\r\n\r\npublic class AbstractListenerPlayInImpl extends ServerGamePacketListenerImpl {\r\n    // TODO: 1.20.6: there are some new methods that should potentially be overriden\r\n\r\n    public static final Field ServerGamePacketListenerImpl_chunkSender = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_chunkSender);\r\n\r\n    public final ServerGamePacketListenerImpl oldListener;\r\n    public final DenizenNetworkManagerImpl denizenNetworkManager;\r\n\r\n    public AbstractListenerPlayInImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer, ServerGamePacketListenerImpl oldListener, CommonListenerCookie cookie) {\r\n        super(MinecraftServer.getServer(), networkManager, entityPlayer, cookie);\r\n        this.oldListener = oldListener;\r\n        this.denizenNetworkManager = networkManager;\r\n        try {\r\n            ServerGamePacketListenerImpl_chunkSender.set(this, oldListener.chunkSender);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        oldListener.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(String s) {\r\n        oldListener.disconnect(s);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1) {\r\n        oldListener.teleport(d0, d1, d2, f, f1);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) {\r\n        oldListener.teleport(d0, d1, d2, f, f1, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set) {\r\n        oldListener.teleport(d0, d1, d2, f, f1, set);\r\n    }\r\n\r\n    @Override\r\n    public boolean teleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set, PlayerTeleportEvent.TeleportCause cause) {\r\n        return oldListener.teleport(d0, d1, d2, f, f1, set, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Location dest) {\r\n        oldListener.teleport(dest);\r\n    }\r\n\r\n    @Override\r\n    public CraftPlayer getCraftPlayer() {\r\n        return oldListener.getCraftPlayer();\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldListener.tick();\r\n    }\r\n\r\n    @Override\r\n    public void resetPosition() {\r\n        oldListener.resetPosition();\r\n    }\r\n\r\n    @Override\r\n    public boolean isAcceptingMessages() {\r\n        return oldListener.isAcceptingMessages();\r\n    }\r\n\r\n    @Override\r\n    public GameProfile getOwner() {\r\n        return oldListener.getOwner();\r\n    }\r\n\r\n    @Override\r\n    public int latency() {\r\n        return oldListener.latency();\r\n    }\r\n\r\n    @Override\r\n    public void onDisconnect(Component ichatbasecomponent) {\r\n        oldListener.onDisconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void ackBlockChangesUpTo(int i) {\r\n        oldListener.ackBlockChangesUpTo(i);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        oldListener.send(packet);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, PacketSendListener listener) {\r\n        oldListener.send(packet, listener);\r\n    }\r\n\r\n    public static Field AWAITING_POS_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_awaitingPositionFromClient, Vec3.class);\r\n    public static Field AWAITING_TELEPORT_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_awaitingTeleport, int.class);\r\n\r\n    public void debugPacketOutput(Packet<?> packet) {\r\n        try {\r\n            if (packet instanceof ServerboundMovePlayerPacket) {\r\n                ServerboundMovePlayerPacket movePacket = (ServerboundMovePlayerPacket) packet;\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet ServerboundMovePlayerPacket sent from \" + player.getScoreboardName() + \" with XYZ=\"\r\n                        + movePacket.x + \", \" + movePacket.y + \", \" + movePacket.z + \", yRot=\" + movePacket.yRot + \", xRot=\" + movePacket.xRot\r\n                        + \", onGround=\" + movePacket.isOnGround() + \", hasPos=\" + movePacket.hasPos + \", hasRot=\" + movePacket.hasRot);\r\n            }\r\n            else if (packet instanceof ServerboundAcceptTeleportationPacket) {\r\n                Vec3 awaitPos = (Vec3) AWAITING_POS_FIELD.get(oldListener);\r\n                int awaitTeleportId = AWAITING_TELEPORT_FIELD.getInt(oldListener);\r\n                ServerboundAcceptTeleportationPacket acceptPacket = (ServerboundAcceptTeleportationPacket) packet;\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet ServerboundAcceptTeleportationPacket sent from \" + player.getScoreboardName()\r\n                        + \" with ID=\" + acceptPacket.getId() + \", awaitingTeleport=\" + awaitTeleportId + \", awaitPos=\" + awaitPos);\r\n            }\r\n            else {\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent from \" + player.getScoreboardName());\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public boolean handlePacketIn(Packet<?> packet) {\r\n        denizenNetworkManager.packetsReceived++;\r\n        if (NMSHandler.debugPackets) {\r\n            debugPacketOutput(packet);\r\n        }\r\n        if (PlayerSendPacketScriptEvent.instance.eventData.isEnabled) {\r\n            if (PlayerSendPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) {\r\n                if (NMSHandler.debugPackets) {\r\n                    DenizenNetworkManagerImpl.doPacketOutput(\"Denied packet-in \" + packet.getClass().getCanonicalName() + \" from \" + player.getScoreboardName() + \" due to event\");\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void handleChatAck(ServerboundChatAckPacket serverboundchatackpacket) {\r\n        oldListener.handleChatAck(serverboundchatackpacket);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(ServerboundPlayerInputPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerInput(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleMoveVehicle(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleAcceptTeleportPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookSeenRecipePacket(ServerboundRecipeBookSeenRecipePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRecipeBookSeenRecipePacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRecipeBookChangeSettingsPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSeenAdvancements(ServerboundSeenAdvancementsPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSeenAdvancements(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCustomCommandSuggestions(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandBlock(ServerboundSetCommandBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCommandBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandMinecart(ServerboundSetCommandMinecartPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCommandMinecart(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePickItem(ServerboundPickItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePickItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRenameItem(ServerboundRenameItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRenameItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetBeaconPacket(ServerboundSetBeaconPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetBeaconPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetStructureBlock(ServerboundSetStructureBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetStructureBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetJigsawBlock(ServerboundSetJigsawBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetJigsawBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleJigsawGenerate(ServerboundJigsawGeneratePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleJigsawGenerate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSelectTrade(ServerboundSelectTradePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSelectTrade(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEditBook(ServerboundEditBookPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleEditBook(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleMovePlayer(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItemOn(ServerboundUseItemOnPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleUseItemOn(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleTeleportToEntityPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void suspendFlushing() {\r\n        oldListener.suspendFlushing();\r\n    }\r\n\r\n    @Override\r\n    public void resumeFlushing() {\r\n        oldListener.resumeFlushing();\r\n    }\r\n\r\n    @Override\r\n    public void handlePaddleBoat(ServerboundPaddleBoatPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePaddleBoat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePong(ServerboundPongPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePong(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChat(ServerboundChatPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChatCommand(ServerboundChatCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChatCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void chat(String s, PlayerChatMessage original, boolean async) {\r\n        oldListener.chat(s, original, async);\r\n    }\r\n\r\n    @Override\r\n    public ConnectionProtocol protocol() {\r\n        return oldListener == null ? ConnectionProtocol.PLAY : oldListener.protocol();\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void addPendingMessage(PlayerChatMessage playerchatmessage) {\r\n        oldListener.addPendingMessage(playerchatmessage);\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerChatMessage(PlayerChatMessage playerchatmessage, ChatType.Bound chatmessagetype_a) {\r\n        oldListener.sendPlayerChatMessage(playerchatmessage, chatmessagetype_a);\r\n    }\r\n\r\n    @Override\r\n    public void sendDisguisedChatMessage(Component ichatbasecomponent, ChatType.Bound chatmessagetype_a) {\r\n        oldListener.sendDisguisedChatMessage(ichatbasecomponent, chatmessagetype_a);\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRemoteAddress() {\r\n        return oldListener.getRemoteAddress();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRawAddress() {\r\n        return oldListener.getRawAddress();\r\n    }\r\n\r\n    @Override\r\n    public void switchToConfig() {\r\n        oldListener.switchToConfig();\r\n    }\r\n\r\n    @Override\r\n    public void handleInteract(ServerboundInteractPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleInteract(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientCommand(ServerboundClientCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleClientCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClose(ServerboundContainerClosePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerClose(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlaceRecipe(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerButtonClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCreativeModeSlot(ServerboundSetCreativeModeSlotPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCreativeModeSlot(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleKeepAlive(ServerboundKeepAlivePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleKeepAlive(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerAbilities(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientInformation(ServerboundClientInformationPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleClientInformation(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChangeDifficulty(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleLockDifficulty(ServerboundLockDifficultyPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleLockDifficulty(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChatSessionUpdate(ServerboundChatSessionUpdatePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChatSessionUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleConfigurationAcknowledged(ServerboundConfigurationAcknowledgedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleConfigurationAcknowledged(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChunkBatchReceived(ServerboundChunkBatchReceivedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChunkBatchReceived(packet);\r\n    }\r\n\r\n    @Override\r\n    public ServerPlayer getPlayer() {\r\n        return oldListener.getPlayer();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow flow() {\r\n        return oldListener == null ? PacketFlow.SERVERBOUND : oldListener.flow();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/DenizenNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerReceivesPacketScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet.*;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptCodeGen;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport io.netty.buffer.Unpooled;\r\nimport io.netty.channel.ChannelHandlerContext;\r\nimport io.netty.channel.ChannelPipeline;\r\nimport net.minecraft.network.*;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.codec.StreamCodec;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.protocol.login.ClientLoginPacketListener;\r\nimport net.minecraft.network.protocol.status.ClientStatusPacketListener;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.network.ServerPlayerConnection;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftRegistry;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport javax.annotation.Nullable;\r\nimport javax.crypto.Cipher;\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.net.SocketAddress;\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.function.BiConsumer;\r\nimport java.util.function.Consumer;\r\nimport java.util.stream.Collectors;\r\n\r\npublic class DenizenNetworkManagerImpl extends Connection {\r\n\r\n    public static <T extends Packet<?>> T copyPacket(T original, StreamCodec<? super RegistryFriendlyByteBuf, T> packetCodec) {\r\n        try {\r\n            RegistryFriendlyByteBuf copier = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry());\r\n            packetCodec.encode(copier, original);\r\n            return packetCodec.decode(copier);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @FunctionalInterface\r\n    public interface PacketHandler<T extends Packet<ClientGamePacketListener>> {\r\n        Packet<ClientGamePacketListener> handlePacket(DenizenNetworkManagerImpl networkManager, T packet) throws Exception;\r\n    }\r\n\r\n    public static final Map<Class<? extends Packet<ClientGamePacketListener>>, List<PacketHandler<?>>> packetHandlers = new HashMap<>();\r\n\r\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetClass, PacketHandler<T> handler) {\r\n        packetHandlers.computeIfAbsent(packetClass, k -> new ArrayList<>()).add(handler);\r\n    }\r\n\r\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetClass, BiConsumer<DenizenNetworkManagerImpl, T> handler) {\r\n        registerPacketHandler(packetClass, (networkManager, packet) -> {\r\n            handler.accept(networkManager, packet);\r\n            return packet;\r\n        });\r\n    }\r\n\r\n    public final Connection oldManager;\r\n    public final DenizenPacketListenerImpl packetListener;\r\n    public final ServerPlayer player;\r\n    public int packetsSent, packetsReceived;\r\n\r\n    public DenizenNetworkManagerImpl(ServerPlayer entityPlayer, Connection oldManager) {\r\n        super(getProtocolDirection(oldManager));\r\n        this.oldManager = oldManager;\r\n        this.channel = oldManager.channel;\r\n        this.player = entityPlayer;\r\n        packetListener = (DenizenPacketListenerImpl) NetworkInterceptCodeGen.generateAppropriateInterceptor(this, entityPlayer, DenizenPacketListenerImpl.class, AbstractListenerPlayInImpl.class, ServerGamePacketListenerImpl.class);\r\n        if (!(oldManager.getPacketListener() instanceof ServerConfigurationPacketListener)) {\r\n            setListener(packetListener);\r\n        }\r\n    }\r\n\r\n    public void setListener(PacketListener listener) {\r\n        try {\r\n            Connection_packetListener.set(oldManager, listener);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n            throw new RuntimeException(\"Failed to set packet listener due to reflection error\", e);\r\n        }\r\n    }\r\n\r\n    public static Connection getConnection(ServerPlayer player) {\r\n        try {\r\n            return (Connection) ServerGamePacketListener_ConnectionField.get(player.connection);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            throw new RuntimeException(\"Failed to get connection from player due to reflection error\", ex);\r\n        }\r\n    }\r\n\r\n    public static Connection getConnection(Player player) {\r\n        return getConnection(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    public static DenizenNetworkManagerImpl getNetworkManager(ServerPlayer player) {\r\n        return (DenizenNetworkManagerImpl) getConnection(player);\r\n    }\r\n\r\n    public static DenizenNetworkManagerImpl getNetworkManager(Player player) {\r\n        return getNetworkManager(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    public static void setNetworkManager(Player player) {\r\n        ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerGamePacketListenerImpl playerConnection = entityPlayer.connection;\r\n        setNetworkManager(playerConnection, new DenizenNetworkManagerImpl(entityPlayer, getConnection(entityPlayer)));\r\n    }\r\n\r\n    public static void enableNetworkManager() {\r\n        for (World w : Bukkit.getWorlds()) {\r\n            for (ChunkMap.TrackedEntity tracker : ((CraftWorld) w).getHandle().getChunkSource().chunkMap.entityMap.values()) {\r\n                ArrayList<ServerPlayerConnection> connections = new ArrayList<>(tracker.seenBy);\r\n                tracker.seenBy.clear();\r\n                for (ServerPlayerConnection connection : connections) {\r\n                    tracker.seenBy.add(connection.getPlayer().connection);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int hashCode() {\r\n        return oldManager.hashCode();\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object c2) {\r\n        return oldManager.equals(c2);\r\n    }\r\n\r\n    @Override\r\n    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelRegistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelUnregistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception {\r\n        oldManager.channelActive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public void channelInactive(ChannelHandlerContext channelhandlercontext) {\r\n        oldManager.channelInactive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public boolean isSharable() {\r\n        return oldManager.isSharable();\r\n    }\r\n\r\n    @Override\r\n    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerAdded(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerRemoved(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) {\r\n        oldManager.exceptionCaught(channelhandlercontext, throwable);\r\n    }\r\n\r\n    @Override\r\n    protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) {\r\n        if (oldManager.channel.isOpen()) {\r\n            try {\r\n                packet.handle(this.packetListener);\r\n            }\r\n            catch (Exception e) {\r\n                // Do nothing\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setListenerForServerboundHandshake(PacketListener packetlistener) {\r\n        oldManager.setListenerForServerboundHandshake(packetlistener);\r\n    }\r\n\r\n    @Override\r\n    public void initiateServerboundStatusConnection(String s, int i, ClientStatusPacketListener packetstatusoutlistener) {\r\n        oldManager.initiateServerboundStatusConnection(s, i, packetstatusoutlistener);\r\n    }\r\n\r\n    @Override\r\n    public void initiateServerboundPlayConnection(String s, int i, ClientLoginPacketListener packetloginoutlistener) {\r\n        oldManager.initiateServerboundPlayConnection(s, i, packetloginoutlistener);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        send(packet, null);\r\n    }\r\n\r\n    public static void doPacketOutput(String text) {\r\n        if (!NMSHandler.debugPackets) {\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPacketFilter == null || NMSHandler.debugPacketFilter.trim().isEmpty()\r\n                || CoreUtilities.toLowerCase(text).contains(NMSHandler.debugPacketFilter)) {\r\n            Debug.log(text);\r\n        }\r\n    }\r\n\r\n    public void debugOutputPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundSetEntityDataPacket) {\r\n            StringBuilder output = new StringBuilder(128);\r\n            output.append(\"Packet: ClientboundSetEntityDataPacket sent to \").append(player.getScoreboardName()).append(\" for entity ID: \").append(((ClientboundSetEntityDataPacket) packet).id()).append(\": \");\r\n            List<SynchedEntityData.DataValue<?>> list = ((ClientboundSetEntityDataPacket) packet).packedItems();\r\n            if (list == null) {\r\n                output.append(\"None\");\r\n            }\r\n            else {\r\n                for (SynchedEntityData.DataValue<?> data : list) {\r\n                    output.append('[').append(data.id()).append(\": \").append(data.value()).append(\"], \");\r\n                }\r\n            }\r\n            doPacketOutput(output.toString());\r\n        }\r\n        else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n            ClientboundSetEntityMotionPacket velPacket = (ClientboundSetEntityMotionPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundSetEntityMotionPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + velPacket.getId() + \": \" + velPacket.getXa() + \",\" + velPacket.getYa() + \",\" + velPacket.getZa());\r\n        }\r\n        else if (packet instanceof ClientboundAddEntityPacket) {\r\n            ClientboundAddEntityPacket addEntityPacket = (ClientboundAddEntityPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundAddEntityPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + addEntityPacket.getId() + \": \" + \"uuid: \" + addEntityPacket.getUUID()\r\n                    + \", type: \" + addEntityPacket.getType() + \", at: \" + addEntityPacket.getX() + \",\" + addEntityPacket.getY() + \",\" + addEntityPacket.getZ() + \", data: \" + addEntityPacket.getData());\r\n        }\r\n        else if (packet instanceof ClientboundMapItemDataPacket) {\r\n            ClientboundMapItemDataPacket mapPacket = (ClientboundMapItemDataPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundMapItemDataPacket sent to \" + player.getScoreboardName() + \" for map ID: \" + mapPacket.mapId() + \", scale: \" + mapPacket.scale() + \", locked: \" + mapPacket.locked());\r\n        }\r\n        else if (packet instanceof ClientboundRemoveEntitiesPacket) {\r\n            ClientboundRemoveEntitiesPacket removePacket = (ClientboundRemoveEntitiesPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundRemoveEntitiesPacket sent to \" + player.getScoreboardName() + \" for entities: \" + removePacket.getEntityIds().stream().map(Object::toString).collect(Collectors.joining(\", \")));\r\n        }\r\n        else if (packet instanceof ClientboundPlayerInfoUpdatePacket) {\r\n            ClientboundPlayerInfoUpdatePacket playerInfoPacket = (ClientboundPlayerInfoUpdatePacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundPlayerInfoPacket sent to \" + player.getScoreboardName() + \" of types \" + playerInfoPacket.actions() + \" for player profiles: \" +\r\n                    playerInfoPacket.entries().stream().map(p -> \"mode=\" + p.gameMode() + \"/latency=\" + p.latency() + \"/display=\" + p.displayName() + \"/name=\" + p.profile().getName() + \"/id=\" + p.profile().getId() + \"/\"\r\n                            + p.profile().getProperties().asMap().entrySet().stream().map(e -> e.getKey() + \"=\" + e.getValue().stream().map(v -> v.value() + \";\" + v.signature()).collect(Collectors.joining(\";;;\"))).collect(Collectors.joining(\"/\"))).collect(Collectors.joining(\", \")));\r\n        }\r\n        else {\r\n            doPacketOutput(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, PacketSendListener genericfuturelistener) {\r\n        send(packet, genericfuturelistener, true);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, @Nullable PacketSendListener genericfuturelistener, boolean flush) {\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            if (Settings.cache_warnOnAsyncPackets\r\n                    && !(packet instanceof ClientboundSystemChatPacket) && !(packet instanceof ClientboundPlayerChatPacket) // Vanilla supports an async chat system, though it's normally disabled, some plugins use this as justification for sending messages async\r\n                    && !(packet instanceof ClientboundCommandSuggestionsPacket)) { // Async tab complete is wholly unsupported in Spigot (and will cause an exception), however Paper explicitly adds async support (for unclear reasons), so let it through too\r\n                Debug.echoError(\"Warning: packet sent off main thread! This is completely unsupported behavior! Denizen network interceptor ignoring packet to avoid crash. Packet class: \"\r\n                        + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName() + \" identify the sender of the packet from the stack trace:\");\r\n                try {\r\n                    throw new RuntimeException(\"Trace\");\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(ex);\r\n                }\r\n            }\r\n            oldManager.send(packet, genericfuturelistener, flush);\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPackets) {\r\n            debugOutputPacket(packet);\r\n        }\r\n        packetsSent++;\r\n        if (packet instanceof ClientboundBundlePacket bundlePacket) {\r\n            List<Packet<? super ClientGamePacketListener>> processedPackets = new ArrayList<>();\r\n            boolean anyChange = false;\r\n            for (Packet<? super ClientGamePacketListener> _subPacket : bundlePacket.subPackets()) {\r\n                // Bundle packets with non-game packets shouldn't ever be sent while the Denizen interceptor is active\r\n                Packet<ClientGamePacketListener> subPacket = (Packet<ClientGamePacketListener>) _subPacket;\r\n                Packet<ClientGamePacketListener> processed = processPacketHandlersFor(subPacket);\r\n                anyChange = anyChange || processed != subPacket;\r\n                if (processed != null) {\r\n                    processedPackets.add(processed);\r\n                }\r\n            }\r\n            if (processedPackets.isEmpty()) {\r\n                return;\r\n            }\r\n            if (anyChange) {\r\n                packet = new ClientboundBundlePacket(processedPackets);\r\n            }\r\n        }\r\n        else {\r\n            Packet<?> processed = processPacketHandlersFor((Packet<ClientGamePacketListener>) packet);\r\n            if (processed == null) {\r\n                return;\r\n            }\r\n            packet = processed;\r\n        }\r\n        oldManager.send(packet, genericfuturelistener, flush);\r\n    }\r\n\r\n    @Override\r\n    public void runOnceConnected(Consumer<Connection> consumer) {\r\n        oldManager.runOnceConnected(consumer);\r\n    }\r\n\r\n    @Override\r\n    public void flushChannel() {\r\n        oldManager.flushChannel();\r\n    }\r\n\r\n    public Packet<ClientGamePacketListener> processPacketHandlersFor(Packet<ClientGamePacketListener> packet) {\r\n        if (packet == null) {\r\n            return null;\r\n        }\r\n        List<PacketHandler<?>> packetHandlers = DenizenNetworkManagerImpl.packetHandlers.get(packet.getClass());\r\n        if (packetHandlers != null) {\r\n            for (PacketHandler<?> _packetHandler : packetHandlers) {\r\n                PacketHandler<Packet<ClientGamePacketListener>> packetHandler = (PacketHandler<Packet<ClientGamePacketListener>>) _packetHandler;\r\n                Packet<ClientGamePacketListener> processed;\r\n                try {\r\n                    processed = packetHandler.handlePacket(this, packet);\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(\"Packet handler for \" + packet.getClass().getCanonicalName() + \" threw an exception:\");\r\n                    Debug.echoError(ex);\r\n                    continue;\r\n                }\r\n                if (processed == null) {\r\n                    if (NMSHandler.debugPackets) {\r\n                        doPacketOutput(\"DENIED PACKET - \" + packet.getClass().getCanonicalName() + \" DENIED FROM SEND TO \" + player.getScoreboardName());\r\n                    }\r\n                    return null;\r\n                }\r\n                packet = processed;\r\n            }\r\n        }\r\n        if (PlayerReceivesPacketScriptEvent.instance.eventData.isEnabled & PlayerReceivesPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) {\r\n            if (NMSHandler.debugPackets) {\r\n                doPacketOutput(\"DENIED PACKET - \" + packet.getClass().getCanonicalName() + \" DENIED FROM SEND TO \" + player.getScoreboardName() + \" due to event\");\r\n            }\r\n            return null;\r\n        }\r\n        return packet;\r\n    }\r\n\r\n    static {\r\n        ActionBarEventPacketHandlers.registerHandlers();\r\n        AttachPacketHandlers.registerHandlers();\r\n        BlockLightPacketHandlers.registerHandlers();\r\n        DenizenPacketHandlerPacketHandlers.registerHandlers();\r\n        EntityMetadataPacketHandlers.registerHandlers();\r\n        DisguisePacketHandlers.registerHandlers();\r\n        FakeBlocksPacketHandlers.registerHandlers();\r\n        FakeEquipmentPacketHandlers.registerHandlers();\r\n        FakePlayerPacketHandlers.registerHandlers();\r\n        HiddenEntitiesPacketHandlers.registerHandlers();\r\n        HideParticlesPacketHandlers.registerHandlers();\r\n        PlayerHearsSoundEventPacketHandlers.registerHandlers();\r\n        ProfileEditorImpl.registerHandlers();\r\n        TablistUpdateEventPacketHandlers.registerHandlers();\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldManager.tick();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRemoteAddress() {\r\n        return oldManager.getRemoteAddress();\r\n    }\r\n\r\n    @Override\r\n    public String getLoggableAddress(boolean flag) {\r\n        return oldManager.getLoggableAddress(flag);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        if (!player.getBukkitEntity().isOnline()) { // Workaround Paper duplicate quit event issue\r\n            return;\r\n        }\r\n        oldManager.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public boolean isMemoryConnection() {\r\n        return oldManager != null && oldManager.isMemoryConnection();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getReceiving() {\r\n        return oldManager.getReceiving();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getSending() {\r\n        return oldManager.getSending();\r\n    }\r\n\r\n    @Override\r\n    public void configurePacketHandler(ChannelPipeline channelpipeline) {\r\n        oldManager.configurePacketHandler(channelpipeline);\r\n    }\r\n\r\n    @Override\r\n    public void setEncryptionKey(Cipher cipher, Cipher cipher1) {\r\n        oldManager.setEncryptionKey(cipher, cipher1);\r\n    }\r\n\r\n    @Override\r\n    public boolean isEncrypted() {\r\n        return oldManager.isEncrypted();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnected() {\r\n        return oldManager.isConnected();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnecting() {\r\n        return oldManager.isConnecting();\r\n    }\r\n\r\n    @Override\r\n    public PacketListener getPacketListener() {\r\n        return oldManager.getPacketListener();\r\n    }\r\n\r\n    @Override\r\n    public Component getDisconnectedReason() {\r\n        return oldManager.getDisconnectedReason();\r\n    }\r\n\r\n    @Override\r\n    public void setReadOnly() {\r\n        oldManager.setReadOnly();\r\n    }\r\n\r\n    @Override\r\n    public void setupCompression(int i, boolean b) {\r\n        oldManager.setupCompression(i, b);\r\n    }\r\n\r\n    @Override\r\n    public void handleDisconnection() {\r\n        oldManager.handleDisconnection();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageReceivedPackets() {\r\n        return oldManager.getAverageReceivedPackets();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageSentPackets() {\r\n        return oldManager.getAverageSentPackets();\r\n    }\r\n\r\n    //////////////////////////////////\r\n    //// Reflection Methods/Fields\r\n    ///////////\r\n\r\n    private static final Field protocolDirectionField = ReflectionHelper.getFields(Connection.class).get(ReflectionMappingsInfo.Connection_receiving, PacketFlow.class);\r\n    public static final Field Connection_packetListener = ReflectionHelper.getFields(Connection.class).get(ReflectionMappingsInfo.Connection_packetListener, PacketListener.class);\r\n    private static final Field ServerGamePacketListener_ConnectionField = ReflectionHelper.getFields(ServerCommonPacketListenerImpl.class).get(ReflectionMappingsInfo.ServerCommonPacketListenerImpl_connection);\r\n    private static final MethodHandle ServerGamePacketListener_ConnectionSetter = ReflectionHelper.getFinalSetter(ServerCommonPacketListenerImpl.class, ReflectionMappingsInfo.ServerCommonPacketListenerImpl_connection);\r\n\r\n    private static PacketFlow getProtocolDirection(Connection networkManager) {\r\n        PacketFlow direction = null;\r\n        try {\r\n            direction = (PacketFlow) protocolDirectionField.get(networkManager);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return direction;\r\n    }\r\n\r\n    private static void setNetworkManager(ServerGamePacketListenerImpl playerConnection, Connection networkManager) {\r\n        try {\r\n            ServerGamePacketListener_ConnectionSetter.invoke(playerConnection, networkManager);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean acceptInboundMessage(Object msg) throws Exception {\r\n        return oldManager.acceptInboundMessage(msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\r\n        oldManager.channelRead(ctx, msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelReadComplete(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\r\n        oldManager.userEventTriggered(ctx, evt);\r\n    }\r\n\r\n    @Override\r\n    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelWritabilityChanged(ctx);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/DenizenPacketListenerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerChangesSignScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerSteersEntityScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.packets.PacketInResourcePackStatusImpl;\r\nimport com.denizenscript.denizen.nms.v1_20.impl.network.packets.PacketInSteerVehicleImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\r\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;\r\nimport net.minecraft.network.protocol.common.ServerboundResourcePackPacket;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.CraftBlock;\r\nimport org.bukkit.event.block.SignChangeEvent;\r\n\r\npublic class DenizenPacketListenerImpl extends AbstractListenerPlayInImpl {\r\n\r\n    public String brand = \"unknown\";\r\n\r\n    public BlockPos fakeSignExpected;\r\n\r\n    public DenizenPacketListenerImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer) {\r\n        super(networkManager, entityPlayer, entityPlayer.connection, new CommonListenerCookie(entityPlayer.getGameProfile(), entityPlayer.connection.latency(), entityPlayer.clientInformation(), entityPlayer.connection.isTransferred()));\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(final ServerboundPlayerInputPacket packet) {\r\n        if (!PlayerSteersEntityScriptEvent.instance.eventData.isEnabled) {\r\n            super.handlePlayerInput(packet);\r\n            return;\r\n        }\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInSteerVehicleImpl(packet), () -> super.handlePlayerInput(packet));\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInResourcePackStatusImpl(packet));\r\n        super.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        DenizenPacketHandler.instance.receivePlacePacket(player.getBukkitEntity());\r\n        super.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        DenizenPacketHandler.instance.receiveDigPacket(player.getBukkitEntity());\r\n        super.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && (override.hand != null || override.offhand != null)) {\r\n            player.getBukkitEntity().updateInventory();\r\n        }\r\n        super.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && override.hand != null) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 2);\r\n        }\r\n        super.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && packet.getContainerId() == 0) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 1);\r\n        }\r\n        super.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        if (NMSHandler.debugPackets) {\r\n            Debug.log(\"Custom packet payload: \" + packet.payload().type().id().toString() + \" sent from \" + player.getScoreboardName());\r\n        }\r\n        super.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        if (fakeSignExpected != null && packet.getPos().equals(fakeSignExpected)) {\r\n            PlayerChangesSignScriptEvent evt = (PlayerChangesSignScriptEvent) PlayerChangesSignScriptEvent.instance.clone();\r\n            evt.material = new MaterialTag(org.bukkit.Material.OAK_WALL_SIGN);\r\n            evt.location = new LocationTag(player.getBukkitEntity().getLocation());\r\n            evt.event = new SignChangeEvent(CraftBlock.at(player.level(), fakeSignExpected), player.getBukkitEntity(), packet.getLines());\r\n            fakeSignExpected = null;\r\n            evt.fire(evt.event);\r\n        }\r\n        super.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        if (DenizenPacketHandler.forceNoclip.contains(player.getUUID())) {\r\n            player.noPhysics = true;\r\n        }\r\n        super.handleMovePlayer(packet);\r\n    }\r\n\r\n    // For compatibility with other plugins using Reflection weirdly...\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        super.send(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/FakeBlockHelper.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.RegistryFriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.Biomes;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.entity.BlockEntity;\r\nimport net.minecraft.world.level.block.entity.BlockEntityType;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.PalettedContainer;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftRegistry;\r\nimport org.bukkit.craftbukkit.v1_20_R4.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockStates;\r\nimport org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Constructor;\r\nimport java.lang.reflect.Field;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\n\r\npublic class FakeBlockHelper {\r\n\r\n    public static Field CHUNKDATA_BLOCK_ENTITIES = ReflectionHelper.getFields(ClientboundLevelChunkPacketData.class).getFirstOfType(List.class);\r\n    public static MethodHandle CHUNKDATA_BLOCK_ENTITY_CONSTRUCTOR = ReflectionHelper.getConstructor(ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0], int.class, int.class, BlockEntityType.class, CompoundTag.class);\r\n    public static MethodHandle CHUNKDATA_BUFFER_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkPacketData.class, byte[].class);\r\n    public static Class CHUNKDATA_BLOCKENTITYINFO_CLASS = ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0];\r\n    public static Field CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get(ReflectionMappingsInfo.ClientboundLevelChunkPacketDataBlockEntityInfo_packedXZ);\r\n    public static Field CHUNKDATA_BLOCKENTITYINFO_Y = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get(ReflectionMappingsInfo.ClientboundLevelChunkPacketDataBlockEntityInfo_y);\r\n    public static MethodHandle CHUNKPACKET_CHUNKDATA_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class);\r\n    public static Constructor<?> PALETTEDCONTAINER_CTOR = Arrays.stream(PalettedContainer.class.getConstructors()).filter(c -> c.getParameterCount() == 3).findFirst().get();\r\n\r\n    public static BlockState getNMSState(FakeBlock block) {\r\n        return ((CraftBlockData) block.material.getModernData()).getState();\r\n    }\r\n\r\n    public static boolean anyBlocksInSection(List<FakeBlock> blocks, int y) {\r\n        int minY = y << 4;\r\n        int maxY = (y << 4) + 16;\r\n        for (FakeBlock block : blocks) {\r\n            int blockY = block.location.getBlockY();\r\n            if (blockY >= minY && blockY < maxY) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static Field PAPER_CHUNK_READY;\r\n    public static boolean tryPaperPatch = true;\r\n\r\n    public static void copyPacketPaperPatch(ClientboundLevelChunkWithLightPacket newPacket, ClientboundLevelChunkWithLightPacket oldPacket) {\r\n        if (!Denizen.supportsPaper || !tryPaperPatch) {\r\n            return;\r\n        }\r\n        try {\r\n            if (PAPER_CHUNK_READY == null) {\r\n                PAPER_CHUNK_READY = ReflectionHelper.getFields(ClientboundLevelChunkWithLightPacket.class).get(\"ready\");\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            tryPaperPatch = false;\r\n            Debug.echoError(\"Paper packet patch failed:\");\r\n            Debug.echoError(ex);\r\n            return;\r\n        }\r\n        try {\r\n            PAPER_CHUNK_READY.setBoolean(newPacket, true);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static ClientboundLevelChunkWithLightPacket handleMapChunkPacket(World world, ClientboundLevelChunkWithLightPacket originalPacket, int chunkX, int chunkZ, List<FakeBlock> blocks) {\r\n        try {\r\n            ClientboundLevelChunkWithLightPacket duplicateCorePacket = DenizenNetworkManagerImpl.copyPacket(originalPacket, ClientboundLevelChunkWithLightPacket.STREAM_CODEC);\r\n            copyPacketPaperPatch(duplicateCorePacket, originalPacket);\r\n            RegistryFriendlyByteBuf copier = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry());\r\n            originalPacket.getChunkData().write(copier);\r\n            ClientboundLevelChunkPacketData packet = new ClientboundLevelChunkPacketData(copier, chunkX, chunkZ);\r\n            FriendlyByteBuf serial = originalPacket.getChunkData().getReadBuffer();\r\n            FriendlyByteBuf outputSerial = new FriendlyByteBuf(Unpooled.buffer(serial.readableBytes()));\r\n            List blockEntities = new ArrayList((List) CHUNKDATA_BLOCK_ENTITIES.get(originalPacket.getChunkData()));\r\n            CHUNKDATA_BLOCK_ENTITIES.set(packet, blockEntities);\r\n            for (int i = 0; i < blockEntities.size(); i++) {\r\n                Object blockEnt = blockEntities.get(i);\r\n                int xz = CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ.getInt(blockEnt);\r\n                int y = CHUNKDATA_BLOCKENTITYINFO_Y.getInt(blockEnt);\r\n                int x = (chunkX << 4) + ((xz >> 4) & 15);\r\n                int z = (chunkZ << 4) + (xz & 15);\r\n                for (FakeBlock block : blocks) {\r\n                    LocationTag loc = block.location;\r\n                    if (loc.getBlockX() == x && loc.getBlockY() == y && loc.getBlockZ() == z && block.material != null) {\r\n                        BlockEntity newBlockEnt = CraftBlockStates.createNewTileEntity(block.material.getMaterial());\r\n                        Object newData = CHUNKDATA_BLOCK_ENTITY_CONSTRUCTOR.invoke(xz, y, newBlockEnt.getType(), newBlockEnt.getUpdateTag(CraftRegistry.getMinecraftRegistry()));\r\n                        blockEntities.set(i, newData);\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            int worldMinY = world.getMinHeight();\r\n            int worldMaxY = world.getMaxHeight();\r\n            int minChunkY = worldMinY >> 4;\r\n            int maxChunkY = worldMaxY >> 4;\r\n            Registry<Biome> biomeRegistry = ((CraftWorld) world).getHandle().registryAccess().registryOrThrow(Registries.BIOME);\r\n            for (int y = minChunkY; y < maxChunkY; y++) {\r\n                int blockCount = serial.readShort();\r\n                // reflected constructors as workaround for spigot remapper bug - Mojang \"IdMap\" became Spigot \"IRegistry\" but should be \"Registry\"\r\n                PalettedContainer<BlockState> states = (PalettedContainer<BlockState>) PALETTEDCONTAINER_CTOR.newInstance(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);\r\n                states.read(serial);\r\n                PalettedContainer<Biome> biomes = (PalettedContainer<Biome>) PALETTEDCONTAINER_CTOR.newInstance(biomeRegistry, biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);\r\n                biomes.read(serial);\r\n                if (anyBlocksInSection(blocks, y)) {\r\n                    int minY = y << 4;\r\n                    int maxY = (y << 4) + 16;\r\n                    for (FakeBlock block : blocks) {\r\n                        int blockY = block.location.getBlockY();\r\n                        if (blockY >= minY && blockY < maxY && block.material != null) {\r\n                            int blockX = block.location.getBlockX();\r\n                            int blockZ = block.location.getBlockZ();\r\n                            blockX -= (blockX >> 4) * 16;\r\n                            blockY -= (blockY >> 4) * 16;\r\n                            blockZ -= (blockZ >> 4) * 16;\r\n                            BlockState oldState = states.get(blockX, blockY, blockZ);\r\n                            BlockState newState = getNMSState(block);\r\n                            if (oldState.isAir() && !newState.isAir()) {\r\n                                blockCount++;\r\n                            }\r\n                            else if (newState.isAir() && !oldState.isAir()) {\r\n                                blockCount--;\r\n                            }\r\n                            states.set(blockX, blockY, blockZ, newState);\r\n                        }\r\n                    }\r\n                }\r\n                outputSerial.writeShort(blockCount);\r\n                states.write(outputSerial);\r\n                biomes.write(outputSerial);\r\n            }\r\n            byte[] outputBytes = outputSerial.array();\r\n            CHUNKDATA_BUFFER_SETTER.invoke(packet, outputBytes);\r\n            CHUNKPACKET_CHUNKDATA_SETTER.invoke(duplicateCorePacket, packet);\r\n            return duplicateCorePacket;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/ActionBarEventPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerReceivesActionbarScriptEvent;\nimport com.denizenscript.denizen.nms.v1_20.Handler;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport net.minecraft.network.chat.Component;\nimport net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket;\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftChatMessage;\n\npublic class ActionBarEventPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetActionBarTextPacket.class, ActionBarEventPacketHandlers::processActionbarPacket);\n    }\n\n    public static ClientboundSetActionBarTextPacket processActionbarPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetActionBarTextPacket actionbarPacket) {\n        PlayerReceivesActionbarScriptEvent event = PlayerReceivesActionbarScriptEvent.instance;\n        if (!event.loaded) {\n            return actionbarPacket;\n        }\n        event.reset();\n        Component actionbarText = actionbarPacket.text();\n        event.message = new ElementTag(FormattedTextHelper.stringify(Handler.componentToSpigot(actionbarText)), true);\n        event.rawJson = new ElementTag(CraftChatMessage.toJSON(actionbarText), true);\n        event.system = new ElementTag(false);\n        event.player = PlayerTag.mirrorBukkitPlayer(networkManager.player.getBukkitEntity());\n        event = (PlayerReceivesActionbarScriptEvent) event.triggerNow();\n        if (event.cancelled) {\n            return null;\n        }\n        if (event.modified) {\n            return new ClientboundSetActionBarTextPacket(Handler.componentToNMS(event.altMessageDetermination));\n        }\n        return actionbarPacket;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/AttachPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.Entity;\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity;\nimport org.bukkit.util.Vector;\n\nimport java.lang.reflect.Field;\n\npublic class AttachPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundRotateHeadPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityMotionPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundTeleportEntityPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundRemoveEntitiesPacket.class, AttachPacketHandlers::processAttachToForPacket);\n    }\n\n    public static Field POS_X_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_xa, short.class);\n    public static Field POS_Y_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_ya, short.class);\n    public static Field POS_Z_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_za, short.class);\n    public static Field YAW_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_yRot, byte.class);\n    public static Field PITCH_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_xRot, byte.class);\n    public static Field ENTITY_ID_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_id, int.class);\n    public static Field POS_X_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_x, double.class);\n    public static Field POS_Y_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_y, double.class);\n    public static Field POS_Z_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_z, double.class);\n    public static Field YAW_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_yRot, byte.class);\n    public static Field PITCH_PACKTELENT = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_xRot, byte.class);\n    public static Field ENTITY_ID_PACKVELENT = ReflectionHelper.getFields(ClientboundSetEntityMotionPacket.class).get(ReflectionMappingsInfo.ClientboundSetEntityMotionPacket_id, int.class);\n\n    public static Vector VECTOR_ZERO = new Vector(0, 0, 0);\n\n    public static void tryProcessMovePacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket packet, Entity e) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    ClientboundMoveEntityPacket pNew;\n                    int newId = att.attached.getBukkitEntity().getEntityId();\n                    if (packet instanceof ClientboundMoveEntityPacket.Pos) {\n                        pNew = new ClientboundMoveEntityPacket.Pos(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.isOnGround());\n                    }\n                    else if (packet instanceof ClientboundMoveEntityPacket.Rot) {\n                        pNew = new ClientboundMoveEntityPacket.Rot(newId, packet.getyRot(), packet.getxRot(), packet.isOnGround());\n                    }\n                    else if (packet instanceof ClientboundMoveEntityPacket.PosRot) {\n                        pNew = new ClientboundMoveEntityPacket.PosRot(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.getyRot(), packet.getxRot(), packet.isOnGround());\n                    }\n                    else {\n                        if (CoreConfiguration.debugVerbose) {\n                            Debug.echoError(\"Impossible move-entity packet class: \" + packet.getClass().getCanonicalName());\n                        }\n                        return;\n                    }\n                    if (att.positionalOffset != null) {\n                        boolean isRotate = packet instanceof ClientboundMoveEntityPacket.PosRot || packet instanceof ClientboundMoveEntityPacket.Rot;\n                        byte yaw, pitch;\n                        if (att.noRotate) {\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                            yaw = EntityAttachmentHelper.compressAngle(attachedEntity.getYRot());\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\n                        }\n                        else if (isRotate) {\n                            yaw = packet.getyRot();\n                            pitch = packet.getxRot();\n                        }\n                        else {\n                            yaw = EntityAttachmentHelper.compressAngle(e.getYRot());\n                            pitch = EntityAttachmentHelper.compressAngle(e.getXRot());\n                        }\n                        if (att.noPitch) {\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\n                        }\n                        byte newYaw = yaw;\n                        if (isRotate) {\n                            newYaw = EntityAttachmentHelper.adaptedCompressedAngle(newYaw, att.positionalOffset.getYaw());\n                            pitch = EntityAttachmentHelper.adaptedCompressedAngle(pitch, att.positionalOffset.getPitch());\n                        }\n                        Vector goalPosition = att.fixedForOffset(new Vector(e.getX(), e.getY(), e.getZ()), e.getYRot(), e.getXRot());\n                        Vector oldPos = att.visiblePositions.get(networkManager.player.getUUID());\n                        boolean forceTele = false;\n                        if (oldPos == null) {\n                            oldPos = att.attached.getLocation().toVector();\n                            forceTele = true;\n                        }\n                        Vector moveNeeded = goalPosition.clone().subtract(oldPos);\n                        att.visiblePositions.put(networkManager.player.getUUID(), goalPosition.clone());\n                        int offX = (int) (moveNeeded.getX() * (32 * 128));\n                        int offY = (int) (moveNeeded.getY() * (32 * 128));\n                        int offZ = (int) (moveNeeded.getZ() * (32 * 128));\n                        if ((isRotate && att.offsetRelative) || forceTele || offX < Short.MIN_VALUE || offX > Short.MAX_VALUE\n                                || offY < Short.MIN_VALUE || offY > Short.MAX_VALUE\n                                || offZ < Short.MIN_VALUE || offZ > Short.MAX_VALUE) {\n                            ClientboundTeleportEntityPacket newTeleportPacket = new ClientboundTeleportEntityPacket(e);\n                            ENTITY_ID_PACKTELENT.setInt(newTeleportPacket, att.attached.getBukkitEntity().getEntityId());\n                            POS_X_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getX());\n                            POS_Y_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getY());\n                            POS_Z_PACKTELENT.setDouble(newTeleportPacket, goalPosition.getZ());\n                            YAW_PACKTELENT.setByte(newTeleportPacket, newYaw);\n                            PITCH_PACKTELENT.setByte(newTeleportPacket, pitch);\n                            if (NMSHandler.debugPackets) {\n                                DenizenNetworkManagerImpl.doPacketOutput(\"Attach Move-Tele Packet: \" + newTeleportPacket.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\n                            }\n                            networkManager.oldManager.send(newTeleportPacket);\n                        }\n                        else {\n                            POS_X_PACKENT.setShort(pNew, (short) Mth.clamp(offX, Short.MIN_VALUE, Short.MAX_VALUE));\n                            POS_Y_PACKENT.setShort(pNew, (short) Mth.clamp(offY, Short.MIN_VALUE, Short.MAX_VALUE));\n                            POS_Z_PACKENT.setShort(pNew, (short) Mth.clamp(offZ, Short.MIN_VALUE, Short.MAX_VALUE));\n                            if (isRotate) {\n                                YAW_PACKENT.setByte(pNew, yaw);\n                                PITCH_PACKENT.setByte(pNew, pitch);\n                            }\n                            if (NMSHandler.debugPackets) {\n                                DenizenNetworkManagerImpl.doPacketOutput(\"Attach Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\n                            }\n                            networkManager.oldManager.send(pNew);\n                        }\n                    }\n                    else {\n                        if (NMSHandler.debugPackets) {\n                            DenizenNetworkManagerImpl.doPacketOutput(\"Attach Replica-Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName());\n                        }\n                        networkManager.oldManager.send(pNew);\n                    }\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessMovePacketForAttach(networkManager, packet, ent);\n            }\n        }\n    }\n\n    public static void tryProcessRotateHeadPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundRotateHeadPacket packet, Entity e) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    byte yaw = packet.getYHeadRot();\n                    Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                    if (att.positionalOffset != null) {\n                        if (att.noRotate) {\n                            yaw = EntityAttachmentHelper.compressAngle(attachedEntity.getYRot());\n                        }\n                        yaw = EntityAttachmentHelper.adaptedCompressedAngle(yaw, att.positionalOffset.getYaw());\n                    }\n                    ClientboundRotateHeadPacket pNew = new ClientboundRotateHeadPacket(attachedEntity, yaw);\n                    if (NMSHandler.debugPackets) {\n                        DenizenNetworkManagerImpl.doPacketOutput(\"Head Rotation Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName());\n                    }\n                    networkManager.oldManager.send(pNew);\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessRotateHeadPacketForAttach(networkManager, packet, ent);\n            }\n        }\n    }\n\n    public static void tryProcessVelocityPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityMotionPacket packet, Entity e) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    ClientboundSetEntityMotionPacket pNew = DenizenNetworkManagerImpl.copyPacket(packet, ClientboundSetEntityMotionPacket.STREAM_CODEC);\n                    ENTITY_ID_PACKVELENT.setInt(pNew, att.attached.getBukkitEntity().getEntityId());\n                    if (NMSHandler.debugPackets) {\n                        DenizenNetworkManagerImpl.doPacketOutput(\"Attach Velocity Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName());\n                    }\n                    networkManager.oldManager.send(pNew);\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessVelocityPacketForAttach(networkManager, packet, ent);\n            }\n        }\n    }\n\n    public static void tryProcessTeleportPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundTeleportEntityPacket packet, Entity e, Vector relative) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    ClientboundTeleportEntityPacket pNew = DenizenNetworkManagerImpl.copyPacket(packet, ClientboundTeleportEntityPacket.STREAM_CODEC);\n                    ENTITY_ID_PACKTELENT.setInt(pNew, att.attached.getBukkitEntity().getEntityId());\n                    Vector resultPos = new Vector(POS_X_PACKTELENT.getDouble(pNew), POS_Y_PACKTELENT.getDouble(pNew), POS_Z_PACKTELENT.getDouble(pNew)).add(relative);\n                    if (att.positionalOffset != null) {\n                        resultPos = att.fixedForOffset(resultPos, e.getYRot(), e.getXRot());\n                        byte yaw, pitch;\n                        if (att.noRotate) {\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                            yaw = EntityAttachmentHelper.compressAngle(attachedEntity.getYRot());\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\n                        }\n                        else {\n                            yaw = packet.getyRot();\n                            pitch = packet.getxRot();\n                        }\n                        if (att.noPitch) {\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                            pitch = EntityAttachmentHelper.compressAngle(attachedEntity.getXRot());\n                        }\n                        byte newYaw = EntityAttachmentHelper.adaptedCompressedAngle(yaw, att.positionalOffset.getYaw());\n                        pitch = EntityAttachmentHelper.adaptedCompressedAngle(pitch, att.positionalOffset.getPitch());\n                        POS_X_PACKTELENT.setDouble(pNew, resultPos.getX());\n                        POS_Y_PACKTELENT.setDouble(pNew, resultPos.getY());\n                        POS_Z_PACKTELENT.setDouble(pNew, resultPos.getZ());\n                        YAW_PACKTELENT.setByte(pNew, newYaw);\n                        PITCH_PACKTELENT.setByte(pNew, pitch);\n                        if (NMSHandler.debugPackets) {\n                            DenizenNetworkManagerImpl.doPacketOutput(\"Attach Teleport Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID()\n                                    + \" sent to \" + networkManager.player.getScoreboardName() + \" with raw yaw \" + yaw + \" adapted to \" + newYaw);\n                        }\n                    }\n                    att.visiblePositions.put(networkManager.player.getUUID(), resultPos.clone());\n                    networkManager.oldManager.send(pNew);\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessTeleportPacketForAttach(networkManager, packet, ent, new Vector(ent.getX() - e.getX(), ent.getY() - e.getY(), ent.getZ() - e.getZ()));\n            }\n        }\n    }\n\n    public static Packet<ClientGamePacketListener> processAttachToForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (EntityAttachmentHelper.toEntityToData.isEmpty()) {\n            return packet;\n        }\n        try {\n            if (packet instanceof ClientboundMoveEntityPacket moveEntityPacket) {\n                Entity e = moveEntityPacket.getEntity(networkManager.player.level());\n                if (e == null) {\n                    return packet;\n                }\n                if (!e.isPassenger()) {\n                    tryProcessMovePacketForAttach(networkManager, moveEntityPacket, e);\n                }\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundRotateHeadPacket rotateHeadPacket) {\n                Entity e = rotateHeadPacket.getEntity(networkManager.player.level());\n                if (e == null) {\n                    return packet;\n                }\n                tryProcessRotateHeadPacketForAttach(networkManager, rotateHeadPacket, e);\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundSetEntityMotionPacket setEntityMotionPacket) {\n                int ider = setEntityMotionPacket.getId();\n                Entity e = networkManager.player.level().getEntity(ider);\n                if (e == null) {\n                    return packet;\n                }\n                tryProcessVelocityPacketForAttach(networkManager, setEntityMotionPacket, e);\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundTeleportEntityPacket teleportEntityPacket) {\n                int ider = teleportEntityPacket.getId();\n                Entity e = networkManager.player.level().getEntity(ider);\n                if (e == null) {\n                    return packet;\n                }\n                tryProcessTeleportPacketForAttach(networkManager, teleportEntityPacket, e, VECTOR_ZERO);\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundRemoveEntitiesPacket removeEntitiesPacket) {\n                for (int id : removeEntitiesPacket.getEntityIds()) {\n                    Entity e = networkManager.player.level().getEntity(id);\n                    if (e != null) {\n                        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n                        if (attList != null) {\n                            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                                if (attMap.attached.isValid() && att != null) {\n                                    att.visiblePositions.remove(networkManager.player.getUUID());\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        catch (Exception ex) {\n            Debug.echoError(ex);\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/BlockLightPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\nimport com.denizenscript.denizen.nms.v1_20.impl.blocks.BlockLightImpl;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;\nimport net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;\n\npublic class BlockLightPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLightUpdatePacket.class, BlockLightPacketHandlers::processLightUpdatePacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, BlockLightPacketHandlers::processBlockUpdatePacket);\n    }\n\n    public static void processLightUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundLightUpdatePacket lightUpdatePacket) {\n        if (!BlockLight.lightsByChunk.isEmpty()) {\n            BlockLightImpl.checkIfLightsBrokenByPacket(lightUpdatePacket, networkManager.player.level());\n        }\n    }\n\n    public static void processBlockUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundBlockUpdatePacket blockUpdatePacket) {\n        if (!BlockLight.lightsByChunk.isEmpty()) {\n            BlockLightImpl.checkIfLightsBrokenByPacket(blockUpdatePacket, networkManager.player.level());\n        }\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/DenizenPacketHandlerPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerReceivesMessageScriptEvent;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.packets.PacketOutChatImpl;\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;\nimport net.minecraft.network.protocol.game.ClientboundSystemChatPacket;\n\npublic class DenizenPacketHandlerPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSystemChatPacket.class, DenizenPacketHandlerPacketHandlers::processPacketHandlerForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerChatPacket.class, DenizenPacketHandlerPacketHandlers::processPacketHandlerForPacket);\n    }\n\n    public static Packet<ClientGamePacketListener> processPacketHandlerForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (DenizenPacketHandler.instance.shouldInterceptChatPacket()) {\n            PacketOutChatImpl packetHelper = null;\n            boolean isActionbar = false;\n            if (packet instanceof ClientboundSystemChatPacket chatPacket) {\n                isActionbar = chatPacket.overlay();\n                packetHelper = new PacketOutChatImpl(chatPacket);\n                if (packetHelper.rawJson == null) { // Makes no sense but this can be null in weird edge cases\n                    return packet;\n                }\n            }\n            else if (packet instanceof ClientboundPlayerChatPacket playerChatPacket) {\n                packetHelper = new PacketOutChatImpl(playerChatPacket);\n            }\n            if (packetHelper != null) {\n                PlayerReceivesMessageScriptEvent result = DenizenPacketHandler.instance.sendPacket(networkManager.player.getBukkitEntity(), packetHelper);\n                if (result != null) {\n                    if (result.cancelled) {\n                        return null;\n                    }\n                    if (result.modified) {\n                        return new ClientboundSystemChatPacket(result.altMessageDetermination, isActionbar);\n                    }\n                }\n            }\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/DisguisePacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\nimport com.denizenscript.denizen.nms.v1_20.helpers.PacketHelperImpl;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.scripts.commands.player.DisguiseCommand;\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.network.syncher.SynchedEntityData;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.level.Level;\nimport org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity;\nimport org.bukkit.entity.EntityType;\nimport org.bukkit.entity.LivingEntity;\n\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.function.BiFunction;\nimport java.util.function.ToIntFunction;\n\npublic class DisguisePacketHandlers {\n\n    public static void registerHandlers() {\n        registerPacketHandler(ClientboundSetEntityDataPacket.class, ClientboundSetEntityDataPacket::id, DisguisePacketHandlers::processEntityDataPacket);\n        registerPacketHandler(ClientboundUpdateAttributesPacket.class, ClientboundUpdateAttributesPacket::getEntityId, DisguisePacketHandlers::processAttributesPacket);\n        registerPacketHandler(ClientboundAddEntityPacket.class, ClientboundAddEntityPacket::getId, DisguisePacketHandlers::sendDisguiseForPacket);\n        registerPacketHandler(ClientboundTeleportEntityPacket.class, ClientboundTeleportEntityPacket::getId, DisguisePacketHandlers::processTeleportPacket);\n        registerPacketHandler(ClientboundMoveEntityPacket.Rot.class, ClientboundMoveEntityPacket::getEntity, DisguisePacketHandlers::processMoveEntityRotPacket);\n        registerPacketHandler(ClientboundMoveEntityPacket.PosRot.class, ClientboundMoveEntityPacket::getEntity, DisguisePacketHandlers::processMoveEntityPosRotPacket);\n    }\n\n    public static final Field TELEPORT_PACKET_YAW = ReflectionHelper.getFields(ClientboundTeleportEntityPacket.class).get(ReflectionMappingsInfo.ClientboundTeleportEntityPacket_yRot, byte.class);\n\n    private static boolean antiDuplicate = false;\n\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetType, ToIntFunction<T> idGetter, DisguisePacketHandler<T> handler) {\n        registerPacketHandler(packetType, (packet, level) -> level.getEntity(idGetter.applyAsInt(packet)), handler);\n    }\n\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetType, BiFunction<T, Level, Entity> entityGetter, DisguisePacketHandler<T> handler) {\n        DenizenNetworkManagerImpl.registerPacketHandler(packetType, (networkManager, packet) -> {\n            if (DisguiseCommand.disguises.isEmpty() || antiDuplicate) {\n                return packet;\n            }\n            Entity entity = entityGetter.apply(packet, networkManager.player.level());\n            if (entity == null) {\n                return packet;\n            }\n            Map<UUID, DisguiseCommand.TrackedDisguise> playerMap = DisguiseCommand.disguises.get(entity.getUUID());\n            if (playerMap == null) {\n                return packet;\n            }\n            DisguiseCommand.TrackedDisguise disguise = playerMap.get(networkManager.player.getUUID());\n            if (disguise == null) {\n                disguise = playerMap.get(null);\n            }\n            if (disguise == null || !disguise.isActive) {\n                return packet;\n            }\n            if (NMSHandler.debugPackets) {\n                DenizenNetworkManagerImpl.doPacketOutput(\"DISGUISED packet \" + packet.getClass().getName() + \" for entity \" + entity.getId() + \" to player \" + networkManager.player.getScoreboardName());\n            }\n            try {\n                return handler.handle(networkManager, packet, disguise);\n            }\n            catch (Exception e) {\n                antiDuplicate = false;\n                throw e; // \"pass it\" to the generic exception handling\n            }\n        });\n    }\n\n    @FunctionalInterface\n    public interface DisguisePacketHandler<T extends Packet<ClientGamePacketListener>> {\n\n        T handle(DenizenNetworkManagerImpl networkManager, T packet, DisguiseCommand.TrackedDisguise disguise) throws Exception;\n    }\n\n    public static ClientboundSetEntityDataPacket processEntityDataPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityDataPacket entityDataPacket, DisguiseCommand.TrackedDisguise disguise) {\n        if (entityDataPacket.id() == networkManager.player.getId()) {\n            if (!disguise.shouldFake) {\n                return entityDataPacket;\n            }\n            for (SynchedEntityData.DataValue<?> dataValue : entityDataPacket.packedItems()) {\n                if (dataValue.id() == 0) { // Entity flags\n                    List<SynchedEntityData.DataValue<?>> newData = new ArrayList<>(entityDataPacket.packedItems());\n                    newData.remove(dataValue);\n                    byte flags = (byte) dataValue.value();\n                    flags |= 0x20; // Invisible flag\n                    newData.add(PacketHelperImpl.createEntityData(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags));\n                    return new ClientboundSetEntityDataPacket(entityDataPacket.id(), newData);\n                }\n            }\n        }\n        else {\n            List<SynchedEntityData.DataValue<?>> data = ((CraftEntity) disguise.toOthers.entity.entity).getHandle().getEntityData().getNonDefaultValues();\n            return data != null ? new ClientboundSetEntityDataPacket(entityDataPacket.id(), data) : null;\n        }\n        return entityDataPacket;\n    }\n\n    public static ClientboundUpdateAttributesPacket processAttributesPacket(DenizenNetworkManagerImpl networkManager, ClientboundUpdateAttributesPacket attributesPacket, DisguiseCommand.TrackedDisguise disguise) {\n        FakeEntity fake = attributesPacket.getEntityId() == networkManager.player.getId() ? disguise.fakeToSelf : disguise.toOthers;\n        return fake == null || fake.entity.entity instanceof LivingEntity ? attributesPacket : null; // Non-living entities don't have attributes\n    }\n\n    public static ClientboundTeleportEntityPacket processTeleportPacket(DenizenNetworkManagerImpl networkManager, ClientboundTeleportEntityPacket teleportEntityPacket, DisguiseCommand.TrackedDisguise disguise) throws IllegalAccessException {\n        if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\n            ClientboundTeleportEntityPacket pNew = DenizenNetworkManagerImpl.copyPacket(teleportEntityPacket, ClientboundTeleportEntityPacket.STREAM_CODEC);\n            TELEPORT_PACKET_YAW.setByte(pNew, EntityAttachmentHelper.adaptedCompressedAngle(teleportEntityPacket.getyRot(), 180));\n            return pNew;\n        }\n        return sendDisguiseForPacket(networkManager, teleportEntityPacket, disguise);\n    }\n\n\n    public static ClientboundMoveEntityPacket.Rot processMoveEntityRotPacket(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket.Rot rotPacket, DisguiseCommand.TrackedDisguise disguise) {\n        if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\n            return new ClientboundMoveEntityPacket.Rot(disguise.entity.getBukkitEntity().getEntityId(), EntityAttachmentHelper.adaptedCompressedAngle(rotPacket.getyRot(), 180), rotPacket.getxRot(), rotPacket.isOnGround());\n        }\n        return sendDisguiseForPacket(networkManager, rotPacket, disguise);\n    }\n\n\n    public static ClientboundMoveEntityPacket.PosRot processMoveEntityPosRotPacket(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket.PosRot posRotPacket, DisguiseCommand.TrackedDisguise disguise) {\n        if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\n            return new ClientboundMoveEntityPacket.PosRot(disguise.entity.getBukkitEntity().getEntityId(), posRotPacket.getXa(), posRotPacket.getYa(), posRotPacket.getZa(), EntityAttachmentHelper.adaptedCompressedAngle(posRotPacket.getyRot(), 180), posRotPacket.getxRot(), posRotPacket.isOnGround());\n        }\n        return sendDisguiseForPacket(networkManager, posRotPacket, disguise);\n    }\n\n    public static <T extends Packet<ClientGamePacketListener>> T sendDisguiseForPacket(DenizenNetworkManagerImpl networkManager, T packet, DisguiseCommand.TrackedDisguise disguise) {\n        antiDuplicate = true;\n        disguise.sendTo(List.of(new PlayerTag(networkManager.player.getUUID())));\n        antiDuplicate = false;\n        return null;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/EntityMetadataPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v1_20.Handler;\nimport com.denizenscript.denizen.nms.v1_20.helpers.PacketHelperImpl;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.scripts.commands.entity.GlowCommand;\nimport com.denizenscript.denizen.scripts.commands.entity.InvisibleCommand;\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\nimport com.denizenscript.denizen.scripts.commands.entity.SneakCommand;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.md_5.bungee.api.ChatColor;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;\nimport net.minecraft.network.syncher.SynchedEntityData;\nimport net.minecraft.world.entity.Entity;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\n\npublic class EntityMetadataPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityDataPacket.class, EntityMetadataPacketHandlers::processMetadataChangesForPacket);\n    }\n\n    public static ClientboundSetEntityDataPacket getModifiedMetadataFor(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityDataPacket metadataPacket) {\n        if (!RenameCommand.hasAnyDynamicRenames() && SneakCommand.forceSetSneak.isEmpty() && InvisibleCommand.helper.noOverrides() && GlowCommand.helper.noOverrides()) {\n            return null;\n        }\n        try {\n            Entity entity = networkManager.player.level().getEntity(metadataPacket.id());\n            if (entity == null) {\n                return null; // If it doesn't exist on-server, it's definitely not relevant, so move on\n            }\n            String nameToApply = RenameCommand.getCustomNameFor(entity.getUUID(), networkManager.player.getBukkitEntity(), false);\n            Boolean forceSneak = SneakCommand.shouldSneak(entity.getUUID(), networkManager.player.getUUID());\n            Boolean isInvisible = InvisibleCommand.helper.getState(entity.getBukkitEntity(), networkManager.player.getUUID(), true);\n            Boolean isGlowing = GlowCommand.helper.getState(entity.getBukkitEntity(), networkManager.player.getUUID(), true);\n            boolean shouldModifyFlags = isInvisible != null || forceSneak != null || isGlowing != null;\n            if (nameToApply == null && !shouldModifyFlags) {\n                return null;\n            }\n            List<SynchedEntityData.DataValue<?>> data = new ArrayList<>(metadataPacket.packedItems().size());\n            Byte currentFlags = null;\n            for (SynchedEntityData.DataValue<?> dataValue : metadataPacket.packedItems()) {\n                if (dataValue.id() == 0 && shouldModifyFlags) { // 0: Entity Flags\n                    currentFlags = (Byte) dataValue.value();\n                }\n                else if (nameToApply == null || (dataValue.id() != 2 && dataValue.id() != 3)) { // 2 and 3: Custom name and custom name visible\n                    data.add(dataValue);\n                }\n            }\n            if (shouldModifyFlags) {\n                byte flags = currentFlags == null ? entity.getEntityData().get(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS) : currentFlags;\n                flags = applyEntityDataFlag(flags, forceSneak, 0x02);\n                flags = applyEntityDataFlag(flags, isInvisible, 0x20);\n                flags = applyEntityDataFlag(flags, isGlowing, 0x40);\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags));\n            }\n            if (nameToApply != null) {\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(nameToApply, ChatColor.WHITE)))));\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true));\n            }\n            return new ClientboundSetEntityDataPacket(metadataPacket.id(), data);\n        }\n        catch (Throwable ex) {\n            Debug.echoError(ex);\n            return null;\n        }\n    }\n\n    public static byte applyEntityDataFlag(byte currentFlags, Boolean value, int flag) {\n        if (value == null) {\n            return currentFlags;\n        }\n        return (byte) (value ? currentFlags | flag : currentFlags & ~flag);\n    }\n\n    public static Packet<ClientGamePacketListener> processMetadataChangesForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!(packet instanceof ClientboundSetEntityDataPacket entityDataPacket)) {\n            return packet;\n        }\n        ClientboundSetEntityDataPacket altPacket = getModifiedMetadataFor(networkManager, entityDataPacket);\n        if (altPacket == null) {\n            return packet;\n        }\n        return altPacket;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/FakeBlocksPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v1_20.ReflectionMappingsInfo;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.FakeBlockHelper;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.SectionPos;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.world.level.block.state.BlockState;\n\nimport java.lang.reflect.Field;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class FakeBlocksPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelChunkWithLightPacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSectionBlocksUpdatePacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket);\n    }\n\n    public static Field SECTIONPOS_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_sectionPos, SectionPos.class);\n    public static Field OFFSETARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_positions, short[].class);\n    public static Field BLOCKARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_states, BlockState[].class);\n\n    public static Packet<ClientGamePacketListener> processShowFakeForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (FakeBlock.blocks.isEmpty()) {\n            return packet;\n        }\n        try {\n            if (packet instanceof ClientboundLevelChunkWithLightPacket) {\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID());\n                if (map == null) {\n                    return packet;\n                }\n                int chunkX = ((ClientboundLevelChunkWithLightPacket) packet).getX();\n                int chunkZ = ((ClientboundLevelChunkWithLightPacket) packet).getZ();\n                ChunkCoordinate chunkCoord = new ChunkCoordinate(chunkX, chunkZ, networkManager.player.level().getWorld().getName());\n                List<FakeBlock> blocks = FakeBlock.getFakeBlocksFor(networkManager.player.getUUID(), chunkCoord);\n                if (blocks == null || blocks.isEmpty()) {\n                    return packet;\n                }\n                ClientboundLevelChunkWithLightPacket newPacket = FakeBlockHelper.handleMapChunkPacket(networkManager.player.getBukkitEntity().getWorld(), (ClientboundLevelChunkWithLightPacket) packet, chunkX, chunkZ, blocks);\n                return newPacket;\n            }\n            else if (packet instanceof ClientboundSectionBlocksUpdatePacket sectionBlocksUpdatePacket) {\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID());\n                if (map == null) {\n                    return sectionBlocksUpdatePacket;\n                }\n                SectionPos coord = (SectionPos) SECTIONPOS_MULTIBLOCKCHANGE.get(sectionBlocksUpdatePacket);\n                ChunkCoordinate coordinateDenizen = new ChunkCoordinate(coord.getX(), coord.getZ(), networkManager.player.level().getWorld().getName());\n                if (!map.byChunk.containsKey(coordinateDenizen)) {\n                    return sectionBlocksUpdatePacket;\n                }\n                ClientboundSectionBlocksUpdatePacket newPacket = DenizenNetworkManagerImpl.copyPacket(sectionBlocksUpdatePacket, ClientboundSectionBlocksUpdatePacket.STREAM_CODEC);\n                LocationTag location = new LocationTag(networkManager.player.level().getWorld(), 0, 0, 0);\n                short[] originalOffsetArray = (short[])OFFSETARRAY_MULTIBLOCKCHANGE.get(newPacket);\n                BlockState[] originalDataArray = (BlockState[])BLOCKARRAY_MULTIBLOCKCHANGE.get(newPacket);\n                short[] offsetArray = Arrays.copyOf(originalOffsetArray, originalOffsetArray.length);\n                BlockState[] dataArray = Arrays.copyOf(originalDataArray, originalDataArray.length);\n                OFFSETARRAY_MULTIBLOCKCHANGE.set(newPacket, offsetArray);\n                BLOCKARRAY_MULTIBLOCKCHANGE.set(newPacket, dataArray);\n                for (int i = 0; i < offsetArray.length; i++) {\n                    short offset = offsetArray[i];\n                    BlockPos pos = coord.relativeToBlockPos(offset);\n                    location.setX(pos.getX());\n                    location.setY(pos.getY());\n                    location.setZ(pos.getZ());\n                    FakeBlock block = map.byLocation.get(location);\n                    if (block != null) {\n                        dataArray[i] = FakeBlockHelper.getNMSState(block);\n                    }\n                }\n                return newPacket;\n            }\n            else if (packet instanceof ClientboundBlockUpdatePacket) {\n                BlockPos pos = ((ClientboundBlockUpdatePacket) packet).getPos();\n                LocationTag loc = new LocationTag(networkManager.player.level().getWorld(), pos.getX(), pos.getY(), pos.getZ());\n                FakeBlock block = FakeBlock.getFakeBlockFor(networkManager.player.getUUID(), loc);\n                if (block != null) {\n                    ClientboundBlockUpdatePacket newPacket = new ClientboundBlockUpdatePacket(((ClientboundBlockUpdatePacket) packet).getPos(), FakeBlockHelper.getNMSState(block));\n                    return newPacket;\n                }\n            }\n            else if (packet instanceof ClientboundBlockChangedAckPacket) {\n                // TODO: 1.19: Can no longer determine what block this packet is for. Would have to track separately? Possibly from the inbound packet rather than the outbound one.\n                /*\n                ClientboundBlockChangedAckPacket origPack = (ClientboundBlockChangedAckPacket) packet;\n                BlockPos pos = origPack.pos();\n                LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ());\n                FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc);\n                if (block != null) {\n                    ClientboundBlockChangedAckPacket newPacket = new ClientboundBlockChangedAckPacket(origPack.pos(), FakeBlockHelper.getNMSState(block), origPack.action(), false);\n                    oldManager.send(newPacket, genericfuturelistener);\n                    return true;\n                }*/\n            }\n        }\n        catch (Throwable ex) {\n            Debug.echoError(ex);\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\nimport com.mojang.datafixers.util.Pair;\nimport net.minecraft.core.NonNullList;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.EquipmentSlot;\nimport net.minecraft.world.entity.LivingEntity;\nimport net.minecraft.world.item.ItemStack;\nimport org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class FakeEquipmentPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEquipmentPacket.class, FakeEquipmentPacketHandlers::processSetEquipmentPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundEntityEventPacket.class, FakeEquipmentPacketHandlers::processEntityEventPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundContainerSetContentPacket.class, FakeEquipmentPacketHandlers::processContainerSetContentPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundContainerSetSlotPacket.class, FakeEquipmentPacketHandlers::processContainerSetSlotPacket);\n    }\n\n    public static ClientboundSetEquipmentPacket processSetEquipmentPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetEquipmentPacket setEquipmentPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return setEquipmentPacket;\n        }\n        Entity entity = networkManager.player.level().getEntity(setEquipmentPacket.getEntity());\n        if (entity == null) {\n            return setEquipmentPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(entity.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null) {\n            return setEquipmentPacket;\n        }\n        List<Pair<EquipmentSlot, ItemStack>> equipment = new ArrayList<>(setEquipmentPacket.getSlots());\n        for (int i = 0; i < equipment.size(); i++) {\n            Pair<EquipmentSlot, ItemStack> pair = equipment.get(i);\n            ItemStack use = switch (pair.getFirst()) {\n                case MAINHAND -> override.hand == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.hand.getItemStack());\n                case OFFHAND -> override.offhand == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.offhand.getItemStack());\n                case CHEST -> override.chest == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.chest.getItemStack());\n                case HEAD -> override.head == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.head.getItemStack());\n                case LEGS -> override.legs == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.legs.getItemStack());\n                case FEET -> override.boots == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.boots.getItemStack());\n                case BODY -> pair.getSecond(); // TODO: 1.20.6: is this actually used here? do we want to allow overriding it?\n            };\n            equipment.set(i, new Pair<>(pair.getFirst(), use));\n        }\n        return new ClientboundSetEquipmentPacket(setEquipmentPacket.getEntity(), equipment);\n    }\n\n    public static Packet<ClientGamePacketListener> processEntityEventPacket(DenizenNetworkManagerImpl networkManager, ClientboundEntityEventPacket entityEventPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return entityEventPacket;\n        }\n        if (entityEventPacket.getEventId() != 55) {\n            return entityEventPacket;\n        }\n        if (!(entityEventPacket.getEntity(networkManager.player.level()) instanceof LivingEntity livingEntity)) {\n            return entityEventPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(livingEntity.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null || (override.hand == null && override.offhand == null)) {\n            return entityEventPacket;\n        }\n        ItemStack hand = override.hand != null ? CraftItemStack.asNMSCopy(override.hand.getItemStack()) : livingEntity.getMainHandItem();\n        ItemStack offhand = override.offhand != null ? CraftItemStack.asNMSCopy(override.offhand.getItemStack()) : livingEntity.getOffhandItem();\n        return new ClientboundSetEquipmentPacket(livingEntity.getId(), List.of(new Pair<>(EquipmentSlot.MAINHAND, hand), new Pair<>(EquipmentSlot.OFFHAND, offhand)));\n    }\n\n    public static ClientboundContainerSetContentPacket processContainerSetContentPacket(DenizenNetworkManagerImpl networkManager, ClientboundContainerSetContentPacket setContentPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return setContentPacket;\n        }\n        if (setContentPacket.getContainerId() != 0) {\n            return setContentPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(networkManager.player.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null) {\n            return setContentPacket;\n        }\n        NonNullList<ItemStack> items = (NonNullList<ItemStack>) setContentPacket.getItems();\n        if (override.head != null) {\n            items.set(5, CraftItemStack.asNMSCopy(override.head.getItemStack()));\n        }\n        if (override.chest != null) {\n            items.set(6, CraftItemStack.asNMSCopy(override.chest.getItemStack()));\n        }\n        if (override.legs != null) {\n            items.set(7, CraftItemStack.asNMSCopy(override.legs.getItemStack()));\n        }\n        if (override.boots != null) {\n            items.set(8, CraftItemStack.asNMSCopy(override.boots.getItemStack()));\n        }\n        if (override.offhand != null) {\n            items.set(45, CraftItemStack.asNMSCopy(override.offhand.getItemStack()));\n        }\n        if (override.hand != null) {\n            items.set(getMainHandSlot(networkManager.player), CraftItemStack.asNMSCopy(override.hand.getItemStack()));\n        }\n        return new ClientboundContainerSetContentPacket(setContentPacket.getContainerId(), setContentPacket.getStateId(), items, setContentPacket.getCarriedItem());\n    }\n\n    public static ClientboundContainerSetSlotPacket processContainerSetSlotPacket(DenizenNetworkManagerImpl networkManager, ClientboundContainerSetSlotPacket setSlotPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return setSlotPacket;\n        }\n        if (setSlotPacket.getContainerId() != 0) {\n            return setSlotPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(networkManager.player.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null) {\n            return setSlotPacket;\n        }\n        ItemTag item = switch (setSlotPacket.getSlot()) {\n            case 5 -> override.head;\n            case 6 -> override.chest;\n            case 7 -> override.legs;\n            case 8 -> override.boots;\n            case 45 -> override.offhand;\n            default -> setSlotPacket.getSlot() == getMainHandSlot(networkManager.player) ? override.hand : null;\n        };\n        if (item == null) {\n            return setSlotPacket;\n        }\n        return new ClientboundContainerSetSlotPacket(setSlotPacket.getContainerId(), setSlotPacket.getStateId(), setSlotPacket.getSlot(), CraftItemStack.asNMSCopy(item.getItemStack()));\n    }\n\n    public static int getMainHandSlot(ServerPlayer player) {\n        return player.getInventory().selected + 36;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/FakePlayerPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\n/*\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.v1_20.impl.entities.EntityFakePlayerImpl;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;\nimport org.bukkit.Bukkit;\n\nimport java.util.List;\n*/\n\npublic class FakePlayerPacketHandlers {\n\n    public static void registerHandlers() {\n        // TODO: 1.20.2: Replace this.\n        //DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddPlayerPacket.class, FakePlayerPacketHandlers::processAddPlayerPacket);\n    }\n\n    /*\n    public static void processAddPlayerPacket(DenizenNetworkManagerImpl networkManager, ClientboundAddPlayerPacket addPlayerPacket) {\n        if (networkManager.player.level().getEntity(addPlayerPacket.getEntityId()) instanceof EntityFakePlayerImpl fakePlayer) {\n            networkManager.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, fakePlayer));\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(),\n                    () -> networkManager.send(new ClientboundPlayerInfoRemovePacket(List.of(fakePlayer.getUUID()))), 5);\n        }\n    }*/\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/HiddenEntitiesPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.entity.HideEntitiesHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.world.entity.Entity;\n\npublic class HiddenEntitiesPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddEntityPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddExperienceOrbPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.Rot.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.Pos.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.PosRot.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityDataPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityMotionPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundTeleportEntityPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n    }\n\n    public static boolean isHidden(ServerPlayer player, Entity entity) {\n        return entity != null && HideEntitiesHelper.playerShouldHide(player.getBukkitEntity().getUniqueId(), entity.getBukkitEntity());\n    }\n\n    public static Packet<ClientGamePacketListener> processHiddenEntitiesForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!HideEntitiesHelper.hasAnyHides()) {\n            return packet;\n        }\n        try {\n            int ider = -1;\n            Entity e = null;\n            if (packet instanceof ClientboundAddEntityPacket) {\n                ider = ((ClientboundAddEntityPacket) packet).getId();\n            }\n            else if (packet instanceof ClientboundAddExperienceOrbPacket) {\n                ider = ((ClientboundAddExperienceOrbPacket) packet).getId();\n            }\n            else if (packet instanceof ClientboundMoveEntityPacket) {\n                e = ((ClientboundMoveEntityPacket) packet).getEntity(networkManager.player.level());\n            }\n            else if (packet instanceof ClientboundSetEntityDataPacket) {\n                ider = ((ClientboundSetEntityDataPacket) packet).id();\n            }\n            else if (packet instanceof ClientboundSetEntityMotionPacket) {\n                ider = ((ClientboundSetEntityMotionPacket) packet).getId();\n            }\n            else if (packet instanceof ClientboundTeleportEntityPacket) {\n                ider = ((ClientboundTeleportEntityPacket) packet).getId();\n            }\n            if (e == null && ider != -1) {\n                e = networkManager.player.level().getEntity(ider);\n            }\n            if (e != null) {\n                if (isHidden(networkManager.player, e)) {\n                    return null;\n                }\n            }\n        }\n        catch (Exception ex) {\n            Debug.echoError(ex);\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/HideParticlesPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.packets.HideParticles;\nimport net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;\nimport org.bukkit.Particle;\nimport org.bukkit.craftbukkit.v1_20_R4.CraftParticle;\n\nimport java.util.Set;\n\npublic class HideParticlesPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelParticlesPacket.class, HideParticlesPacketHandlers::processParticlesPacket);\n    }\n\n    public static ClientboundLevelParticlesPacket processParticlesPacket(DenizenNetworkManagerImpl networkManager, ClientboundLevelParticlesPacket particlesPacket) {\n        if (HideParticles.hidden.isEmpty()) {\n            return particlesPacket;\n        }\n        Set<Particle> hidden = HideParticles.hidden.get(networkManager.player.getUUID());\n        if (hidden == null) {\n            return particlesPacket;\n        }\n        Particle bukkitParticle = CraftParticle.minecraftToBukkit(particlesPacket.getParticle().getType());\n        if (hidden.contains(bukkitParticle)) {\n            return null;\n        }\n        return particlesPacket;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/PlayerHearsSoundEventPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerHearsSoundScriptEvent;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundSoundEntityPacket;\nimport net.minecraft.network.protocol.game.ClientboundSoundPacket;\nimport net.minecraft.world.entity.Entity;\nimport org.bukkit.Location;\n\npublic class PlayerHearsSoundEventPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSoundPacket.class, PlayerHearsSoundEventPacketHandlers::processSoundPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSoundEntityPacket.class, PlayerHearsSoundEventPacketHandlers::processSoundPacket);\n    }\n\n    public static Packet<ClientGamePacketListener> processSoundPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!PlayerHearsSoundScriptEvent.instance.eventData.isEnabled) {\n            return packet;\n        }\n        if (packet instanceof ClientboundSoundPacket) {\n            ClientboundSoundPacket spacket = (ClientboundSoundPacket) packet;\n            return PlayerHearsSoundScriptEvent.instance.run(networkManager.player.getBukkitEntity(), spacket.getSound().value().getLocation().getPath(), spacket.getSource().name(),\n                    false, null, new Location(networkManager.player.getBukkitEntity().getWorld(), spacket.getX(), spacket.getY(), spacket.getZ()), spacket.getVolume(), spacket.getPitch()) ? null : packet;\n        }\n        else if (packet instanceof ClientboundSoundEntityPacket) {\n            ClientboundSoundEntityPacket spacket = (ClientboundSoundEntityPacket) packet;\n            Entity entity = networkManager.player.level().getEntity(spacket.getId());\n            if (entity == null) {\n                return packet;\n            }\n            return PlayerHearsSoundScriptEvent.instance.run(networkManager.player.getBukkitEntity(), spacket.getSound().value().getLocation().getPath(), spacket.getSource().name(),\n                    false, entity.getBukkitEntity(), null, spacket.getVolume(), spacket.getPitch()) ? null : packet;\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerReceivesTablistUpdateScriptEvent;\nimport com.denizenscript.denizen.nms.v1_20.Handler;\nimport com.denizenscript.denizen.nms.v1_20.impl.ProfileEditorImpl;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.google.common.base.Joiner;\nimport com.mojang.authlib.GameProfile;\nimport com.mojang.authlib.properties.Property;\nimport net.md_5.bungee.api.ChatColor;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;\nimport net.minecraft.world.level.GameType;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.UUID;\n\npublic class TablistUpdateEventPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoUpdatePacket.class, TablistUpdateEventPacketHandlers::processTablistPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoRemovePacket.class, TablistUpdateEventPacketHandlers::processTablistPacket);\n    }\n\n    public static boolean tablistBreakOnlyOnce = false;\n\n    // TODO: properly rebundle the packet instead of splitting it up\n    public static Packet<ClientGamePacketListener> processTablistPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!PlayerReceivesTablistUpdateScriptEvent.instance.eventData.isEnabled) {\n            return packet;\n        }\n        if (packet instanceof ClientboundPlayerInfoUpdatePacket) {\n            ClientboundPlayerInfoUpdatePacket infoPacket = (ClientboundPlayerInfoUpdatePacket) packet;\n            String mode = \"\";\n            for (ClientboundPlayerInfoUpdatePacket.Action action : infoPacket.actions()) {\n                switch (action) {\n                    case ADD_PLAYER:\n                        mode = \"add\";\n                        break;\n                    case UPDATE_LATENCY:\n                        mode = mode.isEmpty() ? \"update_latency\" : mode + \"|update_latency\";\n                        break;\n                    case UPDATE_GAME_MODE:\n                        mode = mode.isEmpty() ? \"update_gamemode\" : mode + \"|update_gamemode\";\n                        break;\n                    case UPDATE_DISPLAY_NAME:\n                        mode = mode.isEmpty() ? \"update_display\" : mode + \"|update_display\";\n                        break;\n                    case UPDATE_LISTED:\n                        mode = mode.isEmpty() ? \"update_listed\" : mode + \"|update_listed\";\n                        break;\n                    case INITIALIZE_CHAT:\n                        mode = mode.isEmpty() ? \"initialize_chat\" : mode + \"|initialize_chat\";\n                    default:\n                        break;\n                }\n            }\n            if (mode.isEmpty()) {\n                if (!tablistBreakOnlyOnce) {\n                    tablistBreakOnlyOnce = true;\n                    Debug.echoError(\"Tablist packet processing failed: unknown action \" + Joiner.on(\", \").join(infoPacket.actions()));\n                }\n                return packet;\n            }\n            boolean isOverriding = false;\n            for (ClientboundPlayerInfoUpdatePacket.Entry update : infoPacket.entries()) {\n                GameProfile profile = update.profile();\n                String texture = null, signature = null;\n                if (profile.getProperties().containsKey(\"textures\")) {\n                    Property property = profile.getProperties().get(\"textures\").stream().findFirst().get();\n                    texture = property.value();\n                    signature = property.signature();\n                }\n                String modeText = update.gameMode() == null ? null : update.gameMode().name();\n                PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(mode, profile.getId(), update.listed(), profile.getName(),\n                        update.displayName() == null ? null : FormattedTextHelper.stringify(Handler.componentToSpigot(update.displayName())), modeText, texture, signature, update.latency());\n                PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data);\n                if (data.modified) {\n                    if (!isOverriding) {\n                        isOverriding = true;\n                        for (ClientboundPlayerInfoUpdatePacket.Entry priorUpdate : infoPacket.entries()) {\n                            if (priorUpdate == update) {\n                                break;\n                            }\n                            networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(priorUpdate)));\n                        }\n                    }\n                    if (!data.cancelled) {\n                        GameProfile newProfile = new GameProfile(data.id, data.name);\n                        if (data.texture != null) {\n                            newProfile.getProperties().put(\"textures\", new Property(\"textures\", data.texture, data.signature));\n                        }\n                        ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(newProfile.getId(), newProfile, data.isListed, data.latency, data.gamemode == null ? null : GameType.byName(CoreUtilities.toLowerCase(data.gamemode)),\n                                data.display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(data.display, ChatColor.WHITE)), update.chatSession());\n                        networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(entry)));\n                    }\n                }\n                else if (isOverriding) {\n                    networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(update)));\n                }\n            }\n            return isOverriding ? null : packet;\n        }\n        else if (packet instanceof ClientboundPlayerInfoRemovePacket) {\n            ClientboundPlayerInfoRemovePacket removePacket = (ClientboundPlayerInfoRemovePacket) packet;\n            boolean modified = false;\n            List<UUID> altIds = new ArrayList<>(((ClientboundPlayerInfoRemovePacket) packet).profileIds());\n            for (UUID id : ((ClientboundPlayerInfoRemovePacket) packet).profileIds()) {\n                PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(\"remove\", id, false, null, null, null, null, null, 0);\n                PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data);\n                if (data.modified && data.cancelled) {\n                    modified = true;\n                    altIds.remove(id);\n                }\n            }\n            if (modified) {\n                return new ClientboundPlayerInfoRemovePacket(altIds);\n            }\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/packets/PacketInResourcePackStatusImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInResourcePackStatus;\r\nimport net.minecraft.network.protocol.common.ServerboundResourcePackPacket;\r\n\r\npublic class PacketInResourcePackStatusImpl implements PacketInResourcePackStatus {\r\n\r\n    private ServerboundResourcePackPacket internal;\r\n\r\n    public PacketInResourcePackStatusImpl(ServerboundResourcePackPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public String getStatus() {\r\n        return internal.action().name();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/packets/PacketInSteerVehicleImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInSteerVehicle;\r\nimport net.minecraft.network.protocol.game.ServerboundPlayerInputPacket;\r\n\r\npublic class PacketInSteerVehicleImpl implements PacketInSteerVehicle {\r\n\r\n    private ServerboundPlayerInputPacket internal;\r\n\r\n    public PacketInSteerVehicleImpl(ServerboundPlayerInputPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public float getLeftwardInput() {\r\n        return internal.getXxa();\r\n    }\r\n\r\n    @Override\r\n    public float getForwardInput() {\r\n        return internal.getZza();\r\n    }\r\n\r\n    @Override\r\n    public boolean getJumpInput() {\r\n        return internal.isJumping();\r\n    }\r\n\r\n    @Override\r\n    public boolean getDismountInput() {\r\n        return internal.isShiftKeyDown();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_20/src/main/java/com/denizenscript/denizen/nms/v1_20/impl/network/packets/PacketOutChatImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_20.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSystemChatPacket;\r\nimport org.bukkit.craftbukkit.v1_20_R4.util.CraftChatMessage;\r\n\r\npublic class PacketOutChatImpl extends PacketOutChat {\r\n\r\n    public ClientboundPlayerChatPacket playerPacket;\r\n    public ClientboundSystemChatPacket systemPacket;\r\n    public String message;\r\n    public String rawJson;\r\n    public boolean isOverlayActionbar;\r\n\r\n    public PacketOutChatImpl(ClientboundSystemChatPacket internal) {\r\n        systemPacket = internal;\r\n        rawJson = CraftChatMessage.toJSON(internal.content());\r\n        message = FormattedTextHelper.stringify(ComponentSerializer.parse(rawJson));\r\n        isOverlayActionbar = internal.overlay();\r\n    }\r\n\r\n    public PacketOutChatImpl(ClientboundPlayerChatPacket internal) {\r\n        playerPacket = internal;\r\n        rawJson = ComponentSerializer.toString(internal.body().content());\r\n        message = FormattedTextHelper.stringify(ComponentSerializer.parse(rawJson));\r\n    }\r\n\r\n    @Override\r\n    public boolean isSystem() {\r\n        return systemPacket != null;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActionbar() {\r\n        return isOverlayActionbar;\r\n    }\r\n\r\n    @Override\r\n    public String getMessage() {\r\n        return message;\r\n    }\r\n\r\n    @Override\r\n    public String getRawJson() {\r\n        return rawJson;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.denizenscript</groupId>\n    <artifactId>denizen-v1_21</artifactId>\n    <version>1.3.2-SNAPSHOT</version>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen</artifactId>\n            <version>${project.version}</version>\n            <scope>compile</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>1.21.11-R0.2-SNAPSHOT</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot</artifactId>\n            <version>1.21.11-R0.2-SNAPSHOT</version>\n            <classifier>remapped-mojang</classifier>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>net.md-5</groupId>\n                <artifactId>specialsource-maven-plugin</artifactId>\n                <version>2.0.2</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>remap</goal>\n                        </goals>\n                        <id>remap-obf</id>\n                        <configuration>\n                            <srgIn>org.spigotmc:minecraft-server:1.21.11-R0.2-SNAPSHOT:txt:maps-mojang</srgIn>\n                            <reverse>true</reverse>\n                            <remappedDependencies>org.spigotmc:spigot:1.21.11-R0.2-SNAPSHOT:jar:remapped-mojang</remappedDependencies>\n                            <remappedArtifactAttached>true</remappedArtifactAttached>\n                            <remappedClassifierName>remapped-obf</remappedClassifierName>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>remap</goal>\n                        </goals>\n                        <id>remap-spigot</id>\n                        <configuration>\n                            <inputFile>${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar</inputFile>\n                            <srgIn>org.spigotmc:minecraft-server:1.21.11-R0.2-SNAPSHOT:csrg:maps-spigot</srgIn>\n                            <remappedDependencies>org.spigotmc:spigot:1.21.11-R0.2-SNAPSHOT:jar:remapped-obf</remappedDependencies>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.10.1</version>\n                <configuration>\n                    <source>16</source>\n                    <target>16</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/Handler.java",
    "content": "package com.denizenscript.denizen.nms.v1_21;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_21.helpers.*;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.blocks.BlockLightImpl;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.core.QuaternionTag;\r\nimport com.denizenscript.denizencore.scripts.commands.core.ReflectionSetCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.mojang.serialization.DynamicOps;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport net.minecraft.SharedConstants;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.Rotations;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.nbt.ByteArrayTag;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.nbt.NbtOps;\r\nimport net.minecraft.nbt.StringTag;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.resources.Identifier;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.ProblemReporter;\r\nimport net.minecraft.world.BossEvent;\r\nimport net.minecraft.world.Container;\r\nimport net.minecraft.world.Nameable;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.storage.TagValueInput;\r\nimport net.minecraft.world.level.storage.TagValueOutput;\r\nimport net.minecraft.world.level.storage.ValueInput;\r\nimport net.minecraft.world.level.storage.ValueOutput;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.World;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftRegistry;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_21_R7.boss.CraftBossBar;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryCustom;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryView;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_21_R7.legacy.FieldRename;\r\nimport org.bukkit.craftbukkit.v1_21_R7.persistence.CraftPersistentDataContainer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.*;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryView;\r\nimport org.bukkit.persistence.PersistentDataContainer;\r\nimport org.joml.Quaternionf;\r\nimport org.joml.Vector3f;\r\nimport org.spigotmc.AsyncCatcher;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\nimport java.util.function.Consumer;\r\nimport java.util.function.Function;\r\n\r\npublic class Handler extends NMSHandler {\r\n\r\n    public Handler() {\r\n        advancementHelper = new AdvancementHelperImpl();\r\n        animationHelper = new AnimationHelperImpl();\r\n        blockHelper = new BlockHelperImpl();\r\n        chunkHelper = new ChunkHelperImpl();\r\n        customEntityHelper = new CustomEntityHelperImpl();\r\n        entityHelper = new EntityHelperImpl();\r\n        fishingHelper = new FishingHelperImpl();\r\n        itemHelper = new ItemHelperImpl();\r\n        packetHelper = new PacketHelperImpl();\r\n        playerHelper = new PlayerHelperImpl();\r\n        worldHelper = new WorldHelperImpl();\r\n        enchantmentHelper = new EnchantmentHelperImpl();\r\n\r\n        registerConversion(ItemTag.class, ItemStack.class, item -> CraftItemStack.asNMSCopy(item.getItemStack()));\r\n        registerConversion(ElementTag.class, Component.class, element -> componentToNMS(FormattedTextHelper.parse(element.asString(), ChatColor.WHITE)));\r\n        registerConversion(MaterialTag.class, BlockState.class, material -> ((CraftBlockData) material.getModernData()).getState());\r\n        registerConversion(LocationTag.class, Rotations.class, location -> new Rotations((float) location.getX(), (float) location.getY(), (float) location.getZ()));\r\n        registerConversion(LocationTag.class, BlockPos.class, CraftLocation::toBlockPosition);\r\n        registerConversion(MapTag.class, CompoundTag.class, map -> {\r\n            CompoundBinaryTag compoundTag = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(map, CoreUtilities.noDebugContext, \"(item).\");\r\n            return compoundTag != null ? NBTAdapter.toNMS(compoundTag) : null;\r\n        });\r\n        registerConversion(LocationTag.class, Vector3f.class, location -> new Vector3f((float) location.getX(), (float) location.getY(), (float) location.getZ()));\r\n        registerConversion(QuaternionTag.class, Quaternionf.class, quaternion -> new Quaternionf(quaternion.x, quaternion.y, quaternion.z, quaternion.w));\r\n    }\r\n\r\n    public static <DT extends ObjectTag, JT> void registerConversion(Class<DT> denizenType, Class<JT> javaType, Function<DT, JT> convertor) {\r\n        ReflectionSetCommand.typeConverters.put(javaType, objectTag -> {\r\n            DT denizenObject = objectTag.asType(denizenType, CoreUtilities.noDebugContext);\r\n            return denizenObject != null ? convertor.apply(denizenObject) : null;\r\n        });\r\n    }\r\n\r\n    private final ProfileEditor profileEditor = new ProfileEditorImpl();\r\n\r\n    private boolean wasAsyncCatcherEnabled;\r\n\r\n    @Override\r\n    public void disableAsyncCatcher() {\r\n        wasAsyncCatcherEnabled = AsyncCatcher.enabled;\r\n        AsyncCatcher.enabled = false;\r\n    }\r\n\r\n    @Override\r\n    public void undisableAsyncCatcher() {\r\n        AsyncCatcher.enabled = wasAsyncCatcherEnabled;\r\n    }\r\n\r\n    @Override\r\n    public boolean isExactServerVersionMatch() {\r\n        return Denizen.supportsPaper ? SharedConstants.getCurrentVersion().id().equals(\"1.21.11\") : CraftMagicNumbers.INSTANCE.getMappingsVersion().equals(\"e3cd927e07e6ff434793a0474c51b2b9\");\r\n    }\r\n\r\n    @Override\r\n    public double[] getRecentTps() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().recentTps;\r\n    }\r\n\r\n    @Override\r\n    public Sidebar createSidebar(Player player) {\r\n        return new SidebarImpl(player);\r\n    }\r\n\r\n    @Override\r\n    public BlockLight createBlockLight(Location location, int lightLevel, long ticks) {\r\n        return BlockLightImpl.createLight(location, lightLevel, ticks);\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile fillPlayerProfile(PlayerProfile playerProfile) {\r\n        if (playerProfile == null) {\r\n            return null;\r\n        }\r\n        if (playerProfile.getName() == null && playerProfile.getUniqueId() == null) {\r\n            return playerProfile; // Cannot fill without lookup data\r\n        }\r\n        if (playerProfile.hasTexture() && playerProfile.hasTextureSignature() && playerProfile.getName() != null && playerProfile.getUniqueId() != null) {\r\n            return playerProfile; // Already filled\r\n        }\r\n        try {\r\n            GameProfile profile = null;\r\n            MinecraftServer minecraftServer = ((CraftServer) Bukkit.getServer()).getServer();\r\n            if (playerProfile.getUniqueId() != null) {\r\n                profile = minecraftServer.services().nameToIdCache().get(playerProfile.getUniqueId()).map(result -> new GameProfile(result.id(), result.name())).orElse(null);\r\n            }\r\n            if (profile == null && playerProfile.getName() != null) {\r\n                profile = minecraftServer.services().nameToIdCache().get(playerProfile.getName()).map(result -> new GameProfile(result.id(), result.name())).orElse(null);\r\n            }\r\n            if (profile == null) {\r\n                profile = ProfileEditorImpl.getGameProfileNoProperties(playerProfile);\r\n            }\r\n            Property textures = profile.properties().containsKey(\"textures\") ? Iterables.getFirst(profile.properties().get(\"textures\"), null) : null;\r\n            if (textures == null || !textures.hasSignature() || profile.name() == null || profile.id() == null) {\r\n                profile = minecraftServer.services().profileResolver().fetchById(profile.id()).orElse(null);\r\n                if (profile == null) {\r\n                    return null;\r\n                }\r\n                textures = profile.properties().containsKey(\"textures\") ? Iterables.getFirst(profile.properties().get(\"textures\"), null) : null;\r\n            }\r\n            return new PlayerProfile(profile.name(), profile.id(), textures == null ? null : textures.value(), textures == null ? null : textures.signature());\r\n        }\r\n        catch (Exception e) {\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static MethodHandle PAPER_INVENTORY_TITLE_GETTER;\r\n\r\n    @Override\r\n    public String getTitle(Inventory inventory) {\r\n        Container nms = ((CraftInventory) inventory).getInventory();\r\n        if (inventory instanceof CraftInventoryCustom && Denizen.supportsPaper) {\r\n            try {\r\n                if (PAPER_INVENTORY_TITLE_GETTER == null) {\r\n                    PAPER_INVENTORY_TITLE_GETTER = ReflectionHelper.getMethodHandle(nms.getClass(), \"title\");\r\n                }\r\n                return PaperAPITools.instance.parseComponent(PAPER_INVENTORY_TITLE_GETTER.invoke(nms));\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        if (nms instanceof Nameable) {\r\n            return CraftChatMessage.fromComponent(((Nameable) nms).getDisplayName());\r\n        }\r\n        else if (MINECRAFT_INVENTORY.isInstance(nms)) {\r\n            try {\r\n                return (String) INVENTORY_TITLE.get(nms);\r\n            }\r\n            catch (IllegalAccessException e) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return \"Chest\";\r\n    }\r\n\r\n    public static MethodHandle AbstractContainerMenu_title_SETTER = ReflectionHelper.getFinalSetter(AbstractContainerMenu.class, \"title\");\r\n\r\n    @Override\r\n    public void setInventoryTitle(InventoryView view, String title) {\r\n        AbstractContainerMenu menu = ((CraftInventoryView) view).getHandle();\r\n        try {\r\n            AbstractContainerMenu_title_SETTER.invoke(menu, componentToNMS(FormattedTextHelper.parse(title, ChatColor.DARK_GRAY)));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final Class MINECRAFT_INVENTORY;\r\n    public static final Field INVENTORY_TITLE;\r\n    public static final Field ENTITY_BUKKITYENTITY = ReflectionHelper.getFields(Entity.class).get(\"bukkitEntity\");\r\n\r\n    static {\r\n        Class minecraftInv = null;\r\n        Field title = null;\r\n        try {\r\n            for (Class clzz : CraftInventoryCustom.class.getDeclaredClasses()) {\r\n                if (CoreUtilities.toLowerCase(clzz.getName()).contains(\"minecraftinventory\")) { // MinecraftInventory.\r\n                    minecraftInv = clzz;\r\n                    title = clzz.getDeclaredField(\"title\");\r\n                    title.setAccessible(true);\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            e.printStackTrace();\r\n        }\r\n        MINECRAFT_INVENTORY = minecraftInv;\r\n        INVENTORY_TITLE = title;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Player player) {\r\n        GameProfile gameProfile = ((CraftPlayer) player).getProfile();\r\n        Property property = Iterables.getFirst(gameProfile.properties().get(\"textures\"), null);\r\n        return new PlayerProfile(gameProfile.name(), gameProfile.id(),\r\n                property != null ? property.value() : null,\r\n                property != null ? property.signature() : null);\r\n    }\r\n\r\n    @Override\r\n    public ProfileEditor getProfileEditor() {\r\n        return profileEditor;\r\n    }\r\n\r\n    @Override\r\n    public List<BiomeNMS> getBiomes(World world) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        ArrayList<BiomeNMS> output = new ArrayList<>();\r\n        for (Identifier key : level.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) {\r\n            output.add(new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key)));\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeNMS(World world, NamespacedKey key) {\r\n        BiomeNMSImpl impl = new BiomeNMSImpl(((CraftWorld) world).getHandle(), key);\r\n        if (impl.biomeHolder == null) {\r\n            return null;\r\n        }\r\n        return impl;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeAt(Block block) {\r\n        // Based on CraftWorld source\r\n        ServerLevel level = ((CraftWorld) block.getWorld()).getHandle();\r\n        Holder<Biome> biome = level.getNoiseBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2);\r\n        Identifier key = level.registryAccess().lookupOrThrow(Registries.BIOME).getKey(biome.value());\r\n        return new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key));\r\n    }\r\n\r\n    @Override\r\n    public ArrayList<String> containerListFlags(PersistentDataContainer container, String prefix) {\r\n        prefix = \"denizen:\" + prefix;\r\n        ArrayList<String> output = new ArrayList<>();\r\n        for (String key : ((CraftPersistentDataContainer) container).getRaw().keySet()) {\r\n            if (key.startsWith(prefix)) {\r\n                output.add(key.substring(prefix.length()));\r\n            }\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public boolean containerHas(PersistentDataContainer container, String key) {\r\n        return ((CraftPersistentDataContainer) container).getRaw().containsKey(key);\r\n    }\r\n\r\n    @Override\r\n    public String containerGetString(PersistentDataContainer container, String key) {\r\n        net.minecraft.nbt.Tag base = ((CraftPersistentDataContainer) container).getRaw().get(key);\r\n        if (base instanceof StringTag) {\r\n            return base.asString().get();\r\n        }\r\n        else if (base instanceof ByteArrayTag) {\r\n            return new String(((ByteArrayTag) base).getAsByteArray(), StandardCharsets.UTF_8);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public UUID getBossbarUUID(BossBar bar) {\r\n        return ((CraftBossBar) bar).getHandle().getId();\r\n    }\r\n\r\n    public static MethodHandle BOSSBAR_ID_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BossEvent.class, UUID.class);\r\n\r\n    @Override\r\n    public void setBossbarUUID(BossBar bar, UUID id) {\r\n        try {\r\n            BOSSBAR_ID_SETTER.invoke(((CraftBossBar) bar).getHandle(), id);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static BaseComponent[] componentToSpigot(Component nms) {\r\n        if (nms == null) {\r\n            return null;\r\n        }\r\n        return FormattedTextHelper.parseJson(CraftChatMessage.toJSON(nms));\r\n    }\r\n\r\n    public static Component componentToNMS(BaseComponent[] spigot) {\r\n        if (spigot == null) {\r\n            return null;\r\n        }\r\n        return CraftChatMessage.fromJSONOrNull(FormattedTextHelper.componentToJson(spigot));\r\n    }\r\n\r\n    public static final MethodHandle TAG_VALUE_OUTPUT_CONSTRUCTOR = ReflectionHelper.getConstructor(TagValueOutput.class, ProblemReporter.class, DynamicOps.class, CompoundTag.class);\r\n\r\n    public static CompoundTag useValueOutput(Consumer<ValueOutput> handler) {\r\n        ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector();\r\n        TagValueOutput nmsValueOutput = TagValueOutput.createWithContext(nmsProblemReporter, CraftRegistry.getMinecraftRegistry());\r\n        handler.accept(nmsValueOutput);\r\n        handleProblems(nmsProblemReporter);\r\n        return nmsValueOutput.buildResult();\r\n    }\r\n\r\n    public static CompoundTag useValueOutput(CompoundTag nmsExistingValue, Consumer<ValueOutput> handler) {\r\n        ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector();\r\n        TagValueOutput nmsValueOutput;\r\n        try {\r\n            nmsValueOutput = (TagValueOutput) TAG_VALUE_OUTPUT_CONSTRUCTOR.invoke(nmsProblemReporter, CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsExistingValue);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n            return nmsExistingValue;\r\n        }\r\n        handler.accept(nmsValueOutput);\r\n        handleProblems(nmsProblemReporter);\r\n        return nmsValueOutput.buildResult();\r\n    }\r\n\r\n    public static void useValueInput(CompoundTag nmsTag, Consumer<ValueInput> handler) {\r\n        ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector();\r\n        ValueInput nmsValueInput = TagValueInput.create(nmsProblemReporter, CraftRegistry.getMinecraftRegistry(), nmsTag);\r\n        handler.accept(nmsValueInput);\r\n        handleProblems(nmsProblemReporter);\r\n    }\r\n\r\n    private static void handleProblems(ProblemReporter.Collector nmsProblemReporter) {\r\n        if (!nmsProblemReporter.isEmpty()) {\r\n            Debug.echoError(nmsProblemReporter.getTreeReport());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String updateLegacyName(Class<?> type, String legacyName) {\r\n        return FieldRename.rename(ApiVersion.FIELD_NAME_PARITY, DebugInternals.getFullClassNameOpti(type).replace('.', '/'), legacyName);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/ReflectionMappingsInfo.java",
    "content": "package com.denizenscript.denizen.nms.v1_21;\r\n\r\npublic class ReflectionMappingsInfo {\r\n\r\n    // Content generated by ReflectionMappingsGenerator - https://github.com/DenizenScript/ReflectionMappingsGenerator\r\n\r\n    // net.minecraft.world.level.block.state.BlockBehaviour\r\n    public static String BlockBehaviour_explosionResistance = \"G\";\r\n\r\n    // net.minecraft.core.MappedRegistry\r\n    public static String MappedRegistry_registrationInfos = \"h\";\r\n\r\n    // net.minecraft.world.entity.Entity\r\n    public static String Entity_onGround = \"bc\";\r\n    public static String Entity_DATA_SHARED_FLAGS_ID = \"aA\";\r\n    public static String Entity_DATA_CUSTOM_NAME = \"bm\";\r\n    public static String Entity_DATA_CUSTOM_NAME_VISIBLE = \"bn\";\r\n\r\n    // net.minecraft.world.entity.LivingEntity\r\n    public static String LivingEntity_attackStrengthTicker = \"bz\";\r\n    public static String LivingEntity_autoSpinAttackTicks = \"bW\";\r\n    public static String LivingEntity_setLivingEntityFlag_method = \"c\";\r\n\r\n    // net.minecraft.world.entity.player.Player\r\n    public static String Player_DATA_PLAYER_ABSORPTION_ID = \"b\";\r\n\r\n    // net.minecraft.server.level.ServerPlayer\r\n    public static String ServerPlayer_respawnConfig = \"dr\";\r\n\r\n    // net.minecraft.world.entity.monster.EnderMan\r\n    public static String EnderMan_DATA_CREEPY = \"cx\";\r\n\r\n    // net.minecraft.world.entity.monster.zombie.Zombie\r\n    public static String Zombie_inWaterTime = \"cL\";\r\n\r\n    // net.minecraft.world.item.Item\r\n    public static String Item_components = \"c\";\r\n\r\n    // net.minecraft.world.item.component.ResolvableProfile\r\n    public static String ResolvableProfile_unpack_method = \"a\";\r\n\r\n    // net.minecraft.world.item.component.ResolvableProfile$Partial\r\n    public static String ResolvableProfilePartial_id = \"d\";\r\n\r\n    // net.minecraft.world.level.Level\r\n    public static String Level_isClientSide = \"D\";\r\n\r\n    // net.minecraft.server.level.ThreadedLevelLightEngine\r\n    public static String ThreadedLevelLightEngine_addTask_method = \"a\";\r\n\r\n    // net.minecraft.server.level.ThreadedLevelLightEngine$TaskType\r\n    public static String ThreadedLevelLightEngineTaskType_PRE_UPDATE = \"a\";\r\n\r\n    // net.minecraft.world.entity.ExperienceOrb\r\n    public static String ExperienceOrb_age = \"k\";\r\n\r\n    // net.minecraft.world.entity.item.ItemEntity\r\n    public static String ItemEntity_DATA_ITEM = \"c\";\r\n\r\n    // net.minecraft.world.level.biome.Biome\r\n    public static String Biome_climateSettings = \"i\";\r\n    public static String Biome_attributes = \"l\";\r\n    public static String Biome_specialEffects = \"m\";\r\n\r\n    // net.minecraft.network.Connection\r\n    public static String Connection_receiving = \"h\";\r\n    public static String Connection_packetListener = \"n\";\r\n\r\n    // net.minecraft.server.network.ServerGamePacketListenerImpl\r\n    public static String ServerGamePacketListenerImpl_aboveGroundTickCount = \"L\";\r\n    public static String ServerGamePacketListenerImpl_aboveGroundVehicleTickCount = \"N\";\r\n    public static String ServerGamePacketListenerImpl_awaitingPositionFromClient = \"H\";\r\n    public static String ServerGamePacketListenerImpl_awaitingTeleport = \"I\";\r\n    public static String ServerGamePacketListenerImpl_chunkSender = \"h\";\r\n\r\n    // net.minecraft.server.network.ServerCommonPacketListenerImpl\r\n    public static String ServerCommonPacketListenerImpl_connection = \"e\";\r\n    public static String ServerCommonPacketListenerImpl_createCookie_method = \"a\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket\r\n    public static String ClientboundPlayerAbilitiesPacket_walkingSpeed = \"k\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket\r\n    public static String ClientboundSectionBlocksUpdatePacket_sectionPos = \"c\";\r\n    public static String ClientboundSectionBlocksUpdatePacket_positions = \"d\";\r\n    public static String ClientboundSectionBlocksUpdatePacket_states = \"e\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundMoveEntityPacket\r\n    public static String ClientboundMoveEntityPacket_xa = \"b\";\r\n    public static String ClientboundMoveEntityPacket_ya = \"c\";\r\n    public static String ClientboundMoveEntityPacket_za = \"d\";\r\n    public static String ClientboundMoveEntityPacket_yRot = \"e\";\r\n    public static String ClientboundMoveEntityPacket_xRot = \"f\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket\r\n    public static String ClientboundSetEntityMotionPacket_id = \"b\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundSetPassengersPacket\r\n    public static String ClientboundSetPassengersPacket_passengers = \"c\";\r\n\r\n    // net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo\r\n    public static String ClientboundLevelChunkPacketDataBlockEntityInfo_packedXZ = \"c\";\r\n    public static String ClientboundLevelChunkPacketDataBlockEntityInfo_y = \"d\";\r\n\r\n    // net.minecraft.network.syncher.SynchedEntityData\r\n    public static String SynchedEntityData_itemsById = \"e\";\r\n\r\n    // net.minecraft.world.entity.projectile.FishingHook\r\n    public static String FishingHook_nibble = \"j\";\r\n    public static String FishingHook_timeUntilLured = \"k\";\r\n    public static String FishingHook_timeUntilHooked = \"l\";\r\n\r\n    // net.minecraft.tags.TagNetworkSerialization$NetworkPayload\r\n    public static String TagNetworkSerializationNetworkPayload_tags = \"b\";\r\n\r\n    // net.minecraft.core.HolderSet$Named\r\n    public static String HolderSetNamed_bind_method = \"b\";\r\n\r\n    // net.minecraft.core.Holder$Reference\r\n    public static String HolderReference_bindTags_method = \"a\";\r\n\r\n    // net.minecraft.server.level.ServerLevel\r\n    public static String ServerLevel_sleepStatus = \"O\";\r\n\r\n    // net.minecraft.world.item.AdventureModePredicate\r\n    public static String AdventureModePredicate_predicates = \"g\";\r\n\r\n    // net.minecraft.stats.ServerRecipeBook\r\n    public static String ServerRecipeBook_addHighlight_method = \"e\";\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/AdvancementHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.AdvancementHelper;\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.google.common.collect.ImmutableMap;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.advancements.*;\r\nimport net.minecraft.advancements.criterion.ImpossibleTrigger;\r\nimport net.minecraft.core.ClientAsset;\r\nimport net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket;\r\nimport net.minecraft.resources.Identifier;\r\nimport net.minecraft.server.PlayerAdvancements;\r\nimport net.minecraft.server.ServerAdvancementManager;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.*;\r\n\r\npublic class AdvancementHelperImpl extends AdvancementHelper {\r\n\r\n    private static final String IMPOSSIBLE_KEY = \"impossible\";\r\n    private static final Map<String, Criterion<?>> IMPOSSIBLE_CRITERIA = Map.of(IMPOSSIBLE_KEY, new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance()));\r\n    private static final List<List<String>> IMPOSSIBLE_REQUIREMENTS = List.of(List.of(IMPOSSIBLE_KEY));\r\n\r\n    public static ServerAdvancementManager getNMSAdvancementManager() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getAdvancements();\r\n    }\r\n\r\n    @Override\r\n    public void register(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || advancement.registered) {\r\n            return;\r\n        }\r\n        AdvancementHolder nmsAdvancementHolder = asNMSCopy(advancement);\r\n        Map<Identifier, AdvancementHolder> nmsAdvancements = getNMSAdvancementManager().advancements;\r\n        ImmutableMap.Builder<Identifier, AdvancementHolder> mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() + 1);\r\n        mapBuilder.putAll(nmsAdvancements);\r\n        mapBuilder.put(nmsAdvancementHolder.id(), nmsAdvancementHolder);\r\n        getNMSAdvancementManager().advancements = mapBuilder.build();\r\n\r\n        AdvancementTree tree = getNMSAdvancementManager().tree();\r\n        tree.addAll(List.of(nmsAdvancementHolder));\r\n        // recalculate advancement tree from this advancement's root\r\n        AdvancementNode node = tree.get(nmsAdvancementHolder.id());\r\n        if (node != null) {\r\n            AdvancementNode root = node.root();\r\n            if (root.holder().value().display().isPresent()) {\r\n                TreeNodePosition.run(root);\r\n            }\r\n        }\r\n        advancement.registered = true;\r\n        if (!advancement.hidden && advancement.parent != null) {\r\n            PacketHelperImpl.broadcast(new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancementHolder), Set.of(), Map.of(), false));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void unregister(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || !advancement.registered) {\r\n            return;\r\n        }\r\n        Identifier nmsKey = CraftNamespacedKey.toMinecraft(advancement.key);\r\n        Map<Identifier, AdvancementHolder> nmsAdvancements = getNMSAdvancementManager().advancements;\r\n        ImmutableMap.Builder<Identifier, AdvancementHolder> mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() - 1);\r\n        for (Map.Entry<Identifier, AdvancementHolder> entry : nmsAdvancements.entrySet()) {\r\n            if (!entry.getKey().equals(nmsKey)) {\r\n                mapBuilder.put(entry);\r\n            }\r\n        }\r\n        getNMSAdvancementManager().advancements = mapBuilder.build();\r\n        getNMSAdvancementManager().tree().remove(Set.of(nmsKey));\r\n        advancement.registered = false;\r\n        PacketHelperImpl.broadcast(new ClientboundUpdateAdvancementsPacket(false, List.of(), Set.of(nmsKey), Map.of(), false));\r\n    }\r\n\r\n    @Override\r\n    public void grantPartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) {\r\n        if (advancement.length <= 1) {\r\n            grant(advancement, player);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            AdvancementHolder nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS));\r\n            for (int i = 0; i < len; i++) {\r\n                progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria\r\n            }\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), false));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            for (int i = 0; i < len; i++) {\r\n                ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY + i);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void grant(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.length > 1) {\r\n            grantPartial(advancement, player, advancement.length);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            AdvancementHolder nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS));\r\n            progress.grantProgress(IMPOSSIBLE_KEY); // complete impossible criteria\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), true));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void revokePartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) {\r\n        if (advancement.length <= 1) {\r\n            revoke(advancement, player);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            AdvancementHolder nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS));\r\n            for (int i = 0; i < len; i++) {\r\n                progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria\r\n            }\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), false));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            PlayerAdvancements advancements = ((CraftPlayer) player).getHandle().getAdvancements();\r\n            for (int i = len; i < advancement.length; i++) {\r\n                advancements.revoke(nmsAdvancement, IMPOSSIBLE_KEY + i);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void revoke(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.temporary) {\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(), Set.of(CraftNamespacedKey.toMinecraft(advancement.key)), Map.of(), false));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            PlayerAdvancements advancements = ((CraftPlayer) player).getHandle().getAdvancements();\r\n            for (String criterion : nmsAdvancement.value().criteria().keySet()) {\r\n                advancements.revoke(nmsAdvancement, criterion);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(Player player) {\r\n        // TODO: 1.21.5: should showAdvancements be true?\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        nmsPlayer.connection.send(new ClientboundUpdateAdvancementsPacket(true, List.of(), Set.of(), Map.of(), false));\r\n        PlayerAdvancements data = nmsPlayer.getAdvancements();\r\n        data.save(); // save progress\r\n        data.reload(getNMSAdvancementManager()); // clear progress\r\n        data.flushDirty(nmsPlayer, false); // load progress and update client\r\n    }\r\n\r\n    private static AdvancementHolder asNMSCopy(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        AdvancementHolder parent = advancement.parent != null\r\n                ? getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.parent))\r\n                : null;\r\n        DisplayInfo display = new DisplayInfo(CraftItemStack.asNMSCopy(advancement.icon),\r\n                Handler.componentToNMS(FormattedTextHelper.parse(advancement.title, ChatColor.WHITE)), Handler.componentToNMS(FormattedTextHelper.parse(advancement.description, ChatColor.WHITE)),\r\n                Optional.ofNullable(advancement.background).map(CraftNamespacedKey::toMinecraft).map(ClientAsset.ResourceTexture::new), AdvancementType.valueOf(advancement.frame.name()),\r\n                advancement.toast, advancement.announceToChat, advancement.hidden);\r\n        display.setLocation(advancement.xOffset, advancement.yOffset);\r\n        Map<String, Criterion<?>> criteria = IMPOSSIBLE_CRITERIA;\r\n        List<List<String>> requirements = IMPOSSIBLE_REQUIREMENTS;\r\n        if (advancement.length > 1) {\r\n            criteria = new HashMap<>();\r\n            requirements = new ArrayList<>(advancement.length);\r\n            for (int i = 0; i < advancement.length; i++) {\r\n                criteria.put(IMPOSSIBLE_KEY + i, new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance()));\r\n                requirements.add(List.of(IMPOSSIBLE_KEY + i));\r\n            }\r\n        }\r\n        AdvancementRequirements reqs = new AdvancementRequirements(requirements);\r\n        Advancement adv = new Advancement(parent == null ? Optional.empty() : Optional.of(parent.id()), Optional.of(display), AdvancementRewards.EMPTY, criteria, reqs, false); // TODO: 1.20: do we want to ever enable telemetry?\r\n        return new AdvancementHolder(CraftNamespacedKey.toMinecraft(advancement.key), adv);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/AnimationHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.AnimationHelper;\r\nimport net.minecraft.world.entity.Entity;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftHorse;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPolarBear;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Horse;\r\nimport org.bukkit.entity.IronGolem;\r\n\r\npublic class AnimationHelperImpl extends AnimationHelper {\r\n\r\n    public AnimationHelperImpl() {\r\n        register(\"POLAR_BEAR_START_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(true);\r\n            }\r\n        });\r\n        register(\"POLAR_BEAR_STOP_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(false);\r\n            }\r\n        });\r\n        // TODO: 1.21.6: this is a tick duration now, should become a mechanism\r\n        register(\"HORSE_START_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().setStanding(Integer.MAX_VALUE);\r\n            }\r\n        });\r\n        register(\"HORSE_STOP_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().clearStanding();\r\n            }\r\n        });\r\n        register(\"HORSE_BUCK\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().makeMad();\r\n            }\r\n        });\r\n        register(\"IRON_GOLEM_ATTACK\", entity -> {\r\n            if (entity instanceof IronGolem) {\r\n                Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n                nmsEntity.level().broadcastEntityEvent(nmsEntity, (byte) 4);\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/BlockHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.BlockHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.InclusiveRange;\r\nimport net.minecraft.util.random.WeightedList;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.item.component.ResolvableProfile;\r\nimport net.minecraft.world.level.BaseSpawner;\r\nimport net.minecraft.world.level.SpawnData;\r\nimport net.minecraft.world.level.block.entity.BlockEntity;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockBehaviour;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.status.ChunkStatus;\r\nimport net.minecraft.world.level.material.FluidState;\r\nimport net.minecraft.world.level.material.PushReaction;\r\nimport org.bukkit.Instrument;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.Skull;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftRegistry;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.CraftBlockEntityState;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.CraftSkull;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftLocation;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftMagicNumbers;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.Optional;\r\n\r\npublic class BlockHelperImpl implements BlockHelper {\r\n\r\n    public static final Field craftBlockEntityState_tileEntity;\r\n    public static final Field craftBlockEntityState_snapshot = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"snapshot\");\r\n    public static final Field craftSkull_profile = ReflectionHelper.getFields(CraftSkull.class).get(\"profile\");\r\n\r\n    static {\r\n        Field blockEntityField = ReflectionHelper.getFields(CraftBlockEntityState.class).getNoCheck(\"blockEntity\");\r\n        if (blockEntityField == null) {\r\n            blockEntityField = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"tileEntity\");\r\n        }\r\n        craftBlockEntityState_tileEntity = blockEntityField;\r\n    }\r\n\r\n    @Override\r\n    public void applyPhysics(Location location) {\r\n        ((CraftWorld) location.getWorld()).getHandle().updateNeighborsAt(CraftLocation.toBlockPosition(location), CraftMagicNumbers.getBlock(location.getBlock().getType()));\r\n    }\r\n\r\n    public static <T extends BlockEntity> T getTE(CraftBlockEntityState<T> cbs) {\r\n        try {\r\n            return (T) craftBlockEntityState_tileEntity.get(cbs);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Skull skull) {\r\n        ResolvableProfile profile = getTE(((CraftSkull) skull)).owner;\r\n        if (profile == null) {\r\n            return null;\r\n        }\r\n        com.mojang.authlib.properties.Property property = Iterables.getFirst(profile.partialProfile().properties().get(\"textures\"), null);\r\n        return new PlayerProfile(profile.name().orElse(null), ProfileEditorImpl.getUUID(profile), property != null ? property.value() : null);\r\n    }\r\n\r\n    @Override\r\n    public void setPlayerProfile(Skull skull, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile);\r\n        try {\r\n            craftSkull_profile.set(skull, ResolvableProfile.createResolved(gameProfile));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        skull.update();\r\n    }\r\n\r\n    public BlockEntity getBlockEntity(Block block) {\r\n        CraftBlock craftBlock = ((CraftBlock) block);\r\n        return craftBlock.getHandle().getBlockEntity(craftBlock.getPosition());\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Block block) {\r\n        BlockEntity nmsBlockEntity = getBlockEntity(block);\r\n        if (nmsBlockEntity != null) {\r\n            CompoundTag compound = nmsBlockEntity.saveWithFullMetadata(CraftRegistry.getMinecraftRegistry());\r\n            return NBTAdapter.toAPI(compound);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Block block, CompoundBinaryTag ctag) {\r\n        CompoundTag nmsData = NBTAdapter.toNMS(ctag);\r\n        nmsData.putInt(\"x\", block.getX());\r\n        nmsData.putInt(\"y\", block.getY());\r\n        nmsData.putInt(\"z\", block.getZ());\r\n        Handler.useValueInput(nmsData, getBlockEntity(block)::loadWithComponents);\r\n    }\r\n\r\n    @Override\r\n    public boolean setBlockResistance(Material material, float resistance) {\r\n        net.minecraft.world.level.block.Block block = CraftMagicNumbers.getBlock(material);\r\n        if (block == null) {\r\n            return false;\r\n        }\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, ReflectionMappingsInfo.BlockBehaviour_explosionResistance, block, resistance);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public float getBlockResistance(Material material) {\r\n        net.minecraft.world.level.block.Block block = CraftMagicNumbers.getBlock(material);\r\n        if (block == null) {\r\n            return 0;\r\n        }\r\n        return ReflectionHelper.getFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, ReflectionMappingsInfo.BlockBehaviour_explosionResistance, block);\r\n    }\r\n\r\n    public static final MethodHandle MATERIAL_PUSH_REACTION_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BlockBehaviour.BlockStateBase.class, PushReaction.class);\r\n\r\n    public static final MethodHandle BLOCK_STRENGTH_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase.class, float.class); // destroySpeed\r\n\r\n    public net.minecraft.world.level.block.state.BlockState getMaterialBlockState(Material bukkitMaterial) {\r\n        net.minecraft.world.level.block.Block nmsBlock = CraftMagicNumbers.getBlock(bukkitMaterial);\r\n        return nmsBlock != null ? nmsBlock.defaultBlockState() : null;\r\n    }\r\n\r\n    @Override\r\n    public void setPushReaction(Material mat, PistonPushReaction reaction) {\r\n        try {\r\n            MATERIAL_PUSH_REACTION_SETTER.invoke(getMaterialBlockState(mat), PushReaction.values()[reaction.ordinal()]);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBlockStrength(Material mat) {\r\n        return getMaterialBlockState(mat).destroySpeed;\r\n    }\r\n\r\n    @Override\r\n    public void setBlockStrength(Material mat, float strength) {\r\n        try {\r\n            BLOCK_STRENGTH_SETTER.invoke(getMaterialBlockState(mat), strength);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void doRandomTick(Location location) {\r\n        BlockPos pos = CraftLocation.toBlockPosition(location);\r\n        ChunkAccess nmsChunk = ((CraftChunk) location.getChunk()).getHandle(ChunkStatus.FULL);\r\n        net.minecraft.world.level.block.state.BlockState nmsBlock = nmsChunk.getBlockState(pos);\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        if (nmsBlock.isRandomlyTicking()) {\r\n            nmsBlock.randomTick(nmsWorld, pos, nmsWorld.random);\r\n        }\r\n        FluidState fluid = nmsBlock.getFluidState();\r\n        if (fluid.isRandomlyTicking()) {\r\n            fluid.animateTick(nmsWorld, pos, nmsWorld.random);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Instrument getInstrumentFor(Material mat) {\r\n        return Instrument.values()[getMaterialBlockState(mat).instrument().ordinal()];\r\n    }\r\n\r\n    @Override\r\n    public int getExpDrop(Block block, org.bukkit.inventory.ItemStack item) {\r\n        net.minecraft.world.level.block.Block blockType = CraftMagicNumbers.getBlock(block.getType());\r\n        if (blockType == null) {\r\n            return 0;\r\n        }\r\n        return blockType.getExpDrop(((CraftBlock) block).getNMS(), ((CraftBlock) block).getCraftWorld().getHandle(), ((CraftBlock) block).getPosition(),\r\n                item == null ? null : CraftItemStack.asNMSCopy(item), true);\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnerSpawnedType(CreatureSpawner spawner, EntityTag entity) {\r\n        spawner.setSpawnedType(entity.getBukkitEntityType());\r\n        if (entity.getWaitingMechanisms() == null || entity.getWaitingMechanisms().size() == 0) {\r\n            return;\r\n        }\r\n        try {\r\n            // Wrangle a fake entity\r\n            // TODO: 1.21.6: seems to have a bug where the \"Pos\" value being set prevents it from spawning?\r\n            org.bukkit.entity.Entity bukkitEntity = ((CraftWorld) spawner.getWorld()).createEntity(spawner.getLocation(), entity.getBukkitEntityType().getEntityClass());\r\n            Entity nmsEntity = ((CraftEntity) bukkitEntity).getHandle();\r\n            EntityTag entityTag = new EntityTag(bukkitEntity);\r\n            entityTag.isFake = true;\r\n            entityTag.isFakeValid = true;\r\n            for (Mechanism mechanism : entity.getWaitingMechanisms()) {\r\n                entityTag.safeAdjustDuplicate(mechanism);\r\n            }\r\n            nmsEntity.unsetRemoved();\r\n            // Store it into the spawner\r\n            SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(spawner);\r\n            Handler.useValueOutput(nmsSnapshot.getSpawner().nextSpawnData.getEntityToSpawn(), nmsEntity::saveWithoutId);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnerCustomRules(CreatureSpawner spawner, int skyMin, int skyMax, int blockMin, int blockMax) {\r\n        try {\r\n            CraftCreatureSpawner bukkitSpawner = (CraftCreatureSpawner) spawner;\r\n            SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(bukkitSpawner);\r\n            BaseSpawner nmsSpawner = nmsSnapshot.getSpawner();\r\n            SpawnData toSpawn = nmsSpawner.nextSpawnData;\r\n            SpawnData.CustomSpawnRules rules = skyMin == -1 ? null : new SpawnData.CustomSpawnRules(new InclusiveRange<>(skyMin, skyMax), new InclusiveRange<>(blockMin, blockMax));\r\n            nmsSpawner.nextSpawnData = new SpawnData(toSpawn.entityToSpawn(), Optional.ofNullable(rules), toSpawn.equipment());\r\n            nmsSpawner.spawnPotentials = WeightedList.of();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/ChunkHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.interfaces.ChunkHelper;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.QuartPos;\r\nimport net.minecraft.server.level.ServerChunkCache;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.LevelHeightAccessor;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.LevelChunkSection;\r\nimport net.minecraft.world.level.chunk.PalettedContainer;\r\nimport net.minecraft.world.level.chunk.status.ChunkStatus;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\n\r\npublic class ChunkHelperImpl implements ChunkHelper {\r\n\r\n    public final static Field chunkProviderServerThreadField;\r\n    public final static MethodHandle chunkProviderServerThreadFieldSetter;\r\n    public final static Field worldThreadField;\r\n    public final static MethodHandle worldThreadFieldSetter;\r\n\r\n    static {\r\n        chunkProviderServerThreadField = ReflectionHelper.getFields(ServerChunkCache.class).getFirstOfType(Thread.class);\r\n        chunkProviderServerThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(ServerChunkCache.class, Thread.class);\r\n        worldThreadField = ReflectionHelper.getFields(net.minecraft.world.level.Level.class).getFirstOfType(Thread.class);\r\n        worldThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.Level.class, Thread.class);\r\n    }\r\n\r\n    public Thread resetServerThread;\r\n\r\n    @Override\r\n    public void changeChunkServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread != null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkSource();\r\n        try {\r\n            resetServerThread = (Thread) chunkProviderServerThreadField.get(provider);\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, Thread.currentThread());\r\n            worldThreadFieldSetter.invoke(nmsWorld, Thread.currentThread());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void restoreServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread == null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkSource();\r\n        try {\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, resetServerThread);\r\n            worldThreadFieldSetter.invoke(nmsWorld, resetServerThread);\r\n            resetServerThread = null;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int[] getHeightMap(Chunk chunk) {\r\n        Heightmap map = ((CraftChunk) chunk).getHandle(ChunkStatus.FEATURES).heightmaps.get(Heightmap.Types.MOTION_BLOCKING);\r\n        int[] outputMap = new int[256];\r\n        for (int x = 0; x < 16; x++) {\r\n            for (int y = 0; y < 16; y++) {\r\n                outputMap[x * 16 + y] = map.getFirstAvailable(x, y);\r\n            }\r\n        }\r\n        return outputMap;\r\n    }\r\n\r\n    @Override\r\n    public void setAllBiomes(Chunk chunk, BiomeNMS biome) {\r\n        Holder<Biome> nmsBiome = ((BiomeNMSImpl) biome).biomeHolder;\r\n        ChunkAccess nmsChunk = ((CraftChunk) chunk).getHandle(ChunkStatus.BIOMES);\r\n        ChunkPos chunkcoordintpair = nmsChunk.getPos();\r\n        int i = QuartPos.fromBlock(chunkcoordintpair.getMinBlockX());\r\n        int j = QuartPos.fromBlock(chunkcoordintpair.getMinBlockZ());\r\n        LevelHeightAccessor levelheightaccessor = nmsChunk.getHeightAccessorForGeneration();\r\n        for(int k = levelheightaccessor.getMinSectionY(); k < levelheightaccessor.getMaxSectionY(); ++k) {\r\n            LevelChunkSection chunksection = nmsChunk.getSection(nmsChunk.getSectionIndexFromSectionY(k));\r\n            PalettedContainer<Holder<Biome>> datapaletteblock = (PalettedContainer<Holder<Biome>>) chunksection.getBiomes();\r\n            datapaletteblock.acquire();\r\n            for(int l = 0; l < 4; ++l) {\r\n                for(int i1 = 0; i1 < 4; ++i1) {\r\n                    for(int j1 = 0; j1 < 4; ++j1) {\r\n                        datapaletteblock.getAndSetUnchecked(l, i1, j1, nmsBiome);\r\n                    }\r\n                }\r\n            }\r\n            datapaletteblock.release();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/CustomEntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.CustomEntityHelper;\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.entities.EntityFakeArrowImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.entities.EntityFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.minecraft.server.level.ClientInformation;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scoreboard.Scoreboard;\r\nimport org.bukkit.scoreboard.Team;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.UUID;\r\n\r\npublic class CustomEntityHelperImpl implements CustomEntityHelper {\r\n\r\n    @Override\r\n    public FakeArrow spawnFakeArrow(Location location) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityFakeArrowImpl arrow = new EntityFakeArrowImpl(world, location);\r\n        return arrow.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public ItemProjectile spawnItemProjectile(Location location, ItemStack itemStack) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityItemProjectileImpl entity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n        world.getHandle().addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return entity.getBukkitEntity();\r\n    }\r\n\r\n    public FakePlayer spawnFakePlayer(Location location, String name, String skin, String blob, boolean doAdd) throws IllegalArgumentException {\r\n        BukkitImplDeprecations.fakePlayer.warn();\r\n        String fullName = name;\r\n        String prefix = null;\r\n        String suffix = null;\r\n        if (name == null) {\r\n            Debug.echoError(\"FAKE_PLAYER: null name, cannot spawn\");\r\n            return null;\r\n        }\r\n        else if (fullName.length() > 16) {\r\n            prefix = fullName.substring(0, 16);\r\n            if (fullName.length() > 30) {\r\n                int len = 30;\r\n                name = fullName.substring(16, 30);\r\n                if (name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    if (fullName.length() >= 32) {\r\n                        len = 32;\r\n                        name = fullName.substring(16, 32);\r\n                    }\r\n                    else if (fullName.length() == 31) {\r\n                        len = 31;\r\n                        name = fullName.substring(16, 31);\r\n                    }\r\n                }\r\n                else if (name.length() > 46) {\r\n                    throw new IllegalArgumentException(\"You must specify a name with no more than 46 characters for FAKE_PLAYER entities!\");\r\n                }\r\n                else {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                suffix = fullName.substring(len);\r\n            }\r\n            else {\r\n                name = fullName.substring(16);\r\n                if (!name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                if (name.length() > 16) {\r\n                    suffix = name.substring(16);\r\n                    name = name.substring(0, 16);\r\n                }\r\n            }\r\n        }\r\n        if (skin != null && skin.length() > 16) {\r\n            throw new IllegalArgumentException(\"You must specify a name with no more than 16 characters for FAKE_PLAYER entity skins!\");\r\n        }\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        ServerLevel worldServer = world.getHandle();\r\n        PlayerProfile playerProfile = new PlayerProfile(name, null);\r\n        if (blob != null) {\r\n            int sc = blob.indexOf(';');\r\n            if (sc != -1) {\r\n                playerProfile.setTexture(blob.substring(0, sc));\r\n                playerProfile.setTextureSignature(blob.substring(sc + 1));\r\n            }\r\n        }\r\n        else if (skin == null && !name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n            playerProfile = NMSHandler.instance.fillPlayerProfile(playerProfile);\r\n        }\r\n        if (skin != null) {\r\n            PlayerProfile skinProfile = new PlayerProfile(skin, null);\r\n            skinProfile = NMSHandler.instance.fillPlayerProfile(skinProfile);\r\n            playerProfile.setTexture(skinProfile.getTexture());\r\n            playerProfile.setTextureSignature(skinProfile.getTextureSignature());\r\n        }\r\n        UUID uuid = UUID.randomUUID();\r\n        playerProfile.setUniqueId(uuid);\r\n\r\n        GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile);\r\n        final EntityFakePlayerImpl fakePlayer = new EntityFakePlayerImpl(worldServer.getServer(), worldServer, gameProfile, ClientInformation.createDefault(), doAdd);\r\n\r\n        fakePlayer.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(),\r\n                location.getYaw(), location.getPitch());\r\n        CraftFakePlayerImpl craftFakePlayer = fakePlayer.getBukkitEntity();\r\n        craftFakePlayer.fullName = fullName;\r\n        if (prefix != null) {\r\n            Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();\r\n            String teamName = \"FAKE_PLAYER_TEAM_\" + fullName;\r\n            String hash = null;\r\n            try {\r\n                hash = CoreUtilities.hash_md5(teamName.getBytes(StandardCharsets.UTF_8)).substring(0, 16);\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(e);\r\n            }\r\n            if (hash != null) {\r\n                Team team = scoreboard.getTeam(hash);\r\n                if (team == null) {\r\n                    team = scoreboard.registerNewTeam(hash);\r\n                    team.setPrefix(prefix);\r\n                    if (suffix != null) {\r\n                        team.setSuffix(suffix);\r\n                    }\r\n                }\r\n                team.addPlayer(craftFakePlayer);\r\n            }\r\n        }\r\n        return craftFakePlayer;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EnchantmentHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.EnchantmentHelper;\r\n\r\npublic class EnchantmentHelperImpl extends EnchantmentHelper {\r\n    // TODO: 1.21: Enchantments were entirely reworked, need to update this\r\n    /*\r\n    public static final Field REGISTRY_FROZEN = ReflectionHelper.getFields(MappedRegistry.class).get(ReflectionMappingsInfo.MappedRegistry_frozen, boolean.class);\r\n    public static final Field REGISTRY_INTRUSIVE_HOLDERS = ReflectionHelper.getFields(MappedRegistry.class).get(ReflectionMappingsInfo.MappedRegistry_unregisteredIntrusiveHolders, Map.class);\r\n\r\n    @Override\r\n    public org.bukkit.enchantments.Enchantment registerFakeEnchantment(EnchantmentScriptContainer.EnchantmentReference script) {\r\n        try {\r\n            Map holders = (Map) REGISTRY_INTRUSIVE_HOLDERS.get(BuiltInRegistries.ENCHANTMENT);\r\n            if (holders == null) {\r\n                REGISTRY_INTRUSIVE_HOLDERS.set(BuiltInRegistries.ENCHANTMENT, new IdentityHashMap());\r\n            }\r\n            boolean wasFrozen = REGISTRY_FROZEN.getBoolean(BuiltInRegistries.ENCHANTMENT);\r\n            REGISTRY_FROZEN.setBoolean(BuiltInRegistries.ENCHANTMENT, false);\r\n            EquipmentSlot[] slots = new EquipmentSlot[script.script.slots.size()];\r\n            for (int i = 0; i < slots.length; i++) {\r\n                slots[i] = EquipmentSlot.valueOf(CoreUtilities.toUpperCase(script.script.slots.get(i)));\r\n            }\r\n            // TODO: 1.20.6: rarity is provided as an int, can make our own mirror enum; categories seemed to only over control #canEnchant(ItemStack), so can probably safely phase them out?\r\n             net.minecraft.world.item.enchantment.Enchantment.Rarity.valueOf(script.script.rarity), EnchantmentCategory.valueOf(script.script.category), slots\r\n            net.minecraft.world.item.enchantment.Enchantment nmsEnchant = new net.minecraft.world.item.enchantment.Enchantment(null) {\r\n                // TODO: 1.20.6: methods are final now and the values are provided by EnchantmentDefinition - would probably need to create a new one on reload and modify the existing enchantment\r\n                @Override\r\n                public int getMinLevel() {\r\n                    return script.script.minLevel;\r\n                }\r\n                @Override\r\n                public int getMaxLevel() {\r\n                    return script.script.maxLevel;\r\n                }\r\n                @Override\r\n                public int getMinCost(int level) {\r\n                    return script.script.getMinCost(level);\r\n                }\r\n                @Override\r\n                public int getMaxCost(int level) {\r\n                    return script.script.getMaxCost(level);\r\n                }\r\n                @Override\r\n                public int getDamageProtection(int level, DamageSource src) {\r\n                    return script.script.getDamageProtection(level, src.getMsgId(), src.getEntity() == null ? null : src.getEntity().getBukkitEntity());\r\n                }\r\n                // TODO: 1.20.6: Takes an EntityType now, and MobType seems to have been removed in favor of vanilla tags - can probably use these to backsupport & properly pass the entity type\r\n                @Override\r\n                public float getDamageBonus(int level, EntityType type) {\r\n                    String typeName = \"UNDEFINED\";\r\n                    if (type == MobType.ARTHROPOD) {\r\n                        typeName = \"ARTHROPOD\";\r\n                    }\r\n                    else if (type == MobType.ILLAGER) {\r\n                        typeName = \"ILLAGER\";\r\n                    }\r\n                    else if (type == MobType.UNDEAD) {\r\n                        typeName = \"UNDEAD\";\r\n                    }\r\n                    else if (type == MobType.WATER) {\r\n                        typeName = \"WATER\";\r\n                    }\r\n                    return script.script.getDamageBonus(level, typeName);\r\n                }\r\n                @Override\r\n                protected boolean checkCompatibility(net.minecraft.world.item.enchantment.Enchantment nmsEnchantment) {\r\n                    ResourceLocation nmsKey = BuiltInRegistries.ENCHANTMENT.getKey(nmsEnchantment);\r\n                    NamespacedKey bukkitKey = CraftNamespacedKey.fromMinecraft(nmsKey);\r\n                    org.bukkit.enchantments.Enchantment bukkitEnchant = CraftEnchantment.getByKey(bukkitKey);\r\n                    return script.script.isCompatible(bukkitEnchant);\r\n                }\r\n                @Override\r\n                protected String getOrCreateDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public String getDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public Component getFullname(int level) {\r\n                    return Handler.componentToNMS(script.script.getFullName(level));\r\n                }\r\n                @Override\r\n                public boolean canEnchant(net.minecraft.world.item.ItemStack var0) {\r\n                    return super.canEnchant(var0) && script.script.canEnchant(CraftItemStack.asBukkitCopy(var0));\r\n                }\r\n                @Override\r\n                public void doPostAttack(LivingEntity attacker, Entity victim, int level) {\r\n                    script.script.doPostAttack(attacker.getBukkitEntity(), victim.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public void doPostHurt(LivingEntity victim, Entity attacker, int level) {\r\n                    script.script.doPostHurt(victim.getBukkitEntity(), attacker.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public boolean isTreasureOnly() {\r\n                    return script.script.isTreasureOnly;\r\n                }\r\n                @Override\r\n                public boolean isCurse() {\r\n                    return script.script.isCurse;\r\n                }\r\n                @Override\r\n                public boolean isTradeable() {\r\n                    return script.script.isTradable;\r\n                }\r\n                @Override\r\n                public boolean isDiscoverable() {\r\n                    return script.script.isDiscoverable;\r\n                }\r\n            };\r\n            NamespacedKey enchantmentKey = new NamespacedKey(Denizen.getInstance(), script.script.id);\r\n            Registry.register(BuiltInRegistries.ENCHANTMENT, enchantmentKey.toString(), nmsEnchant);\r\n            String enchName = CoreUtilities.toUpperCase(script.script.id);\r\n            CraftEnchantment ench = new CraftEnchantment(enchantmentKey, nmsEnchant) {\r\n                @Override\r\n                public String getName() {\r\n                    return enchName;\r\n                }\r\n            };\r\n            REGISTRY_INTRUSIVE_HOLDERS.set(BuiltInRegistries.ENCHANTMENT, holders);\r\n            if (wasFrozen) {\r\n                BuiltInRegistries.ENCHANTMENT.freeze();\r\n            }\r\n            return ench;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(\"Failed to register enchantment \" + script.script.id);\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    // TODO: 1.20.6: rarity is just an int now (weight), can deprecate & backsupport by estimating it based on the weight\r\n    @Override\r\n    public String getRarity(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getRarity().name();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDiscoverable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isDiscoverable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isTradable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isTradeable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isCurse(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isCurse();\r\n    }\r\n\r\n    @Override\r\n    public int getMinCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMinCost(level);\r\n    }\r\n\r\n    @Override\r\n    public int getMaxCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMaxCost(level);\r\n    }\r\n\r\n    @Override\r\n    public String getFullName(Enchantment enchantment, int level) {\r\n        return FormattedTextHelper.stringify(Handler.componentToSpigot(((CraftEnchantment) enchantment).getHandle().getFullname(level)));\r\n    }\r\n\r\n    // TODO: 1.20.6: MobType was removed in favor of using the entity type directly - deprecate + potentially backsupport with vanilla tags\r\n    @Override\r\n    public float getDamageBonus(Enchantment enchantment, int level, String type) {\r\n        MobType mobType = switch (type) {\r\n            case \"illager\" -> MobType.ILLAGER;\r\n            case \"undead\" -> MobType.UNDEAD;\r\n            case \"water\" -> MobType.WATER;\r\n            case \"arthropod\" -> MobType.ARTHROPOD;\r\n            default -> MobType.UNDEFINED;\r\n        };\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageBonus(level, mobType);\r\n    }\r\n\r\n    @Override\r\n    public int getDamageProtection(Enchantment enchantment, int level, EntityDamageEvent.DamageCause type, org.bukkit.entity.Entity attacker) {\r\n        Entity nmsAttacker = attacker == null ? null : ((CraftEntity) attacker).getHandle();\r\n        DamageSource src = EntityHelperImpl.getSourceFor(nmsAttacker, type, nmsAttacker);\r\n        if (src instanceof EntityHelperImpl.FakeDamageSrc fakeDamageSrc) {\r\n            src = fakeDamageSrc.real;\r\n        }\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageProtection(level, src);\r\n    }\r\n     */\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EntityDataNameMapper.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\n\n\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport net.minecraft.world.entity.*;\nimport net.minecraft.world.entity.ambient.Bat;\nimport net.minecraft.world.entity.animal.axolotl.Axolotl;\nimport net.minecraft.world.entity.animal.bee.Bee;\nimport net.minecraft.world.entity.animal.camel.Camel;\nimport net.minecraft.world.entity.animal.cow.MushroomCow;\nimport net.minecraft.world.entity.animal.dolphin.Dolphin;\nimport net.minecraft.world.entity.animal.equine.AbstractChestedHorse;\nimport net.minecraft.world.entity.animal.equine.AbstractHorse;\nimport net.minecraft.world.entity.animal.equine.Horse;\nimport net.minecraft.world.entity.animal.equine.Llama;\nimport net.minecraft.world.entity.animal.feline.Cat;\nimport net.minecraft.world.entity.animal.feline.Ocelot;\nimport net.minecraft.world.entity.animal.fish.AbstractFish;\nimport net.minecraft.world.entity.animal.fish.Pufferfish;\nimport net.minecraft.world.entity.animal.fish.TropicalFish;\nimport net.minecraft.world.entity.animal.fox.Fox;\nimport net.minecraft.world.entity.animal.frog.Frog;\nimport net.minecraft.world.entity.animal.goat.Goat;\nimport net.minecraft.world.entity.animal.golem.IronGolem;\nimport net.minecraft.world.entity.animal.golem.SnowGolem;\nimport net.minecraft.world.entity.animal.panda.Panda;\nimport net.minecraft.world.entity.animal.parrot.Parrot;\nimport net.minecraft.world.entity.animal.pig.Pig;\nimport net.minecraft.world.entity.animal.polarbear.PolarBear;\nimport net.minecraft.world.entity.animal.rabbit.Rabbit;\nimport net.minecraft.world.entity.animal.sheep.Sheep;\nimport net.minecraft.world.entity.animal.sniffer.Sniffer;\nimport net.minecraft.world.entity.animal.turtle.Turtle;\nimport net.minecraft.world.entity.animal.wolf.Wolf;\nimport net.minecraft.world.entity.boss.enderdragon.EndCrystal;\nimport net.minecraft.world.entity.boss.enderdragon.EnderDragon;\nimport net.minecraft.world.entity.boss.wither.WitherBoss;\nimport net.minecraft.world.entity.decoration.ArmorStand;\nimport net.minecraft.world.entity.decoration.ItemFrame;\nimport net.minecraft.world.entity.decoration.painting.Painting;\nimport net.minecraft.world.entity.item.PrimedTnt;\nimport net.minecraft.world.entity.monster.*;\nimport net.minecraft.world.entity.monster.hoglin.Hoglin;\nimport net.minecraft.world.entity.monster.illager.Pillager;\nimport net.minecraft.world.entity.monster.illager.SpellcasterIllager;\nimport net.minecraft.world.entity.monster.piglin.AbstractPiglin;\nimport net.minecraft.world.entity.monster.piglin.Piglin;\nimport net.minecraft.world.entity.monster.spider.Spider;\nimport net.minecraft.world.entity.monster.warden.Warden;\nimport net.minecraft.world.entity.monster.zombie.Zombie;\nimport net.minecraft.world.entity.monster.zombie.ZombieVillager;\nimport net.minecraft.world.entity.npc.villager.AbstractVillager;\nimport net.minecraft.world.entity.npc.villager.Villager;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.entity.projectile.EyeOfEnder;\nimport net.minecraft.world.entity.projectile.FireworkRocketEntity;\nimport net.minecraft.world.entity.projectile.FishingHook;\nimport net.minecraft.world.entity.projectile.ThrowableProjectile;\nimport net.minecraft.world.entity.projectile.arrow.AbstractArrow;\nimport net.minecraft.world.entity.projectile.arrow.Arrow;\nimport net.minecraft.world.entity.projectile.arrow.ThrownTrident;\nimport net.minecraft.world.entity.projectile.hurtingprojectile.Fireball;\nimport net.minecraft.world.entity.projectile.hurtingprojectile.SmallFireball;\nimport net.minecraft.world.entity.projectile.hurtingprojectile.WitherSkull;\nimport net.minecraft.world.entity.raid.Raider;\nimport net.minecraft.world.entity.vehicle.boat.Boat;\nimport net.minecraft.world.entity.vehicle.minecart.AbstractMinecart;\nimport net.minecraft.world.entity.vehicle.minecart.MinecartCommandBlock;\nimport net.minecraft.world.entity.vehicle.minecart.MinecartFurnace;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class EntityDataNameMapper {\n\n    public static final Map<Class<? extends Entity>, Map<String, Integer>> entityDataNames = new HashMap<>();\n\n    public static void registerDataName(Class<? extends Entity> entityClass, int id, String name) {\n        entityDataNames.computeIfAbsent(entityClass, k -> new HashMap<>()).put(name, id);\n    }\n\n    static {\n        // Entity\n        registerDataName(Entity.class, 0, \"entity_flags\");\n        registerDataName(Entity.class, 1, \"air_ticks\");\n        registerDataName(Entity.class, 2, \"custom_name\");\n        registerDataName(Entity.class, 3, \"custom_name_visible\");\n        registerDataName(Entity.class, 4, \"silent\");\n        registerDataName(Entity.class, 5, \"no_gravity\");\n        registerDataName(Entity.class, 6, \"pose\");\n        registerDataName(Entity.class, 7, \"frozen_ticks\");\n\n        // Interaction\n        registerDataName(Interaction.class, 8, \"width\");\n        registerDataName(Interaction.class, 9, \"height\");\n        registerDataName(Interaction.class, 10, \"responsive\");\n\n        // Display\n        registerDataName(Display.class, 8, \"transform_interpolation_start\");\n        registerDataName(Display.class, 9, \"transform_interpolation_duration\");\n        registerDataName(Display.class, 10, \"movement_interpolation_duration\");\n        registerDataName(Display.class, 11, \"translation\");\n        registerDataName(Display.class, 12, \"scale\");\n        registerDataName(Display.class, 13, \"left_rotation\");\n        registerDataName(Display.class, 14, \"right_rotation\");\n        registerDataName(Display.class, 15, \"billboard\");\n        registerDataName(Display.class, 16, \"brightness\");\n        registerDataName(Display.class, 17, \"view_range\");\n        registerDataName(Display.class, 18, \"shadow_radius\");\n        registerDataName(Display.class, 19, \"shadow_strength\");\n        registerDataName(Display.class, 20, \"width\");\n        registerDataName(Display.class, 21, \"height\");\n        registerDataName(Display.class, 22, \"glow_color\");\n\n        // Block display\n        registerDataName(Display.BlockDisplay.class, 23, \"material\");\n\n        // Item display\n        registerDataName(Display.ItemDisplay.class, 23, \"item\");\n        registerDataName(Display.ItemDisplay.class, 24, \"model_transform\");\n\n        // Text display\n        registerDataName(Display.TextDisplay.class, 23, \"text\");\n        registerDataName(Display.TextDisplay.class, 24, \"line_width\");\n        registerDataName(Display.TextDisplay.class, 25, \"background_color\");\n        registerDataName(Display.TextDisplay.class, 26, \"text_opacity\");\n        registerDataName(Display.TextDisplay.class, 27, \"text_display_flags\");\n\n        // Thrown item projectile\n        registerDataName(ThrowableProjectile.class, 8, \"item\");\n\n        // Eye of ender\n        registerDataName(EyeOfEnder.class, 8, \"item\");\n\n        // Falling block\n        registerDataName(FireworkRocketEntity.class, 8, \"spawn_position\");\n\n        // Area effect cloud\n        registerDataName(AreaEffectCloud.class, 8, \"radius\");\n        registerDataName(AreaEffectCloud.class, 9, \"color\");\n        registerDataName(AreaEffectCloud.class, 10, \"waiting\");\n        registerDataName(AreaEffectCloud.class, 11, \"particle\");\n\n        // Fishing hook\n        registerDataName(FishingHook.class, 8, \"hooked_entity_id\");\n        registerDataName(FishingHook.class, 9, \"catchable\");\n\n        // Abstract arrow\n        registerDataName(AbstractArrow.class, 8, \"abstract_arrow_flags\");\n        registerDataName(AbstractArrow.class, 9, \"piercing_level\");\n\n        // Arrow\n        registerDataName(Arrow.class, 10, \"color\");\n\n        // Thrown trident\n        registerDataName(ThrownTrident.class, 10, \"loyalty_level\");\n        registerDataName(ThrownTrident.class, 11, \"enchantment_glint\");\n\n        // Boat\n        registerDataName(Boat.class, 8, \"shaking_ticks\");\n        registerDataName(Boat.class, 9, \"shaking_direction\");\n        registerDataName(Boat.class, 10, \"damage_taken\");\n        registerDataName(Boat.class, 11, \"type\");\n        registerDataName(Boat.class, 12, \"left_paddle_moving\");\n        registerDataName(Boat.class, 13, \"right_paddle_moving\");\n        registerDataName(Boat.class, 14, \"bubble_shaking_ticks\");\n\n        // End crystal\n        registerDataName(EndCrystal.class, 8, \"beam_target\");\n        registerDataName(EndCrystal.class, 9, \"showing_bottom\");\n\n        // Small fireball\n        registerDataName(SmallFireball.class, 8, \"item\");\n\n        // Fireball\n        registerDataName(Fireball.class, 8, \"item\");\n\n        // Wither skull\n        registerDataName(WitherSkull.class, 8, \"invulnerable\");\n\n        // Firework rocket\n        registerDataName(FireworkRocketEntity.class, 8, \"item\");\n        registerDataName(FireworkRocketEntity.class, 9, \"shooter_id\");\n        registerDataName(FireworkRocketEntity.class, 10, \"shot_at_angle\");\n\n        // Item frame\n        registerDataName(ItemFrame.class, 8, \"item\");\n        registerDataName(ItemFrame.class, 9, \"rotation\");\n\n        // Painting\n        registerDataName(Painting.class, 8, \"painting_variant\");\n\n        // Living entity\n        registerDataName(LivingEntity.class, 8, \"living_entity_flags\");\n        registerDataName(LivingEntity.class, 9, \"health\");\n        registerDataName(LivingEntity.class, 10, \"potion_effect_color\");\n        registerDataName(LivingEntity.class, 11, \"is_potion_effect_ambient\");\n        registerDataName(LivingEntity.class, 12, \"arrows_in_body\");\n        registerDataName(LivingEntity.class, 13, \"bee_stingers_in_body\");\n        registerDataName(LivingEntity.class, 14, \"bed_location\");\n\n        // Player\n        registerDataName(Player.class, 15, \"additional_hearts\");\n        registerDataName(Player.class, 16, \"score\");\n        registerDataName(Player.class, 17, \"skin_parts\");\n        registerDataName(Player.class, 18, \"main_hand\");\n        registerDataName(Player.class, 19, \"left_shoulder_entity\");\n        registerDataName(Player.class, 20, \"right_shoulder_entity\");\n\n        // Armor stand\n        registerDataName(ArmorStand.class, 15, \"armor_stand_flags\");\n        registerDataName(ArmorStand.class, 16, \"head_rotation\");\n        registerDataName(ArmorStand.class, 17, \"body_rotation\");\n        registerDataName(ArmorStand.class, 18, \"left_arm_rotation\");\n        registerDataName(ArmorStand.class, 19, \"right_arm_rotation\");\n        registerDataName(ArmorStand.class, 20, \"left_leg_rotation\");\n        registerDataName(ArmorStand.class, 21, \"right_leg_rotation\");\n\n        // Mob\n        registerDataName(Mob.class, 15, \"mob_flags\");\n\n        // Bat flags\n        registerDataName(Bat.class, 16, \"bat_flags\");\n\n        // Dolphin\n        registerDataName(Dolphin.class, 16, \"treasure_location\");\n        registerDataName(Dolphin.class, 17, \"has_fish\");\n        registerDataName(Dolphin.class, 18, \"moisture_level\");\n\n        // Abstract Fish\n        registerDataName(AbstractFish.class, 16, \"from_bucket\");\n\n        // PufferFish\n        registerDataName(Pufferfish.class, 17, \"puff_state\");\n\n        // Tropical fish\n        registerDataName(TropicalFish.class, 17, \"variant\");\n\n        // Ageable mob\n        registerDataName(AgeableMob.class, 16, \"is_baby\");\n\n        // Sniffer\n        registerDataName(Sniffer.class, 17, \"sniffer_state\");\n        registerDataName(Sniffer.class, 18, \"finish_dig_time\");\n\n        // Abstract horse\n        registerDataName(AbstractHorse.class, 17, \"horse_flags\");\n\n        // Horse\n        registerDataName(Horse.class, 18, \"variant\");\n\n        // Camel\n        registerDataName(Camel.class, 18, \"is_dashing\");\n        registerDataName(Camel.class, 19, \"last_pose_change\");\n\n        // Chested horse\n        registerDataName(AbstractChestedHorse.class, 18, \"has_chest\");\n\n        // Llama\n        registerDataName(Llama.class, 19, \"strength\");\n        registerDataName(Llama.class, 20, \"carpet_color\");\n        registerDataName(Llama.class, 21, \"variant\");\n\n        // Axolotl\n        registerDataName(Axolotl.class, 17, \"variant\");\n        registerDataName(Axolotl.class, 18, \"playing_dead\");\n        registerDataName(Axolotl.class, 19, \"from_bucket\");\n\n        // Bee\n        registerDataName(Bee.class, 17, \"bee_flags\");\n        registerDataName(Bee.class, 18, \"anger_time\");\n\n        // Fox\n        registerDataName(Fox.class, 17, \"type\");\n        registerDataName(Fox.class, 18, \"fox_flags\");\n        registerDataName(Fox.class, 19, \"first_trusted_uuid\");\n        registerDataName(Fox.class, 20, \"second_trusted_uuid\");\n\n        // Frog\n        registerDataName(Frog.class, 17, \"variant\");\n        registerDataName(Frog.class, 18, \"target_id\");\n\n        // Ocelot\n        registerDataName(Ocelot.class, 17, \"is_trusting\");\n\n        // Panda\n        registerDataName(Panda.class, 17, \"ask_for_bamboo_timer\");\n        registerDataName(Panda.class, 18, \"sneeze_timer\");\n        registerDataName(Panda.class, 19, \"eat_timer\");\n        registerDataName(Panda.class, 20, \"main_gene\");\n        registerDataName(Panda.class, 21, \"hidden_gene\");\n        registerDataName(Panda.class, 22, \"panda_flags\");\n\n        // Pig\n        registerDataName(Pig.class, 17, \"has_saddle\");\n        registerDataName(Pig.class, 18, \"boost_ticks\");\n\n        // Rabbit\n        registerDataName(Rabbit.class, 17, \"type\");\n\n        // Turtle\n        registerDataName(Turtle.class, 17, \"home_location\");\n        registerDataName(Turtle.class, 18, \"has_egg\");\n        registerDataName(Turtle.class, 19, \"laying_egg\");\n        registerDataName(Turtle.class, 20, \"travel_location\");\n        registerDataName(Turtle.class, 21, \"going_home\");\n        registerDataName(Turtle.class, 20, \"traveling\");\n\n        // Polar bear\n        registerDataName(PolarBear.class, 17, \"standing_up\");\n\n        // Hoglin\n        registerDataName(Hoglin.class, 17, \"immune_to_zombification\");\n\n        // Mooshroom\n        registerDataName(MushroomCow.class, 17, \"variant\");\n\n        // Sheep\n        registerDataName(Sheep.class, 17, \"sheep_wool_flags\");\n\n        // Strider\n        registerDataName(Strider.class, 17, \"boost_ticks\");\n        registerDataName(Strider.class, 18, \"shaking\");\n        registerDataName(Strider.class, 19, \"has_saddle\");\n\n        // Tamable animal\n        registerDataName(TamableAnimal.class, 17, \"tamable_animal_flags\");\n        registerDataName(TamableAnimal.class, 18, \"owner\");\n\n        // Cat\n        registerDataName(Cat.class, 19, \"variant\");\n        registerDataName(Cat.class, 20, \"lying\");\n        registerDataName(Cat.class, 20, \"relaxed\");\n        registerDataName(Cat.class, 21, \"collar_color\");\n\n        // Wolf\n        registerDataName(Wolf.class, 19, \"begging\");\n        registerDataName(Wolf.class, 20, \"collar_color\");\n        registerDataName(Wolf.class, 21, \"anger_time\");\n\n        // Parrot\n        registerDataName(Parrot.class, 19, \"variant\");\n\n        // Abstract villager\n        registerDataName(AbstractVillager.class, 17, \"head_shake_ticks\");\n\n        // Villager\n        registerDataName(Villager.class, 18, \"villager_data\");\n\n        // Iron golem\n        registerDataName(IronGolem.class, 16, \"iron_golem_flags\");\n\n        // Snow golem\n        registerDataName(SnowGolem.class, 16, \"snow_golem_pumpkin_flags\");\n\n        // Shulker\n        registerDataName(Shulker.class, 16, \"attach_face\");\n        registerDataName(Shulker.class, 17, \"attachment_location\");\n        registerDataName(Shulker.class, 18, \"peek\");\n        registerDataName(Shulker.class, 19, \"color\");\n\n        // Base piglin\n        registerDataName(AbstractPiglin.class, 16, \"immune_to_zombification\");\n\n        // Piglin\n        registerDataName(Piglin.class, 17, \"is_baby\");\n        registerDataName(Piglin.class, 18, \"charging_crossbow\");\n        registerDataName(Piglin.class, 19, \"dancing\");\n\n        // Blaze\n        registerDataName(Blaze.class, 16, \"blaze_flags\");\n\n        // Creeper\n        registerDataName(Creeper.class, 16, \"state\");\n        registerDataName(Creeper.class, 17, \"charged\");\n        registerDataName(Creeper.class, 18, \"ignited\");\n\n        // Goat\n        registerDataName(Goat.class, 17, \"screaming\");\n        registerDataName(Goat.class, 18, \"has_left_horn\");\n        registerDataName(Goat.class, 19, \"has_right_horn\");\n\n        // Guardian\n        registerDataName(Guardian.class, 16, \"spikes_retracted\");\n        registerDataName(Guardian.class, 17, \"target_id\");\n\n        // Raider\n        registerDataName(Raider.class, 16, \"celebrating\");\n\n        // Pillager\n        registerDataName(Pillager.class, 17, \"charging_crossbow\");\n\n        // Spellcaster illager\n        registerDataName(SpellcasterIllager.class, 17, \"spell\");\n\n        // Witch\n        registerDataName(Witch.class, 17, \"drinking_potion\");\n\n        // Vex\n        registerDataName(Vex.class, 16, \"vex_flags\");\n\n        // Spider\n        registerDataName(Spider.class, 16, \"spider_flags\");\n\n        // Warden\n        registerDataName(Warden.class, 16, \"anger_level\");\n\n        // Wither\n        registerDataName(WitherBoss.class, 16, \"center_head_target\");\n        registerDataName(WitherBoss.class, 17, \"left_head_target\");\n        registerDataName(WitherBoss.class, 18, \"right_head_target\");\n        registerDataName(WitherBoss.class, 19, \"invulnerable_time\");\n\n        // Zoglin\n        registerDataName(Zoglin.class, 16, \"is_baby\");\n\n        // Zombie\n        registerDataName(Zombie.class, 16, \"is_baby\");\n        registerDataName(Zombie.class, 17, \"type\"); // Unused\n        registerDataName(Zombie.class, 18, \"converting_in_water\");\n\n        // Zombie villager\n        registerDataName(ZombieVillager.class, 19, \"is_converting\");\n        registerDataName(ZombieVillager.class, 20, \"villager_data\");\n\n        // Enderman\n        registerDataName(EnderMan.class, 16, \"carried_block\");\n        registerDataName(EnderMan.class, 17, \"screaming\");\n        registerDataName(EnderMan.class, 18, \"staring\");\n\n        // Ender dragon\n        registerDataName(EnderDragon.class, 16, \"phase\");\n\n        // Ghast\n        registerDataName(Ghast.class, 16, \"attacking\");\n\n        // Phantom\n        registerDataName(Phantom.class, 16, \"size\");\n\n        // Slime\n        registerDataName(Slime.class, 16, \"size\");\n\n        // Abstract minecart\n        registerDataName(AbstractMinecart.class, 8, \"shaking_ticks\");\n        registerDataName(AbstractMinecart.class, 9, \"shaking_direction\");\n        registerDataName(AbstractMinecart.class, 10, \"damage_taken\");\n        registerDataName(AbstractMinecart.class, 11, \"display_block_id\");\n        registerDataName(AbstractMinecart.class, 12, \"display_block_y\");\n        registerDataName(AbstractMinecart.class, 13, \"show_display_block\");\n\n        // Minecraft furnace\n        registerDataName(MinecartFurnace.class, 14, \"has_fuel\");\n\n        // Minecraft command block\n        registerDataName(MinecartCommandBlock.class, 14, \"command\");\n        registerDataName(MinecartCommandBlock.class, 15, \"last_output\");\n\n        // Primed TNT\n        registerDataName(PrimedTnt.class, 8, \"fuse_ticks\");\n    }\n\n    public static int getIdForName(Class<? extends Entity> entityClass, String name) {\n        Class<?> currentClass = entityClass;\n        int id = getIdFromClass(currentClass, name);\n        while (id == -1) {\n            currentClass = currentClass.getSuperclass();\n            if (currentClass == Object.class) {\n                break;\n            }\n            id = getIdFromClass(currentClass, name);\n        }\n        return id;\n    }\n\n    private static int getIdFromClass(Class<?> entityClass, String name) {\n        Map<String, Integer> nameToId = entityDataNames.get(entityClass);\n        int id = nameToId != null ? nameToId.getOrDefault(name, -1) : -1;\n        if (id == -1 && ArgumentHelper.matchesInteger(name)) {\n            id = new ElementTag(name).asInt();\n        }\n        return id;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/EntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityState;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.scripts.commands.core.ReflectionSetCommand;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport io.netty.buffer.Unpooled;\r\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.commands.arguments.EntityAnchorArgument;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.dedicated.DedicatedPlayerList;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.server.players.PlayerList;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.InteractionHand;\r\nimport net.minecraft.world.damagesource.CombatRules;\r\nimport net.minecraft.world.damagesource.DamageSource;\r\nimport net.minecraft.world.damagesource.DamageSources;\r\nimport net.minecraft.world.entity.Mob;\r\nimport net.minecraft.world.entity.MoverType;\r\nimport net.minecraft.world.entity.PositionMoveRotation;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.goal.Goal;\r\nimport net.minecraft.world.entity.ai.navigation.PathNavigation;\r\nimport net.minecraft.world.entity.animal.armadillo.Armadillo;\r\nimport net.minecraft.world.entity.item.FallingBlockEntity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.item.PrimedTnt;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.level.ClipContext;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.pathfinder.Path;\r\nimport net.minecraft.world.phys.AABB;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport net.minecraft.world.phys.HitResult;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport net.minecraft.world.phys.shapes.CollisionContext;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.attribute.Attribute;\r\nimport org.bukkit.attribute.AttributeInstance;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.*;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftLocation;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\nimport org.bukkit.util.BoundingBox;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.function.BiConsumer;\r\n\r\npublic class EntityHelperImpl extends EntityHelper {\r\n\r\n    public static final MethodHandle ENTITY_ONGROUND_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_onGround, boolean.class);\r\n\r\n    public static final EntityDataAccessor<Boolean> ENDERMAN_DATA_ACCESSOR_SCREAMING = ReflectionHelper.getFieldValue(EnderMan.class, ReflectionMappingsInfo.EnderMan_DATA_CREEPY, null);\r\n\r\n    @Override\r\n    public void setInvisible(Entity entity, boolean invisible) {\r\n        ((CraftEntity) entity).getHandle().setInvisible(invisible);\r\n    }\r\n\r\n    @Override\r\n    public boolean isInvisible(Entity entity) {\r\n        return ((CraftEntity) entity).getHandle().isInvisible();\r\n    }\r\n\r\n    @Override\r\n    public void setPose(Entity entity, Pose pose) {\r\n        ((CraftEntity) entity).getHandle().setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]);\r\n    }\r\n\r\n    @Override\r\n    public double getDamageTo(LivingEntity attacker, Entity target) {\r\n        double damage = 0;\r\n        AttributeInstance attrib = attacker.getAttribute(Attribute.ATTACK_DAMAGE);\r\n        if (attrib != null) {\r\n            damage = attrib.getValue();\r\n        }\r\n        if (damage <= 0) {\r\n            return 0;\r\n        }\r\n        if (target == null) {\r\n            // Target is required as of MC 1.21, so if unspecified just assume target is equivalent to attacker\r\n            target = attacker;\r\n        }\r\n        DamageSource source;\r\n        net.minecraft.world.entity.Entity nmsTarget = ((CraftEntity) target).getHandle();\r\n        ServerLevel nmsWorld = ((CraftWorld) attacker.getWorld()).getHandle();\r\n        if (attacker instanceof CraftPlayer playerAttacker) {\r\n            source = nmsTarget.level().damageSources().playerAttack(playerAttacker.getHandle());\r\n        }\r\n        else {\r\n            source = nmsTarget.level().damageSources().mobAttack(((CraftLivingEntity) attacker).getHandle());\r\n        }\r\n        net.minecraft.world.entity.LivingEntity nmsLivingTarget = nmsTarget instanceof net.minecraft.world.entity.LivingEntity living ? living : null;\r\n        if (nmsLivingTarget != null ? nmsLivingTarget.isInvulnerableTo(nmsWorld, source) : nmsTarget.isInvulnerableToBase(source)) {\r\n            return 0;\r\n        }\r\n        if (attacker.getEquipment() != null) {\r\n            damage = EnchantmentHelper.modifyDamage(nmsWorld, CraftItemStack.asNMSCopy(attacker.getEquipment().getItemInMainHand()), nmsTarget, source, (float) damage);\r\n        }\r\n        if (nmsLivingTarget == null) {\r\n            return damage;\r\n        }\r\n        damage = CombatRules.getDamageAfterAbsorb(nmsLivingTarget, (float) damage, source, (float) nmsLivingTarget.getArmorValue(), (float) nmsLivingTarget.getAttributeValue(Attributes.ARMOR_TOUGHNESS));\r\n        float enchantDamageModifier = EnchantmentHelper.getDamageProtection(nmsWorld, nmsLivingTarget, source);\r\n        if (enchantDamageModifier > 0) {\r\n            damage = CombatRules.getDamageAfterMagicAbsorb((float) damage, enchantDamageModifier);\r\n        }\r\n        return damage;\r\n    }\r\n\r\n    public static final MethodHandle LIVINGENTITY_AUTOSPINATTACK_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.LivingEntity.class, ReflectionMappingsInfo.LivingEntity_autoSpinAttackTicks);\r\n    public static final MethodHandle LIVINGENTITY_SETLIVINGENTITYFLAG = ReflectionHelper.getMethodHandle(net.minecraft.world.entity.LivingEntity.class, ReflectionMappingsInfo.LivingEntity_setLivingEntityFlag_method, int.class, boolean.class);\r\n\r\n    @Override\r\n    public void setRiptide(Entity entity, boolean state) {\r\n        try {\r\n            net.minecraft.world.entity.LivingEntity nmsEntity = ((CraftLivingEntity) entity).getHandle();\r\n            LIVINGENTITY_AUTOSPINATTACK_SETTER.invoke(nmsEntity, state ? 0 : 1);\r\n            LIVINGENTITY_SETLIVINGENTITYFLAG.invoke(nmsEntity, 4, true);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void forceInteraction(Player player, Location location) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ((CraftBlock) location.getBlock()).getNMS().useItemOn(nmsPlayer.getMainHandItem(), ((CraftWorld) location.getWorld()).getHandle(),\r\n                nmsPlayer, InteractionHand.MAIN_HAND,\r\n                new BlockHitResult(new Vec3(0, 0, 0), null, CraftLocation.toBlockPosition(location), false));\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Entity entity) {\r\n        CompoundTag nmsTag = Handler.useValueOutput(((CraftEntity) entity).getHandle()::saveAsPassenger);\r\n        return NBTAdapter.toAPI(nmsTag);\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Entity entity, CompoundBinaryTag compoundTag) {\r\n        Handler.useValueInput(NBTAdapter.toNMS(compoundTag), ((CraftEntity) entity).getHandle()::load);\r\n    }\r\n\r\n    /*\r\n        Entity Movement\r\n     */\r\n\r\n    private final static Map<UUID, BukkitTask> followTasks = new HashMap<>();\r\n\r\n    @Override\r\n    public void stopFollowing(Entity follower) {\r\n        if (follower == null) {\r\n            return;\r\n        }\r\n        UUID uuid = follower.getUniqueId();\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void stopWalking(Entity entity) {\r\n        if (((CraftEntity) entity).getHandle() instanceof Mob nmsMob) {\r\n            nmsMob.getNavigation().stop();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void follow(final Entity target, final Entity follower, final double speed, final double lead,\r\n                       final double maxRange, final boolean allowWander, final boolean teleport) {\r\n        if (target == null || follower == null) {\r\n            return;\r\n        }\r\n\r\n        final net.minecraft.world.entity.Entity nmsEntityFollower = ((CraftEntity) follower).getHandle();\r\n        if (!(nmsEntityFollower instanceof Mob nmsFollower)) {\r\n            return;\r\n        }\r\n        final PathNavigation followerNavigation = nmsFollower.getNavigation();\r\n\r\n        UUID uuid = follower.getUniqueId();\r\n\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n\r\n        final int locationNearInt = (int) Math.floor(lead);\r\n        final boolean hasMax = maxRange > lead;\r\n\r\n        followTasks.put(follower.getUniqueId(), new BukkitRunnable() {\r\n\r\n            private boolean inRadius = false;\r\n\r\n            public void run() {\r\n                if (!target.isValid() || !follower.isValid()) {\r\n                    this.cancel();\r\n                }\r\n                followerNavigation.setSpeedModifier(2D);\r\n                Location targetLocation = target.getLocation();\r\n                Path path;\r\n\r\n                if (hasMax && !Utilities.checkLocation(targetLocation, follower.getLocation(), maxRange)\r\n                        && !target.isDead() && target.isOnGround()) {\r\n                    if (!inRadius) {\r\n                        if (teleport) {\r\n                            follower.teleport(Utilities.getWalkableLocationNear(targetLocation, locationNearInt));\r\n                        }\r\n                        else {\r\n                            cancel();\r\n                        }\r\n                    }\r\n                    else {\r\n                        inRadius = false;\r\n                        path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                        if (path != null) {\r\n                            followerNavigation.moveTo(path, 1D);\r\n                            followerNavigation.setSpeedModifier(2D);\r\n                        }\r\n                    }\r\n                }\r\n                else if (!inRadius && !Utilities.checkLocation(targetLocation, follower.getLocation(), lead)) {\r\n                    path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                    if (path != null) {\r\n                        followerNavigation.moveTo(path, 1D);\r\n                        followerNavigation.setSpeedModifier(2D);\r\n                    }\r\n                }\r\n                else {\r\n                    inRadius = true;\r\n                }\r\n                if (inRadius && !allowWander) {\r\n                    followerNavigation.stop();\r\n                }\r\n                nmsFollower.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n        }.runTaskTimer(NMSHandler.getJavaPlugin(), 0, 10));\r\n    }\r\n\r\n    @Override\r\n    public void walkTo(final LivingEntity entity, Location location, Double speed, final Runnable callback) {\r\n        if (entity == null || location == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        if (!(nmsEntity instanceof final Mob nmsMob)) {\r\n            return;\r\n        }\r\n        final PathNavigation entityNavigation = nmsMob.getNavigation();\r\n        final Path path;\r\n        final boolean aiDisabled = !entity.hasAI();\r\n        if (aiDisabled) {\r\n            entity.setAI(true);\r\n            try {\r\n                ENTITY_ONGROUND_SETTER.invoke(nmsMob, true);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        path = entityNavigation.createPath(location.getX(), location.getY(), location.getZ(), 1);\r\n        if (path != null) {\r\n            nmsMob.goalSelector.enableControlFlag(Goal.Flag.MOVE);\r\n            entityNavigation.moveTo(path, 1D);\r\n            final double oldSpeed = nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).getBaseValue();\r\n            if (speed != null) {\r\n                nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    if (!entity.isValid()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        cancel();\r\n                        return;\r\n                    }\r\n                    if (aiDisabled && entity instanceof Wolf wolf) {\r\n                        wolf.setAngry(false);\r\n                    }\r\n                    if (entityNavigation.isDone() || path.isDone()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        if (speed != null) {\r\n                            nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(oldSpeed);\r\n                        }\r\n                        if (aiDisabled) {\r\n                            entity.setAI(false);\r\n                        }\r\n                        cancel();\r\n                    }\r\n                }\r\n            }.runTaskTimer(NMSHandler.getJavaPlugin(), 1, 1);\r\n        }\r\n        //if (!Utilities.checkLocation(location, entity.getLocation(), 20)) {\r\n        // TODO: generate waypoints to the target location?\r\n        else {\r\n            entity.teleport(location);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendAllUpdatePackets(Entity entity) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level()).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        if (entityTracker == null) {\r\n            return;\r\n        }\r\n        try {\r\n            ServerEntity serverEntity = (ServerEntity) PacketHelperImpl.ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n            serverEntity.sendChanges();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    /*\r\n        Hide Entity\r\n     */\r\n\r\n    @Override\r\n    public void sendHidePacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player player) {\r\n            pl.hidePlayer(Denizen.getInstance(), player);\r\n            return;\r\n        }\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) pl).getHandle();\r\n        if (nmsPlayer.connection != null && !pl.equals(entity)) {\r\n            ChunkMap.TrackedEntity entry = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                entry.removePlayer(nmsPlayer);\r\n            }\r\n            if (Denizen.supportsPaper) { // Workaround for Paper issue\r\n                nmsPlayer.connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId()));\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendShowPacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player player) {\r\n            pl.showPlayer(Denizen.getInstance(), player);\r\n            return;\r\n        }\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) pl).getHandle();\r\n        if (nmsPlayer.connection != null && !pl.equals(entity)) {\r\n            ChunkMap.TrackedEntity entry = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                entry.removePlayer(nmsPlayer);\r\n                entry.updatePlayer(nmsPlayer);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void rotate(Entity entity, float yaw, float pitch) {\r\n        // If this entity is a real player instead of a player type NPC,\r\n        // it will appear to be online\r\n        if (entity instanceof Player player && player.isOnline()) {\r\n            NetworkInterceptHelper.enable();\r\n            float relYaw = (yaw - entity.getLocation().getYaw()) % 360;\r\n            if (relYaw > 180) {\r\n                relYaw -= 360;\r\n            }\r\n            final float actualRelYaw = relYaw;\r\n            float relPitch = pitch - entity.getLocation().getPitch();\r\n            NMSHandler.packetHelper.sendRelativeLookPacket(player, actualRelYaw, relPitch);\r\n        }\r\n        else if (entity instanceof LivingEntity) {\r\n            if (entity instanceof EnderDragon) {\r\n                yaw = normalizeYaw(yaw - 180);\r\n            }\r\n            look(entity, yaw, pitch);\r\n        }\r\n        else {\r\n            net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n            handle.setYRot(yaw - 360);\r\n            handle.setXRot(pitch);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBaseYaw(LivingEntity entity) {\r\n        return ((CraftLivingEntity) entity).getHandle().yBodyRot;\r\n    }\r\n\r\n    @Override\r\n    public void look(Entity entity, float yaw, float pitch) {\r\n        net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n        if (handle == null) {\r\n            Debug.echoError(\"Cannot set look direction for unspawned entity \" + entity.getUniqueId());\r\n            return;\r\n        }\r\n        handle.setYRot(yaw);\r\n        if (handle instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity) {\r\n            while (yaw < -180.0F) {\r\n                yaw += 360.0F;\r\n            }\r\n            while (yaw >= 180.0F) {\r\n                yaw -= 360.0F;\r\n            }\r\n            nmsLivingEntity.yBodyRotO = yaw;\r\n            if (!(handle instanceof net.minecraft.world.entity.player.Player)) {\r\n                nmsLivingEntity.setYBodyRot(yaw);\r\n            }\r\n            nmsLivingEntity.setYHeadRot(yaw);\r\n        }\r\n        handle.setXRot(pitch);\r\n    }\r\n\r\n    private static HitResult rayTrace(World world, Vector start, Vector end) {\r\n        try {\r\n            NMSHandler.chunkHelper.changeChunkServerThread(world);\r\n            return ((CraftWorld) world).getHandle().clip(new ClipContext(new Vec3(start.getX(), start.getY(), start.getZ()),\r\n                    new Vec3(end.getX(), end.getY(), end.getZ()),\r\n                    ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, CollisionContext.empty()));\r\n        }\r\n        finally {\r\n            NMSHandler.chunkHelper.restoreServerThread(world);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean canTrace(World world, Vector start, Vector end) {\r\n        HitResult pos = rayTrace(world, start, end);\r\n        if (pos == null) {\r\n            return true;\r\n        }\r\n        return pos.getType() == HitResult.Type.MISS;\r\n    }\r\n\r\n    @Override\r\n    public void snapPositionTo(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().setPosRaw(vector.getX(), vector.getY(), vector.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void move(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().move(MoverType.SELF, new Vec3(vector.getX(), vector.getY(), vector.getZ()));\r\n    }\r\n\r\n    @Override\r\n    public boolean internalLook(Player player, Location at) {\r\n        PacketHelperImpl.send(player, new ClientboundPlayerLookAtPacket(EntityAnchorArgument.Anchor.EYES, at.getX(), at.getY(), at.getZ()));\r\n        return true;\r\n    }\r\n\r\n    public static long entityToPacket(double x) {\r\n        return Mth.lfloor(x * 4096.0D);\r\n    }\r\n\r\n    @Override\r\n    public void fakeMove(Entity entity, Vector vector) {\r\n        long x = entityToPacket(vector.getX());\r\n        long y = entityToPacket(vector.getY());\r\n        long z = entityToPacket(vector.getZ());\r\n        ClientboundMoveEntityPacket packet = new ClientboundMoveEntityPacket.Pos(entity.getEntityId(), (short) x, (short) y, (short) z, entity.isOnGround());\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void fakeTeleport(Entity entity, Location location) {\r\n        FriendlyByteBuf packetData = new FriendlyByteBuf(Unpooled.buffer());\r\n        // Referenced from ClientboundTeleportEntityPacket source\r\n        packetData.writeVarInt(entity.getEntityId());\r\n        packetData.writeDouble(location.getX());\r\n        packetData.writeDouble(location.getY());\r\n        packetData.writeDouble(location.getZ());\r\n        packetData.writeByte((byte)((int)(location.getYaw() * 256.0F / 360.0F)));\r\n        packetData.writeByte((byte)((int)(location.getPitch() * 256.0F / 360.0F)));\r\n        packetData.writeBoolean(entity.isOnGround());\r\n        ClientboundTeleportEntityPacket packet = ClientboundTeleportEntityPacket.STREAM_CODEC.decode(packetData);\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void clientResetLoc(Entity entity) {\r\n        ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(entity.getEntityId(), PositionMoveRotation.of(((CraftEntity) entity).getHandle()), Set.of(), entity.isOnGround());\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Entity entity, Location loc) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        nmsEntity.setYRot(loc.getYaw());\r\n        nmsEntity.setXRot(loc.getPitch());\r\n        if (nmsEntity instanceof ServerPlayer) {\r\n            nmsEntity.teleportTo(loc.getX(), loc.getY(), loc.getZ());\r\n        }\r\n        nmsEntity.setPos(loc.getX(), loc.getY(), loc.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void setBoundingBox(Entity entity, BoundingBox box) {\r\n        ((CraftEntity) entity).getHandle().setBoundingBox(new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()));\r\n    }\r\n\r\n    public static final Field EXPERIENCE_ORB_AGE = ReflectionHelper.getFields(net.minecraft.world.entity.ExperienceOrb.class).get(ReflectionMappingsInfo.ExperienceOrb_age, int.class);\r\n\r\n    @Override\r\n    public void setTicksLived(Entity entity, int ticks) {\r\n        // Bypass Spigot's must-be-at-least-1-tick requirement, as negative tick counts are useful\r\n        ((CraftEntity) entity).getHandle().tickCount = ticks;\r\n        if (entity instanceof CraftFallingBlock craftFallingBlock) {\r\n            craftFallingBlock.getHandle().time = ticks;\r\n        }\r\n        else if (entity instanceof CraftItem craftItem) {\r\n            ((ItemEntity) craftItem.getHandle()).age = ticks;\r\n        }\r\n        else if (entity instanceof CraftExperienceOrb craftExperienceOrb) {\r\n            try {\r\n                EXPERIENCE_ORB_AGE.setInt(craftExperienceOrb.getHandle(), ticks);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHeadAngle(LivingEntity entity, float angle) {\r\n        ((CraftLivingEntity) entity).getHandle().setYHeadRot(angle);\r\n    }\r\n\r\n    @Override\r\n    public void setEndermanAngry(Enderman enderman, boolean angry) {\r\n        ((CraftEnderman) enderman).getHandle().getEntityData().set(ENDERMAN_DATA_ACCESSOR_SCREAMING, angry);\r\n    }\r\n\r\n    public static class FakeDamageSrc extends DamageSource { public DamageSource real; public FakeDamageSrc(DamageSource src) { super(null); real = src; } }\r\n\r\n    public static DamageSources backupDamageSources;\r\n\r\n    public static DamageSources getReusableDamageSources() {\r\n        if (backupDamageSources == null) {\r\n            backupDamageSources = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle().damageSources();\r\n        }\r\n        return backupDamageSources;\r\n    }\r\n\r\n    public static DamageSource getSourceFor(net.minecraft.world.entity.Entity nmsSource, EntityDamageEvent.DamageCause cause, net.minecraft.world.entity.Entity nmsSourceProvider) {\r\n        DamageSources sources = nmsSourceProvider == null ? getReusableDamageSources() : nmsSourceProvider.level().damageSources();\r\n        DamageSource src = sources.generic();\r\n        if (nmsSource != null) {\r\n            if (nmsSource instanceof net.minecraft.world.entity.player.Player nmsPlayer) {\r\n                src = nmsSource.level().damageSources().playerAttack(nmsPlayer);\r\n            }\r\n            else if (nmsSource instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity) {\r\n                src = nmsSource.level().damageSources().mobAttack(nmsLivingEntity);\r\n            }\r\n        }\r\n        if (cause == null) {\r\n            return src;\r\n        }\r\n        return switch (cause) {\r\n            case CONTACT -> sources.cactus();\r\n            case ENTITY_ATTACK -> sources.mobAttack(nmsSource instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity ? nmsLivingEntity : null);\r\n            case ENTITY_SWEEP_ATTACK -> src != sources.generic() ? src.sweep() : src;\r\n            case PROJECTILE -> sources.thrown(nmsSource, nmsSource != null && nmsSource.getBukkitEntity() instanceof Projectile projectile\r\n                        && projectile.getShooter() instanceof CraftEntity shooter ? shooter.getHandle() : null);\r\n            case SUFFOCATION -> sources.inWall();\r\n            case FALL -> sources.fall();\r\n            case FIRE -> sources.inFire();\r\n            case FIRE_TICK -> sources.onFire();\r\n            case MELTING -> sources.melting();\r\n            case LAVA -> sources.lava();\r\n            case DROWNING -> sources.drown();\r\n            case BLOCK_EXPLOSION -> nmsSource instanceof PrimedTnt primedTnt ? sources.explosion(primedTnt, primedTnt.getOwner()) : sources.explosion(null);\r\n            case ENTITY_EXPLOSION -> sources.explosion(nmsSource, null);\r\n            case VOID -> sources.fellOutOfWorld();\r\n            case LIGHTNING -> sources.lightningBolt();\r\n            case STARVATION -> sources.starve();\r\n            case POISON -> sources.poison();\r\n            case MAGIC -> sources.magic();\r\n            case WITHER -> sources.wither();\r\n            case FALLING_BLOCK -> sources.fallingBlock(nmsSource);\r\n            case THORNS -> sources.thorns(nmsSource);\r\n            case DRAGON_BREATH -> sources.dragonBreath();\r\n            case CUSTOM -> sources.generic();\r\n            case FLY_INTO_WALL -> sources.flyIntoWall();\r\n            case HOT_FLOOR -> sources.hotFloor();\r\n            case CAMPFIRE -> sources.campfire();\r\n            case CRAMMING -> sources.cramming();\r\n            case DRYOUT -> sources.dryOut();\r\n            case FREEZE -> sources.freeze();\r\n            case SONIC_BOOM -> sources.sonicBoom(nmsSource);\r\n            case WORLD_BORDER -> sources.outOfBorder();\r\n            case KILL -> sources.genericKill();\r\n            case SUICIDE -> new FakeDamageSrc(src);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void damage(LivingEntity target, float amount, EntityTag source, Location sourceLoc, EntityDamageEvent.DamageCause cause) {\r\n        if (target == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.LivingEntity nmsTarget = ((CraftLivingEntity) target).getHandle();\r\n        net.minecraft.world.entity.Entity nmsSource = source == null ? null : ((CraftEntity) source.getBukkitEntity()).getHandle();\r\n        DamageSource src = getSourceFor(nmsSource, cause, nmsTarget);\r\n        if (src instanceof FakeDamageSrc fakeDamageSrc) {\r\n            src = fakeDamageSrc.real;\r\n            if (fireFakeDamageEvent(target, source, sourceLoc, cause, amount).isCancelled()) {\r\n                return;\r\n            }\r\n        }\r\n        nmsTarget.hurt(src, amount);\r\n    }\r\n\r\n    @Override\r\n    public void setLastHurtBy(LivingEntity mob, LivingEntity damager) {\r\n        ((CraftLivingEntity) mob).getHandle().setLastHurtByMob(((CraftLivingEntity) damager).getHandle());\r\n    }\r\n\r\n    public static final Field FALLINGBLOCK_BLOCK_STATE = ReflectionHelper.getFields(FallingBlockEntity.class).getFirstOfType(BlockState.class);\r\n\r\n    @Override\r\n    public void setFallingBlockType(FallingBlock fallingBlock, BlockData block) {\r\n        BlockState state = ((CraftBlockData) block).getState();\r\n        FallingBlockEntity nmsEntity = ((CraftFallingBlock) fallingBlock).getHandle();\r\n        try {\r\n            FALLINGBLOCK_BLOCK_STATE.set(nmsEntity, state);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public EntityTag getMobSpawnerDisplayEntity(CreatureSpawner spawner) {\r\n        SpawnerBlockEntity nmsSpawner = BlockHelperImpl.getTE((CraftCreatureSpawner) spawner);\r\n        ServerLevel level = ((CraftWorld) spawner.getWorld()).getHandle();\r\n        net.minecraft.world.entity.Entity nmsEntity = nmsSpawner.getSpawner().getOrCreateDisplayEntity(level, nmsSpawner.getBlockPos());\r\n        return new EntityTag(nmsEntity.getBukkitEntity());\r\n    }\r\n\r\n    public static final Field ZOMBIE_INWATERTIME = ReflectionHelper.getFields(net.minecraft.world.entity.monster.zombie.Zombie.class).get(ReflectionMappingsInfo.Zombie_inWaterTime, int.class);\r\n\r\n    @Override\r\n    public int getInWaterTime(Zombie zombie) {\r\n        try {\r\n            return ZOMBIE_INWATERTIME.getInt(((CraftZombie) zombie).getHandle());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return 0;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setInWaterTime(Zombie zombie, int ticks) {\r\n        try {\r\n            ZOMBIE_INWATERTIME.setInt(((CraftZombie) zombie).getHandle(), ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final MethodHandle TRACKING_RANGE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ChunkMap.TrackedEntity.class, int.class);\r\n\r\n    @Override\r\n    public void setTrackingRange(Entity entity, int range) {\r\n        try {\r\n            ChunkMap map = ((CraftWorld) entity.getWorld()).getHandle().getChunkSource().chunkMap;\r\n            ChunkMap.TrackedEntity entry = map.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                TRACKING_RANGE_SETTER.invoke(entry, range);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean isAggressive(org.bukkit.entity.Mob mob) {\r\n        return ((CraftMob) mob).getHandle().isAggressive();\r\n    }\r\n\r\n    @Override\r\n    public void setAggressive(org.bukkit.entity.Mob mob, boolean aggressive) {\r\n        ((CraftMob) mob).getHandle().setAggressive(aggressive);\r\n    }\r\n\r\n    // Use reflection because Paper changes the method return type\r\n    public static final MethodHandle PLAYERLIST_REMOVE = ReflectionHelper.getMethodHandle(PlayerList.class, \"remove\", ServerPlayer.class);\r\n\r\n    @Override\r\n    public void setUUID(Entity entity, UUID id) {\r\n        try {\r\n            net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n            nmsEntity.stopRiding();\r\n            nmsEntity.getPassengers().forEach(net.minecraft.world.entity.Entity::stopRiding);\r\n            Level level = nmsEntity.level();\r\n            DedicatedPlayerList playerList = ((CraftServer) Bukkit.getServer()).getHandle();\r\n            if (nmsEntity instanceof ServerPlayer nmsPlayer) {\r\n                PLAYERLIST_REMOVE.invoke(playerList, nmsPlayer);\r\n            }\r\n            else {\r\n                nmsEntity.remove(net.minecraft.world.entity.Entity.RemovalReason.DISCARDED);\r\n            }\r\n            nmsEntity.unsetRemoved();\r\n            nmsEntity.setUUID(id);\r\n            if (nmsEntity instanceof ServerPlayer nmsPlayer) {\r\n                playerList.placeNewPlayer(DenizenNetworkManagerImpl.getConnection(nmsPlayer), nmsPlayer, new CommonListenerCookie(nmsPlayer.getGameProfile(), nmsPlayer.connection.latency(), nmsPlayer.clientInformation(), nmsPlayer.connection.isTransferred()));\r\n            }\r\n            else {\r\n                level.addFreshEntity(nmsEntity);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final Field SynchedEntityData_itemsById = ReflectionHelper.getFields(SynchedEntityData.class).get(ReflectionMappingsInfo.SynchedEntityData_itemsById);\r\n\r\n    public static Int2ObjectMap<SynchedEntityData.DataItem<Object>> getDataItems(Entity entity) {\r\n        try {\r\n            return (Int2ObjectMap<SynchedEntityData.DataItem<Object>>) SynchedEntityData_itemsById.get(((CraftEntity) entity).getHandle().getEntityData());\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            throw new RuntimeException(e); // Stop the code here to avoid NPEs down the road\r\n        }\r\n    }\r\n\r\n    public static void convertToInternalData(Entity entity, MapTag internalData, BiConsumer<SynchedEntityData.DataItem<Object>, Object> processConverted) {\r\n        Int2ObjectMap<SynchedEntityData.DataItem<Object>> dataItemsById = getDataItems(entity);\r\n        for (Map.Entry<StringHolder, ObjectTag> entry : internalData.entrySet()) {\r\n            int id = EntityDataNameMapper.getIdForName(((CraftEntity) entity).getHandle().getClass(), entry.getKey().low);\r\n            if (id == -1) {\r\n                Debug.echoError(\"Invalid internal data key: \" + entry.getKey());\r\n                return;\r\n            }\r\n            SynchedEntityData.DataItem<Object> dataItem = dataItemsById.get(id);\r\n            if (dataItem == null) {\r\n                Debug.echoError(\"Invalid internal data id '\" + id + \"': couldn't be matched to any internal data for entity of type '\" + entity.getType() + \"'.\");\r\n                return;\r\n            }\r\n            Object converted = ReflectionSetCommand.convertObjectTypeFor(dataItem.getValue().getClass(), entry.getValue());\r\n            if (converted != null) {\r\n                processConverted.accept(dataItem, converted);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public List<Object> convertInternalEntityDataValues(Entity entity, MapTag internalData) {\r\n        List<Object> dataValues = new ArrayList<>(internalData.size());\r\n        convertToInternalData(entity, internalData, (dataItem, converted) -> dataValues.add(PacketHelperImpl.createEntityData(dataItem.getAccessor(), converted)));\r\n        return dataValues;\r\n    }\r\n\r\n    @Override\r\n    public void modifyInternalEntityData(Entity entity, MapTag internalData) {\r\n        SynchedEntityData nmsEntityData = ((CraftEntity) entity).getHandle().getEntityData();\r\n        convertToInternalData(entity, internalData, (dataItem, converted) -> nmsEntityData.set(dataItem.getAccessor(), converted));\r\n    }\r\n\r\n    @Override\r\n    public void startUsingItem(LivingEntity entity, EquipmentSlot hand) {\r\n        ((CraftLivingEntity) entity).getHandle().startUsingItem(hand == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND);\r\n    }\r\n\r\n    @Override\r\n    public void stopUsingItem(LivingEntity entity) {\r\n        ((CraftLivingEntity) entity).getHandle().stopUsingItem();\r\n    }\r\n\r\n    @Override\r\n    public void openHorseInventory(Player player, AbstractHorse horse) {\r\n        net.minecraft.world.entity.animal.equine.AbstractHorse nmsHorse = ((CraftAbstractHorse) horse).getHandle();\r\n        ((CraftPlayer) player).getHandle().openHorseInventory(nmsHorse, nmsHorse.inventory);\r\n    }\r\n\r\n    private CompoundTag getRawEntityNBT(net.minecraft.world.entity.Entity entity) {\r\n        return Handler.useValueOutput(entity::saveWithoutId);\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getRawNBT(Entity entity) {\r\n        return NBTAdapter.toAPI(getRawEntityNBT(((CraftEntity) entity).getHandle()));\r\n    }\r\n\r\n    @Override\r\n    public void modifyRawNBT(Entity entity, CompoundBinaryTag tag) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        CompoundTag nmsTag = NBTAdapter.toNMS(tag);\r\n        CompoundTag nmsMergedTag = getRawEntityNBT(nmsEntity).merge(nmsTag);\r\n        UUID uuid = nmsEntity.getUUID();\r\n        Handler.useValueInput(nmsMergedTag, nmsEntity::load);\r\n        nmsEntity.setUUID(uuid);\r\n    }\r\n\r\n    @Override\r\n    public EntityState.ArmadilloState getArmadilloState(org.bukkit.entity.Armadillo entity) {\r\n        Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle();\r\n        return switch (armadillo.getState()) {\r\n            case IDLE -> EntityState.ArmadilloState.IDLE;\r\n            case ROLLING -> EntityState.ArmadilloState.ROLLING;\r\n            case SCARED -> EntityState.ArmadilloState.SCARED;\r\n            case UNROLLING -> EntityState.ArmadilloState.UNROLLING;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void setArmadilloState(org.bukkit.entity.Armadillo entity, EntityState.ArmadilloState state) {\r\n        Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle();\r\n        armadillo.switchToState(switch (state) {\r\n            case IDLE -> Armadillo.ArmadilloState.IDLE;\r\n            case ROLLING -> Armadillo.ArmadilloState.ROLLING;\r\n            case SCARED -> Armadillo.ArmadilloState.SCARED;\r\n            case UNROLLING -> Armadillo.ArmadilloState.UNROLLING;\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/FishingHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FishingHelper;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.entity.projectile.FishingHook;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.level.storage.loot.BuiltInLootTables;\r\nimport net.minecraft.world.level.storage.loot.LootParams;\r\nimport net.minecraft.world.level.storage.loot.LootTable;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParams;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftFishHook;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;\r\nimport org.bukkit.entity.FishHook;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.List;\r\n\r\npublic class FishingHelperImpl implements FishingHelper {\r\n\r\n    @Override\r\n    public org.bukkit.inventory.ItemStack getResult(FishHook fishHook, CatchType catchType) {\r\n        FishingHook nmsHook = ((CraftFishHook) fishHook).getHandle();\r\n        ItemStack result = switch (catchType) {\r\n            case DEFAULT -> {\r\n                ServerLevel nmsWorld = ((CraftWorld) fishHook.getWorld()).getHandle();\r\n                ItemStack nmsFishingRod = nmsHook.getPlayerOwner().getMainHandItem();\r\n                float f = nmsWorld.random.nextFloat();\r\n                float i = EnchantmentHelper.getFishingTimeReduction(nmsWorld, nmsFishingRod, nmsHook.getPlayerOwner());\r\n                int j = EnchantmentHelper.getFishingLuckBonus(nmsWorld, nmsFishingRod, nmsHook.getPlayerOwner());\r\n                float f1 = 0.1F - i * 0.025F - (float) j * 0.01F;\r\n                float f2 = 0.05F + i * 0.01F - (float) j * 0.01F;\r\n\r\n                f1 = Mth.clamp(f1, 0.0F, 1.0F);\r\n                f2 = Mth.clamp(f2, 0.0F, 1.0F);\r\n                if (f < f1) {\r\n                    yield catchRandomJunk(nmsHook);\r\n                }\r\n                else {\r\n                    f -= f1;\r\n                    if (f < f2) {\r\n                        yield catchRandomTreasure(nmsHook);\r\n                    }\r\n                    else {\r\n                        yield catchRandomFish(nmsHook);\r\n                    }\r\n                }\r\n            }\r\n            case JUNK -> catchRandomJunk(nmsHook);\r\n            case TREASURE -> catchRandomTreasure(nmsHook);\r\n            case FISH -> catchRandomFish(nmsHook);\r\n            default -> null;\r\n        };\r\n        return result != null ? CraftItemStack.asBukkitCopy(result) : null;\r\n    }\r\n\r\n    public ItemStack getRandomReward(FishingHook nmsHook, ResourceKey<LootTable> key) {\r\n        ServerLevel nmsWorld = (ServerLevel) nmsHook.level();\r\n        LootParams nmsLootParams = new LootParams.Builder(nmsWorld)\r\n                .withParameter(LootContextParams.ORIGIN, new Vec3(nmsHook.getX(), nmsHook.getY(), nmsHook.getZ()))\r\n                .withParameter(LootContextParams.TOOL, new ItemStack(Items.FISHING_ROD))\r\n                .create(LootContextParamSets.FISHING);\r\n        List<ItemStack> nmsItems = nmsWorld.getServer().reloadableRegistries().getLootTable(key).getRandomItems(nmsLootParams);\r\n        return nmsItems.get(nmsWorld.random.nextInt(nmsItems.size()));\r\n    }\r\n\r\n    @Override\r\n    public FishHook spawnHook(Location location, Player player) {\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        FishingHook hook = new FishingHook(((CraftPlayer) player).getHandle(), nmsWorld, 0, 0);\r\n        nmsWorld.addFreshEntity(hook, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return (FishHook) hook.getBukkitEntity();\r\n    }\r\n\r\n    private ItemStack catchRandomJunk(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_JUNK);\r\n    }\r\n\r\n    private ItemStack catchRandomTreasure(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_TREASURE);\r\n    }\r\n\r\n    private ItemStack catchRandomFish(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_FISH);\r\n    }\r\n\r\n    public static final Field FISHING_HOOK_NIBBLE = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_nibble, int.class);\r\n    public static final Field FISHING_HOOK_LURE_TIME = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_timeUntilLured, int.class);\r\n    public static final Field FISHING_HOOK_HOOK_TIME = ReflectionHelper.getFields(FishingHook.class).get(ReflectionMappingsInfo.FishingHook_timeUntilHooked, int.class);\r\n\r\n    @Override\r\n    public FishHook getHookFrom(Player player) {\r\n        FishingHook nmsHook = ((CraftPlayer) player).getHandle().fishing;\r\n        if (nmsHook == null) {\r\n            return null;\r\n        }\r\n        return (FishHook) nmsHook.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public void setNibble(FishHook hook, int ticks) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_NIBBLE.setInt(nmsHook, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHookTime(FishHook hook, int ticks) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_HOOK_TIME.setInt(nmsHook, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int getLureTime(FishHook hook) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            return FISHING_HOOK_LURE_TIME.getInt(nmsHook);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public void setLureTime(FishHook hook, int ticks) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_LURE_TIME.setInt(nmsHook, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/ItemHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemComponentsPatch;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.*;\r\nimport com.google.gson.JsonObject;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.mojang.serialization.Dynamic;\r\nimport com.mojang.serialization.JsonOps;\r\nimport net.kyori.adventure.nbt.BinaryTag;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.advancements.criterion.BlockPredicate;\r\nimport net.minecraft.advancements.criterion.DataComponentMatchers;\r\nimport net.minecraft.core.*;\r\nimport net.minecraft.core.component.DataComponentMap;\r\nimport net.minecraft.core.component.DataComponentPatch;\r\nimport net.minecraft.core.component.DataComponentType;\r\nimport net.minecraft.core.component.DataComponents;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.nbt.NbtOps;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.resources.Identifier;\r\nimport net.minecraft.resources.RegistryOps;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.util.datafix.fixes.References;\r\nimport net.minecraft.world.flag.FeatureFlagSet;\r\nimport net.minecraft.world.item.AdventureModePredicate;\r\nimport net.minecraft.world.item.BlockItem;\r\nimport net.minecraft.world.item.Item;\r\nimport net.minecraft.world.item.alchemy.PotionBrewing;\r\nimport net.minecraft.world.item.component.CustomData;\r\nimport net.minecraft.world.item.component.ItemLore;\r\nimport net.minecraft.world.item.component.ResolvableProfile;\r\nimport net.minecraft.world.item.component.TypedEntityData;\r\nimport net.minecraft.world.item.crafting.*;\r\nimport net.minecraft.world.item.crafting.BlastingRecipe;\r\nimport net.minecraft.world.item.crafting.Recipe;\r\nimport net.minecraft.world.item.crafting.ShapelessRecipe;\r\nimport net.minecraft.world.item.crafting.SmithingTransformRecipe;\r\nimport net.minecraft.world.item.crafting.SmokingRecipe;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport net.minecraft.world.level.material.FluidState;\r\nimport net.minecraft.world.level.material.MapColor;\r\nimport net.minecraft.world.level.saveddata.maps.MapId;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftRegistry;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntityType;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.*;\r\nimport org.bukkit.craftbukkit.v1_21_R7.map.CraftMapView;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.*;\r\nimport org.bukkit.inventory.ShapedRecipe;\r\nimport org.bukkit.inventory.SmithingTrimRecipe;\r\nimport org.bukkit.inventory.TransmuteRecipe;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.function.Consumer;\r\nimport java.util.function.Predicate;\r\n\r\npublic class ItemHelperImpl extends ItemHelper {\r\n\r\n    public static net.minecraft.world.item.crafting.RecipeHolder<?> getNMSRecipe(NamespacedKey key) {\r\n        ResourceKey<Recipe<?>> nmsKey = ResourceKey.create(Registries.RECIPE, CraftNamespacedKey.toMinecraft(key));\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().byKey(nmsKey).orElse(null);\r\n    }\r\n\r\n    public static final Field Item_components = ReflectionHelper.getFields(Item.class).get(ReflectionMappingsInfo.Item_components, DataComponentMap.class);\r\n\r\n    public static final Field RecipeManager_featureFlagSet = ReflectionHelper.getFields(RecipeManager.class).getFirstOfType(FeatureFlagSet.class);\r\n\r\n    public void setMaxStackSize(Material material, int size) {\r\n        try {\r\n            ReflectionHelper.getFinalSetter(Material.class, \"maxStack\").invoke(material, size);\r\n            Item nmsItem = BuiltInRegistries.ITEM.getValue(CraftNamespacedKey.toMinecraft(material.getKey()));\r\n            DataComponentMap currentComponents = nmsItem.components();\r\n            Item_components.set(nmsItem, DataComponentMap.composite(currentComponents, DataComponentMap.builder().set(DataComponents.MAX_STACK_SIZE, size).build()));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static RecipeManager getRecipeManager() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager();\r\n    }\r\n\r\n    public static CompoundTag serializeNmsItem(net.minecraft.world.item.ItemStack nmsItem) {\r\n        return (CompoundTag) net.minecraft.world.item.ItemStack.CODEC.encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsItem).getOrThrow();\r\n    }\r\n\r\n    public static net.minecraft.world.item.ItemStack parseNmsItem(CompoundTag nmsTag) {\r\n        return net.minecraft.world.item.ItemStack.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsTag).getOrThrow();\r\n    }\r\n\r\n    public Object recipeManagerFeatureFlagSetCache = null;\r\n\r\n    @Override\r\n    public void blockRecipeFinalization() {\r\n        try {\r\n            RecipeManager manager = getRecipeManager();\r\n            Object flags = RecipeManager_featureFlagSet.get(manager);\r\n            if (flags != null) {\r\n                recipeManagerFeatureFlagSetCache = flags;\r\n                RecipeManager_featureFlagSet.set(manager, null);\r\n\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void restoreRecipeFinalization() {\r\n        try {\r\n            RecipeManager manager = getRecipeManager();\r\n            if (recipeManagerFeatureFlagSetCache != null) {\r\n                RecipeManager_featureFlagSet.set(manager, recipeManagerFeatureFlagSetCache);\r\n                manager.finalizeRecipeLoading();\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void removeRecipes(List<NamespacedKey> keys) {\r\n        blockRecipeFinalization();\r\n        RecipeManager manager = getRecipeManager();\r\n        for (NamespacedKey key: keys) {\r\n            ResourceKey<Recipe<?>> nmsKey = ResourceKey.create(Registries.RECIPE, CraftNamespacedKey.toMinecraft(key));\r\n            manager.removeRecipe(nmsKey);\r\n        }\r\n        restoreRecipeFinalization();\r\n    }\r\n\r\n    @Override\r\n    public Integer burnTime(Material material) {\r\n        return MinecraftServer.getServer().fuelValues().burnDuration(new net.minecraft.world.item.ItemStack(CraftMagicNumbers.getItem(material)));\r\n    }\r\n\r\n    @Override\r\n    public void setShapedRecipeIngredient(ShapedRecipe recipe, char c, ItemStack[] item, boolean exact) {\r\n        if (item.length == 1 && item[0].getType() == Material.AIR) {\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(Material.AIR));\r\n        }\r\n        else if (exact) {\r\n            recipe.setIngredient(c, new RecipeChoice.ExactChoice(item));\r\n        }\r\n        else {\r\n            Material[] mats = new Material[item.length];\r\n            for (int i = 0; i < item.length; i++) {\r\n                mats[i] = item[i].getType();\r\n            }\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(mats));\r\n        }\r\n    }\r\n\r\n    // TODO: Recipe registration should be moved to the API\r\n    public static Ingredient itemArrayToRecipe(ItemStack[] items, boolean exact) {\r\n        if (!exact) {\r\n            return Ingredient.of(Arrays.stream(items).map(item -> CraftMagicNumbers.getItem(item.getType())));\r\n        }\r\n        return Ingredient.ofStacks(Arrays.stream(items).map(CraftItemStack::asNMSCopy).toList());\r\n    }\r\n\r\n    public static ResourceKey<Recipe<?>> createRecipeKey(String name) {\r\n        return ResourceKey.create(Registries.RECIPE, Identifier.fromNamespaceAndPath(\"denizen\", name));\r\n    }\r\n\r\n    @Override\r\n    public void registerFurnaceRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, float exp, int time, String type, boolean exact, String category) {\r\n        ResourceKey<Recipe<?>> key = createRecipeKey(keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        AbstractCookingRecipe recipe;\r\n        CookingBookCategory categoryValue = category == null ? CookingBookCategory.MISC : CookingBookCategory.valueOf(CoreUtilities.toUpperCase(category));\r\n        if (type.equalsIgnoreCase(\"smoker\")) {\r\n            recipe = new SmokingRecipe(group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"blast\")) {\r\n            recipe = new BlastingRecipe(group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"campfire\")) {\r\n            recipe = new CampfireCookingRecipe(group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        else {\r\n            recipe = new SmeltingRecipe(group, categoryValue, itemRecipe, CraftItemStack.asNMSCopy(result), exp, time);\r\n        }\r\n        RecipeHolder<AbstractCookingRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public void registerStonecuttingRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, boolean exact) {\r\n        ResourceKey<Recipe<?>> key = createRecipeKey(keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        StonecutterRecipe recipe = new StonecutterRecipe(group, itemRecipe, CraftItemStack.asNMSCopy(result));\r\n        RecipeHolder<StonecutterRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public void registerSmithingRecipe(String keyName, ItemStack result, ItemStack[] baseItem, boolean baseExact, ItemStack[] upgradeItem, boolean upgradeExact, ItemStack[] templateItem, boolean templateExact) {\r\n        ResourceKey<Recipe<?>> key = createRecipeKey(keyName);\r\n        Ingredient templateItemRecipe = templateItem.length == 0 ? null : itemArrayToRecipe(templateItem, templateExact);\r\n        Ingredient baseItemRecipe = itemArrayToRecipe(baseItem, baseExact);\r\n        Ingredient upgradeItemRecipe = itemArrayToRecipe(upgradeItem, upgradeExact);\r\n        net.minecraft.world.item.ItemStack nmsCopy = CraftItemStack.asNMSCopy(result);\r\n        SmithingTransformRecipe recipe = new SmithingTransformRecipe(Optional.ofNullable(templateItemRecipe), baseItemRecipe, Optional.of(upgradeItemRecipe), new TransmuteResult(nmsCopy.getItemHolder(), nmsCopy.getCount(), nmsCopy.getComponentsPatch()));\r\n        RecipeHolder<SmithingTransformRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public void registerShapelessRecipe(String keyName, String group, ItemStack result, List<ItemStack[]> ingredients, boolean[] exact, String category) {\r\n        ResourceKey<Recipe<?>> key = createRecipeKey(keyName);\r\n        ArrayList<Ingredient> ingredientList = new ArrayList<>();\r\n        CraftingBookCategory categoryValue = category == null ? CraftingBookCategory.MISC : CraftingBookCategory.valueOf(CoreUtilities.toUpperCase(category));\r\n        for (int i = 0; i < ingredients.size(); i++) {\r\n            ingredientList.add(itemArrayToRecipe(ingredients.get(i), exact[i]));\r\n        }\r\n        // TODO: 1.19.3: Add support for choosing a CraftingBookCategory\r\n        ShapelessRecipe recipe = new ShapelessRecipe(group, categoryValue, CraftItemStack.asNMSCopy(result), NonNullList.of(null, ingredientList.toArray(new Ingredient[0])));\r\n        RecipeHolder<ShapelessRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public void registerOtherRecipe(org.bukkit.inventory.Recipe recipe) {\r\n        // This method copied from Bukkit CraftServer source, just to bypass unwanted paper patch\r\n        CraftRecipe toAdd;\r\n        if (recipe instanceof CraftRecipe craft) {\r\n            toAdd = craft;\r\n        }\r\n        else if (recipe instanceof ShapedRecipe) {\r\n            toAdd = CraftShapedRecipe.fromBukkitRecipe((ShapedRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof org.bukkit.inventory.ShapelessRecipe) {\r\n            toAdd = CraftShapelessRecipe.fromBukkitRecipe((org.bukkit.inventory.ShapelessRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof FurnaceRecipe) {\r\n            toAdd = CraftFurnaceRecipe.fromBukkitRecipe((FurnaceRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof org.bukkit.inventory.BlastingRecipe) {\r\n            toAdd = CraftBlastingRecipe.fromBukkitRecipe((org.bukkit.inventory.BlastingRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof CampfireRecipe) {\r\n            toAdd = CraftCampfireRecipe.fromBukkitRecipe((CampfireRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof org.bukkit.inventory.SmokingRecipe) {\r\n            toAdd = CraftSmokingRecipe.fromBukkitRecipe((org.bukkit.inventory.SmokingRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof StonecuttingRecipe) {\r\n            toAdd = CraftStonecuttingRecipe.fromBukkitRecipe((StonecuttingRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof org.bukkit.inventory.SmithingTransformRecipe) {\r\n            toAdd = CraftSmithingTransformRecipe.fromBukkitRecipe((org.bukkit.inventory.SmithingTransformRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof org.bukkit.inventory.SmithingTrimRecipe) {\r\n            toAdd = CraftSmithingTrimRecipe.fromBukkitRecipe((SmithingTrimRecipe)recipe);\r\n        }\r\n        else {\r\n            if (!(recipe instanceof org.bukkit.inventory.TransmuteRecipe)) {\r\n                if (recipe instanceof ComplexRecipe) {\r\n                    throw new UnsupportedOperationException(\"Cannot add custom complex recipe\");\r\n                }\r\n                return;\r\n            }\r\n            toAdd = CraftTransmuteRecipe.fromBukkitRecipe((TransmuteRecipe)recipe);\r\n        }\r\n        toAdd.addToCraftingManager();\r\n    }\r\n\r\n    @Override\r\n    public String getJsonString(ItemStack itemStack) {\r\n        String json = CraftItemStack.asNMSCopy(itemStack).getDisplayName().getStyle().toString().replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\");\r\n        return json.substring(176, json.length() - 185);\r\n    }\r\n\r\n    @Override\r\n    public JsonObject getRawHoverComponentsJson(ItemStack item) {\r\n        DataComponentPatch nmsComponents = CraftItemStack.asNMSCopy(item).getComponentsPatch();\r\n        if (nmsComponents.isEmpty()) {\r\n            return null;\r\n        }\r\n        return DataComponentPatch.CODEC.encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE), nmsComponents).getOrThrow().getAsJsonObject();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack applyRawHoverComponentsJson(ItemStack item, JsonObject components) {\r\n        return DataComponentPatch.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE), components).mapOrElse(\r\n                nmsComponents -> {\r\n                    net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item);\r\n                    nmsItem.applyComponents(nmsComponents);\r\n                    return CraftItemStack.asCraftMirror(nmsItem);\r\n                },\r\n                error -> {\r\n                    Debug.echoError(\"Invalid hover item data '\" + components + \"': \" + error.message());\r\n                    return item;\r\n                });\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getSkullSkin(ItemStack is) {\r\n        net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(is);\r\n        ResolvableProfile profile = itemStack.get(DataComponents.PROFILE);\r\n        if (profile != null) {\r\n            Property property = Iterables.getFirst(profile.partialProfile().properties().get(\"textures\"), null);\r\n            return new PlayerProfile(profile.name().orElse(null), ProfileEditorImpl.getUUID(profile),\r\n                    property != null ? property.value() : null,\r\n                    property != null ? property.signature() : null);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setSkullSkin(ItemStack itemStack, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile);\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.set(DataComponents.PROFILE, ResolvableProfile.createResolved(gameProfile));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack addNbtData(ItemStack itemStack, String key, BinaryTag value) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.update(DataComponents.CUSTOM_DATA, CustomData.EMPTY, customData -> customData.update(nmsCompoundTag -> nmsCompoundTag.put(key, NBTAdapter.toNMS(value))));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    // TODO: 1.20.6: this now needs to serialize components into NBT every single time, should probably only return custom NBT data with specialized methods for other usages\r\n    // TODO: 1.20.6: NBT structure is different basically everywhere, usages of this will need an update\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(ItemStack itemStack) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        if (nmsItemStack != null && !nmsItemStack.isEmpty()) {\r\n            return NBTAdapter.toAPI(serializeNmsItem(nmsItemStack));\r\n        }\r\n        return CompoundBinaryTag.empty();\r\n    }\r\n\r\n    // TODO: 1.20.6: same as getNbtData, ideally needs to only set custom NBT data and have specialized methods for other usages\r\n    @Override\r\n    public ItemStack setNbtData(ItemStack itemStack, CompoundBinaryTag compoundTag) {\r\n        return CraftItemStack.asBukkitCopy(parseNmsItem(NBTAdapter.toNMS(compoundTag)));\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getCustomData(ItemStack item) {\r\n        CustomData customData = CraftItemStack.asNMSCopy(item).get(DataComponents.CUSTOM_DATA);\r\n        return customData != null ? NBTAdapter.toAPI(customData.copyTag()) : null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setCustomData(ItemStack item, CompoundBinaryTag data) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        if (data == null) {\r\n            nmsItemStack.remove(DataComponents.CUSTOM_DATA);\r\n        }\r\n        else {\r\n            nmsItemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(NBTAdapter.toNMS(data)));\r\n        }\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    public static final int DATA_VERSION_1_20_4 = 3700;\r\n\r\n    @Override\r\n    public ItemStack setPartialOldNbt(ItemStack item, CompoundBinaryTag oldTag) {\r\n        int currentDataVersion = CraftMagicNumbers.INSTANCE.getDataVersion();\r\n        CompoundTag nmsOldTag = new CompoundTag();\r\n        nmsOldTag.putString(\"id\", item.getType().getKey().toString());\r\n        nmsOldTag.putByte(\"Count\", (byte) item.getAmount());\r\n        nmsOldTag.put(\"tag\", NBTAdapter.toNMS(oldTag));\r\n        CompoundTag nmsUpdatedTag = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, nmsOldTag), DATA_VERSION_1_20_4, currentDataVersion).getValue();\r\n        CompoundTag nmsCurrentTag = serializeNmsItem(CraftItemStack.asNMSCopy(item));\r\n        CompoundTag nmsMergedTag = nmsCurrentTag.merge(nmsUpdatedTag);\r\n        return CraftItemStack.asBukkitCopy(parseNmsItem(nmsMergedTag));\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getEntityData(ItemStack item) {\r\n        TypedEntityData<net.minecraft.world.entity.EntityType<?>> entityData = CraftItemStack.asNMSCopy(item).get(DataComponents.ENTITY_DATA);\r\n        return entityData != null ? NBTAdapter.toAPI(entityData.getUnsafe()) : null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setEntityData(ItemStack item, CompoundBinaryTag entityNbt, EntityType entityType) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        if (entityNbt == null || entityNbt.isEmpty() || (entityNbt.size() == 1 && entityNbt.contains(\"id\"))) {\r\n            nmsItemStack.remove(DataComponents.ENTITY_DATA);\r\n        }\r\n        else {\r\n            CompoundTag nmsEntityNbt = NBTAdapter.toNMS(entityNbt);\r\n            nmsEntityNbt.remove(\"id\");\r\n            nmsItemStack.set(DataComponents.ENTITY_DATA, TypedEntityData.of(CraftEntityType.bukkitToMinecraft(entityType), nmsEntityNbt));\r\n        }\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public MapTag getRawComponentsPatch(ItemStack item, boolean excludeHandled) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        DataComponentPatch patch = nmsItemStack.getComponentsPatch();\r\n        if (excludeHandled) {\r\n            patch = patch.forget(componentType -> {\r\n                Identifier componentId = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(componentType);\r\n                return ItemComponentsPatch.propertyHandledComponents.contains(componentId.toString());\r\n            });\r\n        }\r\n        if (patch.isEmpty()) {\r\n            return new MapTag();\r\n        }\r\n        RegistryOps<net.minecraft.nbt.Tag> registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE);\r\n        CompoundTag nmsPatch = (CompoundTag) DataComponentPatch.CODEC.encodeStart(registryOps, patch).getOrThrow();\r\n        if (excludeHandled && Denizen.supportsPaper) {\r\n            nmsPatch.keySet().removeIf(s -> s.charAt(0) == '!');\r\n            if (nmsPatch.isEmpty()) {\r\n                return new MapTag();\r\n            }\r\n        }\r\n        MapTag rawComponents = (MapTag) ItemRawNBT.nbtTagToObject(NBTAdapter.toAPI(nmsPatch));\r\n        rawComponents.putObject(ItemComponentsPatch.DATA_VERSION_KEY, new ElementTag(CraftMagicNumbers.INSTANCE.getDataVersion()));\r\n        return rawComponents;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setRawComponentsPatch(ItemStack item, MapTag rawComponentsMap, int dataVersion, Consumer<String> errorHandler) {\r\n        int currentDataVersion = CraftMagicNumbers.INSTANCE.getDataVersion();\r\n        CompoundBinaryTag rawComponents = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(rawComponentsMap, CoreUtilities.errorButNoDebugContext, \"\");\r\n        CompoundTag nmsRawComponents = NBTAdapter.toNMS(rawComponents);\r\n        RegistryOps<net.minecraft.nbt.Tag> registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE);\r\n        if (dataVersion < currentDataVersion) {\r\n            CompoundTag legacyItemData = new CompoundTag();\r\n            legacyItemData.putString(\"id\", item.getType().getKey().toString());\r\n            legacyItemData.putInt(\"count\", item.getAmount());\r\n            legacyItemData.put(\"components\", nmsRawComponents);\r\n            CompoundTag nmsUpdatedTag = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(registryOps, legacyItemData), dataVersion, currentDataVersion).getValue();\r\n            nmsRawComponents = nmsUpdatedTag.getCompound(\"components\").orElseGet(CompoundTag::new);\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        DataComponentPatch.CODEC.parse(registryOps, nmsRawComponents)\r\n                .ifError(error -> errorHandler.accept(error.message()))\r\n                .ifSuccess(nmsItemStack::applyComponents);\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    public static final Field AdventureModePredicate_predicates = ReflectionHelper.getFields(AdventureModePredicate.class).get(ReflectionMappingsInfo.AdventureModePredicate_predicates);\r\n\r\n    @Override\r\n    public List<Material> getCanPlaceOn(ItemStack item) {\r\n        return getAdventureModePredicateMaterials(item, DataComponents.CAN_PLACE_ON);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setCanPlaceOn(ItemStack item, List<Material> canPlaceOn) {\r\n        return setAdventureModePredicateMaterials(item, DataComponents.CAN_PLACE_ON, canPlaceOn);\r\n    }\r\n\r\n    @Override\r\n    public List<Material> getCanBreak(ItemStack item) {\r\n        return getAdventureModePredicateMaterials(item, DataComponents.CAN_BREAK);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setCanBreak(ItemStack item, List<Material> canBreak) {\r\n        return setAdventureModePredicateMaterials(item, DataComponents.CAN_BREAK, canBreak);\r\n    }\r\n\r\n    private List<Material> getAdventureModePredicateMaterials(ItemStack item, DataComponentType<AdventureModePredicate> nmsComponent) {\r\n        AdventureModePredicate nmsAdventurePredicate = CraftItemStack.asNMSCopy(item).get(nmsComponent);\r\n        if (nmsAdventurePredicate == null) {\r\n            return null;\r\n        }\r\n        List<BlockPredicate> nmsPredicates;\r\n        try {\r\n            nmsPredicates = (List<BlockPredicate>) AdventureModePredicate_predicates.get(nmsAdventurePredicate);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n            return null;\r\n        }\r\n        List<Material> materials = new ArrayList<>();\r\n        for (BlockPredicate nmsPredicate : nmsPredicates) {\r\n            nmsPredicate.blocks().ifPresent(nmsHolderSet -> {\r\n                for (Holder<Block> nmsHolder : nmsHolderSet) {\r\n                    materials.add(CraftMagicNumbers.getMaterial(nmsHolder.value()));\r\n                }\r\n            });\r\n        }\r\n        return materials;\r\n    }\r\n\r\n    private ItemStack setAdventureModePredicateMaterials(ItemStack item, DataComponentType<AdventureModePredicate> nmsComponent, List<Material> materials) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        AdventureModePredicate nmsAdventurePredicate = nmsItemStack.get(nmsComponent);\r\n        if (materials == null) {\r\n            if (nmsAdventurePredicate == null) {\r\n                return item;\r\n            }\r\n            nmsItemStack.remove(nmsComponent);\r\n            return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n        }\r\n        BlockPredicate nmsPredicate = new BlockPredicate(Optional.of(\r\n                HolderSet.direct(material -> BuiltInRegistries.BLOCK.get(CraftNamespacedKey.toMinecraft(material.getKey())).orElseThrow(), materials)\r\n        ), Optional.empty(), Optional.empty(), DataComponentMatchers.ANY);\r\n        nmsItemStack.set(nmsComponent, new AdventureModePredicate(List.of(nmsPredicate)));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public void setInventoryItem(Inventory inventory, ItemStack item, int slot) {\r\n        if (inventory instanceof CraftInventoryPlayer && ((CraftInventoryPlayer) inventory).getInventory().player == null) {\r\n            ((CraftInventoryPlayer) inventory).getInventory().setItem(slot, CraftItemStack.asNMSCopy(item));\r\n        }\r\n        else {\r\n            inventory.setItem(slot, item);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getDisplayName(ItemTag item) {\r\n        if (!item.getItemMeta().hasDisplayName()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        Component nmsDisplayName = nmsItemStack.get(DataComponents.CUSTOM_NAME);\r\n        return FormattedTextHelper.stringify(Handler.componentToSpigot(nmsDisplayName));\r\n    }\r\n\r\n    @Override\r\n    public List<String> getLore(ItemTag item) {\r\n        if (!item.getItemMeta().hasLore()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        ItemLore nmsLore = nmsItemStack.get(DataComponents.LORE);\r\n        List<String> outList = new ArrayList<>(nmsLore.lines().size());\r\n        for (Component nmsLoreLine : nmsLore.lines()) {\r\n            outList.add(FormattedTextHelper.stringify(Handler.componentToSpigot(nmsLoreLine)));\r\n        }\r\n        return outList;\r\n    }\r\n\r\n    @Override\r\n    public void setDisplayName(ItemTag item, String name) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        if (name == null || name.isEmpty()) {\r\n            nmsItemStack.remove(DataComponents.CUSTOM_NAME);\r\n        }\r\n        else {\r\n            nmsItemStack.set(DataComponents.CUSTOM_NAME, Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)));\r\n        }\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    @Override\r\n    public void setLore(ItemTag item, List<String> lore) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        if (lore == null || lore.isEmpty()) {\r\n            nmsItemStack.remove(DataComponents.LORE);\r\n        }\r\n        else {\r\n            List<Component> nmsLore = new ArrayList<>(lore.size());\r\n            for (String loreLine : lore) {\r\n                nmsLore.add(Handler.componentToNMS(FormattedTextHelper.parse(loreLine, ChatColor.WHITE)));\r\n            }\r\n            nmsItemStack.set(DataComponents.LORE, new ItemLore(nmsLore));\r\n        }\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.getCorrectStateForFluidBlock.\r\n     */\r\n    public static BlockState getCorrectStateForFluidBlock(Level world, BlockState blockState, BlockPos blockPos) {\r\n        FluidState fluid = blockState.getFluidState();\r\n        return !fluid.isEmpty() && !blockState.isFaceSturdy(world, blockPos, Direction.UP) ? fluid.createLegacyBlock() : blockState;\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.update, redesigned slightly to render totally rather than just relative to a player.\r\n     * Some variables manually renamed for readability.\r\n     */\r\n    public static void renderFullMap(MapItemSavedData worldmap, int xMin, int zMin, int xMax, int zMax) {\r\n        Level world = ((CraftWorld) worldmap.mapView.getWorld()).getHandle();\r\n        int scale = 1 << worldmap.scale;\r\n        int mapX = worldmap.centerX;\r\n        int mapZ = worldmap.centerZ;\r\n        for (int x = xMin; x < xMax; x++) {\r\n            double d0 = 0.0D;\r\n            for (int z = zMin; z < zMax; z++) {\r\n                int k2 = (mapX / scale + x - 64) * scale;\r\n                int l2 = (mapZ / scale + z - 64) * scale;\r\n                Multiset<MapColor> multiset = LinkedHashMultiset.create();\r\n                LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2));\r\n                if (!chunk.isEmpty()) {\r\n                    ChunkPos chunkcoordintpair = chunk.getPos();\r\n                    int i3 = k2 & 15;\r\n                    int j3 = l2 & 15;\r\n                    int k3 = 0;\r\n                    double d1 = 0.0D;\r\n                    if (world.dimensionType().hasCeiling()) {\r\n                        int l3 = k2 + l2 * 231871;\r\n                        l3 = l3 * l3 * 31287121 + l3 * 11;\r\n                        if ((l3 >> 20 & 1) == 0) {\r\n                            multiset.add(Blocks.DIRT.defaultBlockState().getMapColor(world, BlockPos.ZERO), 10);\r\n                        }\r\n                        else {\r\n                            multiset.add(Blocks.STONE.defaultBlockState().getMapColor(world, BlockPos.ZERO), 100);\r\n                        }\r\n\r\n                        d1 = 100.0D;\r\n                    }\r\n                    else {\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition1 = new BlockPos.MutableBlockPos();\r\n                        for (int i4 = 0; i4 < scale; ++i4) {\r\n                            for (int j4 = 0; j4 < scale; ++j4) {\r\n                                int k4 = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i4 + i3, j4 + j3) + 1;\r\n                                BlockState iblockdata;\r\n                                if (k4 <= world.getMinY() + 1) {\r\n                                    iblockdata = Blocks.BEDROCK.defaultBlockState();\r\n                                }\r\n                                else {\r\n                                    do {\r\n                                        --k4;\r\n                                        blockposition_mutableblockposition.set(chunkcoordintpair.getMinBlockX() + i4 + i3, k4, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                        iblockdata = chunk.getBlockState(blockposition_mutableblockposition);\r\n                                    } while (iblockdata.getMapColor(world, blockposition_mutableblockposition) == MapColor.NONE && k4 > world.getMinY());\r\n                                    if (k4 > world.getMinY() && !iblockdata.getFluidState().isEmpty()) {\r\n                                        int l4 = k4 - 1;\r\n                                        blockposition_mutableblockposition1.set(blockposition_mutableblockposition);\r\n\r\n                                        BlockState iblockdata1;\r\n                                        do {\r\n                                            blockposition_mutableblockposition1.setY(l4--);\r\n                                            iblockdata1 = chunk.getBlockState(blockposition_mutableblockposition1);\r\n                                            k3++;\r\n                                        } while (l4 > world.getMinY() && !iblockdata1.getFluidState().isEmpty());\r\n                                        iblockdata = getCorrectStateForFluidBlock(world, iblockdata, blockposition_mutableblockposition);\r\n                                    }\r\n                                }\r\n                                worldmap.checkBanners(world, chunkcoordintpair.getMinBlockX() + i4 + i3, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                d1 += (double) k4 / (double) (scale * scale);\r\n                                multiset.add(iblockdata.getMapColor(world, blockposition_mutableblockposition));\r\n                            }\r\n                        }\r\n                    }\r\n                    k3 /= scale * scale;\r\n                    double d2 = (d1 - d0) * 4.0D / (double) (scale + 4) + ((double) (x + z & 1) - 0.5D) * 0.4D;\r\n                    byte b0 = 1;\r\n                    if (d2 > 0.6D) {\r\n                        b0 = 2;\r\n                    }\r\n                    if (d2 < -0.6D) {\r\n                        b0 = 0;\r\n                    }\r\n                    MapColor materialmapcolor = Iterables.getFirst(Multisets.copyHighestCountFirst(multiset), MapColor.NONE);\r\n                    if (materialmapcolor == MapColor.WATER) {\r\n                        d2 = (double) k3 * 0.1D + (double) (x + z & 1) * 0.2D;\r\n                        b0 = 1;\r\n                        if (d2 < 0.5D) {\r\n                            b0 = 2;\r\n                        }\r\n                        if (d2 > 0.9D) {\r\n                            b0 = 0;\r\n                        }\r\n                    }\r\n                    d0 = d1;\r\n                    worldmap.updateColor(x, z, (byte) (materialmapcolor.id * 4 + b0));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean renderEntireMap(int mapId, int xMin, int zMin, int xMax, int zMax) {\r\n        MapItemSavedData worldmap = ((CraftServer) Bukkit.getServer()).getServer().getLevel(net.minecraft.world.level.Level.OVERWORLD).getMapData(new MapId(mapId));\r\n        if (worldmap == null) {\r\n            return false;\r\n        }\r\n        renderFullMap(worldmap, xMin, zMin, xMax, zMax);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public BlockData getPlacedBlock(Material material) {\r\n        Item nmsItem = BuiltInRegistries.ITEM.getOptional(CraftNamespacedKey.toMinecraft(material.getKey())).orElse(null);\r\n        if (nmsItem instanceof BlockItem) {\r\n            Block block = ((BlockItem) nmsItem).getBlock();\r\n            return CraftBlockData.fromData(block.defaultBlockState());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public boolean isValidMix(ItemStack input, ItemStack ingredient) {\r\n        net.minecraft.world.item.ItemStack nmsInput = CraftItemStack.asNMSCopy(input);\r\n        net.minecraft.world.item.ItemStack nmsIngredient = CraftItemStack.asNMSCopy(ingredient);\r\n        return MinecraftServer.getServer().potionBrewing().hasMix(nmsInput, nmsIngredient);\r\n    }\r\n\r\n    public static Class<?> PaperPotionMix_CLASS = null;\r\n    public static Map<NamespacedKey, BrewingRecipe> customBrewingRecipes = null;\r\n\r\n    @Override\r\n    public Map<NamespacedKey, BrewingRecipe> getCustomBrewingRecipes() {\r\n        if (customBrewingRecipes == null) {\r\n            customBrewingRecipes = Maps.transformValues((Map<NamespacedKey, ?>) ReflectionHelper.getFieldValue(PotionBrewing.class, \"customMixes\", MinecraftServer.getServer().potionBrewing()), paperMix -> {\r\n                if (PaperPotionMix_CLASS == null) {\r\n                    PaperPotionMix_CLASS = paperMix.getClass();\r\n                }\r\n                RecipeChoice ingredient = convertChoice(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"ingredient\", paperMix));\r\n                RecipeChoice input = convertChoice(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"input\", paperMix));\r\n                ItemStack result = CraftItemStack.asBukkitCopy(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"result\", paperMix));\r\n                return new BrewingRecipe(input, ingredient, result);\r\n            });\r\n        }\r\n        return customBrewingRecipes;\r\n    }\r\n\r\n    private RecipeChoice convertChoice(Predicate<net.minecraft.world.item.ItemStack> nmsPredicate) {\r\n        // Not an instance of net.minecraft.world.item.crafting.Ingredient = a predicate recipe choice\r\n        if (nmsPredicate instanceof Ingredient ingredient) {\r\n            return CraftRecipe.toBukkit(ingredient);\r\n        }\r\n        return PaperAPITools.instance.createPredicateRecipeChoice(item -> nmsPredicate.test(CraftItemStack.asNMSCopy(item)));\r\n    }\r\n\r\n    @Override\r\n    public byte[] renderMap(MapView mapView, Player player) {\r\n        return ((CraftMapView) mapView).render((CraftPlayer) player).buffer;\r\n    }\r\n\r\n    @Override\r\n    public int getFoodPoints(Material itemType) {\r\n        return CraftMagicNumbers.getItem(itemType).components().get(DataComponents.FOOD).nutrition();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/NBTAdapter.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\n\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport net.kyori.adventure.nbt.*;\nimport net.minecraft.nbt.*;\n\nimport java.lang.invoke.MethodHandle;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class NBTAdapter {\n\n    public static final MethodHandle COMPOUND_TAG_MAP_CONSTRUCTOR = ReflectionHelper.getConstructor(CompoundTag.class, Map.class);\n\n    public static Tag toNMS(BinaryTag tag) {\n        if (tag instanceof ByteBinaryTag byteTag) {\n            return switch (byteTag.value()) {\n                case 0 -> ByteTag.ZERO;\n                case 1 -> ByteTag.ONE;\n                default -> ByteTag.valueOf(byteTag.value());\n            };\n        }\n        else if (tag instanceof ShortBinaryTag shortTag) {\n            return ShortTag.valueOf(shortTag.value());\n        }\n        else if (tag instanceof IntBinaryTag intTag) {\n            return IntTag.valueOf(intTag.value());\n        }\n        else if (tag instanceof LongBinaryTag longTag) {\n            return LongTag.valueOf(longTag.value());\n        }\n        else if (tag instanceof FloatBinaryTag floatTag) {\n            return FloatTag.valueOf(floatTag.value());\n        }\n        else if (tag instanceof DoubleBinaryTag doubleTag) {\n            return DoubleTag.valueOf(doubleTag.value());\n        }\n        else if (tag instanceof ByteArrayBinaryTag byteArrayTag) {\n            return new ByteArrayTag(byteArrayTag.value());\n        }\n        else if (tag instanceof IntArrayBinaryTag intArrayTag) {\n            return new IntArrayTag(intArrayTag.value());\n        }\n        else if (tag instanceof LongArrayBinaryTag longArrayTag) {\n            return new LongArrayTag(longArrayTag.value());\n        }\n        else if (tag instanceof StringBinaryTag stringTag) {\n            return StringTag.valueOf(stringTag.value());\n        }\n        else if (tag instanceof ListBinaryTag listTag) {\n            return toNMS(listTag);\n        }\n        else if (tag instanceof CompoundBinaryTag compoundTag) {\n            return toNMS(compoundTag);\n        }\n        else if (tag instanceof EndBinaryTag) {\n            return EndTag.INSTANCE;\n        }\n        throw new IllegalStateException(\"Unrecognized API tag of type '\" + tag.type() + \"': \" + tag);\n    }\n\n    public static BinaryTag toAPI(Tag nmsTag) {\n        if (nmsTag instanceof ByteTag nmsByteTag) {\n            return ByteBinaryTag.byteBinaryTag(nmsByteTag.value());\n        }\n        else if (nmsTag instanceof ShortTag nmsShortTag) {\n            return ShortBinaryTag.shortBinaryTag(nmsShortTag.value());\n        }\n        else if (nmsTag instanceof IntTag nmsIntTag) {\n            return IntBinaryTag.intBinaryTag(nmsIntTag.value());\n        }\n        else if (nmsTag instanceof LongTag nmsLongTag) {\n            return LongBinaryTag.longBinaryTag(nmsLongTag.value());\n        }\n        else if (nmsTag instanceof FloatTag nmsFloatTag) {\n            return FloatBinaryTag.floatBinaryTag(nmsFloatTag.value());\n        }\n        else if (nmsTag instanceof DoubleTag nmsDoubleTag) {\n            return DoubleBinaryTag.doubleBinaryTag(nmsDoubleTag.value());\n        }\n        else if (nmsTag instanceof ByteArrayTag nmsByteArrayTag) {\n            return ByteArrayBinaryTag.byteArrayBinaryTag(nmsByteArrayTag.getAsByteArray());\n        }\n        else if (nmsTag instanceof IntArrayTag nmsIntArrayTag) {\n            return IntArrayBinaryTag.intArrayBinaryTag(nmsIntArrayTag.getAsIntArray());\n        }\n        else if (nmsTag instanceof LongArrayTag nmsLongArrayTag) {\n            return LongArrayBinaryTag.longArrayBinaryTag(nmsLongArrayTag.getAsLongArray());\n        }\n        else if (nmsTag instanceof StringTag nmsStringTag) {\n            return StringBinaryTag.stringBinaryTag(nmsStringTag.value());\n        }\n        else if (nmsTag instanceof ListTag nmsListTag) {\n            return toAPI(nmsListTag);\n        }\n        else if (nmsTag instanceof CompoundTag nmsCompoundTag) {\n            return toAPI(nmsCompoundTag);\n        }\n        else if (nmsTag instanceof EndTag) {\n            return EndBinaryTag.endBinaryTag();\n        }\n        throw new IllegalStateException(\"Unrecognized NMS tag of type '\" + nmsTag.getClass().getName() + '/' + nmsTag.getType().getName() + \"': \" + nmsTag);\n    }\n\n    public static ListBinaryTag toAPI(ListTag nmsListTag) {\n        ListBinaryTag.Builder<BinaryTag> builder = ListBinaryTag.heterogeneousListBinaryTag(nmsListTag.size());\n        for (Tag nmsValue : nmsListTag) {\n            builder.add(toAPI(nmsValue));\n        }\n        return builder.build();\n    }\n\n    public static ListTag toNMS(ListBinaryTag listTag) {\n        List<Tag> nmsTags = new ArrayList<>(listTag.size());\n        for (BinaryTag value : listTag) {\n            nmsTags.add(toNMS(value));\n        }\n        return new ListTag(nmsTags);\n    }\n\n    public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) {\n        CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size());\n        for (Map.Entry<String, Tag> nmsEntry : nmsCompoundTag.entrySet()) {\n            builder.put(nmsEntry.getKey(), toAPI(nmsEntry.getValue()));\n        }\n        return builder.build();\n    }\n\n    public static CompoundTag toNMS(CompoundBinaryTag compoundTag) {\n        Map<String, Tag> nmsTags = new HashMap<>(compoundTag.size());\n        for (Map.Entry<String, ? extends BinaryTag> entry : compoundTag) {\n            nmsTags.put(entry.getKey(), toNMS(entry.getValue()));\n        }\n        try {\n            return (CompoundTag) COMPOUND_TAG_MAP_CONSTRUCTOR.invokeExact(nmsTags);\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/PacketHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.PacketHelper;\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.TeleportCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.maps.MapImage;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;\r\nimport net.minecraft.network.protocol.common.custom.BrandPayload;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.PositionMoveRotation;\r\nimport net.minecraft.world.entity.Relative;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.monster.spider.CaveSpider;\r\nimport net.minecraft.world.entity.monster.Creeper;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.entity.monster.spider.Spider;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Team;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.EntityEffect;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Sign;\r\nimport org.bukkit.block.sign.Side;\r\nimport org.bukkit.block.sign.SignSide;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_21_R7.map.CraftMapCanvas;\r\nimport org.bukkit.craftbukkit.v1_21_R7.map.CraftMapView;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftLocation;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapPalette;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class PacketHelperImpl implements PacketHelper {\r\n\r\n    public static final EntityDataAccessor<Float> PLAYER_DATA_ACCESSOR_ABSORPTION = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, ReflectionMappingsInfo.Player_DATA_PLAYER_ABSORPTION_ID, null);\r\n\r\n    public static final EntityDataAccessor<Byte> ENTITY_DATA_ACCESSOR_FLAGS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_SHARED_FLAGS_ID, null);\r\n\r\n    public static final MethodHandle ABILITIES_PACKET_FOV_SETTER = ReflectionHelper.getFinalSetter(ClientboundPlayerAbilitiesPacket.class, ReflectionMappingsInfo.ClientboundPlayerAbilitiesPacket_walkingSpeed);\r\n\r\n    public static final Field ENTITY_TRACKER_ENTRY_GETTER = ReflectionHelper.getFields(ChunkMap.TrackedEntity.class).getFirstOfType(ServerEntity.class);\r\n\r\n    public static final MethodHandle CANVAS_GET_BUFFER = ReflectionHelper.getMethodHandle(CraftMapCanvas.class, \"getBuffer\");\r\n    public static final Field MAPVIEW_WORLDMAP = ReflectionHelper.getFields(CraftMapView.class).get(\"worldMap\");\r\n\r\n    public static final EntityDataAccessor<Optional<Component>> ENTITY_DATA_ACCESSOR_CUSTOM_NAME = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_CUSTOM_NAME, null);\r\n    public static final EntityDataAccessor<Boolean> ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, ReflectionMappingsInfo.Entity_DATA_CUSTOM_NAME_VISIBLE, null);\r\n\r\n    @Override\r\n    public void setFakeAbsorption(Player player, float value) {\r\n        send(player, new ClientboundSetEntityDataPacket(player.getEntityId(), List.of(createEntityData(PLAYER_DATA_ACCESSOR_ABSORPTION, value))));\r\n    }\r\n\r\n    @Override\r\n    public void setSlot(Player player, int slot, ItemStack itemStack, boolean playerOnly) {\r\n        AbstractContainerMenu menu = ((CraftPlayer) player).getHandle().containerMenu;\r\n        int windowId = playerOnly ? 0 : menu.containerId;\r\n        send(player, new ClientboundContainerSetSlotPacket(windowId, menu.incrementStateId(), slot, CraftItemStack.asNMSCopy(itemStack)));\r\n    }\r\n\r\n    @Override\r\n    public void setFieldOfView(Player player, float fov) {\r\n        ClientboundPlayerAbilitiesPacket packet = new ClientboundPlayerAbilitiesPacket(((CraftPlayer) player).getHandle().getAbilities());\r\n        if (!Float.isNaN(fov)) {\r\n            try {\r\n                ABILITIES_PACKET_FOV_SETTER.invoke(packet, fov);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void respawn(Player player) {\r\n        ((CraftPlayer) player).getHandle().connection.handleClientCommand(new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN));\r\n    }\r\n\r\n    @Override\r\n    public void setVision(Player player, EntityType entityType) {\r\n        final net.minecraft.world.entity.LivingEntity entity;\r\n        if (entityType == EntityType.CREEPER) {\r\n            entity = new Creeper(net.minecraft.world.entity.EntityType.CREEPER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.SPIDER) {\r\n            entity = new Spider(net.minecraft.world.entity.EntityType.SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.CAVE_SPIDER) {\r\n            entity = new CaveSpider(net.minecraft.world.entity.EntityType.CAVE_SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.ENDERMAN) {\r\n            entity = new EnderMan(net.minecraft.world.entity.EntityType.ENDERMAN, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n\r\n        // Spectating an entity then immediately respawning the player prevents a client shader update,\r\n        // allowing the player to retain whatever vision the mob they spectated had.\r\n        send(player, new ClientboundAddEntityPacket(entity, 0, BlockPos.ZERO));\r\n        send(player, new ClientboundSetCameraPacket(entity));\r\n        NMSHandler.playerHelper.refreshPlayer(player);\r\n        send(player, new ClientboundRemoveEntitiesPacket(entity.getId()));\r\n    }\r\n\r\n    @Override\r\n    public void showBlockAction(Player player, Location location, int action, int state) {\r\n        BlockPos position = CraftLocation.toBlockPosition(location);\r\n        Block block = ((CraftWorld) location.getWorld()).getHandle().getBlockState(position).getBlock();\r\n        send(player, new ClientboundBlockEventPacket(position, block, action, state));\r\n    }\r\n\r\n    @Override\r\n    public void showTabListHeaderFooter(Player player, String header, String footer) {\r\n        Component cHeader = Handler.componentToNMS(FormattedTextHelper.parse(header, ChatColor.WHITE));\r\n        Component cFooter = Handler.componentToNMS(FormattedTextHelper.parse(footer, ChatColor.WHITE));\r\n        send(player, new ClientboundTabListPacket(cHeader, cFooter));\r\n    }\r\n\r\n    @Override\r\n    public void showTitle(Player player, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) {\r\n        send(player, new ClientboundBundlePacket(List.of(\r\n                new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks),\r\n                new ClientboundSetTitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE))),\r\n                new ClientboundSetSubtitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(subtitle, ChatColor.WHITE)))\r\n        )));\r\n    }\r\n\r\n    @Override\r\n    public void showMobHealth(Player player, LivingEntity mob, double health, double maxHealth) {\r\n        AttributeInstance attr = new AttributeInstance(Attributes.MAX_HEALTH, (a) -> {});\r\n        attr.setBaseValue(maxHealth);\r\n        send(player, new ClientboundUpdateAttributesPacket(mob.getEntityId(), List.of(attr)));\r\n        send(player, new ClientboundSetEntityDataPacket(mob.getEntityId(), List.of(createEntityData(net.minecraft.world.entity.LivingEntity.DATA_HEALTH_ID, (float) health))));\r\n    }\r\n\r\n    @Override\r\n    public void showSignEditor(Player player, Location location) { // TODO: MC 1.18: once 1.18 is removed, remove 'location' arg\r\n        NetworkInterceptHelper.enable();\r\n        Sign sign = null;\r\n        BlockPos toOpen = null;\r\n        // It actually allows 8 blocks of distance, but we limit to 7 because the client doesn't properly round down\r\n        for (int i = 0; i < 8; i++) {\r\n            Location toCheck = player.getLocation();\r\n            toCheck.setY(toCheck.getY() - i);\r\n            if (toCheck.getBlock().getState() instanceof Sign foundSign) {\r\n                sign = foundSign;\r\n            }\r\n            else {\r\n                sign = null;\r\n                toOpen = CraftLocation.toBlockPosition(toCheck);\r\n                break;\r\n            }\r\n        }\r\n        if (sign != null) {\r\n            toOpen = CraftLocation.toBlockPosition(sign.getLocation());\r\n            SignSide front = sign.getSide(Side.FRONT);\r\n            for (int line = 0; line < 4; line++) {\r\n                front.setLine(line, \"\");\r\n            }\r\n            player.sendBlockUpdate(sign.getLocation(), sign);\r\n        }\r\n        else {\r\n            player.sendBlockChange(new LocationTag(player.getWorld(), toOpen.getX(), toOpen.getY(), toOpen.getZ()), Material.OAK_WALL_SIGN.createBlockData());\r\n        }\r\n        DenizenNetworkManagerImpl.getNetworkManager(player).packetListener.fakeSignExpected = toOpen;\r\n        send(player, new ClientboundOpenSignEditorPacket(toOpen, true));\r\n    }\r\n\r\n    @Override\r\n    public void forceSpectate(Player player, Entity entity) {\r\n        send(player, new ClientboundSetCameraPacket(((CraftEntity) entity).getHandle()));\r\n    }\r\n\r\n    public static void forceRespawnPlayerEntity(Entity entity, Player viewer) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level()).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        if (entityTracker != null) {\r\n            try {\r\n                ServerEntity entry = (ServerEntity) ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n                if (entry != null) {\r\n                    entry.removePairing(((CraftPlayer) viewer).getHandle());\r\n                    entry.addPairing(((CraftPlayer) viewer).getHandle());\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendRename(Player player, Entity entity, String name, boolean listMode) {\r\n        try {\r\n            if (entity.getType() == EntityType.PLAYER) {\r\n                if (listMode) {\r\n                    send(player, new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, ((CraftPlayer) player).getHandle()));\r\n                }\r\n                else {\r\n                    // For player entities, force a respawn packet and let the dynamic intercept correct the details\r\n                    forceRespawnPlayerEntity(entity, player);\r\n                }\r\n                return;\r\n            }\r\n            List<SynchedEntityData.DataValue<?>> list = List.of(\r\n                    createEntityData(ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)))),\r\n                    createEntityData(ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true)\r\n            );\r\n            send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), list));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static HashMap<UUID, HashMap<UUID, PlayerTeam>> noCollideTeamMap = new HashMap<>();\r\n\r\n    @Override\r\n    public void generateNoCollideTeam(Player player, UUID noCollide) {\r\n        removeNoCollideTeam(player, noCollide);\r\n        PlayerTeam team = new PlayerTeam(SidebarImpl.dummyScoreboard, Utilities.generateRandomColors(8));\r\n        team.getPlayers().add(noCollide.toString());\r\n        team.setCollisionRule(Team.CollisionRule.NEVER);\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>());\r\n        map.put(noCollide, team);\r\n        send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n    }\r\n\r\n    @Override\r\n    public void removeNoCollideTeam(Player player, UUID noCollide) {\r\n        if (noCollide == null || !player.isOnline()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n            return;\r\n        }\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.get(player.getUniqueId());\r\n        if (map == null) {\r\n            return;\r\n        }\r\n        PlayerTeam team = map.remove(noCollide);\r\n        if (team != null) {\r\n            send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        if (map.isEmpty()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityMetadataFlagsUpdate(Player player, Entity entity) {\r\n        byte flags = ((CraftEntity) entity).getHandle().getEntityData().get(ENTITY_DATA_ACCESSOR_FLAGS);\r\n        send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), List.of(createEntityData(ENTITY_DATA_ACCESSOR_FLAGS, flags))));\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityEffect(Player player, Entity entity, EntityEffect effect) {\r\n        send(player, new ClientboundEntityEventPacket(((CraftEntity) entity).getHandle(), effect.getData()));\r\n    }\r\n\r\n    @Override\r\n    public int getPacketStats(Player player, boolean sent) {\r\n        DenizenNetworkManagerImpl netMan = DenizenNetworkManagerImpl.getNetworkManager(player);\r\n        return sent ? netMan.packetsSent : netMan.packetsReceived;\r\n    }\r\n\r\n    @Override\r\n    public void setMapData(MapCanvas canvas, byte[] bytes, int x, int y, MapImage image) {\r\n        if (x > 127 || y > 127) {\r\n            return;\r\n        }\r\n        int width = Math.min(image.width, 128 - x),\r\n                height = Math.min(image.height, 128 - y);\r\n        if (x + width <= 0 || y + height <= 0) {\r\n            return;\r\n        }\r\n        try {\r\n            boolean anyChanged = false;\r\n            byte[] buffer = (byte[]) CANVAS_GET_BUFFER.invoke(canvas);\r\n            for (int x2 = x < 0 ? -x : 0; x2 < width; ++x2) {\r\n                for (int y2 = y < 0 ? -y : 0; y2 < height; ++y2) {\r\n                    byte p = bytes[y2 * image.width + x2];\r\n                    if (p != MapPalette.TRANSPARENT) {\r\n                        int index = (y2 + y) * 128 + (x2 + x);\r\n                        if (buffer[index] != p) {\r\n                            buffer[index] = p;\r\n                            anyChanged = true;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            if (anyChanged) {\r\n                // Flag the whole image as dirty\r\n                MapItemSavedData map = (MapItemSavedData) MAPVIEW_WORLDMAP.get(canvas.getMapView());\r\n                map.setColorsDirty(Math.max(x, 0), Math.max(y, 0));\r\n                map.setColorsDirty(width + x - 1, height + y - 1);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setNetworkManagerFor(Player player) {\r\n        DenizenNetworkManagerImpl.setNetworkManager(player);\r\n    }\r\n\r\n    @Override\r\n    public void enableNetworkManager() {\r\n        DenizenNetworkManagerImpl.enableNetworkManager();\r\n    }\r\n\r\n    @Override\r\n    public void showDebugTestMarker(Player player, Location location, ColorTag color, String name, int time) {\r\n        BlockPos nmsPos = CraftLocation.toBlockPosition(location);\r\n        LocationTag displayPos = !name.isEmpty() ? LocationTag.valueOf(name, CoreUtilities.noDebugContext) : null;\r\n        send(player, new ClientboundGameTestHighlightPosPacket(nmsPos, displayPos != null ? CraftLocation.toBlockPosition(displayPos) : nmsPos));\r\n    }\r\n\r\n    @Override\r\n    public void clearDebugTestMarker(Player player) {\r\n    }\r\n\r\n    @Override\r\n    public void sendBrand(Player player, String brand) {\r\n        BrandPayload payload = new BrandPayload(brand);\r\n        send(player, new ClientboundCustomPayloadPacket(payload));\r\n    }\r\n\r\n    @Override\r\n    public void sendCollectItemEntity(Player player, Entity taker, Entity item, int amount) {\r\n        send(player, new ClientboundTakeItemEntityPacket(item.getEntityId(), taker.getEntityId(), amount));\r\n    }\r\n\r\n    public Relative toNmsRelativeMovement(TeleportCommand.Relative relative) {\r\n        // TODO: 1.21.3: There seem to be more relative movement types now\r\n        return switch (relative) {\r\n            case X -> Relative.X;\r\n            case Y -> Relative.Y;\r\n            case Z -> Relative.Z;\r\n            case YAW -> Relative.Y_ROT;\r\n            case PITCH -> Relative.X_ROT;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void sendRelativePositionPacket(Player player, double x, double y, double z, float yaw, float pitch, List<TeleportCommand.Relative> relativeAxis) {\r\n        Set<Relative> relativeMovements;\r\n        if (relativeAxis == null) {\r\n            relativeMovements = Relative.ALL;\r\n        }\r\n        else {\r\n            relativeMovements = EnumSet.noneOf(Relative.class);\r\n            for (TeleportCommand.Relative relative : relativeAxis) {\r\n                relativeMovements.add(toNmsRelativeMovement(relative));\r\n            }\r\n        }\r\n        ClientboundPlayerPositionPacket packet = new ClientboundPlayerPositionPacket(0, new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), relativeMovements);\r\n        sendAsyncSafe(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendRelativeLookPacket(Player player, float yaw, float pitch) {\r\n        sendRelativePositionPacket(player, 0, 0, 0, yaw, pitch, null);\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityDataPacket(List<Player> players, Entity entity, List<Object> data) {\r\n        ClientboundSetEntityDataPacket setEntityDataPacket = new ClientboundSetEntityDataPacket(entity.getEntityId(), (List<SynchedEntityData.DataValue<?>>) (Object) data);\r\n        Iterator<Player> playerIterator = players.iterator();\r\n        while (playerIterator.hasNext()) {\r\n            Player player = playerIterator.next();\r\n            if (!DenizenNetworkManagerImpl.getConnection(player).isConnected()) {\r\n                playerIterator.remove();\r\n                continue;\r\n            }\r\n            sendAsyncSafe(player, setEntityDataPacket);\r\n        }\r\n    }\r\n\r\n    public static void send(Player player, Packet<?> packet) {\r\n        ((CraftPlayer) player).getHandle().connection.send(packet);\r\n    }\r\n\r\n    public static void broadcast(Packet<?> packet) {\r\n        ((CraftServer) Bukkit.getServer()).getHandle().broadcastAll(packet);\r\n    }\r\n\r\n    public static void sendAsyncSafe(Player player, Packet<?> packet) {\r\n        DenizenNetworkManagerImpl.getConnection(player).channel.writeAndFlush(packet);\r\n    }\r\n\r\n    public static <T> SynchedEntityData.DataValue<T> createEntityData(EntityDataAccessor<T> accessor, T value) {\r\n        return new SynchedEntityData.DataValue<>(accessor.id(), accessor.serializer(), value);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/PlayerHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.enums.CustomEntityType;\r\nimport com.denizenscript.denizen.nms.interfaces.PlayerHelper;\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.ImprovedOfflinePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.AbstractListenerPlayInImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.entity.DenizenEntityType;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport it.unimi.dsi.fastutil.ints.IntList;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.resources.Identifier;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.players.NameAndId;\r\nimport net.minecraft.server.players.PlayerList;\r\nimport net.minecraft.server.players.ServerOpList;\r\nimport net.minecraft.server.players.ServerOpListEntry;\r\nimport net.minecraft.stats.ServerRecipeBook;\r\nimport net.minecraft.tags.BlockTags;\r\nimport net.minecraft.tags.TagNetworkSerialization;\r\nimport net.minecraft.world.effect.MobEffectInstance;\r\nimport net.minecraft.world.entity.Avatar;\r\nimport net.minecraft.world.entity.Leashable;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.entity.PositionMoveRotation;\r\nimport net.minecraft.world.item.ItemCooldowns;\r\nimport net.minecraft.world.item.crafting.RecipeHolder;\r\nimport net.minecraft.world.item.crafting.RecipeManager;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.GameType;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.storage.LevelData;\r\nimport net.minecraft.world.phys.AABB;\r\nimport org.bukkit.*;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.boss.CraftBossBar;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.function.Predicate;\r\n\r\npublic class PlayerHelperImpl extends PlayerHelper {\r\n\r\n    public static final Field ATTACK_COOLDOWN_TICKS = ReflectionHelper.getFields(LivingEntity.class).get(ReflectionMappingsInfo.LivingEntity_attackStrengthTicker, int.class);\r\n\r\n    public static final Field FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundTickCount, int.class);\r\n    public static final Field VEHICLE_FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundVehicleTickCount, int.class);\r\n    public static final Field PASSENGERS_PACKET_PASSENGERS = ReflectionHelper.getFields(ClientboundSetPassengersPacket.class).get(ReflectionMappingsInfo.ClientboundSetPassengersPacket_passengers, int[].class);\r\n    public static final MethodHandle PLAYER_RESPAWNCONFIG_SETTER = ReflectionHelper.getFinalSetter(ServerPlayer.class, ReflectionMappingsInfo.ServerPlayer_respawnConfig, ServerPlayer.RespawnConfig.class);\r\n    public static final MethodHandle SERVER_RECIPE_BOOK_ADD_HIGHLIGHT = ReflectionHelper.getMethodHandle(ServerRecipeBook.class, ReflectionMappingsInfo.ServerRecipeBook_addHighlight_method, ResourceKey.class);\r\n\r\n    @Override\r\n    public void stopSound(Player player, NamespacedKey sound, SoundCategory category) {\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundStopSoundPacket(sound == null ? null : CraftNamespacedKey.toMinecraft(sound), null));\r\n    }\r\n\r\n    @Override\r\n    public void deTrackEntity(Player player, Entity entity) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ChunkMap.TrackedEntity tracker = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n        if (tracker == null) {\r\n            if (NMSHandler.debugPackets) {\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Failed to de-track entity \" + entity.getEntityId() + \" for \" + player.getName() + \": tracker null\");\r\n            }\r\n            return;\r\n        }\r\n        sendEntityDestroy(player, entity);\r\n        tracker.removePlayer(nmsPlayer);\r\n    }\r\n\r\n    public record TrackerData(PlayerTag player, ServerEntity tracker) {}\r\n\r\n    @Override\r\n    public void addFakePassenger(List<PlayerTag> players, Entity vehicle, FakeEntity fakePassenger) {\r\n        ClientboundSetPassengersPacket packet = new ClientboundSetPassengersPacket(((CraftEntity) vehicle).getHandle());\r\n        int[] newPassengers = Arrays.copyOf(packet.getPassengers(), packet.getPassengers().length + 1);\r\n        newPassengers[packet.getPassengers().length] = fakePassenger.id;\r\n        try {\r\n            PASSENGERS_PACKET_PASSENGERS.set(packet, newPassengers);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        for (PlayerTag player : players) {\r\n            PacketHelperImpl.send(player.getPlayerEntity(), packet);\r\n        }\r\n    }\r\n\r\n    public record FakeEntitySynchronizer(ServerGamePacketListenerImpl target) implements ServerEntity.Synchronizer {\r\n\r\n        @Override\r\n        public void sendToTrackingPlayers(Packet<? super ClientGamePacketListener> packet) {\r\n            target.send(packet);\r\n        }\r\n\r\n        @Override\r\n        public void sendToTrackingPlayersAndSelf(Packet<? super ClientGamePacketListener> packet) {\r\n            sendToTrackingPlayers(packet);\r\n        }\r\n\r\n        @Override\r\n        public void sendToTrackingPlayersFiltered(Packet<? super ClientGamePacketListener> packet, Predicate<ServerPlayer> predicate) {\r\n            if (predicate.test(target.getPlayer())) {\r\n                sendToTrackingPlayers(packet);\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public void sendToTrackingPlayersFilteredAndSelf(Packet<? super ClientGamePacketListener> packet, Predicate<ServerPlayer> predicate) {\r\n            sendToTrackingPlayersFiltered(packet, predicate);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public FakeEntity sendEntitySpawn(List<PlayerTag> players, DenizenEntityType entityType, LocationTag location, ArrayList<Mechanism> mechanisms, int customId, UUID customUUID, boolean autoTrack) {\r\n        CraftWorld world = ((CraftWorld) location.getWorld());\r\n        net.minecraft.world.entity.Entity nmsEntity;\r\n        if (entityType.isCustom()) {\r\n            if (entityType.customEntityType == CustomEntityType.ITEM_PROJECTILE) {\r\n                ItemStack itemStack = new ItemStack(Material.STONE);\r\n                for (Mechanism mechanism : mechanisms) {\r\n                    if (mechanism.matches(\"item\") && mechanism.requireObject(ItemTag.class)) {\r\n                        itemStack = mechanism.valueAsType(ItemTag.class).getItemStack();\r\n                    }\r\n                }\r\n                nmsEntity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n            }\r\n            else if (entityType.customEntityType == CustomEntityType.FAKE_PLAYER) {\r\n                String name = null;\r\n                String skin = null;\r\n                String blob = null;\r\n                for (Mechanism mechanism : new ArrayList<>(mechanisms)) {\r\n                    if (mechanism.matches(\"name\")) {\r\n                        name = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin\")) {\r\n                        skin = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin_blob\")) {\r\n                        blob = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    if (name != null && (skin != null || blob != null)) {\r\n                        break;\r\n                    }\r\n                }\r\n                nmsEntity = ((CraftFakePlayerImpl) NMSHandler.customEntityHelper.spawnFakePlayer(location, name, skin, blob, false)).getHandle();\r\n            }\r\n            else {\r\n                throw new IllegalArgumentException(\"entityType\");\r\n            }\r\n        }\r\n        else {\r\n            org.bukkit.entity.Entity entity = world.createEntity(location, entityType.getBukkitEntityType().getEntityClass());\r\n            nmsEntity = ((CraftEntity) entity).getHandle();\r\n        }\r\n        if (customUUID != null) {\r\n            nmsEntity.setId(customId);\r\n            nmsEntity.setUUID(customUUID);\r\n        }\r\n        EntityTag entity = new EntityTag(nmsEntity.getBukkitEntity());\r\n        entity.isFake = true;\r\n        entity.isFakeValid = true;\r\n        for (Mechanism mechanism : mechanisms) {\r\n            entity.safeAdjustDuplicate(mechanism);\r\n        }\r\n        nmsEntity.unsetRemoved();\r\n        FakeEntity fake = new FakeEntity(players, location, entity.getBukkitEntity().getEntityId());\r\n        fake.entity = new EntityTag(entity.getBukkitEntity());\r\n        fake.entity.isFake = true;\r\n        fake.entity.isFakeValid = true;\r\n        List<TrackerData> trackers = new ArrayList<>();\r\n        fake.triggerSpawnPacket = (player) -> {\r\n            ServerPlayer nmsPlayer = ((CraftPlayer) player.getPlayerEntity()).getHandle();\r\n            ServerGamePacketListenerImpl conn = nmsPlayer.connection;\r\n            final ServerEntity tracker = new ServerEntity(world.getHandle(), nmsEntity, 1, true, new FakeEntitySynchronizer(conn), Set.of(conn));\r\n            tracker.addPairing(nmsPlayer);\r\n            final TrackerData data = new TrackerData(player, tracker);\r\n            trackers.add(data);\r\n            if (autoTrack) {\r\n                new BukkitRunnable() {\r\n                    boolean wasOnline = true;\r\n                    @Override\r\n                    public void run() {\r\n                        if (!fake.entity.isFakeValid) {\r\n                            cancel();\r\n                            return;\r\n                        }\r\n                        if (player.isOnline()) {\r\n                            if (!wasOnline) {\r\n                                tracker.addPairing(((CraftPlayer) player.getPlayerEntity()).getHandle());\r\n                                wasOnline = true;\r\n                            }\r\n                            tracker.sendChanges();\r\n                        }\r\n                        else if (wasOnline) {\r\n                            wasOnline = false;\r\n                        }\r\n                    }\r\n                }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n            }\r\n        };\r\n        for (PlayerTag player : players) {\r\n            fake.triggerSpawnPacket.accept(player);\r\n        }\r\n        fake.triggerUpdatePacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.sendChanges();\r\n                }\r\n            }\r\n        };\r\n        fake.triggerDestroyPacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.removePairing(((CraftPlayer) tracker.player.getPlayerEntity()).getHandle());\r\n                }\r\n            }\r\n            trackers.clear();\r\n        };\r\n        return fake;\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityDestroy(Player player, Entity entity) {\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId()));\r\n    }\r\n\r\n    @Override\r\n    public int getFlyKickCooldown(Player player) {\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl denizenListener) {\r\n            conn = denizenListener.oldListener;\r\n        }\r\n        try {\r\n            return Math.max(80 - Math.max(FLY_TICKS.getInt(conn), VEHICLE_FLY_TICKS.getInt(conn)), 0);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return 80;\r\n    }\r\n\r\n    @Override\r\n    public void setFlyKickCooldown(Player player, int ticks) {\r\n        ticks = 80 - ticks;\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl denizenListener) {\r\n            conn = denizenListener.oldListener;\r\n        }\r\n        try {\r\n            FLY_TICKS.setInt(conn, ticks);\r\n            VEHICLE_FLY_TICKS.setInt(conn, ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int ticksPassedDuringCooldown(Player player) {\r\n        try {\r\n            return ATTACK_COOLDOWN_TICKS.getInt(((CraftPlayer) player).getHandle());\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public float getMaxAttackCooldownTicks(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getCurrentItemAttackStrengthDelay() + 3;\r\n    }\r\n\r\n    @Override\r\n    public void setAttackCooldown(Player player, int ticks) {\r\n        try {\r\n            ATTACK_COOLDOWN_TICKS.setInt(((CraftPlayer) player).getHandle(), ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean hasChunkLoaded(Player player, Chunk chunk) {\r\n        return ((CraftWorld) chunk.getWorld()).getHandle().getChunkSource().chunkMap\r\n                .getPlayers(new ChunkPos(chunk.getX(), chunk.getZ()), false).stream()\r\n                .anyMatch(entityPlayer -> entityPlayer.getUUID().equals(player.getUniqueId()));\r\n    }\r\n\r\n    @Override\r\n    public void setTemporaryOp(Player player, boolean op) {\r\n        MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();\r\n        NameAndId nameAndId = new NameAndId(((CraftPlayer) player).getProfile());\r\n        ServerOpList opList = server.getPlayerList().getOps();\r\n        if (op) {\r\n            opList.add(new ServerOpListEntry(nameAndId, server.operatorUserPermissions(), opList.canBypassPlayerLimit(nameAndId)));\r\n        }\r\n        else {\r\n            opList.remove(nameAndId);\r\n        }\r\n        player.recalculatePermissions();\r\n    }\r\n\r\n    @Override\r\n    public void showEndCredits(Player player) {\r\n        ((CraftPlayer) player).getHandle().wonGame = true;\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1f));\r\n    }\r\n\r\n    @Override\r\n    public ImprovedOfflinePlayer getOfflineData(UUID uuid) {\r\n        return new ImprovedOfflinePlayerImpl(uuid);\r\n    }\r\n\r\n    @Override\r\n    public void resendRecipeDetails(Player player) {\r\n        RecipeManager recipeManager = ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager();\r\n        ClientboundUpdateRecipesPacket updatePacket = new ClientboundUpdateRecipesPacket(recipeManager.getSynchronizedItemProperties(), recipeManager.getSynchronizedStonecutterRecipes());\r\n        ((CraftPlayer) player).getHandle().connection.send(updatePacket);\r\n    }\r\n\r\n    @Override\r\n    public void resendDiscoveredRecipes(Player player) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        recipeBook.sendInitialRecipeBook(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    @Override\r\n    public void quietlyAddRecipe(Player player, NamespacedKey key) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        RecipeHolder<?> recipe = ItemHelperImpl.getNMSRecipe(key);\r\n        if (recipe == null) {\r\n            Debug.echoError(\"Cannot add recipe '\" + key + \"': it does not exist.\");\r\n            return;\r\n        }\r\n        recipeBook.add(recipe.id());\r\n        try {\r\n            SERVER_RECIPE_BOOK_ADD_HIGHLIGHT.invoke(recipeBook, recipe.id());\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public byte getSkinLayers(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getEntityData().get(Avatar.DATA_PLAYER_MODE_CUSTOMISATION);\r\n    }\r\n\r\n    @Override\r\n    public void setSkinLayers(Player player, byte flags) {\r\n        ((CraftPlayer) player).getHandle().getEntityData().set(Avatar.DATA_PLAYER_MODE_CUSTOMISATION, flags);\r\n    }\r\n\r\n    @Override\r\n    public void setBossBarTitle(BossBar bar, String title) {\r\n        ((CraftBossBar) bar).getHandle().name = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        ((CraftBossBar) bar).getHandle().broadcast(ClientboundBossEventPacket::createUpdateNamePacket);\r\n    }\r\n\r\n    @Override\r\n    public boolean getSpawnForced(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getRespawnConfig().forced();\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnForced(Player player, boolean forced) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        try {\r\n            ServerPlayer.RespawnConfig config = nmsPlayer.getRespawnConfig();\r\n            config = new ServerPlayer.RespawnConfig(config.respawnData(), forced);\r\n            PLAYER_RESPAWNCONFIG_SETTER.invoke(nmsPlayer, config);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Location getBedSpawnLocation(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerPlayer.RespawnConfig nmsRespawnConfig = nmsPlayer.getRespawnConfig();\r\n        if (nmsRespawnConfig == null) {\r\n            return null;\r\n        }\r\n        LevelData.RespawnData nmsRespawnData = nmsRespawnConfig.respawnData();\r\n        Level nmsWorld = MinecraftServer.getServer().getLevel(nmsRespawnData.dimension());\r\n        return nmsWorld != null ? new Location(nmsWorld.getWorld(), nmsRespawnData.pos().getX(), nmsRespawnData.pos().getY(), nmsRespawnData.pos().getZ(), nmsRespawnData.yaw(), nmsRespawnData.pitch()) : null;\r\n    }\r\n\r\n    @Override\r\n    public long getLastActionTime(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getLastActionTime();\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerInfoAddPacket(Player player, EnumSet<ProfileEditMode> editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) {\r\n        EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action.class);\r\n        for (ProfileEditMode editMode : editModes) {\r\n            actions.add(switch (editMode) {\r\n                case ADD -> ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER;\r\n                case UPDATE_DISPLAY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME;\r\n                case UPDATE_LATENCY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY;\r\n                case UPDATE_GAME_MODE -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE;\r\n                case UPDATE_LISTED -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED;\r\n            });\r\n        }\r\n        GameProfile profile = ProfileEditorImpl.createGameProfile(id, name, texture, signature);\r\n        // TODO: 1.21.3: Player list order and hat visibility support\r\n        ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(id, profile, listed, latency, gameMode == null ? null : GameType.byId(gameMode.getValue()), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)), true, player.getPlayerListOrder(), null);\r\n        PacketHelperImpl.send(player, ProfileEditorImpl.createInfoPacket(actions, List.of(entry)));\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerInfoRemovePacket(Player player, UUID id) {\r\n        PacketHelperImpl.send(player, new ClientboundPlayerInfoRemovePacket(List.of(id)));\r\n    }\r\n\r\n    @Override\r\n    public void sendClimbableMaterials(Player player, List<Material> materials) {\r\n        Map<ResourceKey<? extends Registry<?>>, TagNetworkSerialization.NetworkPayload> packetInput = TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registries());\r\n        Map<Identifier, IntList> tags = ReflectionHelper.getFieldValue(TagNetworkSerialization.NetworkPayload.class, ReflectionMappingsInfo.TagNetworkSerializationNetworkPayload_tags, packetInput.get(BuiltInRegistries.BLOCK.key()));\r\n        IntList climbableBlocks = tags.get(BlockTags.CLIMBABLE.location());\r\n        climbableBlocks.clear();\r\n        for (Material material : materials) {\r\n            climbableBlocks.add(BuiltInRegistries.BLOCK.getId(CraftMagicNumbers.getBlock(material)));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundUpdateTagsPacket(packetInput));\r\n    }\r\n\r\n    @Override\r\n    public void refreshPlayer(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerLevel nmsWorld = nmsPlayer.level();\r\n        nmsPlayer.connection.send(new ClientboundRespawnPacket(nmsPlayer.createCommonSpawnInfo(nmsWorld), ClientboundRespawnPacket.KEEP_ALL_DATA));\r\n        nmsPlayer.connection.internalTeleport(PositionMoveRotation.of(nmsPlayer), Set.of());\r\n        if (nmsPlayer.isPassenger()) {\r\n           nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer.getVehicle()));\r\n        }\r\n        if (nmsPlayer.isVehicle()) {\r\n            nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer));\r\n        }\r\n        AABB boundingBox = AABB.ofSize(nmsPlayer.getBoundingBox().getCenter(), 32, 32, 32);\r\n        for (net.minecraft.world.entity.Entity nmsEntity : nmsWorld.getEntitiesOfClass(net.minecraft.world.entity.Entity.class, boundingBox, nmsEntity -> nmsEntity instanceof Leashable nmsLeashable && nmsPlayer.equals(nmsLeashable.getLeashHolder()))) {\r\n            nmsPlayer.connection.send(new ClientboundSetEntityLinkPacket(nmsEntity, nmsPlayer));\r\n        }\r\n        if (!nmsPlayer.getCooldowns().cooldowns.isEmpty()) {\r\n            int tickCount = nmsPlayer.getCooldowns().tickCount;\r\n            for (Map.Entry<Identifier, ItemCooldowns.CooldownInstance> entry : nmsPlayer.getCooldowns().cooldowns.entrySet()) {\r\n                nmsPlayer.connection.send(new ClientboundCooldownPacket(entry.getKey(), entry.getValue().endTime - tickCount));\r\n            }\r\n        }\r\n        nmsPlayer.connection.send(new ClientboundSetExperiencePacket(nmsPlayer.experienceProgress, nmsPlayer.totalExperience, nmsPlayer.experienceLevel));\r\n        for (MobEffectInstance nmsEffect : nmsPlayer.getActiveEffects()) {\r\n            nmsPlayer.connection.send(new ClientboundUpdateMobEffectPacket(nmsPlayer.getId(), nmsEffect, false));\r\n        }\r\n        nmsPlayer.onUpdateAbilities();\r\n        PlayerList nmsPlayerList = MinecraftServer.getServer().getPlayerList();\r\n        nmsPlayerList.sendPlayerPermissionLevel(nmsPlayer);\r\n        nmsPlayerList.sendLevelInfo(nmsPlayer, nmsWorld);\r\n        nmsPlayerList.sendAllPlayerInfo(nmsPlayer);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/helpers/WorldHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.WorldHelper;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.objects.BiomeTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.mojang.datafixers.util.Pair;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.players.SleepStatus;\r\nimport net.minecraft.world.DifficultyInstance;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.storage.PrimaryLevelData;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\n\r\npublic class WorldHelperImpl implements WorldHelper {\r\n\r\n    @Override\r\n    public boolean isStatic(World world) {\r\n        return ((CraftWorld) world).getHandle().isClientSide();\r\n    }\r\n\r\n    @Override\r\n    public void setStatic(World world, boolean isStatic) {\r\n        ServerLevel worldServer = ((CraftWorld) world).getHandle();\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.Level.class, ReflectionMappingsInfo.Level_isClientSide, worldServer, isStatic);\r\n    }\r\n\r\n    @Override\r\n    public float getLocalDifficulty(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        DifficultyInstance scaler = ((CraftWorld) location.getWorld()).getHandle().getCurrentDifficultyAt(pos);\r\n        return scaler.getEffectiveDifficulty();\r\n    }\r\n\r\n    @Override\r\n    public Location getNearestBiomeLocation(Location start, BiomeTag biome) {\r\n        Pair<BlockPos, Holder<Biome>> result = ((CraftWorld) start.getWorld()).getHandle()\r\n                .findClosestBiome3d(b -> b.is(((BiomeNMSImpl) biome.getBiome()).biomeHolder.unwrapKey().get()), new BlockPos(start.getBlockX(), start.getBlockY(), start.getBlockZ()), 6400, 32, 64);\r\n        if (result == null || result.getFirst() == null) {\r\n            return null;\r\n        }\r\n        return new Location(start.getWorld(), result.getFirst().getX(), result.getFirst().getY(), result.getFirst().getZ());\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughSleeping(World world, int percentage) {\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, ((CraftWorld) world).getHandle());\r\n        return status.areEnoughSleeping(percentage);\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughDeepSleeping(World world, int percentage) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, level);\r\n        return status.areEnoughDeepSleeping(percentage, level.players());\r\n    }\r\n\r\n    @Override\r\n    public int getSkyDarken(World world) {\r\n        return ((CraftWorld) world).getHandle().getSkyDarken();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDay(World world) {\r\n        return ((CraftWorld) world).getHandle().isBrightOutside();\r\n    }\r\n\r\n    @Override\r\n    public boolean isNight(World world) {\r\n        return ((CraftWorld) world).getHandle().isDarkOutside();\r\n    }\r\n\r\n    @Override\r\n    public void setDayTime(World world, long time) {\r\n        ((CraftWorld) world).getHandle().setDayTime(time);\r\n    }\r\n\r\n    @Override\r\n    public void setGameTime(World world, long time) {\r\n        ((PrimaryLevelData) ((CraftWorld) world).getHandle().levelData).setGameTime(time);\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#wakeUpAllPlayers()\r\n    @Override\r\n    public void wakeUpAllPlayers(World world) {\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, ReflectionMappingsInfo.ServerLevel_sleepStatus, nmsWorld);\r\n        status.removeAllSleepers();\r\n        nmsWorld.getPlayers(LivingEntity::isSleeping).forEach((player) -> player.stopSleepInBed(false, false));\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#resetWeatherCycle()\r\n    @Override\r\n    public void clearWeather(World world) {\r\n        PrimaryLevelData data = ((CraftWorld) world).getHandle().serverLevelData;\r\n        data.setRaining(false);\r\n        if (!data.isRaining()) {\r\n            data.setRainTime(0);\r\n        }\r\n        data.setThundering(false);\r\n        if (!data.isThundering()) {\r\n            data.setThunderTime(0);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/BiomeNMSImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.MappedRegistry;\r\nimport net.minecraft.core.RegistrationInfo;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.random.Weighted;\r\nimport net.minecraft.util.random.WeightedList;\r\nimport net.minecraft.world.attribute.EnvironmentAttribute;\r\nimport net.minecraft.world.attribute.EnvironmentAttributeMap;\r\nimport net.minecraft.world.attribute.EnvironmentAttributes;\r\nimport net.minecraft.world.entity.MobCategory;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.BiomeSpecialEffects;\r\nimport net.minecraft.world.level.biome.MobSpawnSettings;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntityType;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftLocation;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.Optional;\r\n\r\npublic class BiomeNMSImpl extends BiomeNMS {\r\n\r\n    public static final MethodHandle BIOME_CLIMATESETTINGS_CONSTRUCTOR = ReflectionHelper.getConstructor(Biome.ClimateSettings.class, boolean.class, float.class, Biome.TemperatureModifier.class, float.class);\r\n    public static final MethodHandle MAPPED_REGISTRY_REGISTRATION_INFOS = ReflectionHelper.getFields(MappedRegistry.class).getGetter(ReflectionMappingsInfo.MappedRegistry_registrationInfos);\r\n    public static final MethodHandle BIOME_ATTRIBUTES_SETTER = ReflectionHelper.getFields(Biome.class).getSetter(ReflectionMappingsInfo.Biome_attributes);\r\n\r\n    public Holder.Reference<Biome> biomeHolder;\r\n    public ServerLevel world;\r\n\r\n    public BiomeNMSImpl(ServerLevel world, NamespacedKey key) {\r\n        super(world.getWorld(), key);\r\n        this.world = world;\r\n        this.biomeHolder = getBiomeRegistry().get(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(key))).orElse(null);\r\n    }\r\n\r\n    private MappedRegistry<Biome> getBiomeRegistry() {\r\n        return (MappedRegistry<Biome>) world.registryAccess().lookupOrThrow(Registries.BIOME);\r\n    }\r\n\r\n    @Override\r\n    public DownfallType getDownfallTypeAt(Location location) {\r\n        Biome.Precipitation precipitation = biomeHolder.value().getPrecipitationAt(CraftLocation.toBlockPosition(location), world.getSeaLevel());\r\n        return switch (precipitation) {\r\n            case RAIN -> DownfallType.RAIN;\r\n            case SNOW -> DownfallType.SNOW;\r\n            case NONE -> DownfallType.NONE;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public float getHumidity() {\r\n        return biomeHolder.value().climateSettings.downfall();\r\n    }\r\n\r\n    @Override\r\n    public float getBaseTemperature() {\r\n        return biomeHolder.value().getBaseTemperature();\r\n    }\r\n\r\n    @Override\r\n    public float getTemperatureAt(Location location) {\r\n        return biomeHolder.value().getTemperature(CraftLocation.toBlockPosition(location), world.getSeaLevel());\r\n    }\r\n\r\n    @Override\r\n    public boolean hasDownfall() {\r\n        return biomeHolder.value().hasPrecipitation();\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getAmbientEntities() {\r\n        return getSpawnableEntities(MobCategory.AMBIENT);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getCreatureEntities() {\r\n        return getSpawnableEntities(MobCategory.CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getMonsterEntities() {\r\n        return getSpawnableEntities(MobCategory.MONSTER);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getWaterEntities() {\r\n        return getSpawnableEntities(MobCategory.WATER_CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public int getFoliageColor() {\r\n        // Check if the biome already has a default color\r\n        if (biomeHolder.value().getFoliageColor() != 0) {\r\n            return biomeHolder.value().getFoliageColor();\r\n        }\r\n        // Based on net.minecraft.world.level.biome.Biome#getFoliageColorFromTexture()\r\n        float temperature = clampColor(getBaseTemperature());\r\n        float humidity = clampColor(getHumidity());\r\n        // Based on net.minecraft.world.level.FoliageColor#get()\r\n        humidity *= temperature;\r\n        int humidityValue = (int)((1.0f - humidity) * 255.0f);\r\n        int temperatureValue = (int)((1.0f - temperature) * 255.0f);\r\n        int index = temperatureValue << 8 | humidityValue;\r\n        return index >= 65536 ? 4764952 : getColor(index / 256, index % 256).asRGB();\r\n    }\r\n\r\n    public void setClimate(boolean hasPrecipitation, float temperature, Biome.TemperatureModifier temperatureModifier, float downfall) {\r\n        try {\r\n            Object newClimate = BIOME_CLIMATESETTINGS_CONSTRUCTOR.invoke(hasPrecipitation, temperature, temperatureModifier, downfall);\r\n            ReflectionHelper.setFieldValue(Biome.class, ReflectionMappingsInfo.Biome_climateSettings, biomeHolder.value(), newClimate);\r\n            setNetworkedRegistrationInfo();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHumidity(float humidity) {\r\n        setClimate(hasDownfall(), getBaseTemperature(), getTemperatureModifier(), humidity);\r\n    }\r\n\r\n    @Override\r\n    public void setBaseTemperature(float baseTemperature) {\r\n        setClimate(hasDownfall(), baseTemperature, getTemperatureModifier(), getHumidity());\r\n    }\r\n\r\n    @Override\r\n    public void setHasDownfall(boolean hasDownfall) {\r\n        setClimate(hasDownfall, getBaseTemperature(), getTemperatureModifier(), getHumidity());\r\n    }\r\n\r\n    @Override\r\n    public void setFoliageColor(int color) {\r\n        BiomeSpecialEffects nmsCurrEffects = biomeHolder.value().getSpecialEffects();\r\n        BiomeSpecialEffects nmsNewEffects = new BiomeSpecialEffects(\r\n                nmsCurrEffects.waterColor(), Optional.of(color), nmsCurrEffects.dryFoliageColorOverride(), nmsCurrEffects.grassColorOverride(), nmsCurrEffects.grassColorModifier()\r\n        );\r\n        ReflectionHelper.setFieldValue(Biome.class, ReflectionMappingsInfo.Biome_specialEffects, biomeHolder.value(), nmsNewEffects);\r\n        setNetworkedRegistrationInfo();\r\n    }\r\n\r\n    @Override\r\n    public int getFogColor() {\r\n        return getEnvironmentAttribute(EnvironmentAttributes.FOG_COLOR);\r\n    }\r\n\r\n    @Override\r\n    public void setFogColor(int color) {\r\n        setEnvironmentAttribute(EnvironmentAttributes.FOG_COLOR, color);\r\n    }\r\n\r\n    @Override\r\n    public int getWaterFogColor() {\r\n        return getEnvironmentAttribute(EnvironmentAttributes.WATER_FOG_COLOR);\r\n    }\r\n\r\n    @Override\r\n    public void setWaterFogColor(int color) {\r\n        setEnvironmentAttribute(EnvironmentAttributes.WATER_FOG_COLOR, color);\r\n    }\r\n\r\n    public <T> T getEnvironmentAttribute(EnvironmentAttribute<T> attribute) {\r\n        return biomeHolder.value().getAttributes().applyModifier(attribute, attribute.defaultValue());\r\n    }\r\n\r\n    public <T> void setEnvironmentAttribute(EnvironmentAttribute<T> attribute, T value) {\r\n        Biome nmsBiome = biomeHolder.value();\r\n        EnvironmentAttributeMap newAttributeMap = EnvironmentAttributeMap.builder().putAll(nmsBiome.getAttributes()).set(attribute, value).build();\r\n        try {\r\n            BIOME_ATTRIBUTES_SETTER.invokeExact(nmsBiome, newAttributeMap);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n        }\r\n        setNetworkedRegistrationInfo();\r\n    }\r\n\r\n    private List<EntityType> getSpawnableEntities(MobCategory creatureType) {\r\n        MobSpawnSettings mobs = biomeHolder.value().getMobSettings();\r\n        WeightedList<MobSpawnSettings.SpawnerData> typeSettingList = mobs.getMobs(creatureType);\r\n        List<EntityType> entityTypes = new ArrayList<>();\r\n        if (typeSettingList == null) {\r\n            return entityTypes;\r\n        }\r\n        for (Weighted<MobSpawnSettings.SpawnerData> meta : typeSettingList.unwrap()) {\r\n            entityTypes.add(CraftEntityType.minecraftToBukkit(meta.value().type()));\r\n        }\r\n        return entityTypes;\r\n    }\r\n\r\n    @Override\r\n    public void setTo(Block block) {\r\n        if (((CraftWorld) block.getWorld()).getHandle() != this.world) {\r\n            NMSHandler.instance.getBiomeNMS(block.getWorld(), getKey()).setTo(block);\r\n            return;\r\n        }\r\n        // Based on CraftWorld source\r\n        BlockPos pos = new BlockPos(block.getX(), 0, block.getZ());\r\n        if (world.hasChunkAt(pos)) {\r\n            LevelChunk chunk = world.getChunkAt(pos);\r\n            if (chunk != null) {\r\n                chunk.setBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2, biomeHolder);\r\n                chunk.markUnsaved();\r\n            }\r\n        }\r\n    }\r\n\r\n    public Biome.TemperatureModifier getTemperatureModifier() {\r\n        return biomeHolder.value().climateSettings.temperatureModifier();\r\n    }\r\n\r\n    private void setNetworkedRegistrationInfo() {\r\n        try {\r\n            Map<ResourceKey<Biome>, RegistrationInfo> registrationInfos = (Map<ResourceKey<Biome>, RegistrationInfo>) MAPPED_REGISTRY_REGISTRATION_INFOS.invokeExact(getBiomeRegistry());\r\n            registrationInfos.put(biomeHolder.key(), RegistrationInfo.BUILT_IN);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(\"Failed to set biome registration info, changes may not be synced correctly.\");\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/ImprovedOfflinePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizen.nms.v1_21.helpers.NBTAdapter;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.nbt.NbtAccounter;\r\nimport net.minecraft.nbt.NbtIo;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ClientInformation;\r\nimport net.minecraft.server.level.ParticleStatus;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.world.ItemStackWithSlot;\r\nimport net.minecraft.world.entity.EntityEquipment;\r\nimport net.minecraft.world.entity.HumanoidArm;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeMap;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.attributes.DefaultAttributes;\r\nimport net.minecraft.world.entity.player.ChatVisiblity;\r\nimport net.minecraft.world.inventory.PlayerEnderChestContainer;\r\nimport net.minecraft.world.level.storage.ValueOutput;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryPlayer;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryHolder;\r\n\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.FileOutputStream;\r\nimport java.util.UUID;\r\nimport java.util.function.Consumer;\r\n\r\npublic class ImprovedOfflinePlayerImpl extends ImprovedOfflinePlayer {\r\n\r\n    public ImprovedOfflinePlayerImpl(UUID playeruuid) {\r\n        super(playeruuid);\r\n    }\r\n\r\n    public static class OfflinePlayerInventory extends net.minecraft.world.entity.player.Inventory {\r\n\r\n        public OfflinePlayerInventory(net.minecraft.world.entity.player.Player entityhuman) {\r\n            super(entityhuman, new EntityEquipment()); // TODO: 1.21.5: is the new Equipment right here?\r\n        }\r\n\r\n        @Override\r\n        public InventoryHolder getOwner() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static class OfflineCraftInventoryPlayer extends CraftInventoryPlayer {\r\n\r\n        public OfflineCraftInventoryPlayer(net.minecraft.world.entity.player.Inventory inventory) {\r\n            super(inventory);\r\n        }\r\n\r\n        @Override\r\n        public HumanEntity getHolder() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static ServerPlayer fakeNmsPlayer;\r\n\r\n    public static ServerPlayer getFakeNmsPlayer() {\r\n        if (fakeNmsPlayer == null) {\r\n            MinecraftServer server = ((CraftServer)Bukkit.getServer()).getServer();\r\n            World world = Bukkit.getWorlds().getFirst();\r\n            GameProfile fakeProfile = new GameProfile(new UUID(0, 0xABC123), \"fakeplayer\");\r\n            ClientInformation fakeClientInfo = new ClientInformation(\"en\", 0, ChatVisiblity.HIDDEN, false, 0, HumanoidArm.LEFT, true, false, ParticleStatus.MINIMAL);\r\n            fakeNmsPlayer = new ServerPlayer(server, ((CraftWorld) world).getHandle(), fakeProfile, fakeClientInfo);\r\n        }\r\n        return fakeNmsPlayer;\r\n    }\r\n\r\n    public void editData(Consumer<ValueOutput> editor) {\r\n        this.compound = NBTAdapter.toAPI(Handler.useValueOutput(NBTAdapter.toNMS(this.compound), editor));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    public org.bukkit.inventory.PlayerInventory getInventory() {\r\n        if (inventory == null) {\r\n            net.minecraft.world.entity.player.Inventory newInv = new OfflinePlayerInventory(getFakeNmsPlayer());\r\n            Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> newInv.load(valueInput.listOrEmpty(\"Inventory\", ItemStackWithSlot.CODEC)));\r\n            inventory = new OfflineCraftInventoryPlayer(newInv);\r\n        }\r\n        return inventory;\r\n    }\r\n\r\n    @Override\r\n    public void setInventory(org.bukkit.inventory.PlayerInventory inventory) {\r\n        CraftInventoryPlayer inv = (CraftInventoryPlayer) inventory;\r\n        editData(valueOutput -> inv.getInventory().save(valueOutput.list(\"Inventory\", ItemStackWithSlot.CODEC)));\r\n    }\r\n\r\n    @Override\r\n    public Inventory getEnderChest() {\r\n        if (enderchest == null) {\r\n            PlayerEnderChestContainer nmsEnderChest = new PlayerEnderChestContainer(getFakeNmsPlayer());\r\n            Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> nmsEnderChest.fromSlots(valueInput.listOrEmpty(\"EnderItems\", ItemStackWithSlot.CODEC)));\r\n            enderchest = new CraftInventory(nmsEnderChest);\r\n        }\r\n        return enderchest;\r\n    }\r\n\r\n    @Override\r\n    public void setEnderChest(Inventory inventory) {\r\n        editData(valueOutput -> ((PlayerEnderChestContainer) ((CraftInventory) inventory).getInventory()).storeAsSlots(valueOutput.list(\"EnderItems\", ItemStackWithSlot.CODEC)));\r\n    }\r\n\r\n    @Override\r\n    public double getMaxHealth() {\r\n        AttributeInstance maxHealth = getAttributes().getInstance(Attributes.MAX_HEALTH);\r\n        return maxHealth == null ? Attributes.MAX_HEALTH.value().getDefaultValue() : maxHealth.getValue();\r\n    }\r\n\r\n    @Override\r\n    public void setMaxHealth(double input) {\r\n        AttributeMap attributes = getAttributes();\r\n        AttributeInstance maxHealth = attributes.getInstance(Attributes.MAX_HEALTH);\r\n        maxHealth.setBaseValue(input);\r\n        setAttributes(attributes);\r\n    }\r\n\r\n    private AttributeMap getAttributes() {\r\n        AttributeMap amb = new AttributeMap(DefaultAttributes.getSupplier(net.minecraft.world.entity.EntityType.PLAYER));\r\n        Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> valueInput.read(\"attributes\", AttributeInstance.Packed.LIST_CODEC).ifPresent(amb::apply));\r\n        return amb;\r\n    }\r\n\r\n    public void setAttributes(AttributeMap attributes) {\r\n        editData(valueOutput -> valueOutput.store(\"attributes\", AttributeInstance.Packed.LIST_CODEC, attributes.pack()));\r\n    }\r\n\r\n    @Override\r\n    protected boolean loadPlayerData(UUID uuid) {\r\n        try {\r\n            this.player = uuid;\r\n            for (org.bukkit.World w : Bukkit.getWorlds()) {\r\n                this.file = new File(w.getWorldFolder(), \"playerdata\" + File.separator + this.player + \".dat\");\r\n                if (this.file.exists()) {\r\n                    this.compound = NBTAdapter.toAPI(NbtIo.readCompressed(new FileInputStream(this.file), NbtAccounter.unlimitedHeap()));\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void saveInternal(CompoundBinaryTag compound) {\r\n        try {\r\n            NbtIo.writeCompressed(NBTAdapter.toNMS(compound), new FileOutputStream(this.file));\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/ProfileEditorImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_21.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.ImmutableMultimap;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.mojang.authlib.properties.PropertyMap;\r\nimport com.mojang.datafixers.util.Either;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.item.component.ResolvableProfile;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.player.PlayerRespawnEvent;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class ProfileEditorImpl extends ProfileEditor {\r\n\r\n    @Override\r\n    protected void updatePlayer(final Player player, final boolean isSkinChanging) {\r\n        final ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        final UUID uuid = player.getUniqueId();\r\n        ClientboundPlayerInfoRemovePacket removePlayerInfoPacket = new ClientboundPlayerInfoRemovePacket(List.of(uuid));\r\n        ClientboundPlayerInfoUpdatePacket addPlayerInfoPacket = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(nmsPlayer));\r\n        for (Player otherPlayer : Bukkit.getServer().getOnlinePlayers()) {\r\n            PacketHelperImpl.send(otherPlayer, removePlayerInfoPacket);\r\n            PacketHelperImpl.send(otherPlayer, addPlayerInfoPacket);\r\n        }\r\n        for (Player otherPlayer : NMSHandler.entityHelper.getPlayersThatSee(player)) {\r\n            if (!otherPlayer.getUniqueId().equals(uuid)) {\r\n                PacketHelperImpl.forceRespawnPlayerEntity(player, otherPlayer);\r\n            }\r\n        }\r\n        if (isSkinChanging) {\r\n            ((CraftServer) Bukkit.getServer()).getHandle().respawn(nmsPlayer, true, Entity.RemovalReason.CHANGED_DIMENSION, PlayerRespawnEvent.RespawnReason.PLUGIN);\r\n        }\r\n        else {\r\n            NMSHandler.playerHelper.refreshPlayer(player);\r\n        }\r\n        player.updateInventory();\r\n    }\r\n\r\n    public static void registerHandlers() {\r\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoUpdatePacket.class, ProfileEditorImpl::processPlayerInfoUpdatePacket);\r\n    }\r\n\r\n    public static ClientboundPlayerInfoUpdatePacket processPlayerInfoUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket) {\r\n        if (ProfileEditor.mirrorUUIDs.isEmpty() && !RenameCommand.hasAnyDynamicRenames() && fakeProfiles.isEmpty()) {\r\n            return playerInfoUpdatePacket;\r\n        }\r\n        EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = playerInfoUpdatePacket.actions();\r\n        if (!actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) && !actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {\r\n            return playerInfoUpdatePacket;\r\n        }\r\n        boolean any = false;\r\n        for (ClientboundPlayerInfoUpdatePacket.Entry entry : playerInfoUpdatePacket.entries()) {\r\n            if (shouldChange(entry)) {\r\n                any = true;\r\n                break;\r\n            }\r\n        }\r\n        if (!any) {\r\n            return playerInfoUpdatePacket;\r\n        }\r\n        GameProfile ownProfile = networkManager.player.getGameProfile();\r\n        List<ClientboundPlayerInfoUpdatePacket.Entry> modifiedEntries = new ArrayList<>(playerInfoUpdatePacket.entries().size());\r\n        for (ClientboundPlayerInfoUpdatePacket.Entry entry : playerInfoUpdatePacket.entries()) {\r\n            if (!shouldChange(entry)) {\r\n                modifiedEntries.add(entry);\r\n                continue;\r\n            }\r\n            String rename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), false);\r\n            GameProfile baseProfile = fakeProfiles.containsKey(entry.profileId()) ? getGameProfile(fakeProfiles.get(entry.profileId())) : entry.profile();\r\n            PropertyMap modifiedProperties;\r\n            if (ProfileEditor.mirrorUUIDs.contains(entry.profileId())) {\r\n                modifiedProperties = ownProfile.properties();\r\n            }\r\n            else {\r\n                // On Paper 1.19+, we use Paper's PlayerProfile API instead of this system\r\n                modifiedProperties = Denizen.supportsPaper ? entry.profile().properties() : baseProfile.properties();\r\n            }\r\n            GameProfile modifiedProfile = new GameProfile(baseProfile.id(), rename != null ? (rename.length() > 16 ? rename.substring(0, 16) : rename) : baseProfile.name(), modifiedProperties);\r\n            String listRename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), true);\r\n            Component displayName = listRename != null ? Handler.componentToNMS(FormattedTextHelper.parse(listRename, ChatColor.WHITE)) : entry.displayName();\r\n            ClientboundPlayerInfoUpdatePacket.Entry modifiedEntry = new ClientboundPlayerInfoUpdatePacket.Entry(entry.profileId(), modifiedProfile, entry.listed(), entry.latency(), entry.gameMode(), displayName, entry.showHat(), entry.listOrder(), entry.chatSession());\r\n            modifiedEntries.add(modifiedEntry);\r\n        }\r\n        return createInfoPacket(actions, modifiedEntries);\r\n    }\r\n\r\n    public static boolean shouldChange(ClientboundPlayerInfoUpdatePacket.Entry entry) {\r\n        return ProfileEditor.mirrorUUIDs.contains(entry.profileId()) || RenameCommand.customNames.containsKey(entry.profileId()) || fakeProfiles.containsKey(entry.profileId());\r\n    }\r\n\r\n    public static final Field ClientboundPlayerInfoUpdatePacket_entries = ReflectionHelper.getFields(ClientboundPlayerInfoUpdatePacket.class).getFirstOfType(List.class);\r\n\r\n    public static ClientboundPlayerInfoUpdatePacket createInfoPacket(EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, List<ClientboundPlayerInfoUpdatePacket.Entry> entries) {\r\n        ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket = new ClientboundPlayerInfoUpdatePacket(actions, List.of());\r\n        try {\r\n            ClientboundPlayerInfoUpdatePacket_entries.set(playerInfoUpdatePacket, entries);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return playerInfoUpdatePacket;\r\n    }\r\n\r\n    public static GameProfile createGameProfile(UUID uuid, String name, String texture, String signature) {\r\n        return new GameProfile(\r\n                uuid != null ? uuid : NIL_UUID, name != null ? name : EMPTY_NAME,\r\n                texture != null ? new PropertyMap(ImmutableMultimap.of(\"textures\", new Property(\"textures\", texture, signature))) : PropertyMap.EMPTY\r\n        );\r\n    }\r\n\r\n    public static GameProfile getGameProfileNoProperties(PlayerProfile playerProfile) {\r\n        return createGameProfile(playerProfile.getUniqueId(), playerProfile.getName(), null, null);\r\n    }\r\n\r\n    public static GameProfile getGameProfile(PlayerProfile playerProfile) {\r\n        return createGameProfile(playerProfile.getUniqueId(), playerProfile.getName(), playerProfile.getTexture(), playerProfile.getTextureSignature());\r\n    }\r\n\r\n    public static final MethodHandle RESOLVABLEPROFILE_UNPACK = ReflectionHelper.getMethodHandle(ResolvableProfile.class, ReflectionMappingsInfo.ResolvableProfile_unpack_method);\r\n    public static final MethodHandle RESOLVABLEPROFILE_PARTIAL_ID = ReflectionHelper\r\n            .getFields(ReflectionHelper.getClassOrThrow(\"net.minecraft.world.item.component.ResolvableProfile$Partial\"))\r\n            .getGetter(ReflectionMappingsInfo.ResolvableProfilePartial_id, Optional.class);\r\n\r\n    public static UUID getUUID(ResolvableProfile resolvableProfile) {\r\n        try {\r\n            Either<GameProfile, Object> unpacked = (Either<GameProfile, Object>) RESOLVABLEPROFILE_UNPACK.invokeExact(resolvableProfile);\r\n            return unpacked.map(GameProfile::id,\r\n                    partial -> {\r\n                        try {\r\n                            Optional<UUID> uuid = (Optional<UUID>) RESOLVABLEPROFILE_PARTIAL_ID.invoke(partial);\r\n                            return uuid.orElse(null);\r\n                        }\r\n                        catch (Throwable e) {\r\n                            Debug.echoError(e);\r\n                            return null;\r\n                        }\r\n                    }\r\n            );\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n            return null;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/SidebarImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizen.nms.v1_21.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.chat.numbers.StyledFormat;\r\nimport net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetScorePacket;\r\nimport net.minecraft.world.scores.DisplaySlot;\r\nimport net.minecraft.world.scores.Objective;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Scoreboard;\r\nimport net.minecraft.world.scores.criteria.ObjectiveCriteria;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.reflect.Constructor;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Optional;\r\n\r\npublic class SidebarImpl extends Sidebar {\r\n\r\n    // TODO: 1.20.3: the scoreboard system saw significant changes, there is likely a better way to do this now\r\n\r\n    public static Scoreboard dummyScoreboard = new Scoreboard();\r\n    public static ObjectiveCriteria dummyCriteria;\r\n\r\n    static {\r\n        try {\r\n            Constructor<ObjectiveCriteria> constructor = ObjectiveCriteria.class.getDeclaredConstructor(String.class);\r\n            constructor.setAccessible(true);\r\n            dummyCriteria = constructor.newInstance(\"dummy\");\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public Objective obj1;\r\n    public Objective obj2;\r\n\r\n    public SidebarImpl(Player player) {\r\n        super(player);\r\n        Component chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        this.obj1 = new Objective(dummyScoreboard, \"dummy_1\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER, false, StyledFormat.SIDEBAR_DEFAULT);\r\n        this.obj2 = new Objective(dummyScoreboard, \"dummy_2\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER, false, StyledFormat.SIDEBAR_DEFAULT);\r\n    }\r\n\r\n    @Override\r\n    protected void setDisplayName(String title) {\r\n        if (this.obj1 != null) {\r\n            Component chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n            this.obj1.setDisplayName(chatComponentTitle);\r\n            this.obj2.setDisplayName(chatComponentTitle);\r\n        }\r\n    }\r\n\r\n    public List<PlayerTeam> generatedTeams = new ArrayList<>();\r\n\r\n    @Override\r\n    public void sendUpdate() {\r\n        List<PlayerTeam> oldTeams = generatedTeams;\r\n        generatedTeams = new ArrayList<>();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj1, 0));\r\n        String[] ids = getIds();\r\n        for (int i = 0; i < this.lines.length; i++) {\r\n            String line = this.lines[i];\r\n            if (line == null) {\r\n                break;\r\n            }\r\n            String lineId = ids[i];\r\n            PlayerTeam team = new PlayerTeam(dummyScoreboard, lineId);\r\n            team.getPlayers().add(lineId);\r\n            team.setPlayerPrefix(Handler.componentToNMS(FormattedTextHelper.parse(line, ChatColor.WHITE)));\r\n            generatedTeams.add(team);\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n            PacketHelperImpl.send(player, new ClientboundSetScorePacket(lineId, obj1.getName(), this.scores[i], Optional.empty(), Optional.of(StyledFormat.SIDEBAR_DEFAULT)));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundSetDisplayObjectivePacket(DisplaySlot.SIDEBAR, this.obj1));\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n        Objective temp = this.obj2;\r\n        this.obj2 = this.obj1;\r\n        this.obj1 = temp;\r\n        for (PlayerTeam team : oldTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        for (PlayerTeam team : generatedTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        generatedTeams.clear();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/blocks/BlockLightImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.blocks;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;\r\nimport net.minecraft.server.level.ThreadedLevelLightEngine;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.LightLayer;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.DataLayer;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.chunk.status.ChunkStatus;\r\nimport net.minecraft.world.level.lighting.LayerLightEventListener;\r\nimport net.minecraft.world.level.lighting.LevelLightEngine;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftChunk;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.CraftBlock;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.BitSet;\r\nimport java.util.List;\r\n\r\npublic class BlockLightImpl extends BlockLight {\r\n\r\n    public static final Class LIGHTENGINETHREADED_TASKTYPE = Arrays.stream(ThreadedLevelLightEngine.class.getDeclaredClasses()).filter(c -> c.isEnum()).findFirst().get(); // TaskType\r\n    public static final Object LIGHTENGINETHREADED_TASKTYPE_PRE;\r\n\r\n    static {\r\n        Object preObj = null;\r\n        try {\r\n            preObj = ReflectionHelper.getFields(LIGHTENGINETHREADED_TASKTYPE).get(ReflectionMappingsInfo.ThreadedLevelLightEngineTaskType_PRE_UPDATE).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n        LIGHTENGINETHREADED_TASKTYPE_PRE = preObj;\r\n    }\r\n\r\n    public static final MethodHandle LIGHTENGINETHREADED_QUEUERUNNABLE = ReflectionHelper.getMethodHandle(ThreadedLevelLightEngine.class, ReflectionMappingsInfo.ThreadedLevelLightEngine_addTask_method,\r\n            int.class, int.class,  LIGHTENGINETHREADED_TASKTYPE, Runnable.class);\r\n\r\n    public static void enqueueRunnable(LevelChunk chunk, Runnable runnable) {\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        if (lightEngine instanceof ThreadedLevelLightEngine) {\r\n            ChunkPos coord = chunk.getPos();\r\n            try {\r\n                LIGHTENGINETHREADED_QUEUERUNNABLE.invoke(lightEngine, coord.x, coord.z, LIGHTENGINETHREADED_TASKTYPE_PRE, runnable);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        else {\r\n            runnable.run();\r\n        }\r\n    }\r\n\r\n    private BlockLightImpl(Location location, long ticks) {\r\n        super(location, ticks);\r\n    }\r\n\r\n    public static BlockLight createLight(Location location, int lightLevel, long ticks) {\r\n        location = location.getBlock().getLocation();\r\n        BlockLight blockLight;\r\n        if (lightsByLocation.containsKey(location)) {\r\n            blockLight = lightsByLocation.get(location);\r\n            if (blockLight.removeTask != null) {\r\n                blockLight.removeTask.cancel();\r\n                blockLight.removeTask = null;\r\n            }\r\n            if (blockLight.updateTask != null) {\r\n                blockLight.updateTask.cancel();\r\n                blockLight.updateTask = null;\r\n            }\r\n            blockLight.removeLater(ticks);\r\n        }\r\n        else {\r\n            blockLight = new BlockLightImpl(location, ticks);\r\n            lightsByLocation.put(location, blockLight);\r\n            if (!lightsByChunk.containsKey(blockLight.chunkCoord)) {\r\n                lightsByChunk.put(blockLight.chunkCoord, new ArrayList<>());\r\n            }\r\n            lightsByChunk.get(blockLight.chunkCoord).add(blockLight);\r\n        }\r\n        blockLight.intendedLevel = lightLevel;\r\n        blockLight.update(lightLevel, true);\r\n        return blockLight;\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundBlockUpdatePacket packet, Level world) {\r\n        try {\r\n            BlockPos pos = packet.getPos();\r\n            int chunkX = pos.getX() >> 4;\r\n            int chunkZ = pos.getZ() >> 4;\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                LevelChunk chunk = world.getChunk(chunkX, chunkZ);\r\n                boolean any = false;\r\n                for (Vector vec : RELATIVE_CHUNKS) {\r\n                    ChunkAccess other = world.getChunk(chunkX + vec.getBlockX(), chunkZ + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n                    if (other instanceof LevelChunk) {\r\n                        List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) other)));\r\n                        if (lights != null) {\r\n                            any = true;\r\n                            for (BlockLight light : lights) {\r\n                                Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates(chunk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundLightUpdatePacket packet, Level world) {\r\n        if (doNotCheck) {\r\n            return;\r\n        }\r\n        try {\r\n            int cX = packet.getX();\r\n            int cZ = packet.getZ();\r\n            BitSet bitMask = packet.getLightData().getBlockYMask();\r\n            List<byte[]> blockData = packet.getLightData().getBlockUpdates();\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                ChunkAccess chk = world.getChunk(cX, cZ, ChunkStatus.FULL, false);\r\n                if (!(chk instanceof LevelChunk)) {\r\n                    return;\r\n                }\r\n                List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) chk)));\r\n                if (lights == null) {\r\n                    return;\r\n                }\r\n                boolean any = false;\r\n                for (BlockLight light : lights) {\r\n                    if (((BlockLightImpl) light).checkIfChangedBy(bitMask, blockData)) {\r\n                        Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                        any = true;\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates((LevelChunk) chk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static boolean doNotCheck = false;\r\n\r\n    public boolean checkIfChangedBy(BitSet bitmask, List<byte[]> data) {\r\n        Location blockLoc = block.getLocation();\r\n        int layer = (blockLoc.getBlockY() >> 4) + 1;\r\n        if (!bitmask.get(layer)) {\r\n            return false;\r\n        }\r\n        int found = 0;\r\n        for (int i = 0; i < 16; i++) {\r\n            if (bitmask.get(i)) {\r\n                if (i == layer) {\r\n                    byte[] blocks = data.get(found);\r\n                    DataLayer arr = new DataLayer(blocks);\r\n                    int x = blockLoc.getBlockX() - (chunkCoord.x << 4);\r\n                    int y = blockLoc.getBlockY() % 16;\r\n                    int z = blockLoc.getBlockZ() - (chunkCoord.z << 4);\r\n                    int level = arr.get(x, y, z);\r\n                    return intendedLevel != level;\r\n                }\r\n                found++;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static void runResetFor(final LevelChunk chunk, final BlockPos pos) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            engineBlock.checkBlock(pos);\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    public static void runSetFor(final LevelChunk chunk, final BlockPos pos, final int level) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            // engineBlock.onBlockEmissionIncrease(pos, level); // TODO: 1.20: ?\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    @Override\r\n    public void reset(boolean updateChunk) {\r\n        runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition());\r\n        if (updateChunk) {\r\n            // This runnable cast is necessary despite what your IDE may claim\r\n            updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(int lightLevel, boolean updateChunk) {\r\n        runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition());\r\n        updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), () -> {\r\n            updateTask = null;\r\n            runSetFor((LevelChunk) ((CraftChunk) chunk).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition(), lightLevel);\r\n            if (updateChunk) {\r\n                // This runnable cast is necessary despite what your IDE may claim\r\n                updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n            }\r\n        }, 1);\r\n    }\r\n\r\n    public static final Vector[] RELATIVE_CHUNKS = new Vector[] {\r\n            new Vector(0, 0, 0),\r\n            new Vector(-1, 0, 0), new Vector(1, 0, 0), new Vector(0, 0, -1), new Vector(0, 0, 1),\r\n            new Vector(-1, 0, -1), new Vector(-1, 0, 1), new Vector(1, 0, -1), new Vector(1, 0, 1)\r\n    };\r\n\r\n    public void sendNearbyChunkUpdates() {\r\n        sendNearbyChunkUpdates((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL));\r\n    }\r\n\r\n    public static void sendNearbyChunkUpdates(LevelChunk chunk) {\r\n        ChunkPos pos = chunk.getPos();\r\n        for (Vector vec : RELATIVE_CHUNKS) {\r\n            ChunkAccess other = chunk.getLevel().getChunk(pos.x + vec.getBlockX(), pos.z + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n            if (other instanceof LevelChunk) {\r\n                sendSingleChunkUpdate((LevelChunk) other);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void sendSingleChunkUpdate(LevelChunk chunk) {\r\n        // TODO: 1.20: ?\r\n        /*\r\n        doNotCheck = true;\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        ChunkPos pos = chunk.getPos();\r\n        //ClientboundLightUpdatePacket packet = new ClientboundLightUpdatePacket(pos, lightEngine, null, null, true); // TODO: 1.16: should 'trust edges' be true here?\r\n        ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(pos, false).forEach((player) -> {\r\n            player.connection.send(packet);\r\n        });\r\n        doNotCheck = false;*/\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport net.minecraft.world.entity.projectile.arrow.AbstractArrow;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftAbstractArrow;\r\n\r\npublic class CraftFakeArrowImpl extends CraftAbstractArrow implements FakeArrow {\r\n\r\n    public CraftFakeArrowImpl(CraftServer craftServer, AbstractArrow entityArrow) {\r\n        super(craftServer, entityArrow);\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        if (getPassenger() != null) {\r\n            return;\r\n        }\r\n        super.remove();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_ARROW\";\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;\r\nimport org.bukkit.metadata.FixedMetadataValue;\r\nimport org.bukkit.metadata.MetadataValue;\r\nimport org.bukkit.plugin.Plugin;\r\n\r\nimport java.util.List;\r\nimport java.util.Set;\r\n\r\npublic class CraftFakePlayerImpl extends CraftPlayer implements FakePlayer {\r\n\r\n    private final CraftServer server;\r\n    public String fullName;\r\n\r\n    public CraftFakePlayerImpl(CraftServer server, EntityFakePlayerImpl entity) {\r\n        super(server, entity);\r\n        this.server = server;\r\n        setMetadata(\"NPC\", new FixedMetadataValue(NMSHandler.getJavaPlugin(), true));\r\n    }\r\n\r\n    @Override\r\n    public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {\r\n        this.server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue);\r\n    }\r\n\r\n    @Override\r\n    public List<MetadataValue> getMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().getMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().hasMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public void removeMetadata(String metadataKey, Plugin owningPlugin) {\r\n        this.server.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin);\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_PLAYER\";\r\n    }\r\n\r\n    @Override\r\n    public String getFullName() {\r\n        return fullName;\r\n    }\r\n\r\n    @Override\r\n    public Block getTargetBlock(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public List<Block> getLastTwoTargetBlocks(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/CraftItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.UUID;\r\n\r\npublic class CraftItemProjectileImpl extends CraftEntity implements ItemProjectile {\r\n\r\n    private boolean doesBounce;\r\n\r\n    public CraftItemProjectileImpl(CraftServer server, EntityItemProjectileImpl entity) {\r\n        super(server, entity);\r\n        MethodHandle handle = ReflectionHelper.getFinalSetterForFirstOfType(CraftEntity.class, EntityType.class);\r\n        if (handle != null) {\r\n            try {\r\n                handle.invoke(this, EntityType.ITEM);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public EntityItemProjectileImpl getHandle() {\r\n        return (EntityItemProjectileImpl) super.getHandle();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return getType().name();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack getItemStack() {\r\n        return CraftItemStack.asBukkitCopy(getHandle().getItemStack());\r\n    }\r\n\r\n    @Override\r\n    public void setItemStack(ItemStack itemStack) {\r\n        getHandle().setItemStack(CraftItemStack.asNMSCopy(itemStack));\r\n    }\r\n\r\n    @Override\r\n    public int getPickupDelay() {\r\n        return 0;\r\n    }\r\n\r\n    @Override\r\n    public void setPickupDelay(int i) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public void setUnlimitedLifetime(boolean b) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnlimitedLifetime() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void setOwner(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getOwner() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setThrower(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getThrower() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ProjectileSource getShooter() {\r\n        return getHandle().projectileSource;\r\n    }\r\n\r\n    @Override\r\n    public void setShooter(ProjectileSource projectileSource) {\r\n        if (projectileSource instanceof CraftEntity) {\r\n            getHandle().setOwner(((CraftEntity) projectileSource).getHandle());\r\n        }\r\n        else {\r\n            getHandle().projectileSource = projectileSource;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean doesBounce() {\r\n        return doesBounce;\r\n    }\r\n\r\n    @Override\r\n    public void setBounce(boolean doesBounce) {\r\n        this.doesBounce = doesBounce;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.world.entity.projectile.arrow.SpectralArrow;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakeArrowImpl extends SpectralArrow {\r\n\r\n    public EntityFakeArrowImpl(CraftWorld craftWorld, Location location) {\r\n        super(net.minecraft.world.entity.EntityType.SPECTRAL_ARROW, craftWorld.getHandle());\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakeArrowImpl((CraftServer) Bukkit.getServer(), this));\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    protected ItemStack getPickupItem() {\r\n        return new ItemStack(Items.ARROW);\r\n    }\r\n\r\n    @Override\r\n    public CraftFakeArrowImpl getBukkitEntity() {\r\n        return (CraftFakeArrowImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_21.Handler;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.fakes.FakeNetworkManagerImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.fakes.FakePlayerConnectionImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ClientInformation;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.world.entity.player.Player;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftServer;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakePlayerImpl extends ServerPlayer {\r\n\r\n    public EntityFakePlayerImpl(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile, ClientInformation clientInfo, boolean doAdd) {\r\n        super(minecraftserver, worldserver, gameprofile, clientInfo);\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakePlayerImpl((CraftServer) Bukkit.getServer(), this));\r\n            net.minecraft.network.Connection networkManager = new FakeNetworkManagerImpl(PacketFlow.CLIENTBOUND);\r\n            connection = new FakePlayerConnectionImpl(minecraftserver, networkManager, this, new CommonListenerCookie(gameprofile, 0, clientInfo, false));\r\n            DenizenNetworkManagerImpl.Connection_packetListener.set(networkManager, connection);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        getEntityData().set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) 127);\r\n        if (doAdd) {\r\n            worldserver.addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public CraftFakePlayerImpl getBukkitEntity() {\r\n        return (CraftFakePlayerImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/entities/EntityItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.base.Preconditions;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.projectile.ThrowableProjectile;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.storage.ValueInput;\r\nimport net.minecraft.world.level.storage.ValueOutput;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport org.bukkit.Location;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\n\r\npublic class EntityItemProjectileImpl extends ThrowableProjectile {\r\n\r\n    public static MethodHandle setBukkitEntityMethod = ReflectionHelper.getFinalSetter(Entity.class, \"bukkitEntity\");\r\n\r\n    public static final EntityDataAccessor<ItemStack> ITEM;\r\n\r\n    static {\r\n        EntityDataAccessor<ItemStack> watcher = null;\r\n        try {\r\n            watcher = (EntityDataAccessor<ItemStack>) ReflectionHelper.getFields(ItemEntity.class).get(ReflectionMappingsInfo.ItemEntity_DATA_ITEM).get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        ITEM = watcher;\r\n    }\r\n\r\n    public EntityItemProjectileImpl(Level world, Location location, ItemStack item) {\r\n        super((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.ITEM, world);\r\n        try {\r\n            setBukkitEntityMethod.invoke(this, new CraftItemProjectileImpl(((ServerLevel) world).getServer().server, this));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        setItemStack(item);\r\n    }\r\n\r\n    @Override\r\n    protected void defineSynchedData(SynchedEntityData.Builder builder) {\r\n        builder.define(ITEM, ItemStack.EMPTY);\r\n    }\r\n\r\n    public ItemStack getItemStack() {\r\n        return this.getEntityData().get(ITEM);\r\n    }\r\n\r\n    public void setItemStack(ItemStack itemstack) {\r\n        Preconditions.checkArgument(!itemstack.isEmpty(), \"Cannot drop air\");\r\n        this.getEntityData().set(ITEM, itemstack);\r\n        this.getEntityData().markDirty(ITEM);\r\n    }\r\n\r\n    @Override\r\n    protected void onHitBlock(BlockHitResult movingobjectpositionblock) {\r\n        super.onHitBlock(movingobjectpositionblock);\r\n        remove(RemovalReason.KILLED);\r\n    }\r\n\r\n    @Override\r\n    public void onSyncedDataUpdated(EntityDataAccessor<?> datawatcherobject) {\r\n        super.onSyncedDataUpdated(datawatcherobject);\r\n        if (ITEM.equals(datawatcherobject)) {\r\n            this.getItemStack().setEntityRepresentation(this);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean save(ValueOutput nmsValueOutput) {\r\n        if (!this.getItemStack().isEmpty()) {\r\n            nmsValueOutput.store(\"Item\", ItemStack.CODEC, this.getItemStack());\r\n        }\r\n        super.save(nmsValueOutput);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public void load(ValueInput nmsValueInput) {\r\n        ItemStack nmsItemStack = nmsValueInput.read(\"Item\", ItemStack.CODEC).orElse(ItemStack.EMPTY);\r\n        if (nmsItemStack.isEmpty()) {\r\n            this.remove(RemovalReason.KILLED);\r\n        }\r\n        else {\r\n            this.setItemStack(nmsItemStack);\r\n        }\r\n        super.load(nmsValueInput);\r\n    }\r\n\r\n    @Override\r\n    public CraftItemProjectileImpl getBukkitEntity() {\r\n        return (CraftItemProjectileImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/fakes/FakeChannelImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.fakes;\r\n\r\nimport io.netty.channel.*;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeChannelImpl extends AbstractChannel {\r\n\r\n    private final ChannelConfig config = new DefaultChannelConfig(this);\r\n\r\n    protected FakeChannelImpl(Channel parent) {\r\n        super(parent);\r\n    }\r\n\r\n    @Override\r\n    public ChannelConfig config() {\r\n        config.setAutoRead(true);\r\n        return config;\r\n    }\r\n\r\n    @Override\r\n    protected AbstractUnsafe newUnsafe() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected boolean isCompatible(EventLoop eventLoop) {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress localAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress remoteAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected void doBind(SocketAddress socketAddress) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doDisconnect() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doClose() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doBeginRead() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doWrite(ChannelOutboundBuffer channelOutboundBuffer) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean isOpen() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActive() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public ChannelMetadata metadata() {\r\n        return new ChannelMetadata(true);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/fakes/FakeNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeNetworkManagerImpl extends Connection {\r\n\r\n    public FakeNetworkManagerImpl(PacketFlow enumprotocoldirection) {\r\n        super(enumprotocoldirection);\r\n        channel = new FakeChannelImpl(null);\r\n        address = new SocketAddress() {\r\n        };\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/fakes/FakePlayerConnectionImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\n\r\npublic class FakePlayerConnectionImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public FakePlayerConnectionImpl(MinecraftServer minecraftserver, Connection networkmanager, ServerPlayer entityplayer, CommonListenerCookie cookie) {\r\n        super(minecraftserver, networkmanager, entityplayer, cookie);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet packet) {\r\n        // Do nothing\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/AbstractListenerPlayInImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerSendPacketScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport io.netty.channel.ChannelFutureListener;\r\nimport net.minecraft.CrashReport;\r\nimport net.minecraft.CrashReportCategory;\r\nimport net.minecraft.ReportedException;\r\nimport net.minecraft.network.ConnectionProtocol;\r\nimport net.minecraft.network.DisconnectionDetails;\r\nimport net.minecraft.network.chat.ChatType;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.chat.PlayerChatMessage;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.network.protocol.common.*;\r\nimport net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.protocol.ping.ServerboundPingRequestPacket;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ClientInformation;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.world.entity.PositionMoveRotation;\r\nimport net.minecraft.world.entity.Relative;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.net.SocketAddress;\r\nimport java.util.Set;\r\n\r\npublic class AbstractListenerPlayInImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public static final Field ServerGamePacketListenerImpl_chunkSender = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_chunkSender);\r\n\r\n    public static final MethodHandle SERVER_COMMON_PACKET_LISTENER_IMPL_CREATE_COOKIE = ReflectionHelper.getMethodHandle(ServerCommonPacketListenerImpl.class, ReflectionMappingsInfo.ServerCommonPacketListenerImpl_createCookie_method, ClientInformation.class);\r\n\r\n    public static CommonListenerCookie createCookie(ServerPlayer nmsPlayer) {\r\n        try {\r\n            return (CommonListenerCookie) SERVER_COMMON_PACKET_LISTENER_IMPL_CREATE_COOKIE.invoke(nmsPlayer.connection, nmsPlayer.clientInformation());\r\n        }\r\n        catch (Throwable e) {\r\n            throw new RuntimeException(e);\r\n        }\r\n    }\r\n\r\n    public final ServerGamePacketListenerImpl oldListener;\r\n    public final DenizenNetworkManagerImpl denizenNetworkManager;\r\n\r\n    public AbstractListenerPlayInImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer, ServerGamePacketListenerImpl oldListener) {\r\n        super(MinecraftServer.getServer(), networkManager, entityPlayer, createCookie(entityPlayer));\r\n        this.oldListener = oldListener;\r\n        this.denizenNetworkManager = networkManager;\r\n        try {\r\n            ServerGamePacketListenerImpl_chunkSender.set(this, oldListener.chunkSender);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        oldListener.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(DisconnectionDetails disconnectiondetails) {\r\n        oldListener.disconnect(disconnectiondetails);\r\n    }\r\n\r\n    @Override\r\n    public void kickPlayer(Component reason) {\r\n        oldListener.kickPlayer(reason);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1) {\r\n        oldListener.teleport(d0, d1, d2, f, f1);\r\n    }\r\n\r\n    @Override\r\n    public boolean teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) {\r\n        return oldListener.teleport(d0, d1, d2, f, f1, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(PositionMoveRotation positionmoverotation, Set<Relative> set) {\r\n        oldListener.teleport(positionmoverotation, set);\r\n    }\r\n\r\n    @Override\r\n    public boolean teleport(PositionMoveRotation positionmoverotation, Set<Relative> set, PlayerTeleportEvent.TeleportCause cause) {\r\n        return oldListener.teleport(positionmoverotation, set, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Location dest) {\r\n        oldListener.teleport(dest);\r\n    }\r\n\r\n    @Override\r\n    public void internalTeleport(PositionMoveRotation positionmoverotation, Set<Relative> set) {\r\n        oldListener.internalTeleport(positionmoverotation, set);\r\n    }\r\n\r\n    @Override\r\n    public CraftPlayer getCraftPlayer() {\r\n        return oldListener.getCraftPlayer();\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldListener.tick();\r\n    }\r\n\r\n    @Override\r\n    public void resetPosition() {\r\n        oldListener.resetPosition();\r\n    }\r\n\r\n    @Override\r\n    public boolean isAcceptingMessages() {\r\n        return oldListener.isAcceptingMessages();\r\n    }\r\n\r\n    @Override\r\n    public boolean shouldHandleMessage(Packet<?> packet) {\r\n        return oldListener.shouldHandleMessage(packet);\r\n    }\r\n\r\n    @Override\r\n    public void fillCrashReport(CrashReport var0) {\r\n        oldListener.fillCrashReport(var0);\r\n    }\r\n\r\n    @Override\r\n    public void fillListenerSpecificCrashDetails(CrashReport var0, CrashReportCategory var1) {\r\n        oldListener.fillListenerSpecificCrashDetails(var0, var1);\r\n    }\r\n\r\n    @Override\r\n    public GameProfile getOwner() {\r\n        return oldListener.getOwner();\r\n    }\r\n\r\n    @Override\r\n    public int latency() {\r\n        return oldListener.latency();\r\n    }\r\n\r\n    @Override\r\n    public void onDisconnect(DisconnectionDetails disconnectionDetails) {\r\n        oldListener.onDisconnect(disconnectionDetails);\r\n    }\r\n\r\n    @Override\r\n    public void ackBlockChangesUpTo(int i) {\r\n        oldListener.ackBlockChangesUpTo(i);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        oldListener.send(packet);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, ChannelFutureListener channelfuturelistener) {\r\n        oldListener.send(packet, channelfuturelistener);\r\n    }\r\n\r\n    public static Field AWAITING_POS_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_awaitingPositionFromClient, Vec3.class);\r\n    public static Field AWAITING_TELEPORT_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_awaitingTeleport, int.class);\r\n\r\n    public void debugPacketOutput(Packet<?> packet) {\r\n        try {\r\n            if (packet instanceof ServerboundMovePlayerPacket) {\r\n                ServerboundMovePlayerPacket movePacket = (ServerboundMovePlayerPacket) packet;\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet ServerboundMovePlayerPacket sent from \" + player.getScoreboardName() + \" with XYZ=\"\r\n                        + movePacket.x + \", \" + movePacket.y + \", \" + movePacket.z + \", yRot=\" + movePacket.yRot + \", xRot=\" + movePacket.xRot\r\n                        + \", onGround=\" + movePacket.isOnGround() + \", hasPos=\" + movePacket.hasPos + \", hasRot=\" + movePacket.hasRot);\r\n            }\r\n            else if (packet instanceof ServerboundAcceptTeleportationPacket) {\r\n                Vec3 awaitPos = (Vec3) AWAITING_POS_FIELD.get(oldListener);\r\n                int awaitTeleportId = AWAITING_TELEPORT_FIELD.getInt(oldListener);\r\n                ServerboundAcceptTeleportationPacket acceptPacket = (ServerboundAcceptTeleportationPacket) packet;\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet ServerboundAcceptTeleportationPacket sent from \" + player.getScoreboardName()\r\n                        + \" with ID=\" + acceptPacket.getId() + \", awaitingTeleport=\" + awaitTeleportId + \", awaitPos=\" + awaitPos);\r\n            }\r\n            else {\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent from \" + player.getScoreboardName());\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public boolean handlePacketIn(Packet<?> packet) {\r\n        denizenNetworkManager.packetsReceived++;\r\n        if (NMSHandler.debugPackets) {\r\n            debugPacketOutput(packet);\r\n        }\r\n        if (PlayerSendPacketScriptEvent.instance.eventData.isEnabled) {\r\n            if (PlayerSendPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) {\r\n                if (NMSHandler.debugPackets) {\r\n                    DenizenNetworkManagerImpl.doPacketOutput(\"Denied packet-in \" + packet.getClass().getCanonicalName() + \" from \" + player.getScoreboardName() + \" due to event\");\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void handleChatAck(ServerboundChatAckPacket serverboundchatackpacket) {\r\n        oldListener.handleChatAck(serverboundchatackpacket);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(ServerboundPlayerInputPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerInput(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleMoveVehicle(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleAcceptTeleportPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookSeenRecipePacket(ServerboundRecipeBookSeenRecipePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRecipeBookSeenRecipePacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRecipeBookChangeSettingsPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSeenAdvancements(ServerboundSeenAdvancementsPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSeenAdvancements(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCustomCommandSuggestions(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandBlock(ServerboundSetCommandBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCommandBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandMinecart(ServerboundSetCommandMinecartPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCommandMinecart(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAcceptPlayerLoad(ServerboundPlayerLoadedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleAcceptPlayerLoad(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePickItemFromBlock(ServerboundPickItemFromBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePickItemFromBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePickItemFromEntity(ServerboundPickItemFromEntityPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePickItemFromEntity(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRenameItem(ServerboundRenameItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRenameItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetBeaconPacket(ServerboundSetBeaconPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetBeaconPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetStructureBlock(ServerboundSetStructureBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetStructureBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetTestBlock(ServerboundSetTestBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetTestBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleTestInstanceBlockAction(ServerboundTestInstanceBlockActionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleTestInstanceBlockAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetJigsawBlock(ServerboundSetJigsawBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetJigsawBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleJigsawGenerate(ServerboundJigsawGeneratePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleJigsawGenerate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSelectTrade(ServerboundSelectTradePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSelectTrade(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEditBook(ServerboundEditBookPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleEditBook(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleMovePlayer(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItemOn(ServerboundUseItemOnPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleUseItemOn(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleTeleportToEntityPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void suspendFlushing() {\r\n        oldListener.suspendFlushing();\r\n    }\r\n\r\n    @Override\r\n    public void resumeFlushing() {\r\n        oldListener.resumeFlushing();\r\n    }\r\n\r\n    @Override\r\n    public void handlePaddleBoat(ServerboundPaddleBoatPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePaddleBoat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePong(ServerboundPongPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePong(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChat(ServerboundChatPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChatCommand(ServerboundChatCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChatCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void chat(String s, PlayerChatMessage original, boolean async) {\r\n        oldListener.chat(s, original, async);\r\n    }\r\n\r\n    @Override\r\n    public ConnectionProtocol protocol() {\r\n        return oldListener == null ? ConnectionProtocol.PLAY : oldListener.protocol();\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerChatMessage(PlayerChatMessage playerchatmessage, ChatType.Bound chatmessagetype_a) {\r\n        oldListener.sendPlayerChatMessage(playerchatmessage, chatmessagetype_a);\r\n    }\r\n\r\n    @Override\r\n    public void sendDisguisedChatMessage(Component ichatbasecomponent, ChatType.Bound chatmessagetype_a) {\r\n        oldListener.sendDisguisedChatMessage(ichatbasecomponent, chatmessagetype_a);\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRemoteAddress() {\r\n        return oldListener.getRemoteAddress();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRawAddress() {\r\n        return oldListener.getRawAddress();\r\n    }\r\n\r\n    @Override\r\n    public void switchToConfig() {\r\n        oldListener.switchToConfig();\r\n    }\r\n\r\n    @Override\r\n    public void handleInteract(ServerboundInteractPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleInteract(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientCommand(ServerboundClientCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleClientCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClose(ServerboundContainerClosePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerClose(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlaceRecipe(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerButtonClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCreativeModeSlot(ServerboundSetCreativeModeSlotPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCreativeModeSlot(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleKeepAlive(ServerboundKeepAlivePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleKeepAlive(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerAbilities(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientInformation(ServerboundClientInformationPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleClientInformation(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChangeDifficulty(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleLockDifficulty(ServerboundLockDifficultyPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleLockDifficulty(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChatSessionUpdate(ServerboundChatSessionUpdatePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChatSessionUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleConfigurationAcknowledged(ServerboundConfigurationAcknowledgedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleConfigurationAcknowledged(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChunkBatchReceived(ServerboundChunkBatchReceivedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChunkBatchReceived(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleBlockEntityTagQuery(ServerboundBlockEntityTagQueryPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleBlockEntityTagQuery(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleBundleItemSelectedPacket(ServerboundSelectBundleItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleBundleItemSelectedPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientTickEnd(ServerboundClientTickEndPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleClientTickEnd(packet);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasInfiniteMaterials() {\r\n        return oldListener.hasInfiniteMaterials();\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerSlotStateChanged(ServerboundContainerSlotStateChangedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerSlotStateChanged(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleDebugSubscriptionRequest(ServerboundDebugSubscriptionRequestPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleDebugSubscriptionRequest(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEntityTagQuery(ServerboundEntityTagQueryPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleEntityTagQuery(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePingRequest(ServerboundPingRequestPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePingRequest(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignedChatCommand(ServerboundChatCommandSignedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSignedChatCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChangeGameMode(ServerboundChangeGameModePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChangeGameMode(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomClickAction(ServerboundCustomClickActionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCustomClickAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCookieResponse(ServerboundCookieResponsePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCookieResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public ServerPlayer getPlayer() {\r\n        return oldListener.getPlayer();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow flow() {\r\n        return oldListener == null ? PacketFlow.SERVERBOUND : oldListener.flow();\r\n    }\r\n\r\n    @Override\r\n    public void onPacketError(Packet var0, Exception var1) throws ReportedException {\r\n        oldListener.onPacketError(var0, var1);\r\n    }\r\n\r\n    @Override\r\n    public DisconnectionDetails createDisconnectionInfo(Component var0, Throwable var1) {\r\n        return oldListener.createDisconnectionInfo(var0, var1);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/DenizenNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerReceivesPacketScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet.*;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptCodeGen;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport io.netty.buffer.Unpooled;\r\nimport io.netty.channel.ChannelFutureListener;\r\nimport io.netty.channel.ChannelHandlerContext;\r\nimport io.netty.channel.ChannelPipeline;\r\nimport net.minecraft.network.*;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.codec.StreamCodec;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.protocol.login.ClientLoginPacketListener;\r\nimport net.minecraft.network.protocol.status.ClientStatusPacketListener;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.network.ServerPlayerConnection;\r\nimport net.minecraft.util.debugchart.LocalSampleLogger;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftRegistry;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport javax.annotation.Nullable;\r\nimport javax.crypto.Cipher;\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.net.SocketAddress;\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.function.BiConsumer;\r\nimport java.util.function.Consumer;\r\nimport java.util.stream.Collectors;\r\n\r\npublic class DenizenNetworkManagerImpl extends Connection {\r\n\r\n    public static <T extends Packet<?>> T copyPacket(T original, StreamCodec<? super RegistryFriendlyByteBuf, T> packetCodec) {\r\n        try {\r\n            RegistryFriendlyByteBuf copier = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry());\r\n            packetCodec.encode(copier, original);\r\n            return packetCodec.decode(copier);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @FunctionalInterface\r\n    public interface PacketHandler<T extends Packet<ClientGamePacketListener>> {\r\n        Packet<ClientGamePacketListener> handlePacket(DenizenNetworkManagerImpl networkManager, T packet) throws Exception;\r\n    }\r\n\r\n    public static final Map<Class<? extends Packet<ClientGamePacketListener>>, List<PacketHandler<?>>> packetHandlers = new HashMap<>();\r\n\r\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetClass, PacketHandler<T> handler) {\r\n        packetHandlers.computeIfAbsent(packetClass, k -> new ArrayList<>()).add(handler);\r\n    }\r\n\r\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetClass, BiConsumer<DenizenNetworkManagerImpl, T> handler) {\r\n        registerPacketHandler(packetClass, (networkManager, packet) -> {\r\n            handler.accept(networkManager, packet);\r\n            return packet;\r\n        });\r\n    }\r\n\r\n    public final Connection oldManager;\r\n    public final DenizenPacketListenerImpl packetListener;\r\n    public final ServerPlayer player;\r\n    public int packetsSent, packetsReceived;\r\n\r\n    public DenizenNetworkManagerImpl(ServerPlayer entityPlayer, Connection oldManager) {\r\n        super(getProtocolDirection(oldManager));\r\n        this.oldManager = oldManager;\r\n        this.channel = oldManager.channel;\r\n        this.player = entityPlayer;\r\n        packetListener = (DenizenPacketListenerImpl) NetworkInterceptCodeGen.generateAppropriateInterceptor(this, entityPlayer, DenizenPacketListenerImpl.class, AbstractListenerPlayInImpl.class, ServerGamePacketListenerImpl.class);\r\n        if (!(oldManager.getPacketListener() instanceof ServerConfigurationPacketListener)) {\r\n            setListener(packetListener);\r\n        }\r\n    }\r\n\r\n    public void setListener(PacketListener listener) {\r\n        try {\r\n            Connection_packetListener.set(oldManager, listener);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n            throw new RuntimeException(\"Failed to set packet listener due to reflection error\", e);\r\n        }\r\n    }\r\n\r\n    public static Connection getConnection(ServerPlayer player) {\r\n        try {\r\n            return (Connection) ServerGamePacketListener_ConnectionField.get(player.connection);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            throw new RuntimeException(\"Failed to get connection from player due to reflection error\", ex);\r\n        }\r\n    }\r\n\r\n    public static Connection getConnection(Player player) {\r\n        return getConnection(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    public static DenizenNetworkManagerImpl getNetworkManager(ServerPlayer player) {\r\n        return (DenizenNetworkManagerImpl) getConnection(player);\r\n    }\r\n\r\n    public static DenizenNetworkManagerImpl getNetworkManager(Player player) {\r\n        return getNetworkManager(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    public static void setNetworkManager(Player player) {\r\n        ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerGamePacketListenerImpl playerConnection = entityPlayer.connection;\r\n        setNetworkManager(playerConnection, new DenizenNetworkManagerImpl(entityPlayer, getConnection(entityPlayer)));\r\n    }\r\n\r\n    public static void enableNetworkManager() {\r\n        for (World w : Bukkit.getWorlds()) {\r\n            for (ChunkMap.TrackedEntity tracker : ((CraftWorld) w).getHandle().getChunkSource().chunkMap.entityMap.values()) {\r\n                ArrayList<ServerPlayerConnection> connections = new ArrayList<>(tracker.seenBy);\r\n                tracker.seenBy.clear();\r\n                for (ServerPlayerConnection connection : connections) {\r\n                    tracker.seenBy.add(connection.getPlayer().connection);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int hashCode() {\r\n        return oldManager.hashCode();\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object c2) {\r\n        return oldManager.equals(c2);\r\n    }\r\n\r\n    @Override\r\n    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelRegistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelUnregistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception {\r\n        oldManager.channelActive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public void channelInactive(ChannelHandlerContext channelhandlercontext) {\r\n        oldManager.channelInactive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public boolean isSharable() {\r\n        return oldManager.isSharable();\r\n    }\r\n\r\n    @Override\r\n    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerAdded(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerRemoved(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) {\r\n        oldManager.exceptionCaught(channelhandlercontext, throwable);\r\n    }\r\n\r\n    @Override\r\n    public <T extends PacketListener> void setupInboundProtocol(ProtocolInfo<T> protocolinfo, T t0) {\r\n        oldManager.setupInboundProtocol(protocolinfo, t0);\r\n    }\r\n\r\n    @Override\r\n    public void setupOutboundProtocol(ProtocolInfo<?> protocolinfo) {\r\n        oldManager.setupOutboundProtocol(protocolinfo);\r\n    }\r\n\r\n    @Override\r\n    protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) {\r\n        if (oldManager.channel.isOpen()) {\r\n            try {\r\n                packet.handle(this.packetListener);\r\n            }\r\n            catch (Exception e) {\r\n                // Do nothing\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setListenerForServerboundHandshake(PacketListener packetlistener) {\r\n        oldManager.setListenerForServerboundHandshake(packetlistener);\r\n    }\r\n\r\n    @Override\r\n    public void initiateServerboundStatusConnection(String s, int i, ClientStatusPacketListener packetstatusoutlistener) {\r\n        oldManager.initiateServerboundStatusConnection(s, i, packetstatusoutlistener);\r\n    }\r\n\r\n    @Override\r\n    public void initiateServerboundPlayConnection(String s, int i, ClientLoginPacketListener packetloginoutlistener) {\r\n        oldManager.initiateServerboundPlayConnection(s, i, packetloginoutlistener);\r\n    }\r\n\r\n    @Override\r\n    public <S extends ServerboundPacketListener, C extends ClientboundPacketListener> void initiateServerboundPlayConnection(String s, int i, ProtocolInfo<S> protocolinfo, ProtocolInfo<C> protocolinfo1, C c0, boolean flag) {\r\n        oldManager.initiateServerboundPlayConnection(s, i, protocolinfo, protocolinfo1, c0, flag);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        send(packet, null);\r\n    }\r\n\r\n    public static void doPacketOutput(String text) {\r\n        if (!NMSHandler.debugPackets) {\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPacketFilter == null || NMSHandler.debugPacketFilter.trim().isEmpty()\r\n                || CoreUtilities.toLowerCase(text).contains(NMSHandler.debugPacketFilter)) {\r\n            Debug.log(text);\r\n        }\r\n    }\r\n\r\n    public void debugOutputPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundSetEntityDataPacket) {\r\n            StringBuilder output = new StringBuilder(128);\r\n            output.append(\"Packet: ClientboundSetEntityDataPacket sent to \").append(player.getScoreboardName()).append(\" for entity ID: \").append(((ClientboundSetEntityDataPacket) packet).id()).append(\": \");\r\n            List<SynchedEntityData.DataValue<?>> list = ((ClientboundSetEntityDataPacket) packet).packedItems();\r\n            if (list == null) {\r\n                output.append(\"None\");\r\n            }\r\n            else {\r\n                for (SynchedEntityData.DataValue<?> data : list) {\r\n                    output.append('[').append(data.id()).append(\": \").append(data.value()).append(\"], \");\r\n                }\r\n            }\r\n            doPacketOutput(output.toString());\r\n        }\r\n        else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n            ClientboundSetEntityMotionPacket velPacket = (ClientboundSetEntityMotionPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundSetEntityMotionPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + velPacket.getId() + \": \" + velPacket.getMovement());\r\n        }\r\n        else if (packet instanceof ClientboundAddEntityPacket) {\r\n            ClientboundAddEntityPacket addEntityPacket = (ClientboundAddEntityPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundAddEntityPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + addEntityPacket.getId() + \": \" + \"uuid: \" + addEntityPacket.getUUID()\r\n                    + \", type: \" + addEntityPacket.getType() + \", at: \" + addEntityPacket.getX() + \",\" + addEntityPacket.getY() + \",\" + addEntityPacket.getZ() + \", data: \" + addEntityPacket.getData());\r\n        }\r\n        else if (packet instanceof ClientboundMapItemDataPacket) {\r\n            ClientboundMapItemDataPacket mapPacket = (ClientboundMapItemDataPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundMapItemDataPacket sent to \" + player.getScoreboardName() + \" for map ID: \" + mapPacket.mapId() + \", scale: \" + mapPacket.scale() + \", locked: \" + mapPacket.locked());\r\n        }\r\n        else if (packet instanceof ClientboundRemoveEntitiesPacket) {\r\n            ClientboundRemoveEntitiesPacket removePacket = (ClientboundRemoveEntitiesPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundRemoveEntitiesPacket sent to \" + player.getScoreboardName() + \" for entities: \" + removePacket.getEntityIds().stream().map(Object::toString).collect(Collectors.joining(\", \")));\r\n        }\r\n        else if (packet instanceof ClientboundPlayerInfoUpdatePacket) {\r\n            ClientboundPlayerInfoUpdatePacket playerInfoPacket = (ClientboundPlayerInfoUpdatePacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundPlayerInfoPacket sent to \" + player.getScoreboardName() + \" of types \" + playerInfoPacket.actions() + \" for player profiles: \" +\r\n                    playerInfoPacket.entries().stream().map(p -> \"mode=\" + p.gameMode() + \"/latency=\" + p.latency() + \"/display=\" + p.displayName() + \"/name=\" + p.profile().name() + \"/id=\" + p.profile().id() + \"/\"\r\n                            + p.profile().properties().asMap().entrySet().stream().map(e -> e.getKey() + \"=\" + e.getValue().stream().map(v -> v.value() + \";\" + v.signature()).collect(Collectors.joining(\";;;\"))).collect(Collectors.joining(\"/\"))).collect(Collectors.joining(\", \")));\r\n        }\r\n        else {\r\n            doPacketOutput(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, ChannelFutureListener channelFutureListener) {\r\n        send(packet, channelFutureListener, true);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, @Nullable ChannelFutureListener channelFutureListener, boolean flush) {\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            if (Settings.cache_warnOnAsyncPackets\r\n                    && !(packet instanceof ClientboundSystemChatPacket) && !(packet instanceof ClientboundPlayerChatPacket) // Vanilla supports an async chat system, though it's normally disabled, some plugins use this as justification for sending messages async\r\n                    && !(packet instanceof ClientboundCommandSuggestionsPacket)) { // Async tab complete is wholly unsupported in Spigot (and will cause an exception), however Paper explicitly adds async support (for unclear reasons), so let it through too\r\n                Debug.echoError(\"Warning: packet sent off main thread! This is completely unsupported behavior! Denizen network interceptor ignoring packet to avoid crash. Packet class: \"\r\n                        + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName() + \" identify the sender of the packet from the stack trace:\");\r\n                try {\r\n                    throw new RuntimeException(\"Trace\");\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(ex);\r\n                }\r\n            }\r\n            oldManager.send(packet, channelFutureListener, flush);\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPackets) {\r\n            debugOutputPacket(packet);\r\n        }\r\n        packetsSent++;\r\n        if (packet instanceof ClientboundBundlePacket bundlePacket) {\r\n            List<Packet<? super ClientGamePacketListener>> processedPackets = new ArrayList<>();\r\n            boolean anyChange = false;\r\n            for (Packet<? super ClientGamePacketListener> _subPacket : bundlePacket.subPackets()) {\r\n                // Bundle packets with non-game packets shouldn't ever be sent while the Denizen interceptor is active\r\n                Packet<ClientGamePacketListener> subPacket = (Packet<ClientGamePacketListener>) _subPacket;\r\n                Packet<ClientGamePacketListener> processed = processPacketHandlersFor(subPacket);\r\n                anyChange = anyChange || processed != subPacket;\r\n                if (processed != null) {\r\n                    processedPackets.add(processed);\r\n                }\r\n            }\r\n            if (processedPackets.isEmpty()) {\r\n                return;\r\n            }\r\n            if (anyChange) {\r\n                packet = new ClientboundBundlePacket(processedPackets);\r\n            }\r\n        }\r\n        else {\r\n            Packet<?> processed = processPacketHandlersFor((Packet<ClientGamePacketListener>) packet);\r\n            if (processed == null) {\r\n                return;\r\n            }\r\n            packet = processed;\r\n        }\r\n        oldManager.send(packet, channelFutureListener, flush);\r\n    }\r\n\r\n    @Override\r\n    public void runOnceConnected(Consumer<Connection> consumer) {\r\n        oldManager.runOnceConnected(consumer);\r\n    }\r\n\r\n    @Override\r\n    public void flushChannel() {\r\n        oldManager.flushChannel();\r\n    }\r\n\r\n    public Packet<ClientGamePacketListener> processPacketHandlersFor(Packet<ClientGamePacketListener> packet) {\r\n        if (packet == null) {\r\n            return null;\r\n        }\r\n        List<PacketHandler<?>> packetHandlers = DenizenNetworkManagerImpl.packetHandlers.get(packet.getClass());\r\n        if (packetHandlers != null) {\r\n            for (PacketHandler<?> _packetHandler : packetHandlers) {\r\n                PacketHandler<Packet<ClientGamePacketListener>> packetHandler = (PacketHandler<Packet<ClientGamePacketListener>>) _packetHandler;\r\n                Packet<ClientGamePacketListener> processed;\r\n                try {\r\n                    processed = packetHandler.handlePacket(this, packet);\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(\"Packet handler for \" + packet.getClass().getCanonicalName() + \" threw an exception:\");\r\n                    Debug.echoError(ex);\r\n                    continue;\r\n                }\r\n                if (processed == null) {\r\n                    if (NMSHandler.debugPackets) {\r\n                        doPacketOutput(\"DENIED PACKET - \" + packet.getClass().getCanonicalName() + \" DENIED FROM SEND TO \" + player.getScoreboardName());\r\n                    }\r\n                    return null;\r\n                }\r\n                packet = processed;\r\n            }\r\n        }\r\n        if (PlayerReceivesPacketScriptEvent.instance.eventData.isEnabled & PlayerReceivesPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) {\r\n            if (NMSHandler.debugPackets) {\r\n                doPacketOutput(\"DENIED PACKET - \" + packet.getClass().getCanonicalName() + \" DENIED FROM SEND TO \" + player.getScoreboardName() + \" due to event\");\r\n            }\r\n            return null;\r\n        }\r\n        return packet;\r\n    }\r\n\r\n    static {\r\n        ActionBarEventPacketHandlers.registerHandlers();\r\n        AttachPacketHandlers.registerHandlers();\r\n        BlockLightPacketHandlers.registerHandlers();\r\n        DenizenPacketHandlerPacketHandlers.registerHandlers();\r\n        EntityMetadataPacketHandlers.registerHandlers();\r\n        DisguisePacketHandlers.registerHandlers();\r\n        FakeBlocksPacketHandlers.registerHandlers();\r\n        FakeEquipmentPacketHandlers.registerHandlers();\r\n        FakePlayerPacketHandlers.registerHandlers();\r\n        HiddenEntitiesPacketHandlers.registerHandlers();\r\n        HideParticlesPacketHandlers.registerHandlers();\r\n        PlayerHearsSoundEventPacketHandlers.registerHandlers();\r\n        ProfileEditorImpl.registerHandlers();\r\n        TablistUpdateEventPacketHandlers.registerHandlers();\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldManager.tick();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRemoteAddress() {\r\n        return oldManager.getRemoteAddress();\r\n    }\r\n\r\n    @Override\r\n    public String getLoggableAddress(boolean flag) {\r\n        return oldManager.getLoggableAddress(flag);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        if (!player.getBukkitEntity().isOnline()) { // Workaround Paper duplicate quit event issue\r\n            return;\r\n        }\r\n        oldManager.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(DisconnectionDetails disconnectiondetails) {\r\n        oldManager.disconnect(disconnectiondetails);\r\n    }\r\n\r\n    @Override\r\n    public boolean isMemoryConnection() {\r\n        return oldManager != null && oldManager.isMemoryConnection();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getReceiving() {\r\n        return oldManager.getReceiving();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getSending() {\r\n        return oldManager.getSending();\r\n    }\r\n\r\n    @Override\r\n    public void configurePacketHandler(ChannelPipeline channelpipeline) {\r\n        oldManager.configurePacketHandler(channelpipeline);\r\n    }\r\n\r\n    @Override\r\n    public void setEncryptionKey(Cipher cipher, Cipher cipher1) {\r\n        oldManager.setEncryptionKey(cipher, cipher1);\r\n    }\r\n\r\n    @Override\r\n    public boolean isEncrypted() {\r\n        return oldManager.isEncrypted();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnected() {\r\n        return oldManager.isConnected();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnecting() {\r\n        return oldManager.isConnecting();\r\n    }\r\n\r\n    @Override\r\n    public PacketListener getPacketListener() {\r\n        return oldManager.getPacketListener();\r\n    }\r\n\r\n    @Override\r\n    public DisconnectionDetails getDisconnectionDetails() {\r\n        return oldManager.getDisconnectionDetails();\r\n    }\r\n\r\n    @Override\r\n    public void setReadOnly() {\r\n        oldManager.setReadOnly();\r\n    }\r\n\r\n    @Override\r\n    public void setupCompression(int i, boolean b) {\r\n        oldManager.setupCompression(i, b);\r\n    }\r\n\r\n    @Override\r\n    public void handleDisconnection() {\r\n        oldManager.handleDisconnection();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageReceivedPackets() {\r\n        return oldManager.getAverageReceivedPackets();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageSentPackets() {\r\n        return oldManager.getAverageSentPackets();\r\n    }\r\n\r\n    @Override\r\n    public void setBandwidthLogger(LocalSampleLogger localsamplelogger) {\r\n        oldManager.setBandwidthLogger(localsamplelogger);\r\n    }\r\n\r\n    //////////////////////////////////\r\n    //// Reflection Methods/Fields\r\n    ///////////\r\n\r\n    private static final Field protocolDirectionField = ReflectionHelper.getFields(Connection.class).get(ReflectionMappingsInfo.Connection_receiving, PacketFlow.class);\r\n    public static final Field Connection_packetListener = ReflectionHelper.getFields(Connection.class).get(ReflectionMappingsInfo.Connection_packetListener, PacketListener.class);\r\n    private static final Field ServerGamePacketListener_ConnectionField = ReflectionHelper.getFields(ServerCommonPacketListenerImpl.class).get(ReflectionMappingsInfo.ServerCommonPacketListenerImpl_connection);\r\n    private static final MethodHandle ServerGamePacketListener_ConnectionSetter = ReflectionHelper.getFinalSetter(ServerCommonPacketListenerImpl.class, ReflectionMappingsInfo.ServerCommonPacketListenerImpl_connection);\r\n\r\n    private static PacketFlow getProtocolDirection(Connection networkManager) {\r\n        PacketFlow direction = null;\r\n        try {\r\n            direction = (PacketFlow) protocolDirectionField.get(networkManager);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return direction;\r\n    }\r\n\r\n    private static void setNetworkManager(ServerGamePacketListenerImpl playerConnection, Connection networkManager) {\r\n        try {\r\n            ServerGamePacketListener_ConnectionSetter.invoke(playerConnection, networkManager);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean acceptInboundMessage(Object msg) throws Exception {\r\n        return oldManager.acceptInboundMessage(msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\r\n        oldManager.channelRead(ctx, msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelReadComplete(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\r\n        oldManager.userEventTriggered(ctx, evt);\r\n    }\r\n\r\n    @Override\r\n    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelWritabilityChanged(ctx);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/DenizenPacketListenerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerChangesSignScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerSteersEntityScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.packets.PacketInResourcePackStatusImpl;\r\nimport com.denizenscript.denizen.nms.v1_21.impl.network.packets.PacketInSteerVehicleImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\r\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;\r\nimport net.minecraft.network.protocol.common.ServerboundResourcePackPacket;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.CraftBlock;\r\nimport org.bukkit.event.block.SignChangeEvent;\r\n\r\npublic class DenizenPacketListenerImpl extends AbstractListenerPlayInImpl {\r\n\r\n    public BlockPos fakeSignExpected;\r\n\r\n    public DenizenPacketListenerImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer) {\r\n        super(networkManager, entityPlayer, entityPlayer.connection);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(final ServerboundPlayerInputPacket packet) {\r\n        if (!PlayerSteersEntityScriptEvent.instance.eventData.isEnabled) {\r\n            super.handlePlayerInput(packet);\r\n            return;\r\n        }\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInSteerVehicleImpl(packet), () -> super.handlePlayerInput(packet));\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInResourcePackStatusImpl(packet));\r\n        super.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        DenizenPacketHandler.instance.receivePlacePacket(player.getBukkitEntity());\r\n        super.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        DenizenPacketHandler.instance.receiveDigPacket(player.getBukkitEntity());\r\n        super.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && (override.hand != null || override.offhand != null)) {\r\n            player.getBukkitEntity().updateInventory();\r\n        }\r\n        super.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && override.hand != null) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 2);\r\n        }\r\n        super.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && packet.containerId() == 0) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 1);\r\n        }\r\n        super.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        if (NMSHandler.debugPackets) {\r\n            Debug.log(\"Custom packet payload: \" + packet.payload().type().id().toString() + \" sent from \" + player.getScoreboardName());\r\n        }\r\n        super.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        if (fakeSignExpected != null && packet.getPos().equals(fakeSignExpected)) {\r\n            LocationTag loc = new LocationTag(player.getBukkitEntity().getWorld(), fakeSignExpected.getX(), fakeSignExpected.getY(), fakeSignExpected.getZ());\r\n            this.connection.send(new ClientboundBlockUpdatePacket(player.level(), fakeSignExpected));\r\n            PlayerChangesSignScriptEvent evt = (PlayerChangesSignScriptEvent) PlayerChangesSignScriptEvent.instance.clone();\r\n            evt.material = new MaterialTag(org.bukkit.Material.OAK_WALL_SIGN);\r\n            evt.location = new LocationTag(player.getBukkitEntity().getLocation());\r\n            evt.event = new SignChangeEvent(CraftBlock.at(player.level(), fakeSignExpected), player.getBukkitEntity(), packet.getLines());\r\n            fakeSignExpected = null;\r\n            evt.fire(evt.event);\r\n        }\r\n        super.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        if (DenizenPacketHandler.forceNoclip.contains(player.getUUID())) {\r\n            player.noPhysics = true;\r\n        }\r\n        super.handleMovePlayer(packet);\r\n    }\r\n\r\n    // For compatibility with other plugins using Reflection weirdly...\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        super.send(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/FakeBlockHelper.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.RegistryFriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.Biomes;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.entity.BlockEntity;\r\nimport net.minecraft.world.level.block.entity.BlockEntityType;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.PalettedContainer;\r\nimport net.minecraft.world.level.chunk.Strategy;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftRegistry;\r\nimport org.bukkit.craftbukkit.v1_21_R7.CraftWorld;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.CraftBlockStates;\r\nimport org.bukkit.craftbukkit.v1_21_R7.block.data.CraftBlockData;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Constructor;\r\nimport java.lang.reflect.Field;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\n\r\npublic class FakeBlockHelper {\r\n\r\n    public static Field CHUNKDATA_BLOCK_ENTITIES = ReflectionHelper.getFields(ClientboundLevelChunkPacketData.class).getFirstOfType(List.class);\r\n    public static MethodHandle CHUNKDATA_BLOCK_ENTITY_CONSTRUCTOR = ReflectionHelper.getConstructor(ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0], int.class, int.class, BlockEntityType.class, CompoundTag.class);\r\n    public static MethodHandle CHUNKDATA_BUFFER_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkPacketData.class, byte[].class);\r\n    public static Class CHUNKDATA_BLOCKENTITYINFO_CLASS = ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0];\r\n    public static Field CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get(ReflectionMappingsInfo.ClientboundLevelChunkPacketDataBlockEntityInfo_packedXZ);\r\n    public static Field CHUNKDATA_BLOCKENTITYINFO_Y = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get(ReflectionMappingsInfo.ClientboundLevelChunkPacketDataBlockEntityInfo_y);\r\n    public static MethodHandle CHUNKPACKET_CHUNKDATA_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class);\r\n    public static Constructor<?> PALETTEDCONTAINER_CTOR = Arrays.stream(PalettedContainer.class.getConstructors()).filter(c -> c.getParameterCount() == 2).findFirst().get();\r\n\r\n    public static BlockState getNMSState(FakeBlock block) {\r\n        return ((CraftBlockData) block.material.getModernData()).getState();\r\n    }\r\n\r\n    public static boolean anyBlocksInSection(List<FakeBlock> blocks, int y) {\r\n        int minY = y << 4;\r\n        int maxY = (y << 4) + 16;\r\n        for (FakeBlock block : blocks) {\r\n            int blockY = block.location.getBlockY();\r\n            if (blockY >= minY && blockY < maxY) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static Field PAPER_CHUNK_READY;\r\n    public static boolean tryPaperPatch = true;\r\n\r\n    public static void copyPacketPaperPatch(ClientboundLevelChunkWithLightPacket newPacket, ClientboundLevelChunkWithLightPacket oldPacket) {\r\n        if (!Denizen.supportsPaper || !tryPaperPatch) {\r\n            return;\r\n        }\r\n        try {\r\n            if (PAPER_CHUNK_READY == null) {\r\n                PAPER_CHUNK_READY = ReflectionHelper.getFields(ClientboundLevelChunkWithLightPacket.class).get(\"ready\");\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            tryPaperPatch = false;\r\n            Debug.echoError(\"Paper packet patch failed:\");\r\n            Debug.echoError(ex);\r\n            return;\r\n        }\r\n        try {\r\n            PAPER_CHUNK_READY.setBoolean(newPacket, true);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static ClientboundLevelChunkWithLightPacket handleMapChunkPacket(World world, ClientboundLevelChunkWithLightPacket originalPacket, int chunkX, int chunkZ, List<FakeBlock> blocks) {\r\n        try {\r\n            ClientboundLevelChunkWithLightPacket duplicateCorePacket = DenizenNetworkManagerImpl.copyPacket(originalPacket, ClientboundLevelChunkWithLightPacket.STREAM_CODEC);\r\n            copyPacketPaperPatch(duplicateCorePacket, originalPacket);\r\n            RegistryFriendlyByteBuf copier = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry());\r\n            originalPacket.getChunkData().write(copier);\r\n            ClientboundLevelChunkPacketData packet = new ClientboundLevelChunkPacketData(copier, chunkX, chunkZ);\r\n            FriendlyByteBuf serial = originalPacket.getChunkData().getReadBuffer();\r\n            FriendlyByteBuf outputSerial = new FriendlyByteBuf(Unpooled.buffer(serial.readableBytes()));\r\n            List blockEntities = new ArrayList((List) CHUNKDATA_BLOCK_ENTITIES.get(originalPacket.getChunkData()));\r\n            CHUNKDATA_BLOCK_ENTITIES.set(packet, blockEntities);\r\n            for (int i = 0; i < blockEntities.size(); i++) {\r\n                Object blockEnt = blockEntities.get(i);\r\n                int xz = CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ.getInt(blockEnt);\r\n                int y = CHUNKDATA_BLOCKENTITYINFO_Y.getInt(blockEnt);\r\n                int x = (chunkX << 4) + ((xz >> 4) & 15);\r\n                int z = (chunkZ << 4) + (xz & 15);\r\n                for (FakeBlock block : blocks) {\r\n                    LocationTag loc = block.location;\r\n                    if (loc.getBlockX() == x && loc.getBlockY() == y && loc.getBlockZ() == z && block.material != null) {\r\n                        BlockEntity newBlockEnt = CraftBlockStates.createNewTileEntity(block.material.getMaterial());\r\n                        Object newData = CHUNKDATA_BLOCK_ENTITY_CONSTRUCTOR.invoke(xz, y, newBlockEnt.getType(), newBlockEnt.getUpdateTag(CraftRegistry.getMinecraftRegistry()));\r\n                        blockEntities.set(i, newData);\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            int worldMinY = world.getMinHeight();\r\n            int worldMaxY = world.getMaxHeight();\r\n            int minChunkY = worldMinY >> 4;\r\n            int maxChunkY = worldMaxY >> 4;\r\n            Registry<Biome> biomeRegistry = ((CraftWorld) world).getHandle().registryAccess().lookupOrThrow(Registries.BIOME);\r\n            for (int y = minChunkY; y < maxChunkY; y++) {\r\n                int blockCount = serial.readShort();\r\n                // reflected constructors as workaround for spigot remapper bug - Mojang \"IdMap\" became Spigot \"IRegistry\" but should be \"Registry\"\r\n                PalettedContainer<BlockState> states = (PalettedContainer<BlockState>) PALETTEDCONTAINER_CTOR.newInstance(Blocks.AIR.defaultBlockState(), Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY));\r\n                states.read(serial);\r\n                PalettedContainer<Biome> biomes = (PalettedContainer<Biome>) PALETTEDCONTAINER_CTOR.newInstance(biomeRegistry.getOrThrow(Biomes.PLAINS), Strategy.createForBiomes(biomeRegistry));\r\n                biomes.read(serial);\r\n                if (anyBlocksInSection(blocks, y)) {\r\n                    int minY = y << 4;\r\n                    int maxY = (y << 4) + 16;\r\n                    for (FakeBlock block : blocks) {\r\n                        int blockY = block.location.getBlockY();\r\n                        if (blockY >= minY && blockY < maxY && block.material != null) {\r\n                            int blockX = block.location.getBlockX();\r\n                            int blockZ = block.location.getBlockZ();\r\n                            blockX -= (blockX >> 4) * 16;\r\n                            blockY -= (blockY >> 4) * 16;\r\n                            blockZ -= (blockZ >> 4) * 16;\r\n                            BlockState oldState = states.get(blockX, blockY, blockZ);\r\n                            BlockState newState = getNMSState(block);\r\n                            if (oldState.isAir() && !newState.isAir()) {\r\n                                blockCount++;\r\n                            }\r\n                            else if (newState.isAir() && !oldState.isAir()) {\r\n                                blockCount--;\r\n                            }\r\n                            states.set(blockX, blockY, blockZ, newState);\r\n                        }\r\n                    }\r\n                }\r\n                outputSerial.writeShort(blockCount);\r\n                states.write(outputSerial);\r\n                biomes.write(outputSerial);\r\n            }\r\n            byte[] outputBytes = outputSerial.array();\r\n            CHUNKDATA_BUFFER_SETTER.invoke(packet, outputBytes);\r\n            CHUNKPACKET_CHUNKDATA_SETTER.invoke(duplicateCorePacket, packet);\r\n            return duplicateCorePacket;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/ActionBarEventPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerReceivesActionbarScriptEvent;\nimport com.denizenscript.denizen.nms.v1_21.Handler;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport net.minecraft.network.chat.Component;\nimport net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket;\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftChatMessage;\n\npublic class ActionBarEventPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetActionBarTextPacket.class, ActionBarEventPacketHandlers::processActionbarPacket);\n    }\n\n    public static ClientboundSetActionBarTextPacket processActionbarPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetActionBarTextPacket actionbarPacket) {\n        PlayerReceivesActionbarScriptEvent event = PlayerReceivesActionbarScriptEvent.instance;\n        if (!event.loaded) {\n            return actionbarPacket;\n        }\n        event.reset();\n        Component actionbarText = actionbarPacket.text();\n        event.message = new ElementTag(FormattedTextHelper.stringify(Handler.componentToSpigot(actionbarText)), true);\n        event.rawJson = new ElementTag(CraftChatMessage.toJSON(actionbarText), true);\n        event.system = new ElementTag(false);\n        event.player = PlayerTag.mirrorBukkitPlayer(networkManager.player.getBukkitEntity());\n        event = (PlayerReceivesActionbarScriptEvent) event.triggerNow();\n        if (event.cancelled) {\n            return null;\n        }\n        if (event.modified) {\n            return new ClientboundSetActionBarTextPacket(Handler.componentToNMS(event.altMessageDetermination));\n        }\n        return actionbarPacket;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/AttachPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.PositionMoveRotation;\nimport net.minecraft.world.phys.Vec3;\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity;\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftVector;\nimport org.bukkit.util.Vector;\n\nimport java.lang.reflect.Field;\nimport java.util.Set;\n\npublic class AttachPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundRotateHeadPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityMotionPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundTeleportEntityPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundRemoveEntitiesPacket.class, AttachPacketHandlers::processAttachToForPacket);\n    }\n\n    public static Field POS_X_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_xa, short.class);\n    public static Field POS_Y_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_ya, short.class);\n    public static Field POS_Z_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_za, short.class);\n    public static Field YAW_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_yRot, byte.class);\n    public static Field PITCH_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(ReflectionMappingsInfo.ClientboundMoveEntityPacket_xRot, byte.class);\n    public static Field ENTITY_ID_PACKVELENT = ReflectionHelper.getFields(ClientboundSetEntityMotionPacket.class).get(ReflectionMappingsInfo.ClientboundSetEntityMotionPacket_id, int.class);\n\n    public static Vector VECTOR_ZERO = new Vector(0, 0, 0);\n\n    public static void tryProcessMovePacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket packet, Entity e) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    ClientboundMoveEntityPacket pNew;\n                    int newId = att.attached.getBukkitEntity().getEntityId();\n                    if (packet instanceof ClientboundMoveEntityPacket.Pos) {\n                        pNew = new ClientboundMoveEntityPacket.Pos(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.isOnGround());\n                    }\n                    else if (packet instanceof ClientboundMoveEntityPacket.Rot) {\n                        pNew = new ClientboundMoveEntityPacket.Rot(newId, Mth.packDegrees(packet.getYRot()), Mth.packDegrees(packet.getXRot()), packet.isOnGround());\n                    }\n                    else if (packet instanceof ClientboundMoveEntityPacket.PosRot) {\n                        pNew = new ClientboundMoveEntityPacket.PosRot(newId, packet.getXa(), packet.getYa(), packet.getZa(), Mth.packDegrees(packet.getYRot()), Mth.packDegrees(packet.getXRot()), packet.isOnGround());\n                    }\n                    else {\n                        if (CoreConfiguration.debugVerbose) {\n                            Debug.echoError(\"Impossible move-entity packet class: \" + packet.getClass().getCanonicalName());\n                        }\n                        return;\n                    }\n                    if (att.positionalOffset != null) {\n                        boolean isRotate = packet instanceof ClientboundMoveEntityPacket.PosRot || packet instanceof ClientboundMoveEntityPacket.Rot;\n                        float yaw, pitch;\n                        if (att.noRotate) {\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                            yaw = attachedEntity.getYRot();\n                            pitch = attachedEntity.getXRot();\n                        }\n                        else if (isRotate) {\n                            yaw = packet.getYRot();\n                            pitch = packet.getXRot();\n                        }\n                        else {\n                            yaw = e.getYRot();\n                            pitch = e.getXRot();\n                        }\n                        if (att.noPitch) {\n                            pitch = ((CraftEntity) att.attached.getBukkitEntity()).getHandle().getXRot();\n                        }\n                        float newYaw = yaw;\n                        if (isRotate) {\n                            newYaw = EntityAttachmentHelper.normalizeAngle(newYaw + att.positionalOffset.getYaw());\n                            pitch = EntityAttachmentHelper.normalizeAngle(pitch + att.positionalOffset.getPitch());\n                        }\n                        Vector goalPosition = att.fixedForOffset(new Vector(e.getX(), e.getY(), e.getZ()), e.getYRot(), e.getXRot());\n                        Vector oldPos = att.visiblePositions.get(networkManager.player.getUUID());\n                        boolean forceTele = false;\n                        if (oldPos == null) {\n                            oldPos = att.attached.getLocation().toVector();\n                            forceTele = true;\n                        }\n                        Vector moveNeeded = goalPosition.clone().subtract(oldPos);\n                        att.visiblePositions.put(networkManager.player.getUUID(), goalPosition.clone());\n                        int offX = (int) (moveNeeded.getX() * (32 * 128));\n                        int offY = (int) (moveNeeded.getY() * (32 * 128));\n                        int offZ = (int) (moveNeeded.getZ() * (32 * 128));\n                        if ((isRotate && att.offsetRelative) || forceTele || offX < Short.MIN_VALUE || offX > Short.MAX_VALUE\n                                || offY < Short.MIN_VALUE || offY > Short.MAX_VALUE\n                                || offZ < Short.MIN_VALUE || offZ > Short.MAX_VALUE) {\n                            ClientboundTeleportEntityPacket newTeleportPacket = new ClientboundTeleportEntityPacket(\n                                    att.attached.getBukkitEntity().getEntityId(),\n                                    new PositionMoveRotation(CraftVector.toNMS(goalPosition), Vec3.ZERO, newYaw, pitch),\n                                    Set.of(),\n                                    e.onGround()\n                            );\n                            if (NMSHandler.debugPackets) {\n                                DenizenNetworkManagerImpl.doPacketOutput(\"Attach Move-Tele Packet: \" + newTeleportPacket.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\n                            }\n                            networkManager.oldManager.send(newTeleportPacket);\n                        }\n                        else {\n                            POS_X_PACKENT.setShort(pNew, (short) Mth.clamp(offX, Short.MIN_VALUE, Short.MAX_VALUE));\n                            POS_Y_PACKENT.setShort(pNew, (short) Mth.clamp(offY, Short.MIN_VALUE, Short.MAX_VALUE));\n                            POS_Z_PACKENT.setShort(pNew, (short) Mth.clamp(offZ, Short.MIN_VALUE, Short.MAX_VALUE));\n                            if (isRotate) {\n                                YAW_PACKENT.setByte(pNew, EntityAttachmentHelper.compressAngle(yaw));\n                                PITCH_PACKENT.setByte(pNew, EntityAttachmentHelper.compressAngle(pitch));\n                            }\n                            if (NMSHandler.debugPackets) {\n                                DenizenNetworkManagerImpl.doPacketOutput(\"Attach Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\n                            }\n                            networkManager.oldManager.send(pNew);\n                        }\n                    }\n                    else {\n                        if (NMSHandler.debugPackets) {\n                            DenizenNetworkManagerImpl.doPacketOutput(\"Attach Replica-Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName());\n                        }\n                        networkManager.oldManager.send(pNew);\n                    }\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessMovePacketForAttach(networkManager, packet, ent);\n            }\n        }\n    }\n\n    public static void tryProcessRotateHeadPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundRotateHeadPacket packet, Entity e) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    float yaw = packet.getYHeadRot();\n                    Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                    if (att.positionalOffset != null) {\n                        if (att.noRotate) {\n                            yaw = attachedEntity.getYRot();\n                        }\n                        yaw = EntityAttachmentHelper.normalizeAngle(yaw + att.positionalOffset.getYaw());\n                    }\n                    ClientboundRotateHeadPacket pNew = new ClientboundRotateHeadPacket(attachedEntity, EntityAttachmentHelper.compressAngle(yaw));\n                    if (NMSHandler.debugPackets) {\n                        DenizenNetworkManagerImpl.doPacketOutput(\"Head Rotation Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName());\n                    }\n                    networkManager.oldManager.send(pNew);\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessRotateHeadPacketForAttach(networkManager, packet, ent);\n            }\n        }\n    }\n\n    public static void tryProcessVelocityPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityMotionPacket packet, Entity e) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    ClientboundSetEntityMotionPacket pNew = DenizenNetworkManagerImpl.copyPacket(packet, ClientboundSetEntityMotionPacket.STREAM_CODEC);\n                    ENTITY_ID_PACKVELENT.setInt(pNew, att.attached.getBukkitEntity().getEntityId());\n                    if (NMSHandler.debugPackets) {\n                        DenizenNetworkManagerImpl.doPacketOutput(\"Attach Velocity Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName());\n                    }\n                    networkManager.oldManager.send(pNew);\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessVelocityPacketForAttach(networkManager, packet, ent);\n            }\n        }\n    }\n\n    public static void tryProcessTeleportPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundTeleportEntityPacket packet, Entity e, Vector relative) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    ClientboundTeleportEntityPacket pNew;\n                    Vector resultPos = CraftVector.toBukkit(packet.change().position()).add(relative);\n                    if (att.positionalOffset != null) {\n                        resultPos = att.fixedForOffset(resultPos, e.getYRot(), e.getXRot());\n                        float yaw, pitch;\n                        if (att.noRotate) {\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                            yaw = attachedEntity.getYRot();\n                            pitch = attachedEntity.getXRot();\n                        }\n                        else {\n                            yaw = packet.change().yRot();\n                            pitch = packet.change().xRot();\n                        }\n                        if (att.noPitch) {\n                            pitch = ((CraftEntity) att.attached.getBukkitEntity()).getHandle().getXRot();\n                        }\n                        float newYaw = EntityAttachmentHelper.normalizeAngle(yaw + att.positionalOffset.getYaw());\n                        pitch = EntityAttachmentHelper.normalizeAngle(pitch + att.positionalOffset.getPitch());\n                        pNew = new ClientboundTeleportEntityPacket(\n                                att.attached.getBukkitEntity().getEntityId(),\n                                new PositionMoveRotation(CraftVector.toNMS(resultPos), packet.change().deltaMovement(), newYaw, pitch),\n                                packet.relatives(),\n                                packet.onGround()\n                        );\n                        if (NMSHandler.debugPackets) {\n                            DenizenNetworkManagerImpl.doPacketOutput(\"Attach Teleport Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID()\n                                    + \" sent to \" + networkManager.player.getScoreboardName() + \" with raw yaw \" + yaw + \" adapted to \" + newYaw);\n                        }\n                    }\n                    else {\n                        pNew = new ClientboundTeleportEntityPacket(att.attached.getBukkitEntity().getEntityId(), packet.change(), packet.relatives(), packet.onGround());\n                    }\n                    att.visiblePositions.put(networkManager.player.getUUID(), resultPos.clone());\n                    networkManager.oldManager.send(pNew);\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessTeleportPacketForAttach(networkManager, packet, ent, new Vector(ent.getX() - e.getX(), ent.getY() - e.getY(), ent.getZ() - e.getZ()));\n            }\n        }\n    }\n\n    public static Packet<ClientGamePacketListener> processAttachToForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (EntityAttachmentHelper.toEntityToData.isEmpty()) {\n            return packet;\n        }\n        try {\n            if (packet instanceof ClientboundMoveEntityPacket moveEntityPacket) {\n                Entity e = moveEntityPacket.getEntity(networkManager.player.level());\n                if (e == null) {\n                    return packet;\n                }\n                if (!e.isPassenger()) {\n                    tryProcessMovePacketForAttach(networkManager, moveEntityPacket, e);\n                }\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundRotateHeadPacket rotateHeadPacket) {\n                Entity e = rotateHeadPacket.getEntity(networkManager.player.level());\n                if (e == null) {\n                    return packet;\n                }\n                tryProcessRotateHeadPacketForAttach(networkManager, rotateHeadPacket, e);\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundSetEntityMotionPacket setEntityMotionPacket) {\n                int ider = setEntityMotionPacket.getId();\n                Entity e = networkManager.player.level().getEntity(ider);\n                if (e == null) {\n                    return packet;\n                }\n                tryProcessVelocityPacketForAttach(networkManager, setEntityMotionPacket, e);\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundTeleportEntityPacket teleportEntityPacket) {\n                int ider = teleportEntityPacket.id();\n                Entity e = networkManager.player.level().getEntity(ider);\n                if (e == null) {\n                    return packet;\n                }\n                tryProcessTeleportPacketForAttach(networkManager, teleportEntityPacket, e, VECTOR_ZERO);\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundRemoveEntitiesPacket removeEntitiesPacket) {\n                for (int id : removeEntitiesPacket.getEntityIds()) {\n                    Entity e = networkManager.player.level().getEntity(id);\n                    if (e != null) {\n                        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n                        if (attList != null) {\n                            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                                if (attMap.attached.isValid() && att != null) {\n                                    att.visiblePositions.remove(networkManager.player.getUUID());\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        catch (Exception ex) {\n            Debug.echoError(ex);\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/BlockLightPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\nimport com.denizenscript.denizen.nms.v1_21.impl.blocks.BlockLightImpl;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;\nimport net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;\n\npublic class BlockLightPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLightUpdatePacket.class, BlockLightPacketHandlers::processLightUpdatePacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, BlockLightPacketHandlers::processBlockUpdatePacket);\n    }\n\n    public static void processLightUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundLightUpdatePacket lightUpdatePacket) {\n        if (!BlockLight.lightsByChunk.isEmpty()) {\n            BlockLightImpl.checkIfLightsBrokenByPacket(lightUpdatePacket, networkManager.player.level());\n        }\n    }\n\n    public static void processBlockUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundBlockUpdatePacket blockUpdatePacket) {\n        if (!BlockLight.lightsByChunk.isEmpty()) {\n            BlockLightImpl.checkIfLightsBrokenByPacket(blockUpdatePacket, networkManager.player.level());\n        }\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/DenizenPacketHandlerPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerReceivesMessageScriptEvent;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.packets.PacketOutChatImpl;\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;\nimport net.minecraft.network.protocol.game.ClientboundSystemChatPacket;\n\npublic class DenizenPacketHandlerPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSystemChatPacket.class, DenizenPacketHandlerPacketHandlers::processPacketHandlerForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerChatPacket.class, DenizenPacketHandlerPacketHandlers::processPacketHandlerForPacket);\n    }\n\n    public static Packet<ClientGamePacketListener> processPacketHandlerForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (DenizenPacketHandler.instance.shouldInterceptChatPacket()) {\n            PacketOutChatImpl packetHelper = null;\n            boolean isActionbar = false;\n            if (packet instanceof ClientboundSystemChatPacket chatPacket) {\n                isActionbar = chatPacket.overlay();\n                packetHelper = new PacketOutChatImpl(chatPacket);\n                if (packetHelper.rawJson == null) { // Makes no sense but this can be null in weird edge cases\n                    return packet;\n                }\n            }\n            else if (packet instanceof ClientboundPlayerChatPacket playerChatPacket) {\n                packetHelper = new PacketOutChatImpl(playerChatPacket);\n            }\n            if (packetHelper != null) {\n                PlayerReceivesMessageScriptEvent result = DenizenPacketHandler.instance.sendPacket(networkManager.player.getBukkitEntity(), packetHelper);\n                if (result != null) {\n                    if (result.cancelled) {\n                        return null;\n                    }\n                    if (result.modified) {\n                        return new ClientboundSystemChatPacket(result.altMessageDetermination, isActionbar);\n                    }\n                }\n            }\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/DisguisePacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.v1_21.helpers.PacketHelperImpl;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.scripts.commands.player.DisguiseCommand;\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.network.syncher.SynchedEntityData;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.PositionMoveRotation;\nimport net.minecraft.world.level.Level;\nimport org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity;\nimport org.bukkit.entity.EntityType;\nimport org.bukkit.entity.LivingEntity;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.function.BiFunction;\nimport java.util.function.ToIntFunction;\n\npublic class DisguisePacketHandlers {\n\n    public static void registerHandlers() {\n        registerPacketHandler(ClientboundSetEntityDataPacket.class, ClientboundSetEntityDataPacket::id, DisguisePacketHandlers::processEntityDataPacket);\n        registerPacketHandler(ClientboundUpdateAttributesPacket.class, ClientboundUpdateAttributesPacket::getEntityId, DisguisePacketHandlers::processAttributesPacket);\n        registerPacketHandler(ClientboundAddEntityPacket.class, ClientboundAddEntityPacket::getId, DisguisePacketHandlers::sendDisguiseForPacket);\n        registerPacketHandler(ClientboundTeleportEntityPacket.class, ClientboundTeleportEntityPacket::id, DisguisePacketHandlers::processTeleportPacket);\n        registerPacketHandler(ClientboundMoveEntityPacket.Rot.class, ClientboundMoveEntityPacket::getEntity, DisguisePacketHandlers::processMoveEntityRotPacket);\n        registerPacketHandler(ClientboundMoveEntityPacket.PosRot.class, ClientboundMoveEntityPacket::getEntity, DisguisePacketHandlers::processMoveEntityPosRotPacket);\n    }\n\n    private static boolean antiDuplicate = false;\n\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetType, ToIntFunction<T> idGetter, DisguisePacketHandler<T> handler) {\n        registerPacketHandler(packetType, (packet, level) -> level.getEntity(idGetter.applyAsInt(packet)), handler);\n    }\n\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetType, BiFunction<T, Level, Entity> entityGetter, DisguisePacketHandler<T> handler) {\n        DenizenNetworkManagerImpl.registerPacketHandler(packetType, (networkManager, packet) -> {\n            if (DisguiseCommand.disguises.isEmpty() || antiDuplicate) {\n                return packet;\n            }\n            Entity entity = entityGetter.apply(packet, networkManager.player.level());\n            if (entity == null) {\n                return packet;\n            }\n            Map<UUID, DisguiseCommand.TrackedDisguise> playerMap = DisguiseCommand.disguises.get(entity.getUUID());\n            if (playerMap == null) {\n                return packet;\n            }\n            DisguiseCommand.TrackedDisguise disguise = playerMap.get(networkManager.player.getUUID());\n            if (disguise == null) {\n                disguise = playerMap.get(null);\n            }\n            if (disguise == null || !disguise.isActive) {\n                return packet;\n            }\n            if (NMSHandler.debugPackets) {\n                DenizenNetworkManagerImpl.doPacketOutput(\"DISGUISED packet \" + packet.getClass().getName() + \" for entity \" + entity.getId() + \" to player \" + networkManager.player.getScoreboardName());\n            }\n            try {\n                return handler.handle(networkManager, packet, disguise);\n            }\n            catch (Exception e) {\n                antiDuplicate = false;\n                throw e; // \"pass it\" to the generic exception handling\n            }\n        });\n    }\n\n    @FunctionalInterface\n    public interface DisguisePacketHandler<T extends Packet<ClientGamePacketListener>> {\n\n        T handle(DenizenNetworkManagerImpl networkManager, T packet, DisguiseCommand.TrackedDisguise disguise) throws Exception;\n    }\n\n    public static ClientboundSetEntityDataPacket processEntityDataPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityDataPacket entityDataPacket, DisguiseCommand.TrackedDisguise disguise) {\n        if (entityDataPacket.id() == networkManager.player.getId()) {\n            if (!disguise.shouldFake) {\n                return entityDataPacket;\n            }\n            for (SynchedEntityData.DataValue<?> dataValue : entityDataPacket.packedItems()) {\n                if (dataValue.id() == 0) { // Entity flags\n                    List<SynchedEntityData.DataValue<?>> newData = new ArrayList<>(entityDataPacket.packedItems());\n                    newData.remove(dataValue);\n                    byte flags = (byte) dataValue.value();\n                    flags |= 0x20; // Invisible flag\n                    newData.add(PacketHelperImpl.createEntityData(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags));\n                    return new ClientboundSetEntityDataPacket(entityDataPacket.id(), newData);\n                }\n            }\n        }\n        else {\n            List<SynchedEntityData.DataValue<?>> data = ((CraftEntity) disguise.toOthers.entity.entity).getHandle().getEntityData().getNonDefaultValues();\n            return data != null ? new ClientboundSetEntityDataPacket(entityDataPacket.id(), data) : null;\n        }\n        return entityDataPacket;\n    }\n\n    public static ClientboundUpdateAttributesPacket processAttributesPacket(DenizenNetworkManagerImpl networkManager, ClientboundUpdateAttributesPacket attributesPacket, DisguiseCommand.TrackedDisguise disguise) {\n        FakeEntity fake = attributesPacket.getEntityId() == networkManager.player.getId() ? disguise.fakeToSelf : disguise.toOthers;\n        return fake == null || fake.entity.entity instanceof LivingEntity ? attributesPacket : null; // Non-living entities don't have attributes\n    }\n\n    public static ClientboundTeleportEntityPacket processTeleportPacket(DenizenNetworkManagerImpl networkManager, ClientboundTeleportEntityPacket teleportEntityPacket, DisguiseCommand.TrackedDisguise disguise) throws IllegalAccessException {\n        if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\n            PositionMoveRotation oldChange = teleportEntityPacket.change();\n            return new ClientboundTeleportEntityPacket(\n                    teleportEntityPacket.id(),\n                    new PositionMoveRotation(oldChange.position(), oldChange.deltaMovement(), EntityAttachmentHelper.normalizeAngle(oldChange.yRot() + 180), oldChange.xRot()),\n                    teleportEntityPacket.relatives(),\n                    teleportEntityPacket.onGround()\n            );\n        }\n        return sendDisguiseForPacket(networkManager, teleportEntityPacket, disguise);\n    }\n\n\n    public static ClientboundMoveEntityPacket.Rot processMoveEntityRotPacket(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket.Rot rotPacket, DisguiseCommand.TrackedDisguise disguise) {\n        if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\n            return new ClientboundMoveEntityPacket.Rot(\n                    disguise.entity.getBukkitEntity().getEntityId(),\n                    EntityAttachmentHelper.compressAngle(rotPacket.getYRot() + 180),\n                    Mth.packDegrees(rotPacket.getXRot()),\n                    rotPacket.isOnGround()\n            );\n        }\n        return sendDisguiseForPacket(networkManager, rotPacket, disguise);\n    }\n\n\n    public static ClientboundMoveEntityPacket.PosRot processMoveEntityPosRotPacket(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket.PosRot posRotPacket, DisguiseCommand.TrackedDisguise disguise) {\n        if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\n            return new ClientboundMoveEntityPacket.PosRot(\n                    disguise.entity.getBukkitEntity().getEntityId(),\n                    posRotPacket.getXa(),\n                    posRotPacket.getYa(),\n                    posRotPacket.getZa(),\n                    EntityAttachmentHelper.compressAngle(posRotPacket.getYRot() + 180),\n                    Mth.packDegrees(posRotPacket.getXRot()),\n                    posRotPacket.isOnGround()\n            );\n        }\n        return sendDisguiseForPacket(networkManager, posRotPacket, disguise);\n    }\n\n    public static <T extends Packet<ClientGamePacketListener>> T sendDisguiseForPacket(DenizenNetworkManagerImpl networkManager, T packet, DisguiseCommand.TrackedDisguise disguise) {\n        antiDuplicate = true;\n        disguise.sendTo(List.of(new PlayerTag(networkManager.player.getUUID())));\n        antiDuplicate = false;\n        return null;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/EntityMetadataPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v1_21.Handler;\nimport com.denizenscript.denizen.nms.v1_21.helpers.PacketHelperImpl;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.scripts.commands.entity.GlowCommand;\nimport com.denizenscript.denizen.scripts.commands.entity.InvisibleCommand;\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\nimport com.denizenscript.denizen.scripts.commands.entity.SneakCommand;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.md_5.bungee.api.ChatColor;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;\nimport net.minecraft.network.syncher.SynchedEntityData;\nimport net.minecraft.world.entity.Entity;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\n\npublic class EntityMetadataPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityDataPacket.class, EntityMetadataPacketHandlers::processMetadataChangesForPacket);\n    }\n\n    public static ClientboundSetEntityDataPacket getModifiedMetadataFor(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityDataPacket metadataPacket) {\n        if (!RenameCommand.hasAnyDynamicRenames() && SneakCommand.forceSetSneak.isEmpty() && InvisibleCommand.helper.noOverrides() && GlowCommand.helper.noOverrides()) {\n            return null;\n        }\n        try {\n            Entity entity = networkManager.player.level().getEntity(metadataPacket.id());\n            if (entity == null) {\n                return null; // If it doesn't exist on-server, it's definitely not relevant, so move on\n            }\n            String nameToApply = RenameCommand.getCustomNameFor(entity.getUUID(), networkManager.player.getBukkitEntity(), false);\n            Boolean forceSneak = SneakCommand.shouldSneak(entity.getUUID(), networkManager.player.getUUID());\n            Boolean isInvisible = InvisibleCommand.helper.getState(entity.getBukkitEntity(), networkManager.player.getUUID(), true);\n            Boolean isGlowing = GlowCommand.helper.getState(entity.getBukkitEntity(), networkManager.player.getUUID(), true);\n            boolean shouldModifyFlags = isInvisible != null || forceSneak != null || isGlowing != null;\n            if (nameToApply == null && !shouldModifyFlags) {\n                return null;\n            }\n            List<SynchedEntityData.DataValue<?>> data = new ArrayList<>(metadataPacket.packedItems().size());\n            Byte currentFlags = null;\n            for (SynchedEntityData.DataValue<?> dataValue : metadataPacket.packedItems()) {\n                if (dataValue.id() == 0 && shouldModifyFlags) { // 0: Entity Flags\n                    currentFlags = (Byte) dataValue.value();\n                }\n                else if (nameToApply == null || (dataValue.id() != 2 && dataValue.id() != 3)) { // 2 and 3: Custom name and custom name visible\n                    data.add(dataValue);\n                }\n            }\n            if (shouldModifyFlags) {\n                byte flags = currentFlags == null ? entity.getEntityData().get(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS) : currentFlags;\n                flags = applyEntityDataFlag(flags, forceSneak, 0x02);\n                flags = applyEntityDataFlag(flags, isInvisible, 0x20);\n                flags = applyEntityDataFlag(flags, isGlowing, 0x40);\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags));\n            }\n            if (nameToApply != null) {\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(nameToApply, ChatColor.WHITE)))));\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true));\n            }\n            return new ClientboundSetEntityDataPacket(metadataPacket.id(), data);\n        }\n        catch (Throwable ex) {\n            Debug.echoError(ex);\n            return null;\n        }\n    }\n\n    public static byte applyEntityDataFlag(byte currentFlags, Boolean value, int flag) {\n        if (value == null) {\n            return currentFlags;\n        }\n        return (byte) (value ? currentFlags | flag : currentFlags & ~flag);\n    }\n\n    public static Packet<ClientGamePacketListener> processMetadataChangesForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!(packet instanceof ClientboundSetEntityDataPacket entityDataPacket)) {\n            return packet;\n        }\n        ClientboundSetEntityDataPacket altPacket = getModifiedMetadataFor(networkManager, entityDataPacket);\n        if (altPacket == null) {\n            return packet;\n        }\n        return altPacket;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakeBlocksPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v1_21.ReflectionMappingsInfo;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.FakeBlockHelper;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.SectionPos;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.world.level.block.state.BlockState;\n\nimport java.lang.reflect.Field;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class FakeBlocksPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelChunkWithLightPacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSectionBlocksUpdatePacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket);\n    }\n\n    public static Field SECTIONPOS_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_sectionPos, SectionPos.class);\n    public static Field OFFSETARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_positions, short[].class);\n    public static Field BLOCKARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(ReflectionMappingsInfo.ClientboundSectionBlocksUpdatePacket_states, BlockState[].class);\n\n    public static Packet<ClientGamePacketListener> processShowFakeForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (FakeBlock.blocks.isEmpty()) {\n            return packet;\n        }\n        try {\n            if (packet instanceof ClientboundLevelChunkWithLightPacket) {\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID());\n                if (map == null) {\n                    return packet;\n                }\n                int chunkX = ((ClientboundLevelChunkWithLightPacket) packet).getX();\n                int chunkZ = ((ClientboundLevelChunkWithLightPacket) packet).getZ();\n                ChunkCoordinate chunkCoord = new ChunkCoordinate(chunkX, chunkZ, networkManager.player.level().getWorld().getName());\n                List<FakeBlock> blocks = FakeBlock.getFakeBlocksFor(networkManager.player.getUUID(), chunkCoord);\n                if (blocks == null || blocks.isEmpty()) {\n                    return packet;\n                }\n                ClientboundLevelChunkWithLightPacket newPacket = FakeBlockHelper.handleMapChunkPacket(networkManager.player.getBukkitEntity().getWorld(), (ClientboundLevelChunkWithLightPacket) packet, chunkX, chunkZ, blocks);\n                return newPacket;\n            }\n            else if (packet instanceof ClientboundSectionBlocksUpdatePacket sectionBlocksUpdatePacket) {\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID());\n                if (map == null) {\n                    return sectionBlocksUpdatePacket;\n                }\n                SectionPos coord = (SectionPos) SECTIONPOS_MULTIBLOCKCHANGE.get(sectionBlocksUpdatePacket);\n                ChunkCoordinate coordinateDenizen = new ChunkCoordinate(coord.getX(), coord.getZ(), networkManager.player.level().getWorld().getName());\n                if (!map.byChunk.containsKey(coordinateDenizen)) {\n                    return sectionBlocksUpdatePacket;\n                }\n                ClientboundSectionBlocksUpdatePacket newPacket = DenizenNetworkManagerImpl.copyPacket(sectionBlocksUpdatePacket, ClientboundSectionBlocksUpdatePacket.STREAM_CODEC);\n                LocationTag location = new LocationTag(networkManager.player.level().getWorld(), 0, 0, 0);\n                short[] originalOffsetArray = (short[])OFFSETARRAY_MULTIBLOCKCHANGE.get(newPacket);\n                BlockState[] originalDataArray = (BlockState[])BLOCKARRAY_MULTIBLOCKCHANGE.get(newPacket);\n                short[] offsetArray = Arrays.copyOf(originalOffsetArray, originalOffsetArray.length);\n                BlockState[] dataArray = Arrays.copyOf(originalDataArray, originalDataArray.length);\n                OFFSETARRAY_MULTIBLOCKCHANGE.set(newPacket, offsetArray);\n                BLOCKARRAY_MULTIBLOCKCHANGE.set(newPacket, dataArray);\n                for (int i = 0; i < offsetArray.length; i++) {\n                    short offset = offsetArray[i];\n                    BlockPos pos = coord.relativeToBlockPos(offset);\n                    location.setX(pos.getX());\n                    location.setY(pos.getY());\n                    location.setZ(pos.getZ());\n                    FakeBlock block = map.byLocation.get(location);\n                    if (block != null) {\n                        dataArray[i] = FakeBlockHelper.getNMSState(block);\n                    }\n                }\n                return newPacket;\n            }\n            else if (packet instanceof ClientboundBlockUpdatePacket) {\n                BlockPos pos = ((ClientboundBlockUpdatePacket) packet).getPos();\n                LocationTag loc = new LocationTag(networkManager.player.level().getWorld(), pos.getX(), pos.getY(), pos.getZ());\n                FakeBlock block = FakeBlock.getFakeBlockFor(networkManager.player.getUUID(), loc);\n                if (block != null) {\n                    ClientboundBlockUpdatePacket newPacket = new ClientboundBlockUpdatePacket(((ClientboundBlockUpdatePacket) packet).getPos(), FakeBlockHelper.getNMSState(block));\n                    return newPacket;\n                }\n            }\n            else if (packet instanceof ClientboundBlockChangedAckPacket) {\n                // TODO: 1.19: Can no longer determine what block this packet is for. Would have to track separately? Possibly from the inbound packet rather than the outbound one.\n                /*\n                ClientboundBlockChangedAckPacket origPack = (ClientboundBlockChangedAckPacket) packet;\n                BlockPos pos = origPack.pos();\n                LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ());\n                FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc);\n                if (block != null) {\n                    ClientboundBlockChangedAckPacket newPacket = new ClientboundBlockChangedAckPacket(origPack.pos(), FakeBlockHelper.getNMSState(block), origPack.action(), false);\n                    oldManager.send(newPacket, genericfuturelistener);\n                    return true;\n                }*/\n            }\n        }\n        catch (Throwable ex) {\n            Debug.echoError(ex);\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\nimport com.mojang.datafixers.util.Pair;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.EquipmentSlot;\nimport net.minecraft.world.entity.LivingEntity;\nimport net.minecraft.world.item.ItemStack;\nimport org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class FakeEquipmentPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEquipmentPacket.class, FakeEquipmentPacketHandlers::processSetEquipmentPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundEntityEventPacket.class, FakeEquipmentPacketHandlers::processEntityEventPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundContainerSetContentPacket.class, FakeEquipmentPacketHandlers::processContainerSetContentPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundContainerSetSlotPacket.class, FakeEquipmentPacketHandlers::processContainerSetSlotPacket);\n    }\n\n    public static ClientboundSetEquipmentPacket processSetEquipmentPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetEquipmentPacket setEquipmentPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return setEquipmentPacket;\n        }\n        Entity entity = networkManager.player.level().getEntity(setEquipmentPacket.getEntity());\n        if (entity == null) {\n            return setEquipmentPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(entity.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null) {\n            return setEquipmentPacket;\n        }\n        List<Pair<EquipmentSlot, ItemStack>> equipment = new ArrayList<>(setEquipmentPacket.getSlots());\n        for (int i = 0; i < equipment.size(); i++) {\n            Pair<EquipmentSlot, ItemStack> pair = equipment.get(i);\n            ItemStack use = switch (pair.getFirst()) {\n                case MAINHAND -> override.hand == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.hand.getItemStack());\n                case OFFHAND -> override.offhand == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.offhand.getItemStack());\n                case CHEST -> override.chest == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.chest.getItemStack());\n                case HEAD -> override.head == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.head.getItemStack());\n                case LEGS -> override.legs == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.legs.getItemStack());\n                case FEET -> override.boots == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.boots.getItemStack());\n                case BODY -> pair.getSecond(); // TODO: 1.20.6: is this actually used here? do we want to allow overriding it?\n                case SADDLE -> pair.getSecond(); // TODO: 1.21.5: same as above\n            };\n            equipment.set(i, new Pair<>(pair.getFirst(), use));\n        }\n        return new ClientboundSetEquipmentPacket(setEquipmentPacket.getEntity(), equipment);\n    }\n\n    public static Packet<ClientGamePacketListener> processEntityEventPacket(DenizenNetworkManagerImpl networkManager, ClientboundEntityEventPacket entityEventPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return entityEventPacket;\n        }\n        if (entityEventPacket.getEventId() != 55) {\n            return entityEventPacket;\n        }\n        if (!(entityEventPacket.getEntity(networkManager.player.level()) instanceof LivingEntity livingEntity)) {\n            return entityEventPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(livingEntity.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null || (override.hand == null && override.offhand == null)) {\n            return entityEventPacket;\n        }\n        ItemStack hand = override.hand != null ? CraftItemStack.asNMSCopy(override.hand.getItemStack()) : livingEntity.getMainHandItem();\n        ItemStack offhand = override.offhand != null ? CraftItemStack.asNMSCopy(override.offhand.getItemStack()) : livingEntity.getOffhandItem();\n        return new ClientboundSetEquipmentPacket(livingEntity.getId(), List.of(new Pair<>(EquipmentSlot.MAINHAND, hand), new Pair<>(EquipmentSlot.OFFHAND, offhand)));\n    }\n\n    public static ClientboundContainerSetContentPacket processContainerSetContentPacket(DenizenNetworkManagerImpl networkManager, ClientboundContainerSetContentPacket setContentPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return setContentPacket;\n        }\n        if (setContentPacket.containerId() != 0) {\n            return setContentPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(networkManager.player.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null) {\n            return setContentPacket;\n        }\n        List<ItemStack> items = setContentPacket.items();\n        if (override.head != null) {\n            items.set(5, CraftItemStack.asNMSCopy(override.head.getItemStack()));\n        }\n        if (override.chest != null) {\n            items.set(6, CraftItemStack.asNMSCopy(override.chest.getItemStack()));\n        }\n        if (override.legs != null) {\n            items.set(7, CraftItemStack.asNMSCopy(override.legs.getItemStack()));\n        }\n        if (override.boots != null) {\n            items.set(8, CraftItemStack.asNMSCopy(override.boots.getItemStack()));\n        }\n        if (override.offhand != null) {\n            items.set(45, CraftItemStack.asNMSCopy(override.offhand.getItemStack()));\n        }\n        if (override.hand != null) {\n            items.set(getMainHandSlot(networkManager.player), CraftItemStack.asNMSCopy(override.hand.getItemStack()));\n        }\n        return new ClientboundContainerSetContentPacket(setContentPacket.containerId(), setContentPacket.stateId(), items, setContentPacket.carriedItem());\n    }\n\n    public static ClientboundContainerSetSlotPacket processContainerSetSlotPacket(DenizenNetworkManagerImpl networkManager, ClientboundContainerSetSlotPacket setSlotPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return setSlotPacket;\n        }\n        if (setSlotPacket.getContainerId() != 0) {\n            return setSlotPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(networkManager.player.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null) {\n            return setSlotPacket;\n        }\n        ItemTag item = switch (setSlotPacket.getSlot()) {\n            case 5 -> override.head;\n            case 6 -> override.chest;\n            case 7 -> override.legs;\n            case 8 -> override.boots;\n            case 45 -> override.offhand;\n            default -> setSlotPacket.getSlot() == getMainHandSlot(networkManager.player) ? override.hand : null;\n        };\n        if (item == null) {\n            return setSlotPacket;\n        }\n        return new ClientboundContainerSetSlotPacket(setSlotPacket.getContainerId(), setSlotPacket.getStateId(), setSlotPacket.getSlot(), CraftItemStack.asNMSCopy(item.getItemStack()));\n    }\n\n    public static int getMainHandSlot(ServerPlayer player) {\n        return player.getInventory().getSelectedSlot() + 36;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/FakePlayerPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\n/*\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.v1_20.impl.entities.EntityFakePlayerImpl;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;\nimport org.bukkit.Bukkit;\n\nimport java.util.List;\n*/\n\npublic class FakePlayerPacketHandlers {\n\n    public static void registerHandlers() {\n        // TODO: 1.20.2: Replace this.\n        //DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddPlayerPacket.class, FakePlayerPacketHandlers::processAddPlayerPacket);\n    }\n\n    /*\n    public static void processAddPlayerPacket(DenizenNetworkManagerImpl networkManager, ClientboundAddPlayerPacket addPlayerPacket) {\n        if (networkManager.player.level().getEntity(addPlayerPacket.getEntityId()) instanceof EntityFakePlayerImpl fakePlayer) {\n            networkManager.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, fakePlayer));\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(),\n                    () -> networkManager.send(new ClientboundPlayerInfoRemovePacket(List.of(fakePlayer.getUUID()))), 5);\n        }\n    }*/\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/HiddenEntitiesPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.entity.HideEntitiesHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.world.entity.Entity;\n\npublic class HiddenEntitiesPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddEntityPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        //DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddExperienceOrbPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.Rot.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.Pos.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.PosRot.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityDataPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityMotionPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundTeleportEntityPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n    }\n\n    public static boolean isHidden(ServerPlayer player, Entity entity) {\n        return entity != null && HideEntitiesHelper.playerShouldHide(player.getBukkitEntity().getUniqueId(), entity.getBukkitEntity());\n    }\n\n    public static Packet<ClientGamePacketListener> processHiddenEntitiesForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!HideEntitiesHelper.hasAnyHides()) {\n            return packet;\n        }\n        try {\n            int ider = -1;\n            Entity e = null;\n            if (packet instanceof ClientboundAddEntityPacket) {\n                ider = ((ClientboundAddEntityPacket) packet).getId();\n            }\n            //TODO: 1.21.5: check this packet list\n            /*\n            else if (packet instanceof ClientboundAddExperienceOrbPacket) {\n                ider = ((ClientboundAddExperienceOrbPacket) packet).getId();\n            }*/\n            else if (packet instanceof ClientboundMoveEntityPacket) {\n                e = ((ClientboundMoveEntityPacket) packet).getEntity(networkManager.player.level());\n            }\n            else if (packet instanceof ClientboundSetEntityDataPacket) {\n                ider = ((ClientboundSetEntityDataPacket) packet).id();\n            }\n            else if (packet instanceof ClientboundSetEntityMotionPacket) {\n                ider = ((ClientboundSetEntityMotionPacket) packet).getId();\n            }\n            else if (packet instanceof ClientboundTeleportEntityPacket) {\n                ider = ((ClientboundTeleportEntityPacket) packet).id();\n            }\n            if (e == null && ider != -1) {\n                e = networkManager.player.level().getEntity(ider);\n            }\n            if (e != null) {\n                if (isHidden(networkManager.player, e)) {\n                    return null;\n                }\n            }\n        }\n        catch (Exception ex) {\n            Debug.echoError(ex);\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/HideParticlesPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.packets.HideParticles;\nimport net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;\nimport org.bukkit.Particle;\nimport org.bukkit.craftbukkit.v1_21_R7.CraftParticle;\n\nimport java.util.Set;\n\npublic class HideParticlesPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelParticlesPacket.class, HideParticlesPacketHandlers::processParticlesPacket);\n    }\n\n    public static ClientboundLevelParticlesPacket processParticlesPacket(DenizenNetworkManagerImpl networkManager, ClientboundLevelParticlesPacket particlesPacket) {\n        if (HideParticles.hidden.isEmpty()) {\n            return particlesPacket;\n        }\n        Set<Particle> hidden = HideParticles.hidden.get(networkManager.player.getUUID());\n        if (hidden == null) {\n            return particlesPacket;\n        }\n        Particle bukkitParticle = CraftParticle.minecraftToBukkit(particlesPacket.getParticle().getType());\n        if (hidden.contains(bukkitParticle)) {\n            return null;\n        }\n        return particlesPacket;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/PlayerHearsSoundEventPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerHearsSoundScriptEvent;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundSoundEntityPacket;\nimport net.minecraft.network.protocol.game.ClientboundSoundPacket;\nimport net.minecraft.world.entity.Entity;\nimport org.bukkit.Location;\n\npublic class PlayerHearsSoundEventPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSoundPacket.class, PlayerHearsSoundEventPacketHandlers::processSoundPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSoundEntityPacket.class, PlayerHearsSoundEventPacketHandlers::processSoundPacket);\n    }\n\n    public static Packet<ClientGamePacketListener> processSoundPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!PlayerHearsSoundScriptEvent.instance.eventData.isEnabled) {\n            return packet;\n        }\n        if (packet instanceof ClientboundSoundPacket) {\n            ClientboundSoundPacket spacket = (ClientboundSoundPacket) packet;\n            return PlayerHearsSoundScriptEvent.instance.run(networkManager.player.getBukkitEntity(), spacket.getSound().value().location().getPath(), spacket.getSource().name(),\n                    false, null, new Location(networkManager.player.getBukkitEntity().getWorld(), spacket.getX(), spacket.getY(), spacket.getZ()), spacket.getVolume(), spacket.getPitch()) ? null : packet;\n        }\n        else if (packet instanceof ClientboundSoundEntityPacket) {\n            ClientboundSoundEntityPacket spacket = (ClientboundSoundEntityPacket) packet;\n            Entity entity = networkManager.player.level().getEntity(spacket.getId());\n            if (entity == null) {\n                return packet;\n            }\n            return PlayerHearsSoundScriptEvent.instance.run(networkManager.player.getBukkitEntity(), spacket.getSound().value().location().getPath(), spacket.getSource().name(),\n                    false, entity.getBukkitEntity(), null, spacket.getVolume(), spacket.getPitch()) ? null : packet;\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerReceivesTablistUpdateScriptEvent;\nimport com.denizenscript.denizen.nms.v1_21.Handler;\nimport com.denizenscript.denizen.nms.v1_21.impl.ProfileEditorImpl;\nimport com.denizenscript.denizen.nms.v1_21.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.google.common.base.Joiner;\nimport com.mojang.authlib.GameProfile;\nimport com.mojang.authlib.properties.Property;\nimport net.md_5.bungee.api.ChatColor;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;\nimport net.minecraft.world.level.GameType;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.UUID;\n\npublic class TablistUpdateEventPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoUpdatePacket.class, TablistUpdateEventPacketHandlers::processTablistPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoRemovePacket.class, TablistUpdateEventPacketHandlers::processTablistPacket);\n    }\n\n    public static boolean tablistBreakOnlyOnce = false;\n\n    // TODO: properly rebundle the packet instead of splitting it up\n    public static Packet<ClientGamePacketListener> processTablistPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!PlayerReceivesTablistUpdateScriptEvent.instance.eventData.isEnabled) {\n            return packet;\n        }\n        if (packet instanceof ClientboundPlayerInfoUpdatePacket) {\n            ClientboundPlayerInfoUpdatePacket infoPacket = (ClientboundPlayerInfoUpdatePacket) packet;\n            String mode = \"\";\n            for (ClientboundPlayerInfoUpdatePacket.Action action : infoPacket.actions()) {\n                switch (action) {\n                    case ADD_PLAYER:\n                        mode = \"add\";\n                        break;\n                    case UPDATE_LATENCY:\n                        mode = mode.isEmpty() ? \"update_latency\" : mode + \"|update_latency\";\n                        break;\n                    case UPDATE_GAME_MODE:\n                        mode = mode.isEmpty() ? \"update_gamemode\" : mode + \"|update_gamemode\";\n                        break;\n                    case UPDATE_DISPLAY_NAME:\n                        mode = mode.isEmpty() ? \"update_display\" : mode + \"|update_display\";\n                        break;\n                    case UPDATE_LISTED:\n                        mode = mode.isEmpty() ? \"update_listed\" : mode + \"|update_listed\";\n                        break;\n                    case INITIALIZE_CHAT:\n                        mode = mode.isEmpty() ? \"initialize_chat\" : mode + \"|initialize_chat\";\n                    default:\n                        break;\n                }\n            }\n            if (mode.isEmpty()) {\n                if (!tablistBreakOnlyOnce) {\n                    tablistBreakOnlyOnce = true;\n                    Debug.echoError(\"Tablist packet processing failed: unknown action \" + Joiner.on(\", \").join(infoPacket.actions()));\n                }\n                return packet;\n            }\n            boolean isOverriding = false;\n            for (ClientboundPlayerInfoUpdatePacket.Entry update : infoPacket.entries()) {\n                GameProfile profile = update.profile();\n                String texture = null, signature = null;\n                if (profile.properties().containsKey(\"textures\")) {\n                    Property property = profile.properties().get(\"textures\").stream().findFirst().get();\n                    texture = property.value();\n                    signature = property.signature();\n                }\n                String modeText = update.gameMode() == null ? null : update.gameMode().name();\n                PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(mode, profile.id(), update.listed(), profile.name(),\n                        update.displayName() == null ? null : FormattedTextHelper.stringify(Handler.componentToSpigot(update.displayName())), modeText, texture, signature, update.latency());\n                PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data);\n                if (data.modified) {\n                    if (!isOverriding) {\n                        isOverriding = true;\n                        for (ClientboundPlayerInfoUpdatePacket.Entry priorUpdate : infoPacket.entries()) {\n                            if (priorUpdate == update) {\n                                break;\n                            }\n                            networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(priorUpdate)));\n                        }\n                    }\n                    if (!data.cancelled) {\n                        GameProfile newProfile = ProfileEditorImpl.createGameProfile(data.id, data.name, data.texture, data.signature);\n                        ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(newProfile.id(), newProfile, data.isListed, data.latency, data.gamemode == null ? null : GameType.byName(CoreUtilities.toLowerCase(data.gamemode)),\n                                data.display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(data.display, ChatColor.WHITE)), update.showHat(), update.listOrder(), update.chatSession());\n                        networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(entry)));\n                    }\n                }\n                else if (isOverriding) {\n                    networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(update)));\n                }\n            }\n            return isOverriding ? null : packet;\n        }\n        else if (packet instanceof ClientboundPlayerInfoRemovePacket) {\n            ClientboundPlayerInfoRemovePacket removePacket = (ClientboundPlayerInfoRemovePacket) packet;\n            boolean modified = false;\n            List<UUID> altIds = new ArrayList<>(((ClientboundPlayerInfoRemovePacket) packet).profileIds());\n            for (UUID id : ((ClientboundPlayerInfoRemovePacket) packet).profileIds()) {\n                PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(\"remove\", id, false, null, null, null, null, null, 0);\n                PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data);\n                if (data.modified && data.cancelled) {\n                    modified = true;\n                    altIds.remove(id);\n                }\n            }\n            if (modified) {\n                return new ClientboundPlayerInfoRemovePacket(altIds);\n            }\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/packets/PacketInResourcePackStatusImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInResourcePackStatus;\r\nimport net.minecraft.network.protocol.common.ServerboundResourcePackPacket;\r\n\r\npublic class PacketInResourcePackStatusImpl implements PacketInResourcePackStatus {\r\n\r\n    private ServerboundResourcePackPacket internal;\r\n\r\n    public PacketInResourcePackStatusImpl(ServerboundResourcePackPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public String getStatus() {\r\n        return internal.action().name();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/packets/PacketInSteerVehicleImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInSteerVehicle;\r\nimport net.minecraft.network.protocol.game.ServerboundPlayerInputPacket;\r\n\r\npublic class PacketInSteerVehicleImpl implements PacketInSteerVehicle {\r\n\r\n    private ServerboundPlayerInputPacket internal;\r\n\r\n    public PacketInSteerVehicleImpl(ServerboundPlayerInputPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public float getLeftwardInput() {\r\n        return internal.input().left() ? 1 : 0;\r\n    }\r\n\r\n    @Override\r\n    public float getForwardInput() {\r\n        return internal.input().forward() ? 1 : 0;\r\n    }\r\n\r\n    @Override\r\n    public boolean getJumpInput() {\r\n        return internal.input().jump();\r\n    }\r\n\r\n    @Override\r\n    public boolean getDismountInput() {\r\n        return internal.input().shift();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v1_21/src/main/java/com/denizenscript/denizen/nms/v1_21/impl/network/packets/PacketOutChatImpl.java",
    "content": "package com.denizenscript.denizen.nms.v1_21.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSystemChatPacket;\r\nimport org.bukkit.craftbukkit.v1_21_R7.util.CraftChatMessage;\r\n\r\npublic class PacketOutChatImpl extends PacketOutChat {\r\n\r\n    public ClientboundPlayerChatPacket playerPacket;\r\n    public ClientboundSystemChatPacket systemPacket;\r\n    public String message;\r\n    public String rawJson;\r\n    public boolean isOverlayActionbar;\r\n\r\n    public PacketOutChatImpl(ClientboundSystemChatPacket internal) {\r\n        systemPacket = internal;\r\n        rawJson = CraftChatMessage.toJSON(internal.content());\r\n        message = FormattedTextHelper.stringify(FormattedTextHelper.parseJson(rawJson));\r\n        isOverlayActionbar = internal.overlay();\r\n    }\r\n\r\n    public PacketOutChatImpl(ClientboundPlayerChatPacket internal) {\r\n        playerPacket = internal;\r\n        rawJson = ComponentSerializer.toString(internal.body().content());\r\n        message = FormattedTextHelper.stringify(FormattedTextHelper.parseJson(rawJson));\r\n    }\r\n\r\n    @Override\r\n    public boolean isSystem() {\r\n        return systemPacket != null;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActionbar() {\r\n        return isOverlayActionbar;\r\n    }\r\n\r\n    @Override\r\n    public String getMessage() {\r\n        return message;\r\n    }\r\n\r\n    @Override\r\n    public String getRawJson() {\r\n        return rawJson;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.denizenscript</groupId>\n    <artifactId>denizen-v26_1</artifactId>\n    <version>1.3.2-SNAPSHOT</version>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.denizenscript</groupId>\n            <artifactId>denizen</artifactId>\n            <version>${project.version}</version>\n            <scope>compile</scope>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>26.1.2-R0.1-SNAPSHOT</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot</artifactId>\n            <version>26.1.2-R0.1-SNAPSHOT</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.10.1</version>\n                <configuration>\n                    <source>16</source>\n                    <target>16</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/Handler.java",
    "content": "package com.denizenscript.denizen.nms.v26_1;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v26_1.helpers.*;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.blocks.BlockLightImpl;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.objects.core.QuaternionTag;\r\nimport com.denizenscript.denizencore.scripts.commands.core.ReflectionSetCommand;\r\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.debugging.DebugInternals;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.mojang.serialization.DynamicOps;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.md_5.bungee.api.chat.BaseComponent;\r\nimport net.minecraft.SharedConstants;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.Rotations;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.nbt.ByteArrayTag;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.nbt.NbtOps;\r\nimport net.minecraft.nbt.StringTag;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.resources.Identifier;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.ProblemReporter;\r\nimport net.minecraft.world.BossEvent;\r\nimport net.minecraft.world.Container;\r\nimport net.minecraft.world.Nameable;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.storage.TagValueInput;\r\nimport net.minecraft.world.level.storage.TagValueOutput;\r\nimport net.minecraft.world.level.storage.ValueInput;\r\nimport net.minecraft.world.level.storage.ValueOutput;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.World;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.craftbukkit.CraftRegistry;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.boss.CraftBossBar;\r\nimport org.bukkit.craftbukkit.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.inventory.CraftInventoryCustom;\r\nimport org.bukkit.craftbukkit.inventory.CraftInventoryView;\r\nimport org.bukkit.craftbukkit.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.legacy.FieldRename;\r\nimport org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;\r\nimport org.bukkit.craftbukkit.util.ApiVersion;\r\nimport org.bukkit.craftbukkit.util.CraftChatMessage;\r\nimport org.bukkit.craftbukkit.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryView;\r\nimport org.bukkit.persistence.PersistentDataContainer;\r\nimport org.joml.Quaternionf;\r\nimport org.joml.Vector3f;\r\nimport org.spigotmc.AsyncCatcher;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\nimport java.util.function.Consumer;\r\nimport java.util.function.Function;\r\n\r\npublic class Handler extends NMSHandler {\r\n\r\n    public static BlockPos toBlockPos(Location location) { // TODO: Paper renamed 'CraftLocation#toBlockPosition', switch back once on Paper NMS\r\n        return new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n    }\r\n\r\n    // TODO: Paper renamed some NMS methods, switch back to direct calls once on Paper NMS\r\n    public static MethodHandle reflectPaperRenamed(Class<?> clazz, String spigot, String paper, Class<?>... params) {\r\n        return ReflectionHelper.getMethodHandle(clazz, Denizen.supportsPaper ? paper : spigot, params);\r\n    }\r\n\r\n    public Handler() {\r\n        advancementHelper = new AdvancementHelperImpl();\r\n        animationHelper = new AnimationHelperImpl();\r\n        blockHelper = new BlockHelperImpl();\r\n        chunkHelper = new ChunkHelperImpl();\r\n        customEntityHelper = new CustomEntityHelperImpl();\r\n        entityHelper = new EntityHelperImpl();\r\n        fishingHelper = new FishingHelperImpl();\r\n        itemHelper = new ItemHelperImpl();\r\n        packetHelper = new PacketHelperImpl();\r\n        playerHelper = new PlayerHelperImpl();\r\n        worldHelper = new WorldHelperImpl();\r\n        enchantmentHelper = new EnchantmentHelperImpl();\r\n\r\n        registerConversion(ItemTag.class, ItemStack.class, item -> CraftItemStack.asNMSCopy(item.getItemStack()));\r\n        registerConversion(ElementTag.class, Component.class, element -> componentToNMS(FormattedTextHelper.parse(element.asString(), ChatColor.WHITE)));\r\n        registerConversion(MaterialTag.class, BlockState.class, material -> ((CraftBlockData) material.getModernData()).getState());\r\n        registerConversion(LocationTag.class, Rotations.class, location -> new Rotations((float) location.getX(), (float) location.getY(), (float) location.getZ()));\r\n        registerConversion(LocationTag.class, BlockPos.class, Handler::toBlockPos);\r\n        registerConversion(MapTag.class, CompoundTag.class, map -> {\r\n            CompoundBinaryTag compoundTag = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(map, CoreUtilities.noDebugContext, \"(item).\");\r\n            return compoundTag != null ? NBTAdapter.toNMS(compoundTag) : null;\r\n        });\r\n        registerConversion(LocationTag.class, Vector3f.class, location -> new Vector3f((float) location.getX(), (float) location.getY(), (float) location.getZ()));\r\n        registerConversion(QuaternionTag.class, Quaternionf.class, quaternion -> new Quaternionf(quaternion.x, quaternion.y, quaternion.z, quaternion.w));\r\n    }\r\n\r\n    public static <DT extends ObjectTag, JT> void registerConversion(Class<DT> denizenType, Class<JT> javaType, Function<DT, JT> convertor) {\r\n        ReflectionSetCommand.typeConverters.put(javaType, objectTag -> {\r\n            DT denizenObject = objectTag.asType(denizenType, CoreUtilities.noDebugContext);\r\n            return denizenObject != null ? convertor.apply(denizenObject) : null;\r\n        });\r\n    }\r\n\r\n    private final ProfileEditor profileEditor = new ProfileEditorImpl();\r\n\r\n    private boolean wasAsyncCatcherEnabled;\r\n\r\n    @Override\r\n    public void disableAsyncCatcher() {\r\n        wasAsyncCatcherEnabled = AsyncCatcher.enabled;\r\n        AsyncCatcher.enabled = false;\r\n    }\r\n\r\n    @Override\r\n    public void undisableAsyncCatcher() {\r\n        AsyncCatcher.enabled = wasAsyncCatcherEnabled;\r\n    }\r\n\r\n    @Override\r\n    public boolean isExactServerVersionMatch() {\r\n        return Denizen.supportsPaper ? SharedConstants.getCurrentVersion().id().equals(\"26.1.2\") : CraftMagicNumbers.INSTANCE.getMappingsVersion().equals(\"e8ece90188c951d866bd2fffc52c803e\");\r\n    }\r\n\r\n    @Override\r\n    public double[] getRecentTps() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().recentTps;\r\n    }\r\n\r\n    @Override\r\n    public Sidebar createSidebar(Player player) {\r\n        return new SidebarImpl(player);\r\n    }\r\n\r\n    @Override\r\n    public BlockLight createBlockLight(Location location, int lightLevel, long ticks) {\r\n        return BlockLightImpl.createLight(location, lightLevel, ticks);\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile fillPlayerProfile(PlayerProfile playerProfile) {\r\n        if (playerProfile == null) {\r\n            return null;\r\n        }\r\n        if (playerProfile.getName() == null && playerProfile.getUniqueId() == null) {\r\n            return playerProfile; // Cannot fill without lookup data\r\n        }\r\n        if (playerProfile.hasTexture() && playerProfile.hasTextureSignature() && playerProfile.getName() != null && playerProfile.getUniqueId() != null) {\r\n            return playerProfile; // Already filled\r\n        }\r\n        try {\r\n            GameProfile profile = null;\r\n            MinecraftServer minecraftServer = ((CraftServer) Bukkit.getServer()).getServer();\r\n            if (playerProfile.getUniqueId() != null) {\r\n                profile = minecraftServer.services().nameToIdCache().get(playerProfile.getUniqueId()).map(result -> new GameProfile(result.id(), result.name())).orElse(null);\r\n            }\r\n            if (profile == null && playerProfile.getName() != null) {\r\n                profile = minecraftServer.services().nameToIdCache().get(playerProfile.getName()).map(result -> new GameProfile(result.id(), result.name())).orElse(null);\r\n            }\r\n            if (profile == null) {\r\n                profile = ProfileEditorImpl.getGameProfileNoProperties(playerProfile);\r\n            }\r\n            Property textures = profile.properties().containsKey(\"textures\") ? Iterables.getFirst(profile.properties().get(\"textures\"), null) : null;\r\n            if (textures == null || !textures.hasSignature() || profile.name() == null || profile.id() == null) {\r\n                profile = minecraftServer.services().profileResolver().fetchById(profile.id()).orElse(null);\r\n                if (profile == null) {\r\n                    return null;\r\n                }\r\n                textures = profile.properties().containsKey(\"textures\") ? Iterables.getFirst(profile.properties().get(\"textures\"), null) : null;\r\n            }\r\n            return new PlayerProfile(profile.name(), profile.id(), textures == null ? null : textures.value(), textures == null ? null : textures.signature());\r\n        }\r\n        catch (Exception e) {\r\n            if (CoreConfiguration.debugVerbose) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static MethodHandle PAPER_INVENTORY_TITLE_GETTER;\r\n\r\n    @Override\r\n    public String getTitle(Inventory inventory) {\r\n        Container nms = ((CraftInventory) inventory).getInventory();\r\n        if (inventory instanceof CraftInventoryCustom && Denizen.supportsPaper) {\r\n            try {\r\n                if (PAPER_INVENTORY_TITLE_GETTER == null) {\r\n                    PAPER_INVENTORY_TITLE_GETTER = ReflectionHelper.getMethodHandle(nms.getClass(), \"title\");\r\n                }\r\n                return PaperAPITools.instance.parseComponent(PAPER_INVENTORY_TITLE_GETTER.invoke(nms));\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        if (nms instanceof Nameable) {\r\n            return CraftChatMessage.fromComponent(((Nameable) nms).getDisplayName());\r\n        }\r\n        else if (MINECRAFT_INVENTORY.isInstance(nms)) {\r\n            try {\r\n                return (String) INVENTORY_TITLE.get(nms);\r\n            }\r\n            catch (IllegalAccessException e) {\r\n                Debug.echoError(e);\r\n            }\r\n        }\r\n        return \"Chest\";\r\n    }\r\n\r\n    public static MethodHandle AbstractContainerMenu_title_SETTER = ReflectionHelper.getFinalSetter(AbstractContainerMenu.class, \"title\");\r\n\r\n    @Override\r\n    public void setInventoryTitle(InventoryView view, String title) {\r\n        AbstractContainerMenu menu = ((CraftInventoryView) view).getHandle();\r\n        try {\r\n            AbstractContainerMenu_title_SETTER.invoke(menu, componentToNMS(FormattedTextHelper.parse(title, ChatColor.DARK_GRAY)));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final Class MINECRAFT_INVENTORY;\r\n    public static final Field INVENTORY_TITLE;\r\n    public static final Field ENTITY_BUKKITYENTITY = ReflectionHelper.getFields(Entity.class).get(\"bukkitEntity\");\r\n\r\n    static {\r\n        Class minecraftInv = null;\r\n        Field title = null;\r\n        try {\r\n            for (Class clzz : CraftInventoryCustom.class.getDeclaredClasses()) {\r\n                if (CoreUtilities.toLowerCase(clzz.getName()).contains(\"minecraftinventory\")) { // MinecraftInventory.\r\n                    minecraftInv = clzz;\r\n                    title = clzz.getDeclaredField(\"title\");\r\n                    title.setAccessible(true);\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            e.printStackTrace();\r\n        }\r\n        MINECRAFT_INVENTORY = minecraftInv;\r\n        INVENTORY_TITLE = title;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Player player) {\r\n        GameProfile gameProfile = ((CraftPlayer) player).getProfile();\r\n        Property property = Iterables.getFirst(gameProfile.properties().get(\"textures\"), null);\r\n        return new PlayerProfile(gameProfile.name(), gameProfile.id(),\r\n                property != null ? property.value() : null,\r\n                property != null ? property.signature() : null);\r\n    }\r\n\r\n    @Override\r\n    public ProfileEditor getProfileEditor() {\r\n        return profileEditor;\r\n    }\r\n\r\n    @Override\r\n    public List<BiomeNMS> getBiomes(World world) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        ArrayList<BiomeNMS> output = new ArrayList<>();\r\n        for (Identifier key : level.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) {\r\n            output.add(new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key)));\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeNMS(World world, NamespacedKey key) {\r\n        BiomeNMSImpl impl = new BiomeNMSImpl(((CraftWorld) world).getHandle(), key);\r\n        if (impl.biomeHolder == null) {\r\n            return null;\r\n        }\r\n        return impl;\r\n    }\r\n\r\n    @Override\r\n    public BiomeNMS getBiomeAt(Block block) {\r\n        // Based on CraftWorld source\r\n        ServerLevel level = ((CraftWorld) block.getWorld()).getHandle();\r\n        Holder<Biome> biome = level.getNoiseBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2);\r\n        Identifier key = level.registryAccess().lookupOrThrow(Registries.BIOME).getKey(biome.value());\r\n        return new BiomeNMSImpl(level, CraftNamespacedKey.fromMinecraft(key));\r\n    }\r\n\r\n    @Override\r\n    public ArrayList<String> containerListFlags(PersistentDataContainer container, String prefix) {\r\n        prefix = \"denizen:\" + prefix;\r\n        ArrayList<String> output = new ArrayList<>();\r\n        for (String key : ((CraftPersistentDataContainer) container).getRaw().keySet()) {\r\n            if (key.startsWith(prefix)) {\r\n                output.add(key.substring(prefix.length()));\r\n            }\r\n        }\r\n        return output;\r\n    }\r\n\r\n    @Override\r\n    public boolean containerHas(PersistentDataContainer container, String key) {\r\n        return ((CraftPersistentDataContainer) container).getRaw().containsKey(key);\r\n    }\r\n\r\n    @Override\r\n    public String containerGetString(PersistentDataContainer container, String key) {\r\n        net.minecraft.nbt.Tag base = ((CraftPersistentDataContainer) container).getRaw().get(key);\r\n        if (base instanceof StringTag) {\r\n            return base.asString().get();\r\n        }\r\n        else if (base instanceof ByteArrayTag) {\r\n            return new String(((ByteArrayTag) base).getAsByteArray(), StandardCharsets.UTF_8);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public UUID getBossbarUUID(BossBar bar) {\r\n        return ((CraftBossBar) bar).getHandle().getId();\r\n    }\r\n\r\n    public static MethodHandle BOSSBAR_ID_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BossEvent.class, UUID.class);\r\n\r\n    @Override\r\n    public void setBossbarUUID(BossBar bar, UUID id) {\r\n        try {\r\n            BOSSBAR_ID_SETTER.invoke(((CraftBossBar) bar).getHandle(), id);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static BaseComponent[] componentToSpigot(Component nms) {\r\n        if (nms == null) {\r\n            return null;\r\n        }\r\n        return FormattedTextHelper.parseJson(CraftChatMessage.toJSON(nms));\r\n    }\r\n\r\n    public static Component componentToNMS(BaseComponent[] spigot) {\r\n        if (spigot == null) {\r\n            return null;\r\n        }\r\n        return CraftChatMessage.fromJSONOrNull(FormattedTextHelper.componentToJson(spigot));\r\n    }\r\n\r\n    public static final MethodHandle TAG_VALUE_OUTPUT_CONSTRUCTOR = ReflectionHelper.getConstructor(TagValueOutput.class, ProblemReporter.class, DynamicOps.class, CompoundTag.class);\r\n\r\n    public static CompoundTag useValueOutput(Consumer<ValueOutput> handler) {\r\n        ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector();\r\n        TagValueOutput nmsValueOutput = TagValueOutput.createWithContext(nmsProblemReporter, CraftRegistry.getMinecraftRegistry());\r\n        handler.accept(nmsValueOutput);\r\n        handleProblems(nmsProblemReporter);\r\n        return nmsValueOutput.buildResult();\r\n    }\r\n\r\n    public static CompoundTag useValueOutput(CompoundTag nmsExistingValue, Consumer<ValueOutput> handler) {\r\n        ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector();\r\n        TagValueOutput nmsValueOutput;\r\n        try {\r\n            nmsValueOutput = (TagValueOutput) TAG_VALUE_OUTPUT_CONSTRUCTOR.invoke(nmsProblemReporter, CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsExistingValue);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n            return nmsExistingValue;\r\n        }\r\n        handler.accept(nmsValueOutput);\r\n        handleProblems(nmsProblemReporter);\r\n        return nmsValueOutput.buildResult();\r\n    }\r\n\r\n    public static void useValueInput(CompoundTag nmsTag, Consumer<ValueInput> handler) {\r\n        ProblemReporter.Collector nmsProblemReporter = new ProblemReporter.Collector();\r\n        ValueInput nmsValueInput = TagValueInput.create(nmsProblemReporter, CraftRegistry.getMinecraftRegistry(), nmsTag);\r\n        handler.accept(nmsValueInput);\r\n        handleProblems(nmsProblemReporter);\r\n    }\r\n\r\n    private static void handleProblems(ProblemReporter.Collector nmsProblemReporter) {\r\n        if (!nmsProblemReporter.isEmpty()) {\r\n            Debug.echoError(nmsProblemReporter.getTreeReport());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String updateLegacyName(Class<?> type, String legacyName) {\r\n        return FieldRename.rename(ApiVersion.FIELD_NAME_PARITY, DebugInternals.getFullClassNameOpti(type).replace('.', '/'), legacyName);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/AdvancementHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.AdvancementHelper;\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.google.common.collect.ImmutableMap;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.advancements.*;\r\nimport net.minecraft.advancements.criterion.ImpossibleTrigger;\r\nimport net.minecraft.core.ClientAsset;\r\nimport net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket;\r\nimport net.minecraft.resources.Identifier;\r\nimport net.minecraft.server.PlayerAdvancements;\r\nimport net.minecraft.server.ServerAdvancementManager;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.util.*;\r\n\r\npublic class AdvancementHelperImpl extends AdvancementHelper {\r\n\r\n    private static final String IMPOSSIBLE_KEY = \"impossible\";\r\n    private static final Map<String, Criterion<?>> IMPOSSIBLE_CRITERIA = Map.of(IMPOSSIBLE_KEY, new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance()));\r\n    private static final List<List<String>> IMPOSSIBLE_REQUIREMENTS = List.of(List.of(IMPOSSIBLE_KEY));\r\n\r\n    public static ServerAdvancementManager getNMSAdvancementManager() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getAdvancements();\r\n    }\r\n\r\n    @Override\r\n    public void register(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || advancement.registered) {\r\n            return;\r\n        }\r\n        AdvancementHolder nmsAdvancementHolder = asNMSCopy(advancement);\r\n        Map<Identifier, AdvancementHolder> nmsAdvancements = getNMSAdvancementManager().advancements;\r\n        ImmutableMap.Builder<Identifier, AdvancementHolder> mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() + 1);\r\n        mapBuilder.putAll(nmsAdvancements);\r\n        mapBuilder.put(nmsAdvancementHolder.id(), nmsAdvancementHolder);\r\n        getNMSAdvancementManager().advancements = mapBuilder.build();\r\n\r\n        AdvancementTree tree = getNMSAdvancementManager().tree();\r\n        tree.addAll(List.of(nmsAdvancementHolder));\r\n        // recalculate advancement tree from this advancement's root\r\n        AdvancementNode node = tree.get(nmsAdvancementHolder.id());\r\n        if (node != null) {\r\n            AdvancementNode root = node.root();\r\n            if (root.holder().value().display().isPresent()) {\r\n                TreeNodePosition.run(root);\r\n            }\r\n        }\r\n        advancement.registered = true;\r\n        if (!advancement.hidden && advancement.parent != null) {\r\n            PacketHelperImpl.broadcast(new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancementHolder), Set.of(), Map.of(), false));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void unregister(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        if (advancement.temporary || !advancement.registered) {\r\n            return;\r\n        }\r\n        Identifier nmsKey = CraftNamespacedKey.toMinecraft(advancement.key);\r\n        Map<Identifier, AdvancementHolder> nmsAdvancements = getNMSAdvancementManager().advancements;\r\n        ImmutableMap.Builder<Identifier, AdvancementHolder> mapBuilder = ImmutableMap.builderWithExpectedSize(nmsAdvancements.size() - 1);\r\n        for (Map.Entry<Identifier, AdvancementHolder> entry : nmsAdvancements.entrySet()) {\r\n            if (!entry.getKey().equals(nmsKey)) {\r\n                mapBuilder.put(entry);\r\n            }\r\n        }\r\n        getNMSAdvancementManager().advancements = mapBuilder.build();\r\n        getNMSAdvancementManager().tree().remove(Set.of(nmsKey));\r\n        advancement.registered = false;\r\n        PacketHelperImpl.broadcast(new ClientboundUpdateAdvancementsPacket(false, List.of(), Set.of(nmsKey), Map.of(), false));\r\n    }\r\n\r\n    @Override\r\n    public void grantPartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) {\r\n        if (advancement.length <= 1) {\r\n            grant(advancement, player);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            AdvancementHolder nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS));\r\n            for (int i = 0; i < len; i++) {\r\n                progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria\r\n            }\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), false));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            for (int i = 0; i < len; i++) {\r\n                ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY + i);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void grant(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.length > 1) {\r\n            grantPartial(advancement, player, advancement.length);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            AdvancementHolder nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS));\r\n            progress.grantProgress(IMPOSSIBLE_KEY); // complete impossible criteria\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), true));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            ((CraftPlayer) player).getHandle().getAdvancements().award(nmsAdvancement, IMPOSSIBLE_KEY);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void revokePartial(com.denizenscript.denizen.nms.util.Advancement advancement, Player player, int len) {\r\n        if (advancement.length <= 1) {\r\n            revoke(advancement, player);\r\n            return;\r\n        }\r\n        if (advancement.temporary) {\r\n            AdvancementHolder nmsAdvancement = asNMSCopy(advancement);\r\n            AdvancementProgress progress = new AdvancementProgress();\r\n            progress.update(new AdvancementRequirements(IMPOSSIBLE_REQUIREMENTS));\r\n            for (int i = 0; i < len; i++) {\r\n                progress.grantProgress(IMPOSSIBLE_KEY + i); // complete impossible criteria\r\n            }\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(nmsAdvancement), Set.of(), Map.of(nmsAdvancement.id(), progress), false));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            PlayerAdvancements advancements = ((CraftPlayer) player).getHandle().getAdvancements();\r\n            for (int i = len; i < advancement.length; i++) {\r\n                advancements.revoke(nmsAdvancement, IMPOSSIBLE_KEY + i);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void revoke(com.denizenscript.denizen.nms.util.Advancement advancement, Player player) {\r\n        if (advancement.temporary) {\r\n            PacketHelperImpl.send(player, new ClientboundUpdateAdvancementsPacket(false, List.of(), Set.of(CraftNamespacedKey.toMinecraft(advancement.key)), Map.of(), false));\r\n        }\r\n        else {\r\n            AdvancementHolder nmsAdvancement = getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.key));\r\n            PlayerAdvancements advancements = ((CraftPlayer) player).getHandle().getAdvancements();\r\n            for (String criterion : nmsAdvancement.value().criteria().keySet()) {\r\n                advancements.revoke(nmsAdvancement, criterion);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(Player player) {\r\n        // TODO: 1.21.5: should showAdvancements be true?\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        nmsPlayer.connection.send(new ClientboundUpdateAdvancementsPacket(true, List.of(), Set.of(), Map.of(), false));\r\n        PlayerAdvancements data = nmsPlayer.getAdvancements();\r\n        data.save(); // save progress\r\n        data.reload(getNMSAdvancementManager()); // clear progress\r\n        data.flushDirty(nmsPlayer, false); // load progress and update client\r\n    }\r\n\r\n    private static AdvancementHolder asNMSCopy(com.denizenscript.denizen.nms.util.Advancement advancement) {\r\n        AdvancementHolder parent = advancement.parent != null\r\n                ? getNMSAdvancementManager().advancements.get(CraftNamespacedKey.toMinecraft(advancement.parent))\r\n                : null;\r\n        DisplayInfo display = new DisplayInfo(ItemHelperImpl.asNMSTemplate(advancement.icon),\r\n                Handler.componentToNMS(FormattedTextHelper.parse(advancement.title, ChatColor.WHITE)), Handler.componentToNMS(FormattedTextHelper.parse(advancement.description, ChatColor.WHITE)),\r\n                Optional.ofNullable(advancement.background).map(CraftNamespacedKey::toMinecraft).map(ClientAsset.ResourceTexture::new), AdvancementType.valueOf(advancement.frame.name()),\r\n                advancement.toast, advancement.announceToChat, advancement.hidden);\r\n        display.setLocation(advancement.xOffset, advancement.yOffset);\r\n        Map<String, Criterion<?>> criteria = IMPOSSIBLE_CRITERIA;\r\n        List<List<String>> requirements = IMPOSSIBLE_REQUIREMENTS;\r\n        if (advancement.length > 1) {\r\n            criteria = new HashMap<>();\r\n            requirements = new ArrayList<>(advancement.length);\r\n            for (int i = 0; i < advancement.length; i++) {\r\n                criteria.put(IMPOSSIBLE_KEY + i, new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance()));\r\n                requirements.add(List.of(IMPOSSIBLE_KEY + i));\r\n            }\r\n        }\r\n        AdvancementRequirements reqs = new AdvancementRequirements(requirements);\r\n        Advancement adv = new Advancement(parent == null ? Optional.empty() : Optional.of(parent.id()), Optional.of(display), AdvancementRewards.EMPTY, criteria, reqs, false); // TODO: 1.20: do we want to ever enable telemetry?\r\n        return new AdvancementHolder(CraftNamespacedKey.toMinecraft(advancement.key), adv);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/AnimationHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.AnimationHelper;\r\nimport net.minecraft.world.entity.Entity;\r\nimport org.bukkit.craftbukkit.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.entity.CraftHorse;\r\nimport org.bukkit.craftbukkit.entity.CraftPolarBear;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Horse;\r\nimport org.bukkit.entity.IronGolem;\r\n\r\npublic class AnimationHelperImpl extends AnimationHelper {\r\n\r\n    public AnimationHelperImpl() {\r\n        register(\"POLAR_BEAR_START_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(true);\r\n            }\r\n        });\r\n        register(\"POLAR_BEAR_STOP_STANDING\", entity -> {\r\n            if (entity.getType() == EntityType.POLAR_BEAR) {\r\n                ((CraftPolarBear) entity).getHandle().setStanding(false);\r\n            }\r\n        });\r\n        // TODO: 1.21.6: this is a tick duration now, should become a mechanism\r\n        register(\"HORSE_START_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().setStanding(Integer.MAX_VALUE);\r\n            }\r\n        });\r\n        register(\"HORSE_STOP_STANDING\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().clearStanding();\r\n            }\r\n        });\r\n        register(\"HORSE_BUCK\", entity -> {\r\n            if (entity instanceof Horse) {\r\n                ((CraftHorse) entity).getHandle().makeMad();\r\n            }\r\n        });\r\n        register(\"IRON_GOLEM_ATTACK\", entity -> {\r\n            if (entity instanceof IronGolem) {\r\n                Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n                nmsEntity.level().broadcastEntityEvent(nmsEntity, (byte) 4);\r\n            }\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/BlockHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.BlockHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.Iterables;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.InclusiveRange;\r\nimport net.minecraft.util.random.WeightedList;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.item.component.ResolvableProfile;\r\nimport net.minecraft.world.level.BaseSpawner;\r\nimport net.minecraft.world.level.LevelAccessor;\r\nimport net.minecraft.world.level.SpawnData;\r\nimport net.minecraft.world.level.block.entity.BlockEntity;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockBehaviour;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.status.ChunkStatus;\r\nimport net.minecraft.world.level.material.FluidState;\r\nimport net.minecraft.world.level.material.PushReaction;\r\nimport org.bukkit.Instrument;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.Skull;\r\nimport org.bukkit.craftbukkit.CraftChunk;\r\nimport org.bukkit.craftbukkit.CraftRegistry;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.block.CraftBlock;\r\nimport org.bukkit.craftbukkit.block.CraftBlockEntityState;\r\nimport org.bukkit.craftbukkit.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.block.CraftSkull;\r\nimport org.bukkit.craftbukkit.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.util.CraftMagicNumbers;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.Optional;\r\n\r\npublic class BlockHelperImpl implements BlockHelper {\r\n\r\n    public static final Field craftBlockEntityState_tileEntity;\r\n    public static final Field craftBlockEntityState_snapshot = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"snapshot\");\r\n    public static final Field craftSkull_profile = ReflectionHelper.getFields(CraftSkull.class).get(\"profile\");\r\n\r\n    static {\r\n        Field blockEntityField = ReflectionHelper.getFields(CraftBlockEntityState.class).getNoCheck(\"blockEntity\");\r\n        if (blockEntityField == null) {\r\n            blockEntityField = ReflectionHelper.getFields(CraftBlockEntityState.class).get(\"tileEntity\");\r\n        }\r\n        craftBlockEntityState_tileEntity = blockEntityField;\r\n    }\r\n\r\n    public static final MethodHandle CRAFT_BLOCK_GET_STATE = Handler.reflectPaperRenamed(CraftBlock.class, \"getNMS\", \"getBlockState\");\r\n\r\n    public static BlockState getNMSState(Block block) {\r\n        try {\r\n            return (BlockState) CRAFT_BLOCK_GET_STATE.invokeExact((CraftBlock) block);\r\n        }\r\n        catch (Throwable e) {\r\n            throw new RuntimeException(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void applyPhysics(Location location) {\r\n        ((CraftWorld) location.getWorld()).getHandle().updateNeighborsAt(Handler.toBlockPos(location), CraftMagicNumbers.getBlock(location.getBlock().getType()));\r\n    }\r\n\r\n    public static <T extends BlockEntity> T getTE(CraftBlockEntityState<T> cbs) {\r\n        try {\r\n            return (T) craftBlockEntityState_tileEntity.get(cbs);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getPlayerProfile(Skull skull) {\r\n        ResolvableProfile profile = getTE(((CraftSkull) skull)).owner;\r\n        if (profile == null) {\r\n            return null;\r\n        }\r\n        com.mojang.authlib.properties.Property property = Iterables.getFirst(profile.partialProfile().properties().get(\"textures\"), null);\r\n        return new PlayerProfile(profile.name().orElse(null), ProfileEditorImpl.getUUID(profile), property != null ? property.value() : null);\r\n    }\r\n\r\n    @Override\r\n    public void setPlayerProfile(Skull skull, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile);\r\n        try {\r\n            craftSkull_profile.set(skull, ResolvableProfile.createResolved(gameProfile));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        skull.update();\r\n    }\r\n\r\n    public static final MethodHandle CRAFT_BLOCK_GET_LEVEL = Handler.reflectPaperRenamed(CraftBlock.class, \"getHandle\", \"getLevel\");\r\n\r\n    public BlockEntity getBlockEntity(Block block) {\r\n        CraftBlock craftBlock = ((CraftBlock) block);\r\n        try {\r\n            return ((LevelAccessor) CRAFT_BLOCK_GET_LEVEL.invokeExact(craftBlock)).getBlockEntity(craftBlock.getPosition());\r\n        }\r\n        catch (Throwable e) {\r\n            throw new RuntimeException(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Block block) {\r\n        BlockEntity nmsBlockEntity = getBlockEntity(block);\r\n        if (nmsBlockEntity != null) {\r\n            CompoundTag compound = nmsBlockEntity.saveWithFullMetadata(CraftRegistry.getMinecraftRegistry());\r\n            return NBTAdapter.toAPI(compound);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Block block, CompoundBinaryTag ctag) {\r\n        CompoundTag nmsData = NBTAdapter.toNMS(ctag);\r\n        nmsData.putInt(\"x\", block.getX());\r\n        nmsData.putInt(\"y\", block.getY());\r\n        nmsData.putInt(\"z\", block.getZ());\r\n        Handler.useValueInput(nmsData, getBlockEntity(block)::loadWithComponents);\r\n    }\r\n\r\n    @Override\r\n    public boolean setBlockResistance(Material material, float resistance) {\r\n        net.minecraft.world.level.block.Block block = CraftMagicNumbers.getBlock(material);\r\n        if (block == null) {\r\n            return false;\r\n        }\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, \"explosionResistance\", block, resistance);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public float getBlockResistance(Material material) {\r\n        net.minecraft.world.level.block.Block block = CraftMagicNumbers.getBlock(material);\r\n        if (block == null) {\r\n            return 0;\r\n        }\r\n        return ReflectionHelper.getFieldValue(net.minecraft.world.level.block.state.BlockBehaviour.class, \"explosionResistance\", block);\r\n    }\r\n\r\n    public static final MethodHandle MATERIAL_PUSH_REACTION_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(BlockBehaviour.BlockStateBase.class, PushReaction.class);\r\n\r\n    public static final MethodHandle BLOCK_STRENGTH_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase.class, float.class); // destroySpeed\r\n\r\n    public net.minecraft.world.level.block.state.BlockState getMaterialBlockState(Material bukkitMaterial) {\r\n        net.minecraft.world.level.block.Block nmsBlock = CraftMagicNumbers.getBlock(bukkitMaterial);\r\n        return nmsBlock != null ? nmsBlock.defaultBlockState() : null;\r\n    }\r\n\r\n    @Override\r\n    public void setPushReaction(Material mat, PistonPushReaction reaction) {\r\n        try {\r\n            MATERIAL_PUSH_REACTION_SETTER.invoke(getMaterialBlockState(mat), PushReaction.values()[reaction.ordinal()]);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBlockStrength(Material mat) {\r\n        return getMaterialBlockState(mat).destroySpeed;\r\n    }\r\n\r\n    @Override\r\n    public void setBlockStrength(Material mat, float strength) {\r\n        try {\r\n            BLOCK_STRENGTH_SETTER.invoke(getMaterialBlockState(mat), strength);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void doRandomTick(Location location) {\r\n        BlockPos pos = Handler.toBlockPos(location);\r\n        ChunkAccess nmsChunk = ((CraftChunk) location.getChunk()).getHandle(ChunkStatus.FULL);\r\n        net.minecraft.world.level.block.state.BlockState nmsBlock = nmsChunk.getBlockState(pos);\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        if (nmsBlock.isRandomlyTicking()) {\r\n            nmsBlock.randomTick(nmsWorld, pos, nmsWorld.getRandom());\r\n        }\r\n        FluidState fluid = nmsBlock.getFluidState();\r\n        if (fluid.isRandomlyTicking()) {\r\n            fluid.animateTick(nmsWorld, pos, nmsWorld.getRandom());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Instrument getInstrumentFor(Material mat) {\r\n        return Instrument.values()[getMaterialBlockState(mat).instrument().ordinal()];\r\n    }\r\n\r\n    @Override\r\n    public int getExpDrop(Block block, org.bukkit.inventory.ItemStack item) {\r\n        net.minecraft.world.level.block.Block blockType = CraftMagicNumbers.getBlock(block.getType());\r\n        if (blockType == null) {\r\n            return 0;\r\n        }\r\n        return blockType.getExpDrop(getNMSState(block), ((CraftBlock) block).getCraftWorld().getHandle(), ((CraftBlock) block).getPosition(),\r\n                item == null ? null : CraftItemStack.asNMSCopy(item), true);\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnerSpawnedType(CreatureSpawner spawner, EntityTag entity) {\r\n        spawner.setSpawnedType(entity.getBukkitEntityType());\r\n        if (entity.getWaitingMechanisms() == null || entity.getWaitingMechanisms().size() == 0) {\r\n            return;\r\n        }\r\n        try {\r\n            // Wrangle a fake entity\r\n            // TODO: 1.21.6: seems to have a bug where the \"Pos\" value being set prevents it from spawning?\r\n            org.bukkit.entity.Entity bukkitEntity = ((CraftWorld) spawner.getWorld()).createEntity(spawner.getLocation(), entity.getBukkitEntityType().getEntityClass());\r\n            Entity nmsEntity = ((CraftEntity) bukkitEntity).getHandle();\r\n            EntityTag entityTag = new EntityTag(bukkitEntity);\r\n            entityTag.isFake = true;\r\n            entityTag.isFakeValid = true;\r\n            for (Mechanism mechanism : entity.getWaitingMechanisms()) {\r\n                entityTag.safeAdjustDuplicate(mechanism);\r\n            }\r\n            nmsEntity.unsetRemoved();\r\n            // Store it into the spawner\r\n            SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(spawner);\r\n            Handler.useValueOutput(nmsSnapshot.getSpawner().nextSpawnData.getEntityToSpawn(), nmsEntity::saveWithoutId);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnerCustomRules(CreatureSpawner spawner, int skyMin, int skyMax, int blockMin, int blockMax) {\r\n        try {\r\n            CraftCreatureSpawner bukkitSpawner = (CraftCreatureSpawner) spawner;\r\n            SpawnerBlockEntity nmsSnapshot = (SpawnerBlockEntity) craftBlockEntityState_snapshot.get(bukkitSpawner);\r\n            BaseSpawner nmsSpawner = nmsSnapshot.getSpawner();\r\n            SpawnData toSpawn = nmsSpawner.nextSpawnData;\r\n            SpawnData.CustomSpawnRules rules = skyMin == -1 ? null : new SpawnData.CustomSpawnRules(new InclusiveRange<>(skyMin, skyMax), new InclusiveRange<>(blockMin, blockMax));\r\n            nmsSpawner.nextSpawnData = new SpawnData(toSpawn.entityToSpawn(), Optional.ofNullable(rules), toSpawn.equipment());\r\n            nmsSpawner.spawnPotentials = WeightedList.of();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/ChunkHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.interfaces.ChunkHelper;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizencore.tags.TagManager;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.QuartPos;\r\nimport net.minecraft.server.level.ServerChunkCache;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.LevelHeightAccessor;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.LevelChunkSection;\r\nimport net.minecraft.world.level.chunk.PalettedContainer;\r\nimport net.minecraft.world.level.chunk.status.ChunkStatus;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport org.bukkit.Chunk;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.CraftChunk;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\n\r\npublic class ChunkHelperImpl implements ChunkHelper {\r\n\r\n    public final static Field chunkProviderServerThreadField;\r\n    public final static MethodHandle chunkProviderServerThreadFieldSetter;\r\n    public final static Field worldThreadField;\r\n    public final static MethodHandle worldThreadFieldSetter;\r\n\r\n    static {\r\n        chunkProviderServerThreadField = ReflectionHelper.getFields(ServerChunkCache.class).getFirstOfType(Thread.class);\r\n        chunkProviderServerThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(ServerChunkCache.class, Thread.class);\r\n        worldThreadField = ReflectionHelper.getFields(net.minecraft.world.level.Level.class).getFirstOfType(Thread.class);\r\n        worldThreadFieldSetter = ReflectionHelper.getFinalSetterForFirstOfType(net.minecraft.world.level.Level.class, Thread.class);\r\n    }\r\n\r\n    public Thread resetServerThread;\r\n\r\n    @Override\r\n    public void changeChunkServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread != null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkSource();\r\n        try {\r\n            resetServerThread = (Thread) chunkProviderServerThreadField.get(provider);\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, Thread.currentThread());\r\n            worldThreadFieldSetter.invoke(nmsWorld, Thread.currentThread());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void restoreServerThread(World world) {\r\n        if (TagManager.tagThread == null) {\r\n            return;\r\n        }\r\n        if (resetServerThread == null) {\r\n            return;\r\n        }\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        ServerChunkCache provider = nmsWorld.getChunkSource();\r\n        try {\r\n            chunkProviderServerThreadFieldSetter.invoke(provider, resetServerThread);\r\n            worldThreadFieldSetter.invoke(nmsWorld, resetServerThread);\r\n            resetServerThread = null;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int[] getHeightMap(Chunk chunk) {\r\n        Heightmap map = ((CraftChunk) chunk).getHandle(ChunkStatus.FEATURES).heightmaps.get(Heightmap.Types.MOTION_BLOCKING);\r\n        int[] outputMap = new int[256];\r\n        for (int x = 0; x < 16; x++) {\r\n            for (int y = 0; y < 16; y++) {\r\n                outputMap[x * 16 + y] = map.getFirstAvailable(x, y);\r\n            }\r\n        }\r\n        return outputMap;\r\n    }\r\n\r\n    @Override\r\n    public void setAllBiomes(Chunk chunk, BiomeNMS biome) {\r\n        Holder<Biome> nmsBiome = ((BiomeNMSImpl) biome).biomeHolder;\r\n        ChunkAccess nmsChunk = ((CraftChunk) chunk).getHandle(ChunkStatus.BIOMES);\r\n        ChunkPos chunkcoordintpair = nmsChunk.getPos();\r\n        int i = QuartPos.fromBlock(chunkcoordintpair.getMinBlockX());\r\n        int j = QuartPos.fromBlock(chunkcoordintpair.getMinBlockZ());\r\n        LevelHeightAccessor levelheightaccessor = nmsChunk.getHeightAccessorForGeneration();\r\n        for(int k = levelheightaccessor.getMinSectionY(); k < levelheightaccessor.getMaxSectionY(); ++k) {\r\n            LevelChunkSection chunksection = nmsChunk.getSection(nmsChunk.getSectionIndexFromSectionY(k));\r\n            PalettedContainer<Holder<Biome>> datapaletteblock = (PalettedContainer<Holder<Biome>>) chunksection.getBiomes();\r\n            datapaletteblock.acquire();\r\n            for(int l = 0; l < 4; ++l) {\r\n                for(int i1 = 0; i1 < 4; ++i1) {\r\n                    for(int j1 = 0; j1 < 4; ++j1) {\r\n                        datapaletteblock.getAndSetUnchecked(l, i1, j1, nmsBiome);\r\n                    }\r\n                }\r\n            }\r\n            datapaletteblock.release();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/CustomEntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.CustomEntityHelper;\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.entities.EntityFakeArrowImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.entities.EntityFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizen.utilities.BukkitImplDeprecations;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.minecraft.server.level.ClientInformation;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.ChatColor;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.inventory.CraftItemStack;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scoreboard.Scoreboard;\r\nimport org.bukkit.scoreboard.Team;\r\n\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.UUID;\r\n\r\npublic class CustomEntityHelperImpl implements CustomEntityHelper {\r\n\r\n    @Override\r\n    public FakeArrow spawnFakeArrow(Location location) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityFakeArrowImpl arrow = new EntityFakeArrowImpl(world, location);\r\n        return arrow.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public ItemProjectile spawnItemProjectile(Location location, ItemStack itemStack) {\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        EntityItemProjectileImpl entity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n        world.getHandle().addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return entity.getBukkitEntity();\r\n    }\r\n\r\n    public FakePlayer spawnFakePlayer(Location location, String name, String skin, String blob, boolean doAdd) throws IllegalArgumentException {\r\n        BukkitImplDeprecations.fakePlayer.warn();\r\n        String fullName = name;\r\n        String prefix = null;\r\n        String suffix = null;\r\n        if (name == null) {\r\n            Debug.echoError(\"FAKE_PLAYER: null name, cannot spawn\");\r\n            return null;\r\n        }\r\n        else if (fullName.length() > 16) {\r\n            prefix = fullName.substring(0, 16);\r\n            if (fullName.length() > 30) {\r\n                int len = 30;\r\n                name = fullName.substring(16, 30);\r\n                if (name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    if (fullName.length() >= 32) {\r\n                        len = 32;\r\n                        name = fullName.substring(16, 32);\r\n                    }\r\n                    else if (fullName.length() == 31) {\r\n                        len = 31;\r\n                        name = fullName.substring(16, 31);\r\n                    }\r\n                }\r\n                else if (name.length() > 46) {\r\n                    throw new IllegalArgumentException(\"You must specify a name with no more than 46 characters for FAKE_PLAYER entities!\");\r\n                }\r\n                else {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                suffix = fullName.substring(len);\r\n            }\r\n            else {\r\n                name = fullName.substring(16);\r\n                if (!name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n                    name = ChatColor.RESET + name;\r\n                }\r\n                if (name.length() > 16) {\r\n                    suffix = name.substring(16);\r\n                    name = name.substring(0, 16);\r\n                }\r\n            }\r\n        }\r\n        if (skin != null && skin.length() > 16) {\r\n            throw new IllegalArgumentException(\"You must specify a name with no more than 16 characters for FAKE_PLAYER entity skins!\");\r\n        }\r\n        CraftWorld world = (CraftWorld) location.getWorld();\r\n        ServerLevel worldServer = world.getHandle();\r\n        PlayerProfile playerProfile = new PlayerProfile(name, null);\r\n        if (blob != null) {\r\n            int sc = blob.indexOf(';');\r\n            if (sc != -1) {\r\n                playerProfile.setTexture(blob.substring(0, sc));\r\n                playerProfile.setTextureSignature(blob.substring(sc + 1));\r\n            }\r\n        }\r\n        else if (skin == null && !name.matches(\".*[^A-Za-z0-9_].*\")) {\r\n            playerProfile = NMSHandler.instance.fillPlayerProfile(playerProfile);\r\n        }\r\n        if (skin != null) {\r\n            PlayerProfile skinProfile = new PlayerProfile(skin, null);\r\n            skinProfile = NMSHandler.instance.fillPlayerProfile(skinProfile);\r\n            playerProfile.setTexture(skinProfile.getTexture());\r\n            playerProfile.setTextureSignature(skinProfile.getTextureSignature());\r\n        }\r\n        UUID uuid = UUID.randomUUID();\r\n        playerProfile.setUniqueId(uuid);\r\n\r\n        GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile);\r\n        final EntityFakePlayerImpl fakePlayer = new EntityFakePlayerImpl(worldServer.getServer(), worldServer, gameProfile, ClientInformation.createDefault(), doAdd);\r\n\r\n        fakePlayer.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(),\r\n                location.getYaw(), location.getPitch());\r\n        CraftFakePlayerImpl craftFakePlayer = fakePlayer.getBukkitEntity();\r\n        craftFakePlayer.fullName = fullName;\r\n        if (prefix != null) {\r\n            Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();\r\n            String teamName = \"FAKE_PLAYER_TEAM_\" + fullName;\r\n            String hash = null;\r\n            try {\r\n                hash = CoreUtilities.hash_md5(teamName.getBytes(StandardCharsets.UTF_8)).substring(0, 16);\r\n            }\r\n            catch (Exception e) {\r\n                Debug.echoError(e);\r\n            }\r\n            if (hash != null) {\r\n                Team team = scoreboard.getTeam(hash);\r\n                if (team == null) {\r\n                    team = scoreboard.registerNewTeam(hash);\r\n                    team.setPrefix(prefix);\r\n                    if (suffix != null) {\r\n                        team.setSuffix(suffix);\r\n                    }\r\n                }\r\n                team.addPlayer(craftFakePlayer);\r\n            }\r\n        }\r\n        return craftFakePlayer;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EnchantmentHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.EnchantmentHelper;\r\n\r\npublic class EnchantmentHelperImpl extends EnchantmentHelper {\r\n    // TODO: 1.21: Enchantments were entirely reworked, need to update this\r\n    /*\r\n    public static final Field REGISTRY_FROZEN = ReflectionHelper.getFields(MappedRegistry.class).get(\"frozen\", boolean.class);\r\n    public static final Field REGISTRY_INTRUSIVE_HOLDERS = ReflectionHelper.getFields(MappedRegistry.class).get(\"unregisteredIntrusiveHolders\", Map.class);\r\n\r\n    @Override\r\n    public org.bukkit.enchantments.Enchantment registerFakeEnchantment(EnchantmentScriptContainer.EnchantmentReference script) {\r\n        try {\r\n            Map holders = (Map) REGISTRY_INTRUSIVE_HOLDERS.get(BuiltInRegistries.ENCHANTMENT);\r\n            if (holders == null) {\r\n                REGISTRY_INTRUSIVE_HOLDERS.set(BuiltInRegistries.ENCHANTMENT, new IdentityHashMap());\r\n            }\r\n            boolean wasFrozen = REGISTRY_FROZEN.getBoolean(BuiltInRegistries.ENCHANTMENT);\r\n            REGISTRY_FROZEN.setBoolean(BuiltInRegistries.ENCHANTMENT, false);\r\n            EquipmentSlot[] slots = new EquipmentSlot[script.script.slots.size()];\r\n            for (int i = 0; i < slots.length; i++) {\r\n                slots[i] = EquipmentSlot.valueOf(CoreUtilities.toUpperCase(script.script.slots.get(i)));\r\n            }\r\n            // TODO: 1.20.6: rarity is provided as an int, can make our own mirror enum; categories seemed to only over control #canEnchant(ItemStack), so can probably safely phase them out?\r\n             net.minecraft.world.item.enchantment.Enchantment.Rarity.valueOf(script.script.rarity), EnchantmentCategory.valueOf(script.script.category), slots\r\n            net.minecraft.world.item.enchantment.Enchantment nmsEnchant = new net.minecraft.world.item.enchantment.Enchantment(null) {\r\n                // TODO: 1.20.6: methods are final now and the values are provided by EnchantmentDefinition - would probably need to create a new one on reload and modify the existing enchantment\r\n                @Override\r\n                public int getMinLevel() {\r\n                    return script.script.minLevel;\r\n                }\r\n                @Override\r\n                public int getMaxLevel() {\r\n                    return script.script.maxLevel;\r\n                }\r\n                @Override\r\n                public int getMinCost(int level) {\r\n                    return script.script.getMinCost(level);\r\n                }\r\n                @Override\r\n                public int getMaxCost(int level) {\r\n                    return script.script.getMaxCost(level);\r\n                }\r\n                @Override\r\n                public int getDamageProtection(int level, DamageSource src) {\r\n                    return script.script.getDamageProtection(level, src.getMsgId(), src.getEntity() == null ? null : src.getEntity().getBukkitEntity());\r\n                }\r\n                // TODO: 1.20.6: Takes an EntityType now, and MobType seems to have been removed in favor of vanilla tags - can probably use these to backsupport & properly pass the entity type\r\n                @Override\r\n                public float getDamageBonus(int level, EntityType type) {\r\n                    String typeName = \"UNDEFINED\";\r\n                    if (type == MobType.ARTHROPOD) {\r\n                        typeName = \"ARTHROPOD\";\r\n                    }\r\n                    else if (type == MobType.ILLAGER) {\r\n                        typeName = \"ILLAGER\";\r\n                    }\r\n                    else if (type == MobType.UNDEAD) {\r\n                        typeName = \"UNDEAD\";\r\n                    }\r\n                    else if (type == MobType.WATER) {\r\n                        typeName = \"WATER\";\r\n                    }\r\n                    return script.script.getDamageBonus(level, typeName);\r\n                }\r\n                @Override\r\n                protected boolean checkCompatibility(net.minecraft.world.item.enchantment.Enchantment nmsEnchantment) {\r\n                    ResourceLocation nmsKey = BuiltInRegistries.ENCHANTMENT.getKey(nmsEnchantment);\r\n                    NamespacedKey bukkitKey = CraftNamespacedKey.fromMinecraft(nmsKey);\r\n                    org.bukkit.enchantments.Enchantment bukkitEnchant = CraftEnchantment.getByKey(bukkitKey);\r\n                    return script.script.isCompatible(bukkitEnchant);\r\n                }\r\n                @Override\r\n                protected String getOrCreateDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public String getDescriptionId() {\r\n                    return script.script.descriptionId;\r\n                }\r\n                @Override\r\n                public Component getFullname(int level) {\r\n                    return Handler.componentToNMS(script.script.getFullName(level));\r\n                }\r\n                @Override\r\n                public boolean canEnchant(net.minecraft.world.item.ItemStack var0) {\r\n                    return super.canEnchant(var0) && script.script.canEnchant(CraftItemStack.asBukkitCopy(var0));\r\n                }\r\n                @Override\r\n                public void doPostAttack(LivingEntity attacker, Entity victim, int level) {\r\n                    script.script.doPostAttack(attacker.getBukkitEntity(), victim.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public void doPostHurt(LivingEntity victim, Entity attacker, int level) {\r\n                    script.script.doPostHurt(victim.getBukkitEntity(), attacker.getBukkitEntity(), level);\r\n                }\r\n                @Override\r\n                public boolean isTreasureOnly() {\r\n                    return script.script.isTreasureOnly;\r\n                }\r\n                @Override\r\n                public boolean isCurse() {\r\n                    return script.script.isCurse;\r\n                }\r\n                @Override\r\n                public boolean isTradeable() {\r\n                    return script.script.isTradable;\r\n                }\r\n                @Override\r\n                public boolean isDiscoverable() {\r\n                    return script.script.isDiscoverable;\r\n                }\r\n            };\r\n            NamespacedKey enchantmentKey = new NamespacedKey(Denizen.getInstance(), script.script.id);\r\n            Registry.register(BuiltInRegistries.ENCHANTMENT, enchantmentKey.toString(), nmsEnchant);\r\n            String enchName = CoreUtilities.toUpperCase(script.script.id);\r\n            CraftEnchantment ench = new CraftEnchantment(enchantmentKey, nmsEnchant) {\r\n                @Override\r\n                public String getName() {\r\n                    return enchName;\r\n                }\r\n            };\r\n            REGISTRY_INTRUSIVE_HOLDERS.set(BuiltInRegistries.ENCHANTMENT, holders);\r\n            if (wasFrozen) {\r\n                BuiltInRegistries.ENCHANTMENT.freeze();\r\n            }\r\n            return ench;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(\"Failed to register enchantment \" + script.script.id);\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    // TODO: 1.20.6: rarity is just an int now (weight), can deprecate & backsupport by estimating it based on the weight\r\n    @Override\r\n    public String getRarity(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getRarity().name();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDiscoverable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isDiscoverable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isTradable(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isTradeable();\r\n    }\r\n\r\n    @Override\r\n    public boolean isCurse(Enchantment enchantment) {\r\n        return ((CraftEnchantment) enchantment).getHandle().isCurse();\r\n    }\r\n\r\n    @Override\r\n    public int getMinCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMinCost(level);\r\n    }\r\n\r\n    @Override\r\n    public int getMaxCost(Enchantment enchantment, int level) {\r\n        return ((CraftEnchantment) enchantment).getHandle().getMaxCost(level);\r\n    }\r\n\r\n    @Override\r\n    public String getFullName(Enchantment enchantment, int level) {\r\n        return FormattedTextHelper.stringify(Handler.componentToSpigot(((CraftEnchantment) enchantment).getHandle().getFullname(level)));\r\n    }\r\n\r\n    // TODO: 1.20.6: MobType was removed in favor of using the entity type directly - deprecate + potentially backsupport with vanilla tags\r\n    @Override\r\n    public float getDamageBonus(Enchantment enchantment, int level, String type) {\r\n        MobType mobType = switch (type) {\r\n            case \"illager\" -> MobType.ILLAGER;\r\n            case \"undead\" -> MobType.UNDEAD;\r\n            case \"water\" -> MobType.WATER;\r\n            case \"arthropod\" -> MobType.ARTHROPOD;\r\n            default -> MobType.UNDEFINED;\r\n        };\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageBonus(level, mobType);\r\n    }\r\n\r\n    @Override\r\n    public int getDamageProtection(Enchantment enchantment, int level, EntityDamageEvent.DamageCause type, org.bukkit.entity.Entity attacker) {\r\n        Entity nmsAttacker = attacker == null ? null : ((CraftEntity) attacker).getHandle();\r\n        DamageSource src = EntityHelperImpl.getSourceFor(nmsAttacker, type, nmsAttacker);\r\n        if (src instanceof EntityHelperImpl.FakeDamageSrc fakeDamageSrc) {\r\n            src = fakeDamageSrc.real;\r\n        }\r\n        return ((CraftEnchantment) enchantment).getHandle().getDamageProtection(level, src);\r\n    }\r\n     */\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EntityDataNameMapper.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\n\n\nimport com.denizenscript.denizencore.objects.ArgumentHelper;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport net.minecraft.world.entity.*;\nimport net.minecraft.world.entity.ambient.Bat;\nimport net.minecraft.world.entity.animal.axolotl.Axolotl;\nimport net.minecraft.world.entity.animal.bee.Bee;\nimport net.minecraft.world.entity.animal.camel.Camel;\nimport net.minecraft.world.entity.animal.cow.MushroomCow;\nimport net.minecraft.world.entity.animal.dolphin.Dolphin;\nimport net.minecraft.world.entity.animal.equine.AbstractChestedHorse;\nimport net.minecraft.world.entity.animal.equine.AbstractHorse;\nimport net.minecraft.world.entity.animal.equine.Horse;\nimport net.minecraft.world.entity.animal.equine.Llama;\nimport net.minecraft.world.entity.animal.feline.Cat;\nimport net.minecraft.world.entity.animal.feline.Ocelot;\nimport net.minecraft.world.entity.animal.fish.AbstractFish;\nimport net.minecraft.world.entity.animal.fish.Pufferfish;\nimport net.minecraft.world.entity.animal.fish.TropicalFish;\nimport net.minecraft.world.entity.animal.fox.Fox;\nimport net.minecraft.world.entity.animal.frog.Frog;\nimport net.minecraft.world.entity.animal.goat.Goat;\nimport net.minecraft.world.entity.animal.golem.IronGolem;\nimport net.minecraft.world.entity.animal.golem.SnowGolem;\nimport net.minecraft.world.entity.animal.panda.Panda;\nimport net.minecraft.world.entity.animal.parrot.Parrot;\nimport net.minecraft.world.entity.animal.pig.Pig;\nimport net.minecraft.world.entity.animal.polarbear.PolarBear;\nimport net.minecraft.world.entity.animal.rabbit.Rabbit;\nimport net.minecraft.world.entity.animal.sheep.Sheep;\nimport net.minecraft.world.entity.animal.sniffer.Sniffer;\nimport net.minecraft.world.entity.animal.turtle.Turtle;\nimport net.minecraft.world.entity.animal.wolf.Wolf;\nimport net.minecraft.world.entity.boss.enderdragon.EndCrystal;\nimport net.minecraft.world.entity.boss.enderdragon.EnderDragon;\nimport net.minecraft.world.entity.boss.wither.WitherBoss;\nimport net.minecraft.world.entity.decoration.ArmorStand;\nimport net.minecraft.world.entity.decoration.ItemFrame;\nimport net.minecraft.world.entity.decoration.painting.Painting;\nimport net.minecraft.world.entity.item.PrimedTnt;\nimport net.minecraft.world.entity.monster.*;\nimport net.minecraft.world.entity.monster.hoglin.Hoglin;\nimport net.minecraft.world.entity.monster.illager.Pillager;\nimport net.minecraft.world.entity.monster.illager.SpellcasterIllager;\nimport net.minecraft.world.entity.monster.piglin.AbstractPiglin;\nimport net.minecraft.world.entity.monster.piglin.Piglin;\nimport net.minecraft.world.entity.monster.spider.Spider;\nimport net.minecraft.world.entity.monster.warden.Warden;\nimport net.minecraft.world.entity.monster.zombie.Zombie;\nimport net.minecraft.world.entity.monster.zombie.ZombieVillager;\nimport net.minecraft.world.entity.npc.villager.AbstractVillager;\nimport net.minecraft.world.entity.npc.villager.Villager;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.entity.projectile.EyeOfEnder;\nimport net.minecraft.world.entity.projectile.FireworkRocketEntity;\nimport net.minecraft.world.entity.projectile.FishingHook;\nimport net.minecraft.world.entity.projectile.ThrowableProjectile;\nimport net.minecraft.world.entity.projectile.arrow.AbstractArrow;\nimport net.minecraft.world.entity.projectile.arrow.Arrow;\nimport net.minecraft.world.entity.projectile.arrow.ThrownTrident;\nimport net.minecraft.world.entity.projectile.hurtingprojectile.Fireball;\nimport net.minecraft.world.entity.projectile.hurtingprojectile.SmallFireball;\nimport net.minecraft.world.entity.projectile.hurtingprojectile.WitherSkull;\nimport net.minecraft.world.entity.raid.Raider;\nimport net.minecraft.world.entity.vehicle.boat.Boat;\nimport net.minecraft.world.entity.vehicle.minecart.AbstractMinecart;\nimport net.minecraft.world.entity.vehicle.minecart.MinecartCommandBlock;\nimport net.minecraft.world.entity.vehicle.minecart.MinecartFurnace;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class EntityDataNameMapper {\n\n    public static final Map<Class<? extends Entity>, Map<String, Integer>> entityDataNames = new HashMap<>();\n\n    public static void registerDataName(Class<? extends Entity> entityClass, int id, String name) {\n        entityDataNames.computeIfAbsent(entityClass, k -> new HashMap<>()).put(name, id);\n    }\n\n    static {\n        // Entity\n        registerDataName(Entity.class, 0, \"entity_flags\");\n        registerDataName(Entity.class, 1, \"air_ticks\");\n        registerDataName(Entity.class, 2, \"custom_name\");\n        registerDataName(Entity.class, 3, \"custom_name_visible\");\n        registerDataName(Entity.class, 4, \"silent\");\n        registerDataName(Entity.class, 5, \"no_gravity\");\n        registerDataName(Entity.class, 6, \"pose\");\n        registerDataName(Entity.class, 7, \"frozen_ticks\");\n\n        // Interaction\n        registerDataName(Interaction.class, 8, \"width\");\n        registerDataName(Interaction.class, 9, \"height\");\n        registerDataName(Interaction.class, 10, \"responsive\");\n\n        // Display\n        registerDataName(Display.class, 8, \"transform_interpolation_start\");\n        registerDataName(Display.class, 9, \"transform_interpolation_duration\");\n        registerDataName(Display.class, 10, \"movement_interpolation_duration\");\n        registerDataName(Display.class, 11, \"translation\");\n        registerDataName(Display.class, 12, \"scale\");\n        registerDataName(Display.class, 13, \"left_rotation\");\n        registerDataName(Display.class, 14, \"right_rotation\");\n        registerDataName(Display.class, 15, \"billboard\");\n        registerDataName(Display.class, 16, \"brightness\");\n        registerDataName(Display.class, 17, \"view_range\");\n        registerDataName(Display.class, 18, \"shadow_radius\");\n        registerDataName(Display.class, 19, \"shadow_strength\");\n        registerDataName(Display.class, 20, \"width\");\n        registerDataName(Display.class, 21, \"height\");\n        registerDataName(Display.class, 22, \"glow_color\");\n\n        // Block display\n        registerDataName(Display.BlockDisplay.class, 23, \"material\");\n\n        // Item display\n        registerDataName(Display.ItemDisplay.class, 23, \"item\");\n        registerDataName(Display.ItemDisplay.class, 24, \"model_transform\");\n\n        // Text display\n        registerDataName(Display.TextDisplay.class, 23, \"text\");\n        registerDataName(Display.TextDisplay.class, 24, \"line_width\");\n        registerDataName(Display.TextDisplay.class, 25, \"background_color\");\n        registerDataName(Display.TextDisplay.class, 26, \"text_opacity\");\n        registerDataName(Display.TextDisplay.class, 27, \"text_display_flags\");\n\n        // Thrown item projectile\n        registerDataName(ThrowableProjectile.class, 8, \"item\");\n\n        // Eye of ender\n        registerDataName(EyeOfEnder.class, 8, \"item\");\n\n        // Falling block\n        registerDataName(FireworkRocketEntity.class, 8, \"spawn_position\");\n\n        // Area effect cloud\n        registerDataName(AreaEffectCloud.class, 8, \"radius\");\n        registerDataName(AreaEffectCloud.class, 9, \"color\");\n        registerDataName(AreaEffectCloud.class, 10, \"waiting\");\n        registerDataName(AreaEffectCloud.class, 11, \"particle\");\n\n        // Fishing hook\n        registerDataName(FishingHook.class, 8, \"hooked_entity_id\");\n        registerDataName(FishingHook.class, 9, \"catchable\");\n\n        // Abstract arrow\n        registerDataName(AbstractArrow.class, 8, \"abstract_arrow_flags\");\n        registerDataName(AbstractArrow.class, 9, \"piercing_level\");\n\n        // Arrow\n        registerDataName(Arrow.class, 10, \"color\");\n\n        // Thrown trident\n        registerDataName(ThrownTrident.class, 10, \"loyalty_level\");\n        registerDataName(ThrownTrident.class, 11, \"enchantment_glint\");\n\n        // Boat\n        registerDataName(Boat.class, 8, \"shaking_ticks\");\n        registerDataName(Boat.class, 9, \"shaking_direction\");\n        registerDataName(Boat.class, 10, \"damage_taken\");\n        registerDataName(Boat.class, 11, \"type\");\n        registerDataName(Boat.class, 12, \"left_paddle_moving\");\n        registerDataName(Boat.class, 13, \"right_paddle_moving\");\n        registerDataName(Boat.class, 14, \"bubble_shaking_ticks\");\n\n        // End crystal\n        registerDataName(EndCrystal.class, 8, \"beam_target\");\n        registerDataName(EndCrystal.class, 9, \"showing_bottom\");\n\n        // Small fireball\n        registerDataName(SmallFireball.class, 8, \"item\");\n\n        // Fireball\n        registerDataName(Fireball.class, 8, \"item\");\n\n        // Wither skull\n        registerDataName(WitherSkull.class, 8, \"invulnerable\");\n\n        // Firework rocket\n        registerDataName(FireworkRocketEntity.class, 8, \"item\");\n        registerDataName(FireworkRocketEntity.class, 9, \"shooter_id\");\n        registerDataName(FireworkRocketEntity.class, 10, \"shot_at_angle\");\n\n        // Item frame\n        registerDataName(ItemFrame.class, 8, \"item\");\n        registerDataName(ItemFrame.class, 9, \"rotation\");\n\n        // Painting\n        registerDataName(Painting.class, 8, \"painting_variant\");\n\n        // Living entity\n        registerDataName(LivingEntity.class, 8, \"living_entity_flags\");\n        registerDataName(LivingEntity.class, 9, \"health\");\n        registerDataName(LivingEntity.class, 10, \"potion_effect_color\");\n        registerDataName(LivingEntity.class, 11, \"is_potion_effect_ambient\");\n        registerDataName(LivingEntity.class, 12, \"arrows_in_body\");\n        registerDataName(LivingEntity.class, 13, \"bee_stingers_in_body\");\n        registerDataName(LivingEntity.class, 14, \"bed_location\");\n\n        // Player\n        registerDataName(Player.class, 15, \"additional_hearts\");\n        registerDataName(Player.class, 16, \"score\");\n        registerDataName(Player.class, 17, \"skin_parts\");\n        registerDataName(Player.class, 18, \"main_hand\");\n        registerDataName(Player.class, 19, \"left_shoulder_entity\");\n        registerDataName(Player.class, 20, \"right_shoulder_entity\");\n\n        // Armor stand\n        registerDataName(ArmorStand.class, 15, \"armor_stand_flags\");\n        registerDataName(ArmorStand.class, 16, \"head_rotation\");\n        registerDataName(ArmorStand.class, 17, \"body_rotation\");\n        registerDataName(ArmorStand.class, 18, \"left_arm_rotation\");\n        registerDataName(ArmorStand.class, 19, \"right_arm_rotation\");\n        registerDataName(ArmorStand.class, 20, \"left_leg_rotation\");\n        registerDataName(ArmorStand.class, 21, \"right_leg_rotation\");\n\n        // Mob\n        registerDataName(Mob.class, 15, \"mob_flags\");\n\n        // Bat flags\n        registerDataName(Bat.class, 16, \"bat_flags\");\n\n        // Dolphin\n        registerDataName(Dolphin.class, 16, \"treasure_location\");\n        registerDataName(Dolphin.class, 17, \"has_fish\");\n        registerDataName(Dolphin.class, 18, \"moisture_level\");\n\n        // Abstract Fish\n        registerDataName(AbstractFish.class, 16, \"from_bucket\");\n\n        // PufferFish\n        registerDataName(Pufferfish.class, 17, \"puff_state\");\n\n        // Tropical fish\n        registerDataName(TropicalFish.class, 17, \"variant\");\n\n        // Ageable mob\n        registerDataName(AgeableMob.class, 16, \"is_baby\");\n\n        // Sniffer\n        registerDataName(Sniffer.class, 17, \"sniffer_state\");\n        registerDataName(Sniffer.class, 18, \"finish_dig_time\");\n\n        // Abstract horse\n        registerDataName(AbstractHorse.class, 17, \"horse_flags\");\n\n        // Horse\n        registerDataName(Horse.class, 18, \"variant\");\n\n        // Camel\n        registerDataName(Camel.class, 18, \"is_dashing\");\n        registerDataName(Camel.class, 19, \"last_pose_change\");\n\n        // Chested horse\n        registerDataName(AbstractChestedHorse.class, 18, \"has_chest\");\n\n        // Llama\n        registerDataName(Llama.class, 19, \"strength\");\n        registerDataName(Llama.class, 20, \"carpet_color\");\n        registerDataName(Llama.class, 21, \"variant\");\n\n        // Axolotl\n        registerDataName(Axolotl.class, 17, \"variant\");\n        registerDataName(Axolotl.class, 18, \"playing_dead\");\n        registerDataName(Axolotl.class, 19, \"from_bucket\");\n\n        // Bee\n        registerDataName(Bee.class, 17, \"bee_flags\");\n        registerDataName(Bee.class, 18, \"anger_time\");\n\n        // Fox\n        registerDataName(Fox.class, 17, \"type\");\n        registerDataName(Fox.class, 18, \"fox_flags\");\n        registerDataName(Fox.class, 19, \"first_trusted_uuid\");\n        registerDataName(Fox.class, 20, \"second_trusted_uuid\");\n\n        // Frog\n        registerDataName(Frog.class, 17, \"variant\");\n        registerDataName(Frog.class, 18, \"target_id\");\n\n        // Ocelot\n        registerDataName(Ocelot.class, 17, \"is_trusting\");\n\n        // Panda\n        registerDataName(Panda.class, 17, \"ask_for_bamboo_timer\");\n        registerDataName(Panda.class, 18, \"sneeze_timer\");\n        registerDataName(Panda.class, 19, \"eat_timer\");\n        registerDataName(Panda.class, 20, \"main_gene\");\n        registerDataName(Panda.class, 21, \"hidden_gene\");\n        registerDataName(Panda.class, 22, \"panda_flags\");\n\n        // Pig\n        registerDataName(Pig.class, 17, \"has_saddle\");\n        registerDataName(Pig.class, 18, \"boost_ticks\");\n\n        // Rabbit\n        registerDataName(Rabbit.class, 17, \"type\");\n\n        // Turtle\n        registerDataName(Turtle.class, 17, \"home_location\");\n        registerDataName(Turtle.class, 18, \"has_egg\");\n        registerDataName(Turtle.class, 19, \"laying_egg\");\n        registerDataName(Turtle.class, 20, \"travel_location\");\n        registerDataName(Turtle.class, 21, \"going_home\");\n        registerDataName(Turtle.class, 20, \"traveling\");\n\n        // Polar bear\n        registerDataName(PolarBear.class, 17, \"standing_up\");\n\n        // Hoglin\n        registerDataName(Hoglin.class, 17, \"immune_to_zombification\");\n\n        // Mooshroom\n        registerDataName(MushroomCow.class, 17, \"variant\");\n\n        // Sheep\n        registerDataName(Sheep.class, 17, \"sheep_wool_flags\");\n\n        // Strider\n        registerDataName(Strider.class, 17, \"boost_ticks\");\n        registerDataName(Strider.class, 18, \"shaking\");\n        registerDataName(Strider.class, 19, \"has_saddle\");\n\n        // Tamable animal\n        registerDataName(TamableAnimal.class, 17, \"tamable_animal_flags\");\n        registerDataName(TamableAnimal.class, 18, \"owner\");\n\n        // Cat\n        registerDataName(Cat.class, 19, \"variant\");\n        registerDataName(Cat.class, 20, \"lying\");\n        registerDataName(Cat.class, 20, \"relaxed\");\n        registerDataName(Cat.class, 21, \"collar_color\");\n\n        // Wolf\n        registerDataName(Wolf.class, 19, \"begging\");\n        registerDataName(Wolf.class, 20, \"collar_color\");\n        registerDataName(Wolf.class, 21, \"anger_time\");\n\n        // Parrot\n        registerDataName(Parrot.class, 19, \"variant\");\n\n        // Abstract villager\n        registerDataName(AbstractVillager.class, 17, \"head_shake_ticks\");\n\n        // Villager\n        registerDataName(Villager.class, 18, \"villager_data\");\n\n        // Iron golem\n        registerDataName(IronGolem.class, 16, \"iron_golem_flags\");\n\n        // Snow golem\n        registerDataName(SnowGolem.class, 16, \"snow_golem_pumpkin_flags\");\n\n        // Shulker\n        registerDataName(Shulker.class, 16, \"attach_face\");\n        registerDataName(Shulker.class, 17, \"attachment_location\");\n        registerDataName(Shulker.class, 18, \"peek\");\n        registerDataName(Shulker.class, 19, \"color\");\n\n        // Base piglin\n        registerDataName(AbstractPiglin.class, 16, \"immune_to_zombification\");\n\n        // Piglin\n        registerDataName(Piglin.class, 17, \"is_baby\");\n        registerDataName(Piglin.class, 18, \"charging_crossbow\");\n        registerDataName(Piglin.class, 19, \"dancing\");\n\n        // Blaze\n        registerDataName(Blaze.class, 16, \"blaze_flags\");\n\n        // Creeper\n        registerDataName(Creeper.class, 16, \"state\");\n        registerDataName(Creeper.class, 17, \"charged\");\n        registerDataName(Creeper.class, 18, \"ignited\");\n\n        // Goat\n        registerDataName(Goat.class, 17, \"screaming\");\n        registerDataName(Goat.class, 18, \"has_left_horn\");\n        registerDataName(Goat.class, 19, \"has_right_horn\");\n\n        // Guardian\n        registerDataName(Guardian.class, 16, \"spikes_retracted\");\n        registerDataName(Guardian.class, 17, \"target_id\");\n\n        // Raider\n        registerDataName(Raider.class, 16, \"celebrating\");\n\n        // Pillager\n        registerDataName(Pillager.class, 17, \"charging_crossbow\");\n\n        // Spellcaster illager\n        registerDataName(SpellcasterIllager.class, 17, \"spell\");\n\n        // Witch\n        registerDataName(Witch.class, 17, \"drinking_potion\");\n\n        // Vex\n        registerDataName(Vex.class, 16, \"vex_flags\");\n\n        // Spider\n        registerDataName(Spider.class, 16, \"spider_flags\");\n\n        // Warden\n        registerDataName(Warden.class, 16, \"anger_level\");\n\n        // Wither\n        registerDataName(WitherBoss.class, 16, \"center_head_target\");\n        registerDataName(WitherBoss.class, 17, \"left_head_target\");\n        registerDataName(WitherBoss.class, 18, \"right_head_target\");\n        registerDataName(WitherBoss.class, 19, \"invulnerable_time\");\n\n        // Zoglin\n        registerDataName(Zoglin.class, 16, \"is_baby\");\n\n        // Zombie\n        registerDataName(Zombie.class, 16, \"is_baby\");\n        registerDataName(Zombie.class, 17, \"type\"); // Unused\n        registerDataName(Zombie.class, 18, \"converting_in_water\");\n\n        // Zombie villager\n        registerDataName(ZombieVillager.class, 19, \"is_converting\");\n        registerDataName(ZombieVillager.class, 20, \"villager_data\");\n\n        // Enderman\n        registerDataName(EnderMan.class, 16, \"carried_block\");\n        registerDataName(EnderMan.class, 17, \"screaming\");\n        registerDataName(EnderMan.class, 18, \"staring\");\n\n        // Ender dragon\n        registerDataName(EnderDragon.class, 16, \"phase\");\n\n        // Ghast\n        registerDataName(Ghast.class, 16, \"attacking\");\n\n        // Phantom\n        registerDataName(Phantom.class, 16, \"size\");\n\n        // Slime\n        registerDataName(Slime.class, 16, \"size\");\n\n        // Abstract minecart\n        registerDataName(AbstractMinecart.class, 8, \"shaking_ticks\");\n        registerDataName(AbstractMinecart.class, 9, \"shaking_direction\");\n        registerDataName(AbstractMinecart.class, 10, \"damage_taken\");\n        registerDataName(AbstractMinecart.class, 11, \"display_block_id\");\n        registerDataName(AbstractMinecart.class, 12, \"display_block_y\");\n        registerDataName(AbstractMinecart.class, 13, \"show_display_block\");\n\n        // Minecraft furnace\n        registerDataName(MinecartFurnace.class, 14, \"has_fuel\");\n\n        // Minecraft command block\n        registerDataName(MinecartCommandBlock.class, 14, \"command\");\n        registerDataName(MinecartCommandBlock.class, 15, \"last_output\");\n\n        // Primed TNT\n        registerDataName(PrimedTnt.class, 8, \"fuse_ticks\");\n    }\n\n    public static int getIdForName(Class<? extends Entity> entityClass, String name) {\n        Class<?> currentClass = entityClass;\n        int id = getIdFromClass(currentClass, name);\n        while (id == -1) {\n            currentClass = currentClass.getSuperclass();\n            if (currentClass == Object.class) {\n                break;\n            }\n            id = getIdFromClass(currentClass, name);\n        }\n        return id;\n    }\n\n    private static int getIdFromClass(Class<?> entityClass, String name) {\n        Map<String, Integer> nameToId = entityDataNames.get(entityClass);\n        int id = nameToId != null ? nameToId.getOrDefault(name, -1) : -1;\n        if (id == -1 && ArgumentHelper.matchesInteger(name)) {\n            id = new ElementTag(name).asInt();\n        }\n        return id;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/EntityHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.EntityHelper;\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.properties.entity.EntityState;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.ObjectTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.scripts.commands.core.ReflectionSetCommand;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.denizenscript.denizencore.utilities.text.StringHolder;\r\nimport io.netty.buffer.Unpooled;\r\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.commands.arguments.EntityAnchorArgument;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.dedicated.DedicatedPlayerList;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.server.players.PlayerList;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.InteractionHand;\r\nimport net.minecraft.world.damagesource.CombatRules;\r\nimport net.minecraft.world.damagesource.DamageSource;\r\nimport net.minecraft.world.damagesource.DamageSources;\r\nimport net.minecraft.world.entity.Mob;\r\nimport net.minecraft.world.entity.MoverType;\r\nimport net.minecraft.world.entity.PositionMoveRotation;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.goal.Goal;\r\nimport net.minecraft.world.entity.ai.navigation.PathNavigation;\r\nimport net.minecraft.world.entity.animal.armadillo.Armadillo;\r\nimport net.minecraft.world.entity.item.FallingBlockEntity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.item.PrimedTnt;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.level.ClipContext;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.entity.SpawnerBlockEntity;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.pathfinder.Path;\r\nimport net.minecraft.world.phys.AABB;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport net.minecraft.world.phys.HitResult;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport net.minecraft.world.phys.shapes.CollisionContext;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.attribute.Attribute;\r\nimport org.bukkit.attribute.AttributeInstance;\r\nimport org.bukkit.block.CreatureSpawner;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.block.CraftCreatureSpawner;\r\nimport org.bukkit.craftbukkit.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.entity.*;\r\nimport org.bukkit.craftbukkit.inventory.CraftItemStack;\r\nimport org.bukkit.entity.*;\r\nimport org.bukkit.event.entity.EntityDamageEvent;\r\nimport org.bukkit.inventory.EquipmentSlot;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\nimport org.bukkit.scheduler.BukkitTask;\r\nimport org.bukkit.util.BoundingBox;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.function.BiConsumer;\r\n\r\npublic class EntityHelperImpl extends EntityHelper {\r\n\r\n    public static final MethodHandle ENTITY_ONGROUND_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.Entity.class, \"onGround\", boolean.class);\r\n\r\n    public static final EntityDataAccessor<Boolean> ENDERMAN_DATA_ACCESSOR_SCREAMING = ReflectionHelper.getFieldValue(EnderMan.class, \"DATA_CREEPY\", null);\r\n\r\n    @Override\r\n    public void setInvisible(Entity entity, boolean invisible) {\r\n        ((CraftEntity) entity).getHandle().setInvisible(invisible);\r\n    }\r\n\r\n    @Override\r\n    public boolean isInvisible(Entity entity) {\r\n        return ((CraftEntity) entity).getHandle().isInvisible();\r\n    }\r\n\r\n    @Override\r\n    public void setPose(Entity entity, Pose pose) {\r\n        ((CraftEntity) entity).getHandle().setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]);\r\n    }\r\n\r\n    @Override\r\n    public double getDamageTo(LivingEntity attacker, Entity target) {\r\n        double damage = 0;\r\n        AttributeInstance attrib = attacker.getAttribute(Attribute.ATTACK_DAMAGE);\r\n        if (attrib != null) {\r\n            damage = attrib.getValue();\r\n        }\r\n        if (damage <= 0) {\r\n            return 0;\r\n        }\r\n        if (target == null) {\r\n            // Target is required as of MC 1.21, so if unspecified just assume target is equivalent to attacker\r\n            target = attacker;\r\n        }\r\n        DamageSource source;\r\n        net.minecraft.world.entity.Entity nmsTarget = ((CraftEntity) target).getHandle();\r\n        ServerLevel nmsWorld = ((CraftWorld) attacker.getWorld()).getHandle();\r\n        if (attacker instanceof CraftPlayer playerAttacker) {\r\n            source = nmsTarget.level().damageSources().playerAttack(playerAttacker.getHandle());\r\n        }\r\n        else {\r\n            source = nmsTarget.level().damageSources().mobAttack(((CraftLivingEntity) attacker).getHandle());\r\n        }\r\n        net.minecraft.world.entity.LivingEntity nmsLivingTarget = nmsTarget instanceof net.minecraft.world.entity.LivingEntity living ? living : null;\r\n        if (nmsLivingTarget != null ? nmsLivingTarget.isInvulnerableTo(nmsWorld, source) : nmsTarget.isInvulnerableToBase(source)) {\r\n            return 0;\r\n        }\r\n        if (attacker.getEquipment() != null) {\r\n            damage = EnchantmentHelper.modifyDamage(nmsWorld, CraftItemStack.asNMSCopy(attacker.getEquipment().getItemInMainHand()), nmsTarget, source, (float) damage);\r\n        }\r\n        if (nmsLivingTarget == null) {\r\n            return damage;\r\n        }\r\n        damage = CombatRules.getDamageAfterAbsorb(nmsLivingTarget, (float) damage, source, (float) nmsLivingTarget.getArmorValue(), (float) nmsLivingTarget.getAttributeValue(Attributes.ARMOR_TOUGHNESS));\r\n        float enchantDamageModifier = EnchantmentHelper.getDamageProtection(nmsWorld, nmsLivingTarget, source);\r\n        if (enchantDamageModifier > 0) {\r\n            damage = CombatRules.getDamageAfterMagicAbsorb((float) damage, enchantDamageModifier);\r\n        }\r\n        return damage;\r\n    }\r\n\r\n    public static final MethodHandle LIVINGENTITY_AUTOSPINATTACK_SETTER = ReflectionHelper.getFinalSetter(net.minecraft.world.entity.LivingEntity.class, \"autoSpinAttackTicks\");\r\n    public static final MethodHandle LIVINGENTITY_SETLIVINGENTITYFLAG = ReflectionHelper.getMethodHandle(net.minecraft.world.entity.LivingEntity.class, \"setLivingEntityFlag\", int.class, boolean.class);\r\n\r\n    @Override\r\n    public void setRiptide(Entity entity, boolean state) {\r\n        try {\r\n            net.minecraft.world.entity.LivingEntity nmsEntity = ((CraftLivingEntity) entity).getHandle();\r\n            LIVINGENTITY_AUTOSPINATTACK_SETTER.invoke(nmsEntity, state ? 0 : 1);\r\n            LIVINGENTITY_SETLIVINGENTITYFLAG.invoke(nmsEntity, 4, true);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void forceInteraction(Player player, Location location) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        BlockHelperImpl.getNMSState(location.getBlock()).useItemOn(nmsPlayer.getMainHandItem(), ((CraftWorld) location.getWorld()).getHandle(),\r\n                nmsPlayer, InteractionHand.MAIN_HAND,\r\n                new BlockHitResult(new Vec3(0, 0, 0), null, Handler.toBlockPos(location), false));\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(Entity entity) {\r\n        CompoundTag nmsTag = Handler.useValueOutput(((CraftEntity) entity).getHandle()::saveAsPassenger);\r\n        return NBTAdapter.toAPI(nmsTag);\r\n    }\r\n\r\n    @Override\r\n    public void setNbtData(Entity entity, CompoundBinaryTag compoundTag) {\r\n        Handler.useValueInput(NBTAdapter.toNMS(compoundTag), ((CraftEntity) entity).getHandle()::load);\r\n    }\r\n\r\n    /*\r\n        Entity Movement\r\n     */\r\n\r\n    private final static Map<UUID, BukkitTask> followTasks = new HashMap<>();\r\n\r\n    @Override\r\n    public void stopFollowing(Entity follower) {\r\n        if (follower == null) {\r\n            return;\r\n        }\r\n        UUID uuid = follower.getUniqueId();\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void stopWalking(Entity entity) {\r\n        if (((CraftEntity) entity).getHandle() instanceof Mob nmsMob) {\r\n            nmsMob.getNavigation().stop();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void follow(final Entity target, final Entity follower, final double speed, final double lead,\r\n                       final double maxRange, final boolean allowWander, final boolean teleport) {\r\n        if (target == null || follower == null) {\r\n            return;\r\n        }\r\n\r\n        final net.minecraft.world.entity.Entity nmsEntityFollower = ((CraftEntity) follower).getHandle();\r\n        if (!(nmsEntityFollower instanceof Mob nmsFollower)) {\r\n            return;\r\n        }\r\n        final PathNavigation followerNavigation = nmsFollower.getNavigation();\r\n\r\n        UUID uuid = follower.getUniqueId();\r\n\r\n        if (followTasks.containsKey(uuid)) {\r\n            followTasks.get(uuid).cancel();\r\n        }\r\n\r\n        final int locationNearInt = (int) Math.floor(lead);\r\n        final boolean hasMax = maxRange > lead;\r\n\r\n        followTasks.put(follower.getUniqueId(), new BukkitRunnable() {\r\n\r\n            private boolean inRadius = false;\r\n\r\n            public void run() {\r\n                if (!target.isValid() || !follower.isValid()) {\r\n                    this.cancel();\r\n                }\r\n                followerNavigation.setSpeedModifier(2D);\r\n                Location targetLocation = target.getLocation();\r\n                Path path;\r\n\r\n                if (hasMax && !Utilities.checkLocation(targetLocation, follower.getLocation(), maxRange)\r\n                        && !target.isDead() && target.isOnGround()) {\r\n                    if (!inRadius) {\r\n                        if (teleport) {\r\n                            follower.teleport(Utilities.getWalkableLocationNear(targetLocation, locationNearInt));\r\n                        }\r\n                        else {\r\n                            cancel();\r\n                        }\r\n                    }\r\n                    else {\r\n                        inRadius = false;\r\n                        path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                        if (path != null) {\r\n                            followerNavigation.moveTo(path, 1D);\r\n                            followerNavigation.setSpeedModifier(2D);\r\n                        }\r\n                    }\r\n                }\r\n                else if (!inRadius && !Utilities.checkLocation(targetLocation, follower.getLocation(), lead)) {\r\n                    path = followerNavigation.createPath(targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(), 0);\r\n                    if (path != null) {\r\n                        followerNavigation.moveTo(path, 1D);\r\n                        followerNavigation.setSpeedModifier(2D);\r\n                    }\r\n                }\r\n                else {\r\n                    inRadius = true;\r\n                }\r\n                if (inRadius && !allowWander) {\r\n                    followerNavigation.stop();\r\n                }\r\n                nmsFollower.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n        }.runTaskTimer(NMSHandler.getJavaPlugin(), 0, 10));\r\n    }\r\n\r\n    @Override\r\n    public void walkTo(final LivingEntity entity, Location location, Double speed, final Runnable callback) {\r\n        if (entity == null || location == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        if (!(nmsEntity instanceof final Mob nmsMob)) {\r\n            return;\r\n        }\r\n        final PathNavigation entityNavigation = nmsMob.getNavigation();\r\n        final Path path;\r\n        final boolean aiDisabled = !entity.hasAI();\r\n        if (aiDisabled) {\r\n            entity.setAI(true);\r\n            try {\r\n                ENTITY_ONGROUND_SETTER.invoke(nmsMob, true);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        path = entityNavigation.createPath(location.getX(), location.getY(), location.getZ(), 1);\r\n        if (path != null) {\r\n            nmsMob.goalSelector.enableControlFlag(Goal.Flag.MOVE);\r\n            entityNavigation.moveTo(path, 1D);\r\n            final double oldSpeed = nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).getBaseValue();\r\n            if (speed != null) {\r\n                nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(speed);\r\n            }\r\n            new BukkitRunnable() {\r\n                @Override\r\n                public void run() {\r\n                    if (!entity.isValid()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        cancel();\r\n                        return;\r\n                    }\r\n                    if (aiDisabled && entity instanceof Wolf wolf) {\r\n                        wolf.setAngry(false);\r\n                    }\r\n                    if (entityNavigation.isDone() || path.isDone()) {\r\n                        if (callback != null) {\r\n                            callback.run();\r\n                        }\r\n                        if (speed != null) {\r\n                            nmsMob.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(oldSpeed);\r\n                        }\r\n                        if (aiDisabled) {\r\n                            entity.setAI(false);\r\n                        }\r\n                        cancel();\r\n                    }\r\n                }\r\n            }.runTaskTimer(NMSHandler.getJavaPlugin(), 1, 1);\r\n        }\r\n        //if (!Utilities.checkLocation(location, entity.getLocation(), 20)) {\r\n        // TODO: generate waypoints to the target location?\r\n        else {\r\n            entity.teleport(location);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendAllUpdatePackets(Entity entity) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level()).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        if (entityTracker == null) {\r\n            return;\r\n        }\r\n        try {\r\n            ServerEntity serverEntity = (ServerEntity) PacketHelperImpl.ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n            serverEntity.sendChanges();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    /*\r\n        Hide Entity\r\n     */\r\n\r\n    @Override\r\n    public void sendHidePacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player player) {\r\n            pl.hidePlayer(Denizen.getInstance(), player);\r\n            return;\r\n        }\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) pl).getHandle();\r\n        if (nmsPlayer.connection != null && !pl.equals(entity)) {\r\n            ChunkMap.TrackedEntity entry = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                entry.removePlayer(nmsPlayer);\r\n            }\r\n            if (Denizen.supportsPaper) { // Workaround for Paper issue\r\n                nmsPlayer.connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId()));\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendShowPacket(Player pl, Entity entity) {\r\n        if (entity instanceof Player player) {\r\n            pl.showPlayer(Denizen.getInstance(), player);\r\n            return;\r\n        }\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) pl).getHandle();\r\n        if (nmsPlayer.connection != null && !pl.equals(entity)) {\r\n            ChunkMap.TrackedEntity entry = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                entry.removePlayer(nmsPlayer);\r\n                entry.updatePlayer(nmsPlayer);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void rotate(Entity entity, float yaw, float pitch) {\r\n        // If this entity is a real player instead of a player type NPC,\r\n        // it will appear to be online\r\n        if (entity instanceof Player player && player.isOnline()) {\r\n            NetworkInterceptHelper.enable();\r\n            float relYaw = (yaw - entity.getLocation().getYaw()) % 360;\r\n            if (relYaw > 180) {\r\n                relYaw -= 360;\r\n            }\r\n            final float actualRelYaw = relYaw;\r\n            float relPitch = pitch - entity.getLocation().getPitch();\r\n            NMSHandler.packetHelper.sendRelativeLookPacket(player, actualRelYaw, relPitch);\r\n        }\r\n        else if (entity instanceof LivingEntity) {\r\n            if (entity instanceof EnderDragon) {\r\n                yaw = normalizeYaw(yaw - 180);\r\n            }\r\n            look(entity, yaw, pitch);\r\n        }\r\n        else {\r\n            net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n            handle.setYRot(yaw - 360);\r\n            handle.setXRot(pitch);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public float getBaseYaw(LivingEntity entity) {\r\n        return ((CraftLivingEntity) entity).getHandle().yBodyRot;\r\n    }\r\n\r\n    @Override\r\n    public void look(Entity entity, float yaw, float pitch) {\r\n        net.minecraft.world.entity.Entity handle = ((CraftEntity) entity).getHandle();\r\n        if (handle == null) {\r\n            Debug.echoError(\"Cannot set look direction for unspawned entity \" + entity.getUniqueId());\r\n            return;\r\n        }\r\n        handle.setYRot(yaw);\r\n        if (handle instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity) {\r\n            while (yaw < -180.0F) {\r\n                yaw += 360.0F;\r\n            }\r\n            while (yaw >= 180.0F) {\r\n                yaw -= 360.0F;\r\n            }\r\n            nmsLivingEntity.yBodyRotO = yaw;\r\n            if (!(handle instanceof net.minecraft.world.entity.player.Player)) {\r\n                nmsLivingEntity.setYBodyRot(yaw);\r\n            }\r\n            nmsLivingEntity.setYHeadRot(yaw);\r\n        }\r\n        handle.setXRot(pitch);\r\n    }\r\n\r\n    private static HitResult rayTrace(World world, Vector start, Vector end) {\r\n        try {\r\n            NMSHandler.chunkHelper.changeChunkServerThread(world);\r\n            return ((CraftWorld) world).getHandle().clip(new ClipContext(new Vec3(start.getX(), start.getY(), start.getZ()),\r\n                    new Vec3(end.getX(), end.getY(), end.getZ()),\r\n                    ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, CollisionContext.empty()));\r\n        }\r\n        finally {\r\n            NMSHandler.chunkHelper.restoreServerThread(world);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean canTrace(World world, Vector start, Vector end) {\r\n        HitResult pos = rayTrace(world, start, end);\r\n        if (pos == null) {\r\n            return true;\r\n        }\r\n        return pos.getType() == HitResult.Type.MISS;\r\n    }\r\n\r\n    @Override\r\n    public void snapPositionTo(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().setPosRaw(vector.getX(), vector.getY(), vector.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void move(Entity entity, Vector vector) {\r\n        ((CraftEntity) entity).getHandle().move(MoverType.SELF, new Vec3(vector.getX(), vector.getY(), vector.getZ()));\r\n    }\r\n\r\n    @Override\r\n    public boolean internalLook(Player player, Location at) {\r\n        PacketHelperImpl.send(player, new ClientboundPlayerLookAtPacket(EntityAnchorArgument.Anchor.EYES, at.getX(), at.getY(), at.getZ()));\r\n        return true;\r\n    }\r\n\r\n    public static long entityToPacket(double x) {\r\n        return Mth.lfloor(x * 4096.0D);\r\n    }\r\n\r\n    @Override\r\n    public void fakeMove(Entity entity, Vector vector) {\r\n        long x = entityToPacket(vector.getX());\r\n        long y = entityToPacket(vector.getY());\r\n        long z = entityToPacket(vector.getZ());\r\n        ClientboundMoveEntityPacket packet = new ClientboundMoveEntityPacket.Pos(entity.getEntityId(), (short) x, (short) y, (short) z, entity.isOnGround());\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void fakeTeleport(Entity entity, Location location) {\r\n        FriendlyByteBuf packetData = new FriendlyByteBuf(Unpooled.buffer());\r\n        // Referenced from ClientboundTeleportEntityPacket source\r\n        packetData.writeVarInt(entity.getEntityId());\r\n        packetData.writeDouble(location.getX());\r\n        packetData.writeDouble(location.getY());\r\n        packetData.writeDouble(location.getZ());\r\n        packetData.writeByte((byte)((int)(location.getYaw() * 256.0F / 360.0F)));\r\n        packetData.writeByte((byte)((int)(location.getPitch() * 256.0F / 360.0F)));\r\n        packetData.writeBoolean(entity.isOnGround());\r\n        ClientboundTeleportEntityPacket packet = ClientboundTeleportEntityPacket.STREAM_CODEC.decode(packetData);\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void clientResetLoc(Entity entity) {\r\n        ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(entity.getEntityId(), PositionMoveRotation.of(((CraftEntity) entity).getHandle()), Set.of(), entity.isOnGround());\r\n        for (Player player : getPlayersThatSee(entity)) {\r\n            PacketHelperImpl.send(player, packet);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Entity entity, Location loc) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        nmsEntity.setYRot(loc.getYaw());\r\n        nmsEntity.setXRot(loc.getPitch());\r\n        if (nmsEntity instanceof ServerPlayer) {\r\n            nmsEntity.teleportTo(loc.getX(), loc.getY(), loc.getZ());\r\n        }\r\n        nmsEntity.setPos(loc.getX(), loc.getY(), loc.getZ());\r\n    }\r\n\r\n    @Override\r\n    public void setBoundingBox(Entity entity, BoundingBox box) {\r\n        ((CraftEntity) entity).getHandle().setBoundingBox(new AABB(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ()));\r\n    }\r\n\r\n    public static final Field EXPERIENCE_ORB_AGE = ReflectionHelper.getFields(net.minecraft.world.entity.ExperienceOrb.class).get(\"age\", int.class);\r\n\r\n    @Override\r\n    public void setTicksLived(Entity entity, int ticks) {\r\n        // Bypass Spigot's must-be-at-least-1-tick requirement, as negative tick counts are useful\r\n        ((CraftEntity) entity).getHandle().tickCount = ticks;\r\n        if (entity instanceof CraftFallingBlock craftFallingBlock) {\r\n            craftFallingBlock.getHandle().time = ticks;\r\n        }\r\n        else if (entity instanceof CraftItem craftItem) {\r\n            ((ItemEntity) craftItem.getHandle()).age = ticks;\r\n        }\r\n        else if (entity instanceof CraftExperienceOrb craftExperienceOrb) {\r\n            try {\r\n                EXPERIENCE_ORB_AGE.setInt(craftExperienceOrb.getHandle(), ticks);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHeadAngle(LivingEntity entity, float angle) {\r\n        ((CraftLivingEntity) entity).getHandle().setYHeadRot(angle);\r\n    }\r\n\r\n    @Override\r\n    public void setEndermanAngry(Enderman enderman, boolean angry) {\r\n        ((CraftEnderman) enderman).getHandle().getEntityData().set(ENDERMAN_DATA_ACCESSOR_SCREAMING, angry);\r\n    }\r\n\r\n    public static class FakeDamageSrc extends DamageSource { public DamageSource real; public FakeDamageSrc(DamageSource src) { super(null); real = src; } }\r\n\r\n    public static DamageSources backupDamageSources;\r\n\r\n    public static DamageSources getReusableDamageSources() {\r\n        if (backupDamageSources == null) {\r\n            backupDamageSources = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle().damageSources();\r\n        }\r\n        return backupDamageSources;\r\n    }\r\n\r\n    public static DamageSource getSourceFor(net.minecraft.world.entity.Entity nmsSource, EntityDamageEvent.DamageCause cause, net.minecraft.world.entity.Entity nmsSourceProvider) {\r\n        DamageSources sources = nmsSourceProvider == null ? getReusableDamageSources() : nmsSourceProvider.level().damageSources();\r\n        DamageSource src = sources.generic();\r\n        if (nmsSource != null) {\r\n            if (nmsSource instanceof net.minecraft.world.entity.player.Player nmsPlayer) {\r\n                src = nmsSource.level().damageSources().playerAttack(nmsPlayer);\r\n            }\r\n            else if (nmsSource instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity) {\r\n                src = nmsSource.level().damageSources().mobAttack(nmsLivingEntity);\r\n            }\r\n        }\r\n        if (cause == null) {\r\n            return src;\r\n        }\r\n        return switch (cause) {\r\n            case CONTACT -> sources.cactus();\r\n            case ENTITY_ATTACK -> sources.mobAttack(nmsSource instanceof net.minecraft.world.entity.LivingEntity nmsLivingEntity ? nmsLivingEntity : null);\r\n            case ENTITY_SWEEP_ATTACK -> src != sources.generic() ? src.sweep() : src;\r\n            case PROJECTILE -> sources.thrown(nmsSource, nmsSource != null && nmsSource.getBukkitEntity() instanceof Projectile projectile\r\n                        && projectile.getShooter() instanceof CraftEntity shooter ? shooter.getHandle() : null);\r\n            case SUFFOCATION -> sources.inWall();\r\n            case FALL -> sources.fall();\r\n            case FIRE -> sources.inFire();\r\n            case FIRE_TICK -> sources.onFire();\r\n            case MELTING -> sources.melting();\r\n            case LAVA -> sources.lava();\r\n            case DROWNING -> sources.drown();\r\n            case BLOCK_EXPLOSION -> nmsSource instanceof PrimedTnt primedTnt ? sources.explosion(primedTnt, primedTnt.getOwner()) : sources.explosion(null);\r\n            case ENTITY_EXPLOSION -> sources.explosion(nmsSource, null);\r\n            case VOID -> sources.fellOutOfWorld();\r\n            case LIGHTNING -> sources.lightningBolt();\r\n            case STARVATION -> sources.starve();\r\n            case POISON -> sources.poison();\r\n            case MAGIC -> sources.magic();\r\n            case WITHER -> sources.wither();\r\n            case FALLING_BLOCK -> sources.fallingBlock(nmsSource);\r\n            case THORNS -> sources.thorns(nmsSource);\r\n            case DRAGON_BREATH -> sources.dragonBreath();\r\n            case CUSTOM -> sources.generic();\r\n            case FLY_INTO_WALL -> sources.flyIntoWall();\r\n            case HOT_FLOOR -> sources.hotFloor();\r\n            case CAMPFIRE -> sources.campfire();\r\n            case CRAMMING -> sources.cramming();\r\n            case DRYOUT -> sources.dryOut();\r\n            case FREEZE -> sources.freeze();\r\n            case SONIC_BOOM -> sources.sonicBoom(nmsSource);\r\n            case WORLD_BORDER -> sources.outOfBorder();\r\n            case KILL -> sources.genericKill();\r\n            case SUICIDE -> new FakeDamageSrc(src);\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void damage(LivingEntity target, float amount, EntityTag source, Location sourceLoc, EntityDamageEvent.DamageCause cause) {\r\n        if (target == null) {\r\n            return;\r\n        }\r\n        net.minecraft.world.entity.LivingEntity nmsTarget = ((CraftLivingEntity) target).getHandle();\r\n        net.minecraft.world.entity.Entity nmsSource = source == null ? null : ((CraftEntity) source.getBukkitEntity()).getHandle();\r\n        DamageSource src = getSourceFor(nmsSource, cause, nmsTarget);\r\n        if (src instanceof FakeDamageSrc fakeDamageSrc) {\r\n            src = fakeDamageSrc.real;\r\n            if (fireFakeDamageEvent(target, source, sourceLoc, cause, amount).isCancelled()) {\r\n                return;\r\n            }\r\n        }\r\n        nmsTarget.hurt(src, amount);\r\n    }\r\n\r\n    @Override\r\n    public void setLastHurtBy(LivingEntity mob, LivingEntity damager) {\r\n        ((CraftLivingEntity) mob).getHandle().setLastHurtByMob(((CraftLivingEntity) damager).getHandle());\r\n    }\r\n\r\n    public static final Field FALLINGBLOCK_BLOCK_STATE = ReflectionHelper.getFields(FallingBlockEntity.class).getFirstOfType(BlockState.class);\r\n\r\n    @Override\r\n    public void setFallingBlockType(FallingBlock fallingBlock, BlockData block) {\r\n        BlockState state = ((CraftBlockData) block).getState();\r\n        FallingBlockEntity nmsEntity = ((CraftFallingBlock) fallingBlock).getHandle();\r\n        try {\r\n            FALLINGBLOCK_BLOCK_STATE.set(nmsEntity, state);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public EntityTag getMobSpawnerDisplayEntity(CreatureSpawner spawner) {\r\n        SpawnerBlockEntity nmsSpawner = BlockHelperImpl.getTE((CraftCreatureSpawner) spawner);\r\n        ServerLevel level = ((CraftWorld) spawner.getWorld()).getHandle();\r\n        net.minecraft.world.entity.Entity nmsEntity = nmsSpawner.getSpawner().getOrCreateDisplayEntity(level, nmsSpawner.getBlockPos());\r\n        return new EntityTag(nmsEntity.getBukkitEntity());\r\n    }\r\n\r\n    public static final Field ZOMBIE_INWATERTIME = ReflectionHelper.getFields(net.minecraft.world.entity.monster.zombie.Zombie.class).get(\"inWaterTime\", int.class);\r\n\r\n    @Override\r\n    public int getInWaterTime(Zombie zombie) {\r\n        try {\r\n            return ZOMBIE_INWATERTIME.getInt(((CraftZombie) zombie).getHandle());\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return 0;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setInWaterTime(Zombie zombie, int ticks) {\r\n        try {\r\n            ZOMBIE_INWATERTIME.setInt(((CraftZombie) zombie).getHandle(), ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final MethodHandle TRACKING_RANGE_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ChunkMap.TrackedEntity.class, int.class);\r\n\r\n    @Override\r\n    public void setTrackingRange(Entity entity, int range) {\r\n        try {\r\n            ChunkMap map = ((CraftWorld) entity.getWorld()).getHandle().getChunkSource().chunkMap;\r\n            ChunkMap.TrackedEntity entry = map.entityMap.get(entity.getEntityId());\r\n            if (entry != null) {\r\n                TRACKING_RANGE_SETTER.invoke(entry, range);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean isAggressive(org.bukkit.entity.Mob mob) {\r\n        return ((CraftMob) mob).getHandle().isAggressive();\r\n    }\r\n\r\n    @Override\r\n    public void setAggressive(org.bukkit.entity.Mob mob, boolean aggressive) {\r\n        ((CraftMob) mob).getHandle().setAggressive(aggressive);\r\n    }\r\n\r\n    // Use reflection because Paper changes the method return type\r\n    public static final MethodHandle PLAYERLIST_REMOVE = ReflectionHelper.getMethodHandle(PlayerList.class, \"remove\", ServerPlayer.class);\r\n\r\n    @Override\r\n    public void setUUID(Entity entity, UUID id) {\r\n        try {\r\n            net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n            nmsEntity.stopRiding();\r\n            nmsEntity.getPassengers().forEach(net.minecraft.world.entity.Entity::stopRiding);\r\n            Level level = nmsEntity.level();\r\n            DedicatedPlayerList playerList = ((CraftServer) Bukkit.getServer()).getHandle();\r\n            if (nmsEntity instanceof ServerPlayer nmsPlayer) {\r\n                PLAYERLIST_REMOVE.invoke(playerList, nmsPlayer);\r\n            }\r\n            else {\r\n                nmsEntity.remove(net.minecraft.world.entity.Entity.RemovalReason.DISCARDED);\r\n            }\r\n            nmsEntity.unsetRemoved();\r\n            nmsEntity.setUUID(id);\r\n            if (nmsEntity instanceof ServerPlayer nmsPlayer) {\r\n                playerList.placeNewPlayer(DenizenNetworkManagerImpl.getConnection(nmsPlayer), nmsPlayer, new CommonListenerCookie(nmsPlayer.getGameProfile(), nmsPlayer.connection.latency(), nmsPlayer.clientInformation(), nmsPlayer.connection.isTransferred()));\r\n            }\r\n            else {\r\n                level.addFreshEntity(nmsEntity);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static final Field SynchedEntityData_itemsById = ReflectionHelper.getFields(SynchedEntityData.class).get(\"itemsById\");\r\n\r\n    public static Int2ObjectMap<SynchedEntityData.DataItem<Object>> getDataItems(Entity entity) {\r\n        try {\r\n            return (Int2ObjectMap<SynchedEntityData.DataItem<Object>>) SynchedEntityData_itemsById.get(((CraftEntity) entity).getHandle().getEntityData());\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            throw new RuntimeException(e); // Stop the code here to avoid NPEs down the road\r\n        }\r\n    }\r\n\r\n    public static void convertToInternalData(Entity entity, MapTag internalData, BiConsumer<SynchedEntityData.DataItem<Object>, Object> processConverted) {\r\n        Int2ObjectMap<SynchedEntityData.DataItem<Object>> dataItemsById = getDataItems(entity);\r\n        for (Map.Entry<StringHolder, ObjectTag> entry : internalData.entrySet()) {\r\n            int id = EntityDataNameMapper.getIdForName(((CraftEntity) entity).getHandle().getClass(), entry.getKey().low);\r\n            if (id == -1) {\r\n                Debug.echoError(\"Invalid internal data key: \" + entry.getKey());\r\n                return;\r\n            }\r\n            SynchedEntityData.DataItem<Object> dataItem = dataItemsById.get(id);\r\n            if (dataItem == null) {\r\n                Debug.echoError(\"Invalid internal data id '\" + id + \"': couldn't be matched to any internal data for entity of type '\" + entity.getType() + \"'.\");\r\n                return;\r\n            }\r\n            Object converted = ReflectionSetCommand.convertObjectTypeFor(dataItem.getValue().getClass(), entry.getValue());\r\n            if (converted != null) {\r\n                processConverted.accept(dataItem, converted);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public List<Object> convertInternalEntityDataValues(Entity entity, MapTag internalData) {\r\n        List<Object> dataValues = new ArrayList<>(internalData.size());\r\n        convertToInternalData(entity, internalData, (dataItem, converted) -> dataValues.add(PacketHelperImpl.createEntityData(dataItem.getAccessor(), converted)));\r\n        return dataValues;\r\n    }\r\n\r\n    @Override\r\n    public void modifyInternalEntityData(Entity entity, MapTag internalData) {\r\n        SynchedEntityData nmsEntityData = ((CraftEntity) entity).getHandle().getEntityData();\r\n        convertToInternalData(entity, internalData, (dataItem, converted) -> nmsEntityData.set(dataItem.getAccessor(), converted));\r\n    }\r\n\r\n    @Override\r\n    public void startUsingItem(LivingEntity entity, EquipmentSlot hand) {\r\n        ((CraftLivingEntity) entity).getHandle().startUsingItem(hand == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND);\r\n    }\r\n\r\n    @Override\r\n    public void stopUsingItem(LivingEntity entity) {\r\n        ((CraftLivingEntity) entity).getHandle().stopUsingItem();\r\n    }\r\n\r\n    @Override\r\n    public void openHorseInventory(Player player, AbstractHorse horse) {\r\n        net.minecraft.world.entity.animal.equine.AbstractHorse nmsHorse = ((CraftAbstractHorse) horse).getHandle();\r\n        ((CraftPlayer) player).getHandle().openHorseInventory(nmsHorse, nmsHorse.inventory);\r\n    }\r\n\r\n    private CompoundTag getRawEntityNBT(net.minecraft.world.entity.Entity entity) {\r\n        return Handler.useValueOutput(entity::saveWithoutId);\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getRawNBT(Entity entity) {\r\n        return NBTAdapter.toAPI(getRawEntityNBT(((CraftEntity) entity).getHandle()));\r\n    }\r\n\r\n    @Override\r\n    public void modifyRawNBT(Entity entity, CompoundBinaryTag tag) {\r\n        net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();\r\n        CompoundTag nmsTag = NBTAdapter.toNMS(tag);\r\n        CompoundTag nmsMergedTag = getRawEntityNBT(nmsEntity).merge(nmsTag);\r\n        UUID uuid = nmsEntity.getUUID();\r\n        Handler.useValueInput(nmsMergedTag, nmsEntity::load);\r\n        nmsEntity.setUUID(uuid);\r\n    }\r\n\r\n    @Override\r\n    public EntityState.ArmadilloState getArmadilloState(org.bukkit.entity.Armadillo entity) {\r\n        Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle();\r\n        return switch (armadillo.getState()) {\r\n            case IDLE -> EntityState.ArmadilloState.IDLE;\r\n            case ROLLING -> EntityState.ArmadilloState.ROLLING;\r\n            case SCARED -> EntityState.ArmadilloState.SCARED;\r\n            case UNROLLING -> EntityState.ArmadilloState.UNROLLING;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void setArmadilloState(org.bukkit.entity.Armadillo entity, EntityState.ArmadilloState state) {\r\n        Armadillo armadillo = (Armadillo) ((CraftEntity) entity).getHandle();\r\n        armadillo.switchToState(switch (state) {\r\n            case IDLE -> Armadillo.ArmadilloState.IDLE;\r\n            case ROLLING -> Armadillo.ArmadilloState.ROLLING;\r\n            case SCARED -> Armadillo.ArmadilloState.SCARED;\r\n            case UNROLLING -> Armadillo.ArmadilloState.UNROLLING;\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/FishingHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FishingHelper;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.Mth;\r\nimport net.minecraft.world.entity.projectile.FishingHook;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport net.minecraft.world.item.enchantment.EnchantmentHelper;\r\nimport net.minecraft.world.level.storage.loot.BuiltInLootTables;\r\nimport net.minecraft.world.level.storage.loot.LootParams;\r\nimport net.minecraft.world.level.storage.loot.LootTable;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;\r\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParams;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.entity.CraftFishHook;\r\nimport org.bukkit.craftbukkit.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.inventory.CraftItemStack;\r\nimport org.bukkit.entity.FishHook;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\nimport java.lang.reflect.Field;\r\nimport java.util.List;\r\n\r\npublic class FishingHelperImpl implements FishingHelper {\r\n\r\n    @Override\r\n    public org.bukkit.inventory.ItemStack getResult(FishHook fishHook, CatchType catchType) {\r\n        FishingHook nmsHook = ((CraftFishHook) fishHook).getHandle();\r\n        ItemStack result = switch (catchType) {\r\n            case DEFAULT -> {\r\n                ServerLevel nmsWorld = ((CraftWorld) fishHook.getWorld()).getHandle();\r\n                ItemStack nmsFishingRod = nmsHook.getPlayerOwner().getMainHandItem();\r\n                float f = nmsWorld.getRandom().nextFloat();\r\n                float i = EnchantmentHelper.getFishingTimeReduction(nmsWorld, nmsFishingRod, nmsHook.getPlayerOwner());\r\n                int j = EnchantmentHelper.getFishingLuckBonus(nmsWorld, nmsFishingRod, nmsHook.getPlayerOwner());\r\n                float f1 = 0.1F - i * 0.025F - (float) j * 0.01F;\r\n                float f2 = 0.05F + i * 0.01F - (float) j * 0.01F;\r\n\r\n                f1 = Mth.clamp(f1, 0.0F, 1.0F);\r\n                f2 = Mth.clamp(f2, 0.0F, 1.0F);\r\n                if (f < f1) {\r\n                    yield catchRandomJunk(nmsHook);\r\n                }\r\n                else {\r\n                    f -= f1;\r\n                    if (f < f2) {\r\n                        yield catchRandomTreasure(nmsHook);\r\n                    }\r\n                    else {\r\n                        yield catchRandomFish(nmsHook);\r\n                    }\r\n                }\r\n            }\r\n            case JUNK -> catchRandomJunk(nmsHook);\r\n            case TREASURE -> catchRandomTreasure(nmsHook);\r\n            case FISH -> catchRandomFish(nmsHook);\r\n            default -> null;\r\n        };\r\n        return result != null ? CraftItemStack.asBukkitCopy(result) : null;\r\n    }\r\n\r\n    public ItemStack getRandomReward(FishingHook nmsHook, ResourceKey<LootTable> key) {\r\n        ServerLevel nmsWorld = (ServerLevel) nmsHook.level();\r\n        LootParams nmsLootParams = new LootParams.Builder(nmsWorld)\r\n                .withParameter(LootContextParams.ORIGIN, new Vec3(nmsHook.getX(), nmsHook.getY(), nmsHook.getZ()))\r\n                .withParameter(LootContextParams.TOOL, new ItemStack(Items.FISHING_ROD))\r\n                .create(LootContextParamSets.FISHING);\r\n        List<ItemStack> nmsItems = nmsWorld.getServer().reloadableRegistries().getLootTable(key).getRandomItems(nmsLootParams);\r\n        return nmsItems.get(nmsWorld.getRandom().nextInt(nmsItems.size()));\r\n    }\r\n\r\n    @Override\r\n    public FishHook spawnHook(Location location, Player player) {\r\n        ServerLevel nmsWorld = ((CraftWorld) location.getWorld()).getHandle();\r\n        FishingHook hook = new FishingHook(((CraftPlayer) player).getHandle(), nmsWorld, 0, 0);\r\n        nmsWorld.addFreshEntity(hook, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        return (FishHook) hook.getBukkitEntity();\r\n    }\r\n\r\n    private ItemStack catchRandomJunk(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_JUNK);\r\n    }\r\n\r\n    private ItemStack catchRandomTreasure(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_TREASURE);\r\n    }\r\n\r\n    private ItemStack catchRandomFish(FishingHook fishHook) {\r\n        return getRandomReward(fishHook, BuiltInLootTables.FISHING_FISH);\r\n    }\r\n\r\n    public static final Field FISHING_HOOK_NIBBLE = ReflectionHelper.getFields(FishingHook.class).get(\"nibble\", int.class);\r\n    public static final Field FISHING_HOOK_LURE_TIME = ReflectionHelper.getFields(FishingHook.class).get(\"timeUntilLured\", int.class);\r\n    public static final Field FISHING_HOOK_HOOK_TIME = ReflectionHelper.getFields(FishingHook.class).get(\"timeUntilHooked\", int.class);\r\n\r\n    @Override\r\n    public FishHook getHookFrom(Player player) {\r\n        FishingHook nmsHook = ((CraftPlayer) player).getHandle().fishing;\r\n        if (nmsHook == null) {\r\n            return null;\r\n        }\r\n        return (FishHook) nmsHook.getBukkitEntity();\r\n    }\r\n\r\n    @Override\r\n    public void setNibble(FishHook hook, int ticks) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_NIBBLE.setInt(nmsHook, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHookTime(FishHook hook, int ticks) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_HOOK_TIME.setInt(nmsHook, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int getLureTime(FishHook hook) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            return FISHING_HOOK_LURE_TIME.getInt(nmsHook);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public void setLureTime(FishHook hook, int ticks) {\r\n        FishingHook nmsHook = ((CraftFishHook) hook).getHandle();\r\n        try {\r\n            FISHING_HOOK_LURE_TIME.setInt(nmsHook, ticks);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/ItemHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.interfaces.ItemHelper;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemComponentsPatch;\r\nimport com.denizenscript.denizen.objects.properties.item.ItemRawNBT;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.PaperAPITools;\r\nimport com.denizenscript.denizencore.objects.core.ElementTag;\r\nimport com.denizenscript.denizencore.objects.core.MapTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.*;\r\nimport com.google.gson.JsonObject;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.mojang.serialization.Dynamic;\r\nimport com.mojang.serialization.JsonOps;\r\nimport net.kyori.adventure.nbt.BinaryTag;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.advancements.criterion.BlockPredicate;\r\nimport net.minecraft.advancements.criterion.DataComponentMatchers;\r\nimport net.minecraft.core.*;\r\nimport net.minecraft.core.component.DataComponentMap;\r\nimport net.minecraft.core.component.DataComponentPatch;\r\nimport net.minecraft.core.component.DataComponentType;\r\nimport net.minecraft.core.component.DataComponents;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.nbt.NbtOps;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.resources.Identifier;\r\nimport net.minecraft.resources.RegistryOps;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.util.datafix.fixes.References;\r\nimport net.minecraft.world.flag.FeatureFlagSet;\r\nimport net.minecraft.world.item.AdventureModePredicate;\r\nimport net.minecraft.world.item.BlockItem;\r\nimport net.minecraft.world.item.Item;\r\nimport net.minecraft.world.item.ItemStackTemplate;\r\nimport net.minecraft.world.item.alchemy.PotionBrewing;\r\nimport net.minecraft.world.item.component.CustomData;\r\nimport net.minecraft.world.item.component.ItemLore;\r\nimport net.minecraft.world.item.component.ResolvableProfile;\r\nimport net.minecraft.world.item.component.TypedEntityData;\r\nimport net.minecraft.world.item.crafting.*;\r\nimport net.minecraft.world.item.crafting.BlastingRecipe;\r\nimport net.minecraft.world.item.crafting.CraftingRecipe;\r\nimport net.minecraft.world.item.crafting.Recipe;\r\nimport net.minecraft.world.item.crafting.ShapelessRecipe;\r\nimport net.minecraft.world.item.crafting.SmithingTransformRecipe;\r\nimport net.minecraft.world.item.crafting.SmokingRecipe;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.levelgen.Heightmap;\r\nimport net.minecraft.world.level.material.FluidState;\r\nimport net.minecraft.world.level.material.MapColor;\r\nimport net.minecraft.world.level.saveddata.maps.MapId;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.data.BlockData;\r\nimport org.bukkit.craftbukkit.CraftRegistry;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.block.data.CraftBlockData;\r\nimport org.bukkit.craftbukkit.entity.CraftEntityType;\r\nimport org.bukkit.craftbukkit.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.inventory.*;\r\nimport org.bukkit.craftbukkit.map.CraftMapView;\r\nimport org.bukkit.craftbukkit.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.*;\r\nimport org.bukkit.inventory.ShapedRecipe;\r\nimport org.bukkit.inventory.SmithingTrimRecipe;\r\nimport org.bukkit.inventory.TransmuteRecipe;\r\nimport org.bukkit.map.MapView;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.function.Consumer;\r\nimport java.util.function.Predicate;\r\n\r\npublic class ItemHelperImpl extends ItemHelper {\r\n\r\n    public static final Recipe.CommonInfo BASE_RECIPE_INFO = new Recipe.CommonInfo(true);\r\n\r\n    public static net.minecraft.world.item.crafting.RecipeHolder<?> getNMSRecipe(NamespacedKey key) {\r\n        ResourceKey<Recipe<?>> nmsKey = ResourceKey.create(Registries.RECIPE, CraftNamespacedKey.toMinecraft(key));\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager().byKey(nmsKey).orElse(null);\r\n    }\r\n\r\n    public static final MethodHandle CRAFT_ITEM_STACK_AS_TEMPLATE = Handler.reflectPaperRenamed(CraftItemStack.class, \"asNMSTemplate\", \"asTemplate\", ItemStack.class);\r\n\r\n    public static ItemStackTemplate asNMSTemplate(ItemStack item) {\r\n        try {\r\n            return (ItemStackTemplate) CRAFT_ITEM_STACK_AS_TEMPLATE.invokeExact(item);\r\n        }\r\n        catch (Throwable e) {\r\n            throw new RuntimeException(e);\r\n        }\r\n    }\r\n\r\n    public static final Field RecipeManager_featureFlagSet = ReflectionHelper.getFields(RecipeManager.class).getFirstOfType(FeatureFlagSet.class);\r\n\r\n    public void setMaxStackSize(Material material, int size) {\r\n        try {\r\n            ReflectionHelper.getFinalSetter(Material.class, \"maxStack\").invoke(material, size);\r\n            Holder.Reference<Item> nmsItemHolder = BuiltInRegistries.ITEM.get(CraftNamespacedKey.toMinecraft(material.getKey())).orElseThrow();\r\n            nmsItemHolder.bindComponents(DataComponentMap.composite(nmsItemHolder.components(), DataComponentMap.builder().set(DataComponents.MAX_STACK_SIZE, size).build()));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static RecipeManager getRecipeManager() {\r\n        return ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager();\r\n    }\r\n\r\n    public static CompoundTag serializeNmsItem(net.minecraft.world.item.ItemStack nmsItem) {\r\n        return (CompoundTag) net.minecraft.world.item.ItemStack.CODEC.encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsItem).getOrThrow();\r\n    }\r\n\r\n    public static net.minecraft.world.item.ItemStack parseNmsItem(CompoundTag nmsTag) {\r\n        return net.minecraft.world.item.ItemStack.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), nmsTag).getOrThrow();\r\n    }\r\n\r\n    public Object recipeManagerFeatureFlagSetCache = null;\r\n\r\n    @Override\r\n    public void blockRecipeFinalization() {\r\n        try {\r\n            RecipeManager manager = getRecipeManager();\r\n            Object flags = RecipeManager_featureFlagSet.get(manager);\r\n            if (flags != null) {\r\n                recipeManagerFeatureFlagSetCache = flags;\r\n                RecipeManager_featureFlagSet.set(manager, null);\r\n\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void restoreRecipeFinalization() {\r\n        try {\r\n            RecipeManager manager = getRecipeManager();\r\n            if (recipeManagerFeatureFlagSetCache != null) {\r\n                RecipeManager_featureFlagSet.set(manager, recipeManagerFeatureFlagSetCache);\r\n                manager.finalizeRecipeLoading();\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void removeRecipes(List<NamespacedKey> keys) {\r\n        blockRecipeFinalization();\r\n        RecipeManager manager = getRecipeManager();\r\n        for (NamespacedKey key: keys) {\r\n            ResourceKey<Recipe<?>> nmsKey = ResourceKey.create(Registries.RECIPE, CraftNamespacedKey.toMinecraft(key));\r\n            manager.removeRecipe(nmsKey);\r\n        }\r\n        restoreRecipeFinalization();\r\n    }\r\n\r\n    @Override\r\n    public Integer burnTime(Material material) {\r\n        return MinecraftServer.getServer().fuelValues().burnDuration(new net.minecraft.world.item.ItemStack(CraftMagicNumbers.getItem(material)));\r\n    }\r\n\r\n    @Override\r\n    public void setShapedRecipeIngredient(ShapedRecipe recipe, char c, ItemStack[] item, boolean exact) {\r\n        if (item.length == 1 && item[0].getType() == Material.AIR) {\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(Material.AIR));\r\n        }\r\n        else if (exact) {\r\n            recipe.setIngredient(c, new RecipeChoice.ExactChoice(item));\r\n        }\r\n        else {\r\n            Material[] mats = new Material[item.length];\r\n            for (int i = 0; i < item.length; i++) {\r\n                mats[i] = item[i].getType();\r\n            }\r\n            recipe.setIngredient(c, new RecipeChoice.MaterialChoice(mats));\r\n        }\r\n    }\r\n\r\n    // TODO: Recipe registration should be moved to the API\r\n    public static Ingredient itemArrayToRecipe(ItemStack[] items, boolean exact) {\r\n        if (!exact) {\r\n            return Ingredient.of(Arrays.stream(items).map(item -> CraftMagicNumbers.getItem(item.getType())));\r\n        }\r\n        return Ingredient.ofStacks(Arrays.stream(items).map(CraftItemStack::asNMSCopy).toList());\r\n    }\r\n\r\n    public static ResourceKey<Recipe<?>> createRecipeKey(String name) {\r\n        return ResourceKey.create(Registries.RECIPE, Identifier.fromNamespaceAndPath(\"denizen\", name));\r\n    }\r\n\r\n    @Override\r\n    public void registerFurnaceRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, float exp, int time, String type, boolean exact, String category) {\r\n        ResourceKey<Recipe<?>> key = createRecipeKey(keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        AbstractCookingRecipe recipe;\r\n        AbstractCookingRecipe.CookingBookInfo bookInfo = new AbstractCookingRecipe.CookingBookInfo(category == null ? CookingBookCategory.MISC : CookingBookCategory.valueOf(CoreUtilities.toUpperCase(category)), group);\r\n        if (type.equalsIgnoreCase(\"smoker\")) {\r\n            recipe = new SmokingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"blast\")) {\r\n            recipe = new BlastingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time);\r\n        }\r\n        else if (type.equalsIgnoreCase(\"campfire\")) {\r\n            recipe = new CampfireCookingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time);\r\n        }\r\n        else {\r\n            recipe = new SmeltingRecipe(BASE_RECIPE_INFO, bookInfo, itemRecipe, asNMSTemplate(result), exp, time);\r\n        }\r\n        RecipeHolder<AbstractCookingRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public void registerStonecuttingRecipe(String keyName, String group, ItemStack result, ItemStack[] ingredient, boolean exact) {\r\n        ResourceKey<Recipe<?>> key = createRecipeKey(keyName);\r\n        Ingredient itemRecipe = itemArrayToRecipe(ingredient, exact);\r\n        StonecutterRecipe recipe = new StonecutterRecipe(BASE_RECIPE_INFO, itemRecipe, asNMSTemplate(result));\r\n        RecipeHolder<StonecutterRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public void registerSmithingRecipe(String keyName, ItemStack result, ItemStack[] baseItem, boolean baseExact, ItemStack[] upgradeItem, boolean upgradeExact, ItemStack[] templateItem, boolean templateExact) {\r\n        ResourceKey<Recipe<?>> key = createRecipeKey(keyName);\r\n        Ingredient templateItemRecipe = templateItem.length == 0 ? null : itemArrayToRecipe(templateItem, templateExact);\r\n        Ingredient baseItemRecipe = itemArrayToRecipe(baseItem, baseExact);\r\n        Ingredient upgradeItemRecipe = itemArrayToRecipe(upgradeItem, upgradeExact);\r\n        SmithingTransformRecipe recipe = new SmithingTransformRecipe(BASE_RECIPE_INFO, Optional.ofNullable(templateItemRecipe), baseItemRecipe, Optional.of(upgradeItemRecipe), asNMSTemplate(result));\r\n        RecipeHolder<SmithingTransformRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    @Override\r\n    public void registerShapelessRecipe(String keyName, String group, ItemStack result, List<ItemStack[]> ingredients, boolean[] exact, String category) {\r\n        ResourceKey<Recipe<?>> key = createRecipeKey(keyName);\r\n        ArrayList<Ingredient> ingredientList = new ArrayList<>();\r\n        CraftingBookCategory categoryValue = category == null ? CraftingBookCategory.MISC : CraftingBookCategory.valueOf(CoreUtilities.toUpperCase(category));\r\n        for (int i = 0; i < ingredients.size(); i++) {\r\n            ingredientList.add(itemArrayToRecipe(ingredients.get(i), exact[i]));\r\n        }\r\n        ShapelessRecipe recipe = new ShapelessRecipe(BASE_RECIPE_INFO, new CraftingRecipe.CraftingBookInfo(categoryValue, group), asNMSTemplate(result), NonNullList.of(null, ingredientList.toArray(new Ingredient[0])));\r\n        RecipeHolder<ShapelessRecipe> holder = new RecipeHolder<>(key, recipe);\r\n        getRecipeManager().addRecipe(holder);\r\n    }\r\n\r\n    public static final MethodHandle CRAFT_RECIPE_ADD_TO_MANAGER = Handler.reflectPaperRenamed(CraftRecipe.class, \"addToCraftingManager\", \"addToRecipeManager\");\r\n\r\n    @Override\r\n    public void registerOtherRecipe(org.bukkit.inventory.Recipe recipe) {\r\n        // This method copied from Bukkit CraftServer source, just to bypass unwanted paper patch\r\n        CraftRecipe toAdd;\r\n        if (recipe instanceof CraftRecipe craft) {\r\n            toAdd = craft;\r\n        }\r\n        else if (recipe instanceof ShapedRecipe) {\r\n            toAdd = CraftShapedRecipe.fromBukkitRecipe((ShapedRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof org.bukkit.inventory.ShapelessRecipe) {\r\n            toAdd = CraftShapelessRecipe.fromBukkitRecipe((org.bukkit.inventory.ShapelessRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof FurnaceRecipe) {\r\n            toAdd = CraftFurnaceRecipe.fromBukkitRecipe((FurnaceRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof org.bukkit.inventory.BlastingRecipe) {\r\n            toAdd = CraftBlastingRecipe.fromBukkitRecipe((org.bukkit.inventory.BlastingRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof CampfireRecipe) {\r\n            toAdd = CraftCampfireRecipe.fromBukkitRecipe((CampfireRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof org.bukkit.inventory.SmokingRecipe) {\r\n            toAdd = CraftSmokingRecipe.fromBukkitRecipe((org.bukkit.inventory.SmokingRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof StonecuttingRecipe) {\r\n            toAdd = CraftStonecuttingRecipe.fromBukkitRecipe((StonecuttingRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof org.bukkit.inventory.SmithingTransformRecipe) {\r\n            toAdd = CraftSmithingTransformRecipe.fromBukkitRecipe((org.bukkit.inventory.SmithingTransformRecipe)recipe);\r\n        }\r\n        else if (recipe instanceof org.bukkit.inventory.SmithingTrimRecipe) {\r\n            toAdd = CraftSmithingTrimRecipe.fromBukkitRecipe((SmithingTrimRecipe)recipe);\r\n        }\r\n        else {\r\n            if (!(recipe instanceof org.bukkit.inventory.TransmuteRecipe)) {\r\n                if (recipe instanceof ComplexRecipe) {\r\n                    throw new UnsupportedOperationException(\"Cannot add custom complex recipe\");\r\n                }\r\n                return;\r\n            }\r\n            toAdd = CraftTransmuteRecipe.fromBukkitRecipe((TransmuteRecipe)recipe);\r\n        }\r\n        try {\r\n            CRAFT_RECIPE_ADD_TO_MANAGER.invokeExact(toAdd);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getJsonString(ItemStack itemStack) {\r\n        String json = CraftItemStack.asNMSCopy(itemStack).getDisplayName().getStyle().toString().replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\");\r\n        return json.substring(176, json.length() - 185);\r\n    }\r\n\r\n    @Override\r\n    public JsonObject getRawHoverComponentsJson(ItemStack item) {\r\n        DataComponentPatch nmsComponents = CraftItemStack.asNMSCopy(item).getComponentsPatch();\r\n        if (nmsComponents.isEmpty()) {\r\n            return null;\r\n        }\r\n        return DataComponentPatch.CODEC.encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE), nmsComponents).getOrThrow().getAsJsonObject();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack applyRawHoverComponentsJson(ItemStack item, JsonObject components) {\r\n        return DataComponentPatch.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE), components).mapOrElse(\r\n                nmsComponents -> {\r\n                    net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item);\r\n                    nmsItem.applyComponents(nmsComponents);\r\n                    return CraftItemStack.asCraftMirror(nmsItem);\r\n                },\r\n                error -> {\r\n                    Debug.echoError(\"Invalid hover item data '\" + components + \"': \" + error.message());\r\n                    return item;\r\n                });\r\n    }\r\n\r\n    @Override\r\n    public PlayerProfile getSkullSkin(ItemStack is) {\r\n        net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(is);\r\n        ResolvableProfile profile = itemStack.get(DataComponents.PROFILE);\r\n        if (profile != null) {\r\n            Property property = Iterables.getFirst(profile.partialProfile().properties().get(\"textures\"), null);\r\n            return new PlayerProfile(profile.name().orElse(null), ProfileEditorImpl.getUUID(profile),\r\n                    property != null ? property.value() : null,\r\n                    property != null ? property.signature() : null);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setSkullSkin(ItemStack itemStack, PlayerProfile playerProfile) {\r\n        GameProfile gameProfile = ProfileEditorImpl.getGameProfile(playerProfile);\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.set(DataComponents.PROFILE, ResolvableProfile.createResolved(gameProfile));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack addNbtData(ItemStack itemStack, String key, BinaryTag value) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        nmsItemStack.update(DataComponents.CUSTOM_DATA, CustomData.EMPTY, customData -> customData.update(nmsCompoundTag -> nmsCompoundTag.put(key, NBTAdapter.toNMS(value))));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    // TODO: 1.20.6: this now needs to serialize components into NBT every single time, should probably only return custom NBT data with specialized methods for other usages\r\n    // TODO: 1.20.6: NBT structure is different basically everywhere, usages of this will need an update\r\n    @Override\r\n    public CompoundBinaryTag getNbtData(ItemStack itemStack) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);\r\n        if (nmsItemStack != null && !nmsItemStack.isEmpty()) {\r\n            return NBTAdapter.toAPI(serializeNmsItem(nmsItemStack));\r\n        }\r\n        return CompoundBinaryTag.empty();\r\n    }\r\n\r\n    // TODO: 1.20.6: same as getNbtData, ideally needs to only set custom NBT data and have specialized methods for other usages\r\n    @Override\r\n    public ItemStack setNbtData(ItemStack itemStack, CompoundBinaryTag compoundTag) {\r\n        return CraftItemStack.asBukkitCopy(parseNmsItem(NBTAdapter.toNMS(compoundTag)));\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getCustomData(ItemStack item) {\r\n        CustomData customData = CraftItemStack.asNMSCopy(item).get(DataComponents.CUSTOM_DATA);\r\n        return customData != null ? NBTAdapter.toAPI(customData.copyTag()) : null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setCustomData(ItemStack item, CompoundBinaryTag data) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        if (data == null) {\r\n            nmsItemStack.remove(DataComponents.CUSTOM_DATA);\r\n        }\r\n        else {\r\n            nmsItemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(NBTAdapter.toNMS(data)));\r\n        }\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    public static final int DATA_VERSION_1_20_4 = 3700;\r\n\r\n    @Override\r\n    public ItemStack setPartialOldNbt(ItemStack item, CompoundBinaryTag oldTag) {\r\n        int currentDataVersion = CraftMagicNumbers.INSTANCE.getDataVersion();\r\n        CompoundTag nmsOldTag = new CompoundTag();\r\n        nmsOldTag.putString(\"id\", item.getType().getKey().toString());\r\n        nmsOldTag.putByte(\"Count\", (byte) item.getAmount());\r\n        nmsOldTag.put(\"tag\", NBTAdapter.toNMS(oldTag));\r\n        CompoundTag nmsUpdatedTag = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, nmsOldTag), DATA_VERSION_1_20_4, currentDataVersion).getValue();\r\n        CompoundTag nmsCurrentTag = serializeNmsItem(CraftItemStack.asNMSCopy(item));\r\n        CompoundTag nmsMergedTag = nmsCurrentTag.merge(nmsUpdatedTag);\r\n        return CraftItemStack.asBukkitCopy(parseNmsItem(nmsMergedTag));\r\n    }\r\n\r\n    @Override\r\n    public CompoundBinaryTag getEntityData(ItemStack item) {\r\n        TypedEntityData<net.minecraft.world.entity.EntityType<?>> entityData = CraftItemStack.asNMSCopy(item).get(DataComponents.ENTITY_DATA);\r\n        return entityData != null ? NBTAdapter.toAPI(entityData.getUnsafe()) : null;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setEntityData(ItemStack item, CompoundBinaryTag entityNbt, EntityType entityType) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        if (entityNbt == null || entityNbt.isEmpty() || (entityNbt.size() == 1 && entityNbt.contains(\"id\"))) {\r\n            nmsItemStack.remove(DataComponents.ENTITY_DATA);\r\n        }\r\n        else {\r\n            CompoundTag nmsEntityNbt = NBTAdapter.toNMS(entityNbt);\r\n            nmsEntityNbt.remove(\"id\");\r\n            nmsItemStack.set(DataComponents.ENTITY_DATA, TypedEntityData.of(CraftEntityType.bukkitToMinecraft(entityType), nmsEntityNbt));\r\n        }\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public MapTag getRawComponentsPatch(ItemStack item, boolean excludeHandled) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        DataComponentPatch patch = nmsItemStack.getComponentsPatch();\r\n        if (excludeHandled) {\r\n            patch = patch.forget(componentType -> {\r\n                Identifier componentId = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(componentType);\r\n                return ItemComponentsPatch.propertyHandledComponents.contains(componentId.toString());\r\n            });\r\n        }\r\n        if (patch.isEmpty()) {\r\n            return new MapTag();\r\n        }\r\n        RegistryOps<net.minecraft.nbt.Tag> registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE);\r\n        CompoundTag nmsPatch = (CompoundTag) DataComponentPatch.CODEC.encodeStart(registryOps, patch).getOrThrow();\r\n        if (excludeHandled && Denizen.supportsPaper) {\r\n            nmsPatch.keySet().removeIf(s -> s.charAt(0) == '!');\r\n            if (nmsPatch.isEmpty()) {\r\n                return new MapTag();\r\n            }\r\n        }\r\n        MapTag rawComponents = (MapTag) ItemRawNBT.nbtTagToObject(NBTAdapter.toAPI(nmsPatch));\r\n        rawComponents.putObject(ItemComponentsPatch.DATA_VERSION_KEY, new ElementTag(CraftMagicNumbers.INSTANCE.getDataVersion()));\r\n        return rawComponents;\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setRawComponentsPatch(ItemStack item, MapTag rawComponentsMap, int dataVersion, Consumer<String> errorHandler) {\r\n        int currentDataVersion = CraftMagicNumbers.INSTANCE.getDataVersion();\r\n        CompoundBinaryTag rawComponents = (CompoundBinaryTag) ItemRawNBT.convertObjectToNbt(rawComponentsMap, CoreUtilities.errorButNoDebugContext, \"\");\r\n        CompoundTag nmsRawComponents = NBTAdapter.toNMS(rawComponents);\r\n        RegistryOps<net.minecraft.nbt.Tag> registryOps = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE);\r\n        if (dataVersion < currentDataVersion) {\r\n            CompoundTag legacyItemData = new CompoundTag();\r\n            legacyItemData.putString(\"id\", item.getType().getKey().toString());\r\n            legacyItemData.putInt(\"count\", item.getAmount());\r\n            legacyItemData.put(\"components\", nmsRawComponents);\r\n            CompoundTag nmsUpdatedTag = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(registryOps, legacyItemData), dataVersion, currentDataVersion).getValue();\r\n            nmsRawComponents = nmsUpdatedTag.getCompound(\"components\").orElseGet(CompoundTag::new);\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        DataComponentPatch.CODEC.parse(registryOps, nmsRawComponents)\r\n                .ifError(error -> errorHandler.accept(error.message()))\r\n                .ifSuccess(nmsItemStack::applyComponents);\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    public static final Field AdventureModePredicate_predicates = ReflectionHelper.getFields(AdventureModePredicate.class).get(\"predicates\");\r\n\r\n    @Override\r\n    public List<Material> getCanPlaceOn(ItemStack item) {\r\n        return getAdventureModePredicateMaterials(item, DataComponents.CAN_PLACE_ON);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setCanPlaceOn(ItemStack item, List<Material> canPlaceOn) {\r\n        return setAdventureModePredicateMaterials(item, DataComponents.CAN_PLACE_ON, canPlaceOn);\r\n    }\r\n\r\n    @Override\r\n    public List<Material> getCanBreak(ItemStack item) {\r\n        return getAdventureModePredicateMaterials(item, DataComponents.CAN_BREAK);\r\n    }\r\n\r\n    @Override\r\n    public ItemStack setCanBreak(ItemStack item, List<Material> canBreak) {\r\n        return setAdventureModePredicateMaterials(item, DataComponents.CAN_BREAK, canBreak);\r\n    }\r\n\r\n    private List<Material> getAdventureModePredicateMaterials(ItemStack item, DataComponentType<AdventureModePredicate> nmsComponent) {\r\n        AdventureModePredicate nmsAdventurePredicate = CraftItemStack.asNMSCopy(item).get(nmsComponent);\r\n        if (nmsAdventurePredicate == null) {\r\n            return null;\r\n        }\r\n        List<BlockPredicate> nmsPredicates;\r\n        try {\r\n            nmsPredicates = (List<BlockPredicate>) AdventureModePredicate_predicates.get(nmsAdventurePredicate);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n            return null;\r\n        }\r\n        List<Material> materials = new ArrayList<>();\r\n        for (BlockPredicate nmsPredicate : nmsPredicates) {\r\n            nmsPredicate.blocks().ifPresent(nmsHolderSet -> {\r\n                for (Holder<Block> nmsHolder : nmsHolderSet) {\r\n                    materials.add(CraftMagicNumbers.getMaterial(nmsHolder.value()));\r\n                }\r\n            });\r\n        }\r\n        return materials;\r\n    }\r\n\r\n    private ItemStack setAdventureModePredicateMaterials(ItemStack item, DataComponentType<AdventureModePredicate> nmsComponent, List<Material> materials) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item);\r\n        AdventureModePredicate nmsAdventurePredicate = nmsItemStack.get(nmsComponent);\r\n        if (materials == null) {\r\n            if (nmsAdventurePredicate == null) {\r\n                return item;\r\n            }\r\n            nmsItemStack.remove(nmsComponent);\r\n            return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n        }\r\n        BlockPredicate nmsPredicate = new BlockPredicate(Optional.of(\r\n                HolderSet.direct(material -> BuiltInRegistries.BLOCK.get(CraftNamespacedKey.toMinecraft(material.getKey())).orElseThrow(), materials)\r\n        ), Optional.empty(), Optional.empty(), DataComponentMatchers.ANY);\r\n        nmsItemStack.set(nmsComponent, new AdventureModePredicate(List.of(nmsPredicate)));\r\n        return CraftItemStack.asBukkitCopy(nmsItemStack);\r\n    }\r\n\r\n    @Override\r\n    public void setInventoryItem(Inventory inventory, ItemStack item, int slot) {\r\n        if (inventory instanceof CraftInventoryPlayer && ((CraftInventoryPlayer) inventory).getInventory().player == null) {\r\n            ((CraftInventoryPlayer) inventory).getInventory().setItem(slot, CraftItemStack.asNMSCopy(item));\r\n        }\r\n        else {\r\n            inventory.setItem(slot, item);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getDisplayName(ItemTag item) {\r\n        if (!item.getItemMeta().hasDisplayName()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        Component nmsDisplayName = nmsItemStack.get(DataComponents.CUSTOM_NAME);\r\n        return FormattedTextHelper.stringify(Handler.componentToSpigot(nmsDisplayName));\r\n    }\r\n\r\n    @Override\r\n    public List<String> getLore(ItemTag item) {\r\n        if (!item.getItemMeta().hasLore()) {\r\n            return null;\r\n        }\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        ItemLore nmsLore = nmsItemStack.get(DataComponents.LORE);\r\n        List<String> outList = new ArrayList<>(nmsLore.lines().size());\r\n        for (Component nmsLoreLine : nmsLore.lines()) {\r\n            outList.add(FormattedTextHelper.stringify(Handler.componentToSpigot(nmsLoreLine)));\r\n        }\r\n        return outList;\r\n    }\r\n\r\n    @Override\r\n    public void setDisplayName(ItemTag item, String name) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        if (name == null || name.isEmpty()) {\r\n            nmsItemStack.remove(DataComponents.CUSTOM_NAME);\r\n        }\r\n        else {\r\n            nmsItemStack.set(DataComponents.CUSTOM_NAME, Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)));\r\n        }\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    @Override\r\n    public void setLore(ItemTag item, List<String> lore) {\r\n        net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item.getItemStack());\r\n        if (lore == null || lore.isEmpty()) {\r\n            nmsItemStack.remove(DataComponents.LORE);\r\n        }\r\n        else {\r\n            List<Component> nmsLore = new ArrayList<>(lore.size());\r\n            for (String loreLine : lore) {\r\n                nmsLore.add(Handler.componentToNMS(FormattedTextHelper.parse(loreLine, ChatColor.WHITE)));\r\n            }\r\n            nmsItemStack.set(DataComponents.LORE, new ItemLore(nmsLore));\r\n        }\r\n        item.setItemStack(CraftItemStack.asBukkitCopy(nmsItemStack));\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.getCorrectStateForFluidBlock.\r\n     */\r\n    public static BlockState getCorrectStateForFluidBlock(Level world, BlockState blockState, BlockPos blockPos) {\r\n        FluidState fluid = blockState.getFluidState();\r\n        return !fluid.isEmpty() && !blockState.isFaceSturdy(world, blockPos, Direction.UP) ? fluid.createLegacyBlock() : blockState;\r\n    }\r\n\r\n    /**\r\n     * Copied from MapItem.update, redesigned slightly to render totally rather than just relative to a player.\r\n     * Some variables manually renamed for readability.\r\n     */\r\n    public static void renderFullMap(MapItemSavedData worldmap, int xMin, int zMin, int xMax, int zMax) {\r\n        Level world = ((CraftWorld) worldmap.mapView.getWorld()).getHandle();\r\n        int scale = 1 << worldmap.scale;\r\n        int mapX = worldmap.centerX;\r\n        int mapZ = worldmap.centerZ;\r\n        for (int x = xMin; x < xMax; x++) {\r\n            double d0 = 0.0D;\r\n            for (int z = zMin; z < zMax; z++) {\r\n                int k2 = (mapX / scale + x - 64) * scale;\r\n                int l2 = (mapZ / scale + z - 64) * scale;\r\n                Multiset<MapColor> multiset = LinkedHashMultiset.create();\r\n                LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2));\r\n                if (!chunk.isEmpty()) {\r\n                    ChunkPos chunkcoordintpair = chunk.getPos();\r\n                    int i3 = k2 & 15;\r\n                    int j3 = l2 & 15;\r\n                    int k3 = 0;\r\n                    double d1 = 0.0D;\r\n                    if (world.dimensionType().hasCeiling()) {\r\n                        int l3 = k2 + l2 * 231871;\r\n                        l3 = l3 * l3 * 31287121 + l3 * 11;\r\n                        if ((l3 >> 20 & 1) == 0) {\r\n                            multiset.add(Blocks.DIRT.defaultBlockState().getMapColor(world, BlockPos.ZERO), 10);\r\n                        }\r\n                        else {\r\n                            multiset.add(Blocks.STONE.defaultBlockState().getMapColor(world, BlockPos.ZERO), 100);\r\n                        }\r\n\r\n                        d1 = 100.0D;\r\n                    }\r\n                    else {\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();\r\n                        BlockPos.MutableBlockPos blockposition_mutableblockposition1 = new BlockPos.MutableBlockPos();\r\n                        for (int i4 = 0; i4 < scale; ++i4) {\r\n                            for (int j4 = 0; j4 < scale; ++j4) {\r\n                                int k4 = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i4 + i3, j4 + j3) + 1;\r\n                                BlockState iblockdata;\r\n                                if (k4 <= world.getMinY() + 1) {\r\n                                    iblockdata = Blocks.BEDROCK.defaultBlockState();\r\n                                }\r\n                                else {\r\n                                    do {\r\n                                        --k4;\r\n                                        blockposition_mutableblockposition.set(chunkcoordintpair.getMinBlockX() + i4 + i3, k4, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                        iblockdata = chunk.getBlockState(blockposition_mutableblockposition);\r\n                                    } while (iblockdata.getMapColor(world, blockposition_mutableblockposition) == MapColor.NONE && k4 > world.getMinY());\r\n                                    if (k4 > world.getMinY() && !iblockdata.getFluidState().isEmpty()) {\r\n                                        int l4 = k4 - 1;\r\n                                        blockposition_mutableblockposition1.set(blockposition_mutableblockposition);\r\n\r\n                                        BlockState iblockdata1;\r\n                                        do {\r\n                                            blockposition_mutableblockposition1.setY(l4--);\r\n                                            iblockdata1 = chunk.getBlockState(blockposition_mutableblockposition1);\r\n                                            k3++;\r\n                                        } while (l4 > world.getMinY() && !iblockdata1.getFluidState().isEmpty());\r\n                                        iblockdata = getCorrectStateForFluidBlock(world, iblockdata, blockposition_mutableblockposition);\r\n                                    }\r\n                                }\r\n                                worldmap.checkBanners(world, chunkcoordintpair.getMinBlockX() + i4 + i3, chunkcoordintpair.getMinBlockZ() + j4 + j3);\r\n                                d1 += (double) k4 / (double) (scale * scale);\r\n                                multiset.add(iblockdata.getMapColor(world, blockposition_mutableblockposition));\r\n                            }\r\n                        }\r\n                    }\r\n                    k3 /= scale * scale;\r\n                    double d2 = (d1 - d0) * 4.0D / (double) (scale + 4) + ((double) (x + z & 1) - 0.5D) * 0.4D;\r\n                    byte b0 = 1;\r\n                    if (d2 > 0.6D) {\r\n                        b0 = 2;\r\n                    }\r\n                    if (d2 < -0.6D) {\r\n                        b0 = 0;\r\n                    }\r\n                    MapColor materialmapcolor = Iterables.getFirst(Multisets.copyHighestCountFirst(multiset), MapColor.NONE);\r\n                    if (materialmapcolor == MapColor.WATER) {\r\n                        d2 = (double) k3 * 0.1D + (double) (x + z & 1) * 0.2D;\r\n                        b0 = 1;\r\n                        if (d2 < 0.5D) {\r\n                            b0 = 2;\r\n                        }\r\n                        if (d2 > 0.9D) {\r\n                            b0 = 0;\r\n                        }\r\n                    }\r\n                    d0 = d1;\r\n                    worldmap.updateColor(x, z, (byte) (materialmapcolor.id * 4 + b0));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean renderEntireMap(int mapId, int xMin, int zMin, int xMax, int zMax) {\r\n        MapItemSavedData worldmap = ((CraftServer) Bukkit.getServer()).getServer().getLevel(net.minecraft.world.level.Level.OVERWORLD).getMapData(new MapId(mapId));\r\n        if (worldmap == null) {\r\n            return false;\r\n        }\r\n        renderFullMap(worldmap, xMin, zMin, xMax, zMax);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public BlockData getPlacedBlock(Material material) {\r\n        Item nmsItem = BuiltInRegistries.ITEM.getOptional(CraftNamespacedKey.toMinecraft(material.getKey())).orElse(null);\r\n        if (nmsItem instanceof BlockItem) {\r\n            Block block = ((BlockItem) nmsItem).getBlock();\r\n            return CraftBlockData.fromData(block.defaultBlockState());\r\n        }\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public boolean isValidMix(ItemStack input, ItemStack ingredient) {\r\n        net.minecraft.world.item.ItemStack nmsInput = CraftItemStack.asNMSCopy(input);\r\n        net.minecraft.world.item.ItemStack nmsIngredient = CraftItemStack.asNMSCopy(ingredient);\r\n        return MinecraftServer.getServer().potionBrewing().hasMix(nmsInput, nmsIngredient);\r\n    }\r\n\r\n    public static Class<?> PaperPotionMix_CLASS = null;\r\n    public static Map<NamespacedKey, BrewingRecipe> customBrewingRecipes = null;\r\n\r\n    @Override\r\n    public Map<NamespacedKey, BrewingRecipe> getCustomBrewingRecipes() {\r\n        if (customBrewingRecipes == null) {\r\n            customBrewingRecipes = Maps.transformValues((Map<NamespacedKey, ?>) ReflectionHelper.getFieldValue(PotionBrewing.class, \"customMixes\", MinecraftServer.getServer().potionBrewing()), paperMix -> {\r\n                if (PaperPotionMix_CLASS == null) {\r\n                    PaperPotionMix_CLASS = paperMix.getClass();\r\n                }\r\n                RecipeChoice ingredient = convertChoice(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"ingredient\", paperMix));\r\n                RecipeChoice input = convertChoice(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"input\", paperMix));\r\n                ItemStack result = CraftItemStack.asBukkitCopy(ReflectionHelper.getFieldValue(PaperPotionMix_CLASS, \"result\", paperMix));\r\n                return new BrewingRecipe(input, ingredient, result);\r\n            });\r\n        }\r\n        return customBrewingRecipes;\r\n    }\r\n\r\n    private RecipeChoice convertChoice(Predicate<net.minecraft.world.item.ItemStack> nmsPredicate) {\r\n        // Not an instance of net.minecraft.world.item.crafting.Ingredient = a predicate recipe choice\r\n        if (nmsPredicate instanceof Ingredient ingredient) {\r\n            return CraftRecipe.toBukkit(ingredient);\r\n        }\r\n        return PaperAPITools.instance.createPredicateRecipeChoice(item -> nmsPredicate.test(CraftItemStack.asNMSCopy(item)));\r\n    }\r\n\r\n    @Override\r\n    public byte[] renderMap(MapView mapView, Player player) {\r\n        return ((CraftMapView) mapView).render((CraftPlayer) player).buffer;\r\n    }\r\n\r\n    @Override\r\n    public int getFoodPoints(Material itemType) {\r\n        return CraftMagicNumbers.getItem(itemType).components().get(DataComponents.FOOD).nutrition();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/NBTAdapter.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\n\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport net.kyori.adventure.nbt.*;\nimport net.minecraft.nbt.*;\n\nimport java.lang.invoke.MethodHandle;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class NBTAdapter {\n\n    public static final MethodHandle COMPOUND_TAG_MAP_CONSTRUCTOR = ReflectionHelper.getConstructor(CompoundTag.class, Map.class);\n\n    public static Tag toNMS(BinaryTag tag) {\n        if (tag instanceof ByteBinaryTag byteTag) {\n            return switch (byteTag.value()) {\n                case 0 -> ByteTag.ZERO;\n                case 1 -> ByteTag.ONE;\n                default -> ByteTag.valueOf(byteTag.value());\n            };\n        }\n        else if (tag instanceof ShortBinaryTag shortTag) {\n            return ShortTag.valueOf(shortTag.value());\n        }\n        else if (tag instanceof IntBinaryTag intTag) {\n            return IntTag.valueOf(intTag.value());\n        }\n        else if (tag instanceof LongBinaryTag longTag) {\n            return LongTag.valueOf(longTag.value());\n        }\n        else if (tag instanceof FloatBinaryTag floatTag) {\n            return FloatTag.valueOf(floatTag.value());\n        }\n        else if (tag instanceof DoubleBinaryTag doubleTag) {\n            return DoubleTag.valueOf(doubleTag.value());\n        }\n        else if (tag instanceof ByteArrayBinaryTag byteArrayTag) {\n            return new ByteArrayTag(byteArrayTag.value());\n        }\n        else if (tag instanceof IntArrayBinaryTag intArrayTag) {\n            return new IntArrayTag(intArrayTag.value());\n        }\n        else if (tag instanceof LongArrayBinaryTag longArrayTag) {\n            return new LongArrayTag(longArrayTag.value());\n        }\n        else if (tag instanceof StringBinaryTag stringTag) {\n            return StringTag.valueOf(stringTag.value());\n        }\n        else if (tag instanceof ListBinaryTag listTag) {\n            return toNMS(listTag);\n        }\n        else if (tag instanceof CompoundBinaryTag compoundTag) {\n            return toNMS(compoundTag);\n        }\n        else if (tag instanceof EndBinaryTag) {\n            return EndTag.INSTANCE;\n        }\n        throw new IllegalStateException(\"Unrecognized API tag of type '\" + tag.type() + \"': \" + tag);\n    }\n\n    public static BinaryTag toAPI(Tag nmsTag) {\n        if (nmsTag instanceof ByteTag nmsByteTag) {\n            return ByteBinaryTag.byteBinaryTag(nmsByteTag.value());\n        }\n        else if (nmsTag instanceof ShortTag nmsShortTag) {\n            return ShortBinaryTag.shortBinaryTag(nmsShortTag.value());\n        }\n        else if (nmsTag instanceof IntTag nmsIntTag) {\n            return IntBinaryTag.intBinaryTag(nmsIntTag.value());\n        }\n        else if (nmsTag instanceof LongTag nmsLongTag) {\n            return LongBinaryTag.longBinaryTag(nmsLongTag.value());\n        }\n        else if (nmsTag instanceof FloatTag nmsFloatTag) {\n            return FloatBinaryTag.floatBinaryTag(nmsFloatTag.value());\n        }\n        else if (nmsTag instanceof DoubleTag nmsDoubleTag) {\n            return DoubleBinaryTag.doubleBinaryTag(nmsDoubleTag.value());\n        }\n        else if (nmsTag instanceof ByteArrayTag nmsByteArrayTag) {\n            return ByteArrayBinaryTag.byteArrayBinaryTag(nmsByteArrayTag.getAsByteArray());\n        }\n        else if (nmsTag instanceof IntArrayTag nmsIntArrayTag) {\n            return IntArrayBinaryTag.intArrayBinaryTag(nmsIntArrayTag.getAsIntArray());\n        }\n        else if (nmsTag instanceof LongArrayTag nmsLongArrayTag) {\n            return LongArrayBinaryTag.longArrayBinaryTag(nmsLongArrayTag.getAsLongArray());\n        }\n        else if (nmsTag instanceof StringTag nmsStringTag) {\n            return StringBinaryTag.stringBinaryTag(nmsStringTag.value());\n        }\n        else if (nmsTag instanceof ListTag nmsListTag) {\n            return toAPI(nmsListTag);\n        }\n        else if (nmsTag instanceof CompoundTag nmsCompoundTag) {\n            return toAPI(nmsCompoundTag);\n        }\n        else if (nmsTag instanceof EndTag) {\n            return EndBinaryTag.endBinaryTag();\n        }\n        throw new IllegalStateException(\"Unrecognized NMS tag of type '\" + nmsTag.getClass().getName() + '/' + nmsTag.getType().getName() + \"': \" + nmsTag);\n    }\n\n    public static ListBinaryTag toAPI(ListTag nmsListTag) {\n        ListBinaryTag.Builder<BinaryTag> builder = ListBinaryTag.heterogeneousListBinaryTag(nmsListTag.size());\n        for (Tag nmsValue : nmsListTag) {\n            builder.add(toAPI(nmsValue));\n        }\n        return builder.build();\n    }\n\n    public static ListTag toNMS(ListBinaryTag listTag) {\n        List<Tag> nmsTags = new ArrayList<>(listTag.size());\n        for (BinaryTag value : listTag) {\n            nmsTags.add(toNMS(value));\n        }\n        return new ListTag(nmsTags);\n    }\n\n    public static CompoundBinaryTag toAPI(CompoundTag nmsCompoundTag) {\n        CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(nmsCompoundTag.size());\n        for (Map.Entry<String, Tag> nmsEntry : nmsCompoundTag.entrySet()) {\n            builder.put(nmsEntry.getKey(), toAPI(nmsEntry.getValue()));\n        }\n        return builder.build();\n    }\n\n    public static CompoundTag toNMS(CompoundBinaryTag compoundTag) {\n        Map<String, Tag> nmsTags = new HashMap<>(compoundTag.size());\n        for (Map.Entry<String, ? extends BinaryTag> entry : compoundTag) {\n            nmsTags.put(entry.getKey(), toNMS(entry.getValue()));\n        }\n        try {\n            return (CompoundTag) COMPOUND_TAG_MAP_CONSTRUCTOR.invokeExact(nmsTags);\n        }\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/PacketHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.interfaces.PacketHelper;\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.SidebarImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.TeleportCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.Utilities;\r\nimport com.denizenscript.denizen.utilities.maps.MapImage;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;\r\nimport com.denizenscript.denizencore.objects.core.ColorTag;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;\r\nimport net.minecraft.network.protocol.common.custom.BrandPayload;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.PositionMoveRotation;\r\nimport net.minecraft.world.entity.Relative;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.monster.Creeper;\r\nimport net.minecraft.world.entity.monster.EnderMan;\r\nimport net.minecraft.world.entity.monster.spider.CaveSpider;\r\nimport net.minecraft.world.entity.monster.spider.Spider;\r\nimport net.minecraft.world.inventory.AbstractContainerMenu;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.saveddata.maps.MapItemSavedData;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Team;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.EntityEffect;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Sign;\r\nimport org.bukkit.block.sign.Side;\r\nimport org.bukkit.block.sign.SignSide;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.map.CraftMapCanvas;\r\nimport org.bukkit.craftbukkit.map.CraftMapView;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.entity.LivingEntity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.map.MapCanvas;\r\nimport org.bukkit.map.MapPalette;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class PacketHelperImpl implements PacketHelper {\r\n\r\n    public static final EntityDataAccessor<Float> PLAYER_DATA_ACCESSOR_ABSORPTION = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, \"DATA_PLAYER_ABSORPTION_ID\", null);\r\n\r\n    public static final EntityDataAccessor<Byte> ENTITY_DATA_ACCESSOR_FLAGS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, \"DATA_SHARED_FLAGS_ID\", null);\r\n\r\n    public static final MethodHandle ABILITIES_PACKET_FOV_SETTER = ReflectionHelper.getFinalSetter(ClientboundPlayerAbilitiesPacket.class, \"walkingSpeed\");\r\n\r\n    public static final Field ENTITY_TRACKER_ENTRY_GETTER = ReflectionHelper.getFields(ChunkMap.TrackedEntity.class).getFirstOfType(ServerEntity.class);\r\n\r\n    public static final MethodHandle CANVAS_GET_BUFFER = ReflectionHelper.getMethodHandle(CraftMapCanvas.class, \"getBuffer\");\r\n    public static final Field MAPVIEW_WORLDMAP = ReflectionHelper.getFields(CraftMapView.class).get(\"worldMap\");\r\n\r\n    public static final EntityDataAccessor<Optional<Component>> ENTITY_DATA_ACCESSOR_CUSTOM_NAME = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, \"DATA_CUSTOM_NAME\", null);\r\n    public static final EntityDataAccessor<Boolean> ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE = ReflectionHelper.getFieldValue(net.minecraft.world.entity.Entity.class, \"DATA_CUSTOM_NAME_VISIBLE\", null);\r\n\r\n    @Override\r\n    public void setFakeAbsorption(Player player, float value) {\r\n        send(player, new ClientboundSetEntityDataPacket(player.getEntityId(), List.of(createEntityData(PLAYER_DATA_ACCESSOR_ABSORPTION, value))));\r\n    }\r\n\r\n    @Override\r\n    public void setSlot(Player player, int slot, ItemStack itemStack, boolean playerOnly) {\r\n        AbstractContainerMenu menu = ((CraftPlayer) player).getHandle().containerMenu;\r\n        int windowId = playerOnly ? 0 : menu.containerId;\r\n        send(player, new ClientboundContainerSetSlotPacket(windowId, menu.incrementStateId(), slot, CraftItemStack.asNMSCopy(itemStack)));\r\n    }\r\n\r\n    @Override\r\n    public void setFieldOfView(Player player, float fov) {\r\n        ClientboundPlayerAbilitiesPacket packet = new ClientboundPlayerAbilitiesPacket(((CraftPlayer) player).getHandle().getAbilities());\r\n        if (!Float.isNaN(fov)) {\r\n            try {\r\n                ABILITIES_PACKET_FOV_SETTER.invoke(packet, fov);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        send(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void respawn(Player player) {\r\n        ((CraftPlayer) player).getHandle().connection.handleClientCommand(new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN));\r\n    }\r\n\r\n    @Override\r\n    public void setVision(Player player, EntityType entityType) {\r\n        final net.minecraft.world.entity.LivingEntity entity;\r\n        if (entityType == EntityType.CREEPER) {\r\n            entity = new Creeper(net.minecraft.world.entity.EntityType.CREEPER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.SPIDER) {\r\n            entity = new Spider(net.minecraft.world.entity.EntityType.SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.CAVE_SPIDER) {\r\n            entity = new CaveSpider(net.minecraft.world.entity.EntityType.CAVE_SPIDER, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else if (entityType == EntityType.ENDERMAN) {\r\n            entity = new EnderMan(net.minecraft.world.entity.EntityType.ENDERMAN, ((CraftWorld) player.getWorld()).getHandle());\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n\r\n        // Spectating an entity then immediately respawning the player prevents a client shader update,\r\n        // allowing the player to retain whatever vision the mob they spectated had.\r\n        send(player, new ClientboundAddEntityPacket(entity, 0, BlockPos.ZERO));\r\n        send(player, new ClientboundSetCameraPacket(entity));\r\n        NMSHandler.playerHelper.refreshPlayer(player);\r\n        send(player, new ClientboundRemoveEntitiesPacket(entity.getId()));\r\n    }\r\n\r\n    @Override\r\n    public void showBlockAction(Player player, Location location, int action, int state) {\r\n        BlockPos position = Handler.toBlockPos(location);\r\n        Block block = ((CraftWorld) location.getWorld()).getHandle().getBlockState(position).getBlock();\r\n        send(player, new ClientboundBlockEventPacket(position, block, action, state));\r\n    }\r\n\r\n    @Override\r\n    public void showTabListHeaderFooter(Player player, String header, String footer) {\r\n        Component cHeader = Handler.componentToNMS(FormattedTextHelper.parse(header, ChatColor.WHITE));\r\n        Component cFooter = Handler.componentToNMS(FormattedTextHelper.parse(footer, ChatColor.WHITE));\r\n        send(player, new ClientboundTabListPacket(cHeader, cFooter));\r\n    }\r\n\r\n    @Override\r\n    public void showTitle(Player player, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) {\r\n        send(player, new ClientboundBundlePacket(List.of(\r\n                new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks),\r\n                new ClientboundSetTitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE))),\r\n                new ClientboundSetSubtitleTextPacket(Handler.componentToNMS(FormattedTextHelper.parse(subtitle, ChatColor.WHITE)))\r\n        )));\r\n    }\r\n\r\n    @Override\r\n    public void showMobHealth(Player player, LivingEntity mob, double health, double maxHealth) {\r\n        AttributeInstance attr = new AttributeInstance(Attributes.MAX_HEALTH, (a) -> {});\r\n        attr.setBaseValue(maxHealth);\r\n        send(player, new ClientboundUpdateAttributesPacket(mob.getEntityId(), List.of(attr)));\r\n        send(player, new ClientboundSetEntityDataPacket(mob.getEntityId(), List.of(createEntityData(net.minecraft.world.entity.LivingEntity.DATA_HEALTH_ID, (float) health))));\r\n    }\r\n\r\n    @Override\r\n    public void showSignEditor(Player player, Location location) { // TODO: MC 1.18: once 1.18 is removed, remove 'location' arg\r\n        NetworkInterceptHelper.enable();\r\n        Sign sign = null;\r\n        BlockPos toOpen = null;\r\n        // It actually allows 8 blocks of distance, but we limit to 7 because the client doesn't properly round down\r\n        for (int i = 0; i < 8; i++) {\r\n            Location toCheck = player.getLocation();\r\n            toCheck.setY(toCheck.getY() - i);\r\n            if (toCheck.getBlock().getState() instanceof Sign foundSign) {\r\n                sign = foundSign;\r\n            }\r\n            else {\r\n                sign = null;\r\n                toOpen = Handler.toBlockPos(toCheck);\r\n                break;\r\n            }\r\n        }\r\n        if (sign != null) {\r\n            toOpen = Handler.toBlockPos(sign.getLocation());\r\n            SignSide front = sign.getSide(Side.FRONT);\r\n            for (int line = 0; line < 4; line++) {\r\n                front.setLine(line, \"\");\r\n            }\r\n            player.sendBlockUpdate(sign.getLocation(), sign);\r\n        }\r\n        else {\r\n            player.sendBlockChange(new LocationTag(player.getWorld(), toOpen.getX(), toOpen.getY(), toOpen.getZ()), Material.OAK_WALL_SIGN.createBlockData());\r\n        }\r\n        DenizenNetworkManagerImpl.getNetworkManager(player).packetListener.fakeSignExpected = toOpen;\r\n        send(player, new ClientboundOpenSignEditorPacket(toOpen, true));\r\n    }\r\n\r\n    @Override\r\n    public void forceSpectate(Player player, Entity entity) {\r\n        send(player, new ClientboundSetCameraPacket(((CraftEntity) entity).getHandle()));\r\n    }\r\n\r\n    public static void forceRespawnPlayerEntity(Entity entity, Player viewer) {\r\n        ChunkMap tracker = ((ServerLevel) ((CraftEntity) entity).getHandle().level()).getChunkSource().chunkMap;\r\n        ChunkMap.TrackedEntity entityTracker = tracker.entityMap.get(entity.getEntityId());\r\n        if (entityTracker != null) {\r\n            try {\r\n                ServerEntity entry = (ServerEntity) ENTITY_TRACKER_ENTRY_GETTER.get(entityTracker);\r\n                if (entry != null) {\r\n                    entry.removePairing(((CraftPlayer) viewer).getHandle());\r\n                    entry.addPairing(((CraftPlayer) viewer).getHandle());\r\n                }\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendRename(Player player, Entity entity, String name, boolean listMode) {\r\n        try {\r\n            if (entity.getType() == EntityType.PLAYER) {\r\n                if (listMode) {\r\n                    send(player, new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, ((CraftPlayer) player).getHandle()));\r\n                }\r\n                else {\r\n                    // For player entities, force a respawn packet and let the dynamic intercept correct the details\r\n                    forceRespawnPlayerEntity(entity, player);\r\n                }\r\n                return;\r\n            }\r\n            List<SynchedEntityData.DataValue<?>> list = List.of(\r\n                    createEntityData(ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(name, ChatColor.WHITE)))),\r\n                    createEntityData(ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true)\r\n            );\r\n            send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), list));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static HashMap<UUID, HashMap<UUID, PlayerTeam>> noCollideTeamMap = new HashMap<>();\r\n\r\n    @Override\r\n    public void generateNoCollideTeam(Player player, UUID noCollide) {\r\n        removeNoCollideTeam(player, noCollide);\r\n        PlayerTeam team = new PlayerTeam(SidebarImpl.dummyScoreboard, Utilities.generateRandomColors(8));\r\n        team.getPlayers().add(noCollide.toString());\r\n        team.setCollisionRule(Team.CollisionRule.NEVER);\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>());\r\n        map.put(noCollide, team);\r\n        send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n    }\r\n\r\n    @Override\r\n    public void removeNoCollideTeam(Player player, UUID noCollide) {\r\n        if (noCollide == null || !player.isOnline()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n            return;\r\n        }\r\n        HashMap<UUID, PlayerTeam> map = noCollideTeamMap.get(player.getUniqueId());\r\n        if (map == null) {\r\n            return;\r\n        }\r\n        PlayerTeam team = map.remove(noCollide);\r\n        if (team != null) {\r\n            send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        if (map.isEmpty()) {\r\n            noCollideTeamMap.remove(player.getUniqueId());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityMetadataFlagsUpdate(Player player, Entity entity) {\r\n        byte flags = ((CraftEntity) entity).getHandle().getEntityData().get(ENTITY_DATA_ACCESSOR_FLAGS);\r\n        send(player, new ClientboundSetEntityDataPacket(entity.getEntityId(), List.of(createEntityData(ENTITY_DATA_ACCESSOR_FLAGS, flags))));\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityEffect(Player player, Entity entity, EntityEffect effect) {\r\n        send(player, new ClientboundEntityEventPacket(((CraftEntity) entity).getHandle(), effect.getData()));\r\n    }\r\n\r\n    @Override\r\n    public int getPacketStats(Player player, boolean sent) {\r\n        DenizenNetworkManagerImpl netMan = DenizenNetworkManagerImpl.getNetworkManager(player);\r\n        return sent ? netMan.packetsSent : netMan.packetsReceived;\r\n    }\r\n\r\n    @Override\r\n    public void setMapData(MapCanvas canvas, byte[] bytes, int x, int y, MapImage image) {\r\n        if (x > 127 || y > 127) {\r\n            return;\r\n        }\r\n        int width = Math.min(image.width, 128 - x),\r\n                height = Math.min(image.height, 128 - y);\r\n        if (x + width <= 0 || y + height <= 0) {\r\n            return;\r\n        }\r\n        try {\r\n            boolean anyChanged = false;\r\n            byte[] buffer = (byte[]) CANVAS_GET_BUFFER.invoke(canvas);\r\n            for (int x2 = x < 0 ? -x : 0; x2 < width; ++x2) {\r\n                for (int y2 = y < 0 ? -y : 0; y2 < height; ++y2) {\r\n                    byte p = bytes[y2 * image.width + x2];\r\n                    if (p != MapPalette.TRANSPARENT) {\r\n                        int index = (y2 + y) * 128 + (x2 + x);\r\n                        if (buffer[index] != p) {\r\n                            buffer[index] = p;\r\n                            anyChanged = true;\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            if (anyChanged) {\r\n                // Flag the whole image as dirty\r\n                MapItemSavedData map = (MapItemSavedData) MAPVIEW_WORLDMAP.get(canvas.getMapView());\r\n                map.setColorsDirty(Math.max(x, 0), Math.max(y, 0));\r\n                map.setColorsDirty(width + x - 1, height + y - 1);\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setNetworkManagerFor(Player player) {\r\n        DenizenNetworkManagerImpl.setNetworkManager(player);\r\n    }\r\n\r\n    @Override\r\n    public void enableNetworkManager() {\r\n        DenizenNetworkManagerImpl.enableNetworkManager();\r\n    }\r\n\r\n    @Override\r\n    public void showDebugTestMarker(Player player, Location location, ColorTag color, String name, int time) {\r\n        BlockPos nmsPos = Handler.toBlockPos(location);\r\n        LocationTag displayPos = !name.isEmpty() ? LocationTag.valueOf(name, CoreUtilities.noDebugContext) : null;\r\n        send(player, new ClientboundGameTestHighlightPosPacket(nmsPos, displayPos != null ? Handler.toBlockPos(displayPos) : nmsPos));\r\n    }\r\n\r\n    @Override\r\n    public void clearDebugTestMarker(Player player) {\r\n    }\r\n\r\n    @Override\r\n    public void sendBrand(Player player, String brand) {\r\n        BrandPayload payload = new BrandPayload(brand);\r\n        send(player, new ClientboundCustomPayloadPacket(payload));\r\n    }\r\n\r\n    @Override\r\n    public void sendCollectItemEntity(Player player, Entity taker, Entity item, int amount) {\r\n        send(player, new ClientboundTakeItemEntityPacket(item.getEntityId(), taker.getEntityId(), amount));\r\n    }\r\n\r\n    public Relative toNmsRelativeMovement(TeleportCommand.Relative relative) {\r\n        // TODO: 1.21.3: There seem to be more relative movement types now\r\n        return switch (relative) {\r\n            case X -> Relative.X;\r\n            case Y -> Relative.Y;\r\n            case Z -> Relative.Z;\r\n            case YAW -> Relative.Y_ROT;\r\n            case PITCH -> Relative.X_ROT;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public void sendRelativePositionPacket(Player player, double x, double y, double z, float yaw, float pitch, List<TeleportCommand.Relative> relativeAxis) {\r\n        Set<Relative> relativeMovements;\r\n        if (relativeAxis == null) {\r\n            relativeMovements = Relative.ALL;\r\n        }\r\n        else {\r\n            relativeMovements = EnumSet.noneOf(Relative.class);\r\n            for (TeleportCommand.Relative relative : relativeAxis) {\r\n                relativeMovements.add(toNmsRelativeMovement(relative));\r\n            }\r\n        }\r\n        ClientboundPlayerPositionPacket packet = new ClientboundPlayerPositionPacket(0, new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), relativeMovements);\r\n        sendAsyncSafe(player, packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendRelativeLookPacket(Player player, float yaw, float pitch) {\r\n        sendRelativePositionPacket(player, 0, 0, 0, yaw, pitch, null);\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityDataPacket(List<Player> players, Entity entity, List<Object> data) {\r\n        ClientboundSetEntityDataPacket setEntityDataPacket = new ClientboundSetEntityDataPacket(entity.getEntityId(), (List<SynchedEntityData.DataValue<?>>) (Object) data);\r\n        Iterator<Player> playerIterator = players.iterator();\r\n        while (playerIterator.hasNext()) {\r\n            Player player = playerIterator.next();\r\n            if (!DenizenNetworkManagerImpl.getConnection(player).isConnected()) {\r\n                playerIterator.remove();\r\n                continue;\r\n            }\r\n            sendAsyncSafe(player, setEntityDataPacket);\r\n        }\r\n    }\r\n\r\n    public static void send(Player player, Packet<?> packet) {\r\n        ((CraftPlayer) player).getHandle().connection.send(packet);\r\n    }\r\n\r\n    public static void broadcast(Packet<?> packet) {\r\n        ((CraftServer) Bukkit.getServer()).getHandle().broadcastAll(packet);\r\n    }\r\n\r\n    public static void sendAsyncSafe(Player player, Packet<?> packet) {\r\n        DenizenNetworkManagerImpl.getConnection(player).channel.writeAndFlush(packet);\r\n    }\r\n\r\n    public static <T> SynchedEntityData.DataValue<T> createEntityData(EntityDataAccessor<T> accessor, T value) {\r\n        return new SynchedEntityData.DataValue<>(accessor.id(), accessor.serializer(), value);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/PlayerHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.enums.CustomEntityType;\r\nimport com.denizenscript.denizen.nms.interfaces.PlayerHelper;\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.ImprovedOfflinePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.entities.CraftFakePlayerImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.entities.EntityItemProjectileImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.AbstractListenerPlayInImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.objects.EntityTag;\r\nimport com.denizenscript.denizen.objects.ItemTag;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.PlayerTag;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizen.utilities.entity.DenizenEntityType;\r\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\r\nimport com.denizenscript.denizencore.objects.Mechanism;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport it.unimi.dsi.fastutil.ints.IntList;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.core.registries.BuiltInRegistries;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.resources.Identifier;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerEntity;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.players.NameAndId;\r\nimport net.minecraft.server.players.PlayerList;\r\nimport net.minecraft.server.players.ServerOpList;\r\nimport net.minecraft.server.players.ServerOpListEntry;\r\nimport net.minecraft.stats.ServerRecipeBook;\r\nimport net.minecraft.tags.BlockTags;\r\nimport net.minecraft.tags.TagNetworkSerialization;\r\nimport net.minecraft.world.effect.MobEffectInstance;\r\nimport net.minecraft.world.entity.Avatar;\r\nimport net.minecraft.world.entity.Leashable;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.entity.PositionMoveRotation;\r\nimport net.minecraft.world.item.ItemCooldowns;\r\nimport net.minecraft.world.item.crafting.RecipeHolder;\r\nimport net.minecraft.world.item.crafting.RecipeManager;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.GameType;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.storage.LevelData;\r\nimport net.minecraft.world.phys.AABB;\r\nimport org.bukkit.*;\r\nimport org.bukkit.boss.BossBar;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.boss.CraftBossBar;\r\nimport org.bukkit.craftbukkit.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.entity.CraftPlayer;\r\nimport org.bukkit.craftbukkit.inventory.CraftItemStack;\r\nimport org.bukkit.craftbukkit.util.CraftMagicNumbers;\r\nimport org.bukkit.craftbukkit.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.Entity;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.scheduler.BukkitRunnable;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\nimport java.util.function.Predicate;\r\n\r\npublic class PlayerHelperImpl extends PlayerHelper {\r\n\r\n    public static final Field ATTACK_COOLDOWN_TICKS = ReflectionHelper.getFields(LivingEntity.class).get(\"attackStrengthTicker\", int.class);\r\n\r\n    public static final Field FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(\"aboveGroundTickCount\", int.class);\r\n    public static final Field VEHICLE_FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(\"aboveGroundVehicleTickCount\", int.class);\r\n    public static final Field PASSENGERS_PACKET_PASSENGERS = ReflectionHelper.getFields(ClientboundSetPassengersPacket.class).get(\"passengers\", int[].class);\r\n    public static final MethodHandle PLAYER_RESPAWNCONFIG_SETTER = ReflectionHelper.getFinalSetter(ServerPlayer.class, \"respawnConfig\", ServerPlayer.RespawnConfig.class);\r\n    public static final MethodHandle SERVER_RECIPE_BOOK_ADD_HIGHLIGHT = ReflectionHelper.getMethodHandle(ServerRecipeBook.class, \"addHighlight\", ResourceKey.class);\r\n\r\n    @Override\r\n    public void stopSound(Player player, NamespacedKey sound, SoundCategory category) {\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundStopSoundPacket(sound == null ? null : CraftNamespacedKey.toMinecraft(sound), null));\r\n    }\r\n\r\n    @Override\r\n    public void deTrackEntity(Player player, Entity entity) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ChunkMap.TrackedEntity tracker = nmsPlayer.level().getChunkSource().chunkMap.entityMap.get(entity.getEntityId());\r\n        if (tracker == null) {\r\n            if (NMSHandler.debugPackets) {\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Failed to de-track entity \" + entity.getEntityId() + \" for \" + player.getName() + \": tracker null\");\r\n            }\r\n            return;\r\n        }\r\n        sendEntityDestroy(player, entity);\r\n        tracker.removePlayer(nmsPlayer);\r\n    }\r\n\r\n    public record TrackerData(PlayerTag player, ServerEntity tracker) {}\r\n\r\n    @Override\r\n    public void addFakePassenger(List<PlayerTag> players, Entity vehicle, FakeEntity fakePassenger) {\r\n        ClientboundSetPassengersPacket packet = new ClientboundSetPassengersPacket(((CraftEntity) vehicle).getHandle());\r\n        int[] newPassengers = Arrays.copyOf(packet.getPassengers(), packet.getPassengers().length + 1);\r\n        newPassengers[packet.getPassengers().length] = fakePassenger.id;\r\n        try {\r\n            PASSENGERS_PACKET_PASSENGERS.set(packet, newPassengers);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        for (PlayerTag player : players) {\r\n            PacketHelperImpl.send(player.getPlayerEntity(), packet);\r\n        }\r\n    }\r\n\r\n    public record FakeEntitySynchronizer(ServerGamePacketListenerImpl target) implements ServerEntity.Synchronizer {\r\n\r\n        @Override\r\n        public void sendToTrackingPlayers(Packet<? super ClientGamePacketListener> packet) {\r\n            target.send(packet);\r\n        }\r\n\r\n        @Override\r\n        public void sendToTrackingPlayersAndSelf(Packet<? super ClientGamePacketListener> packet) {\r\n            sendToTrackingPlayers(packet);\r\n        }\r\n\r\n        @Override\r\n        public void sendToTrackingPlayersFiltered(Packet<? super ClientGamePacketListener> packet, Predicate<ServerPlayer> predicate) {\r\n            if (predicate.test(target.getPlayer())) {\r\n                sendToTrackingPlayers(packet);\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public void sendToTrackingPlayersFilteredAndSelf(Packet<? super ClientGamePacketListener> packet, Predicate<ServerPlayer> predicate) {\r\n            sendToTrackingPlayersFiltered(packet, predicate);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public FakeEntity sendEntitySpawn(List<PlayerTag> players, DenizenEntityType entityType, LocationTag location, ArrayList<Mechanism> mechanisms, int customId, UUID customUUID, boolean autoTrack) {\r\n        CraftWorld world = ((CraftWorld) location.getWorld());\r\n        net.minecraft.world.entity.Entity nmsEntity;\r\n        if (entityType.isCustom()) {\r\n            if (entityType.customEntityType == CustomEntityType.ITEM_PROJECTILE) {\r\n                ItemStack itemStack = new ItemStack(Material.STONE);\r\n                for (Mechanism mechanism : mechanisms) {\r\n                    if (mechanism.matches(\"item\") && mechanism.requireObject(ItemTag.class)) {\r\n                        itemStack = mechanism.valueAsType(ItemTag.class).getItemStack();\r\n                    }\r\n                }\r\n                nmsEntity = new EntityItemProjectileImpl(world.getHandle(), location, CraftItemStack.asNMSCopy(itemStack));\r\n            }\r\n            else if (entityType.customEntityType == CustomEntityType.FAKE_PLAYER) {\r\n                String name = null;\r\n                String skin = null;\r\n                String blob = null;\r\n                for (Mechanism mechanism : new ArrayList<>(mechanisms)) {\r\n                    if (mechanism.matches(\"name\")) {\r\n                        name = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin\")) {\r\n                        skin = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    else if (mechanism.matches(\"skin_blob\")) {\r\n                        blob = mechanism.getValue().asString();\r\n                        mechanisms.remove(mechanism);\r\n                    }\r\n                    if (name != null && (skin != null || blob != null)) {\r\n                        break;\r\n                    }\r\n                }\r\n                nmsEntity = ((CraftFakePlayerImpl) NMSHandler.customEntityHelper.spawnFakePlayer(location, name, skin, blob, false)).getHandle();\r\n            }\r\n            else {\r\n                throw new IllegalArgumentException(\"entityType\");\r\n            }\r\n        }\r\n        else {\r\n            org.bukkit.entity.Entity entity = world.createEntity(location, entityType.getBukkitEntityType().getEntityClass());\r\n            nmsEntity = ((CraftEntity) entity).getHandle();\r\n        }\r\n        if (customUUID != null) {\r\n            nmsEntity.setId(customId);\r\n            nmsEntity.setUUID(customUUID);\r\n        }\r\n        EntityTag entity = new EntityTag(nmsEntity.getBukkitEntity());\r\n        entity.isFake = true;\r\n        entity.isFakeValid = true;\r\n        for (Mechanism mechanism : mechanisms) {\r\n            entity.safeAdjustDuplicate(mechanism);\r\n        }\r\n        nmsEntity.unsetRemoved();\r\n        FakeEntity fake = new FakeEntity(players, location, entity.getBukkitEntity().getEntityId());\r\n        fake.entity = new EntityTag(entity.getBukkitEntity());\r\n        fake.entity.isFake = true;\r\n        fake.entity.isFakeValid = true;\r\n        List<TrackerData> trackers = new ArrayList<>();\r\n        fake.triggerSpawnPacket = (player) -> {\r\n            ServerPlayer nmsPlayer = ((CraftPlayer) player.getPlayerEntity()).getHandle();\r\n            ServerGamePacketListenerImpl conn = nmsPlayer.connection;\r\n            final ServerEntity tracker = new ServerEntity(world.getHandle(), nmsEntity, 1, true, new FakeEntitySynchronizer(conn), Set.of(conn));\r\n            tracker.addPairing(nmsPlayer);\r\n            final TrackerData data = new TrackerData(player, tracker);\r\n            trackers.add(data);\r\n            if (autoTrack) {\r\n                new BukkitRunnable() {\r\n                    boolean wasOnline = true;\r\n                    @Override\r\n                    public void run() {\r\n                        if (!fake.entity.isFakeValid) {\r\n                            cancel();\r\n                            return;\r\n                        }\r\n                        if (player.isOnline()) {\r\n                            if (!wasOnline) {\r\n                                tracker.addPairing(((CraftPlayer) player.getPlayerEntity()).getHandle());\r\n                                wasOnline = true;\r\n                            }\r\n                            tracker.sendChanges();\r\n                        }\r\n                        else if (wasOnline) {\r\n                            wasOnline = false;\r\n                        }\r\n                    }\r\n                }.runTaskTimer(Denizen.getInstance(), 1, 1);\r\n            }\r\n        };\r\n        for (PlayerTag player : players) {\r\n            fake.triggerSpawnPacket.accept(player);\r\n        }\r\n        fake.triggerUpdatePacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.sendChanges();\r\n                }\r\n            }\r\n        };\r\n        fake.triggerDestroyPacket = () -> {\r\n            for (TrackerData tracker : trackers) {\r\n                if (tracker.player.isOnline()) {\r\n                    tracker.tracker.removePairing(((CraftPlayer) tracker.player.getPlayerEntity()).getHandle());\r\n                }\r\n            }\r\n            trackers.clear();\r\n        };\r\n        return fake;\r\n    }\r\n\r\n    @Override\r\n    public void sendEntityDestroy(Player player, Entity entity) {\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(entity.getEntityId()));\r\n    }\r\n\r\n    @Override\r\n    public int getFlyKickCooldown(Player player) {\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl denizenListener) {\r\n            conn = denizenListener.oldListener;\r\n        }\r\n        try {\r\n            return Math.max(80 - Math.max(FLY_TICKS.getInt(conn), VEHICLE_FLY_TICKS.getInt(conn)), 0);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return 80;\r\n    }\r\n\r\n    @Override\r\n    public void setFlyKickCooldown(Player player, int ticks) {\r\n        ticks = 80 - ticks;\r\n        ServerGamePacketListenerImpl conn = ((CraftPlayer) player).getHandle().connection;\r\n        if (conn instanceof AbstractListenerPlayInImpl denizenListener) {\r\n            conn = denizenListener.oldListener;\r\n        }\r\n        try {\r\n            FLY_TICKS.setInt(conn, ticks);\r\n            VEHICLE_FLY_TICKS.setInt(conn, ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int ticksPassedDuringCooldown(Player player) {\r\n        try {\r\n            return ATTACK_COOLDOWN_TICKS.getInt(((CraftPlayer) player).getHandle());\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public float getMaxAttackCooldownTicks(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getCurrentItemAttackStrengthDelay() + 3;\r\n    }\r\n\r\n    @Override\r\n    public void setAttackCooldown(Player player, int ticks) {\r\n        try {\r\n            ATTACK_COOLDOWN_TICKS.setInt(((CraftPlayer) player).getHandle(), ticks);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean hasChunkLoaded(Player player, Chunk chunk) {\r\n        return ((CraftWorld) chunk.getWorld()).getHandle().getChunkSource().chunkMap\r\n                .getPlayers(new ChunkPos(chunk.getX(), chunk.getZ()), false).stream()\r\n                .anyMatch(entityPlayer -> entityPlayer.getUUID().equals(player.getUniqueId()));\r\n    }\r\n\r\n    @Override\r\n    public void setTemporaryOp(Player player, boolean op) {\r\n        MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();\r\n        NameAndId nameAndId = new NameAndId(((CraftPlayer) player).getProfile());\r\n        ServerOpList opList = server.getPlayerList().getOps();\r\n        if (op) {\r\n            opList.add(new ServerOpListEntry(nameAndId, server.operatorUserPermissions(), opList.canBypassPlayerLimit(nameAndId)));\r\n        }\r\n        else {\r\n            opList.remove(nameAndId);\r\n        }\r\n        player.recalculatePermissions();\r\n    }\r\n\r\n    @Override\r\n    public void showEndCredits(Player player) {\r\n        ((CraftPlayer) player).getHandle().wonGame = true;\r\n        ((CraftPlayer) player).getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1f));\r\n    }\r\n\r\n    @Override\r\n    public ImprovedOfflinePlayer getOfflineData(UUID uuid) {\r\n        return new ImprovedOfflinePlayerImpl(uuid);\r\n    }\r\n\r\n    @Override\r\n    public void resendRecipeDetails(Player player) {\r\n        RecipeManager recipeManager = ((CraftServer) Bukkit.getServer()).getServer().getRecipeManager();\r\n        ClientboundUpdateRecipesPacket updatePacket = new ClientboundUpdateRecipesPacket(recipeManager.getSynchronizedItemProperties(), recipeManager.getSynchronizedStonecutterRecipes());\r\n        ((CraftPlayer) player).getHandle().connection.send(updatePacket);\r\n    }\r\n\r\n    @Override\r\n    public void resendDiscoveredRecipes(Player player) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        recipeBook.sendInitialRecipeBook(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    @Override\r\n    public void quietlyAddRecipe(Player player, NamespacedKey key) {\r\n        ServerRecipeBook recipeBook = ((CraftPlayer) player).getHandle().getRecipeBook();\r\n        RecipeHolder<?> recipe = ItemHelperImpl.getNMSRecipe(key);\r\n        if (recipe == null) {\r\n            Debug.echoError(\"Cannot add recipe '\" + key + \"': it does not exist.\");\r\n            return;\r\n        }\r\n        recipeBook.add(recipe.id());\r\n        try {\r\n            SERVER_RECIPE_BOOK_ADD_HIGHLIGHT.invoke(recipeBook, recipe.id());\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public byte getSkinLayers(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getEntityData().get(Avatar.DATA_PLAYER_MODE_CUSTOMISATION);\r\n    }\r\n\r\n    @Override\r\n    public void setSkinLayers(Player player, byte flags) {\r\n        ((CraftPlayer) player).getHandle().getEntityData().set(Avatar.DATA_PLAYER_MODE_CUSTOMISATION, flags);\r\n    }\r\n\r\n    @Override\r\n    public void setBossBarTitle(BossBar bar, String title) {\r\n        ((CraftBossBar) bar).getHandle().name = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        ((CraftBossBar) bar).getHandle().broadcast(ClientboundBossEventPacket::createUpdateNamePacket);\r\n    }\r\n\r\n    @Override\r\n    public boolean getSpawnForced(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getRespawnConfig().forced();\r\n    }\r\n\r\n    @Override\r\n    public void setSpawnForced(Player player, boolean forced) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        try {\r\n            ServerPlayer.RespawnConfig config = nmsPlayer.getRespawnConfig();\r\n            config = new ServerPlayer.RespawnConfig(config.respawnData(), forced);\r\n            PLAYER_RESPAWNCONFIG_SETTER.invoke(nmsPlayer, config);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Location getBedSpawnLocation(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerPlayer.RespawnConfig nmsRespawnConfig = nmsPlayer.getRespawnConfig();\r\n        if (nmsRespawnConfig == null) {\r\n            return null;\r\n        }\r\n        LevelData.RespawnData nmsRespawnData = nmsRespawnConfig.respawnData();\r\n        Level nmsWorld = MinecraftServer.getServer().getLevel(nmsRespawnData.dimension());\r\n        return nmsWorld != null ? new Location(nmsWorld.getWorld(), nmsRespawnData.pos().getX(), nmsRespawnData.pos().getY(), nmsRespawnData.pos().getZ(), nmsRespawnData.yaw(), nmsRespawnData.pitch()) : null;\r\n    }\r\n\r\n    @Override\r\n    public long getLastActionTime(Player player) {\r\n        return ((CraftPlayer) player).getHandle().getLastActionTime();\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerInfoAddPacket(Player player, EnumSet<ProfileEditMode> editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) {\r\n        EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action.class);\r\n        for (ProfileEditMode editMode : editModes) {\r\n            actions.add(switch (editMode) {\r\n                case ADD -> ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER;\r\n                case UPDATE_DISPLAY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME;\r\n                case UPDATE_LATENCY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY;\r\n                case UPDATE_GAME_MODE -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE;\r\n                case UPDATE_LISTED -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED;\r\n            });\r\n        }\r\n        GameProfile profile = ProfileEditorImpl.createGameProfile(id, name, texture, signature);\r\n        // TODO: 1.21.3: Player list order and hat visibility support\r\n        ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(id, profile, listed, latency, gameMode == null ? null : GameType.byId(gameMode.getValue()), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)), true, player.getPlayerListOrder(), null);\r\n        PacketHelperImpl.send(player, ProfileEditorImpl.createInfoPacket(actions, List.of(entry)));\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerInfoRemovePacket(Player player, UUID id) {\r\n        PacketHelperImpl.send(player, new ClientboundPlayerInfoRemovePacket(List.of(id)));\r\n    }\r\n\r\n    @Override\r\n    public void sendClimbableMaterials(Player player, List<Material> materials) {\r\n        Map<ResourceKey<? extends Registry<?>>, TagNetworkSerialization.NetworkPayload> packetInput = TagNetworkSerialization.serializeTagsToNetwork(((CraftServer) Bukkit.getServer()).getServer().registries());\r\n        Map<Identifier, IntList> tags = ReflectionHelper.getFieldValue(TagNetworkSerialization.NetworkPayload.class, \"tags\", packetInput.get(BuiltInRegistries.BLOCK.key()));\r\n        IntList climbableBlocks = tags.get(BlockTags.CLIMBABLE.location());\r\n        climbableBlocks.clear();\r\n        for (Material material : materials) {\r\n            climbableBlocks.add(BuiltInRegistries.BLOCK.getId(CraftMagicNumbers.getBlock(material)));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundUpdateTagsPacket(packetInput));\r\n    }\r\n\r\n    @Override\r\n    public void refreshPlayer(Player player) {\r\n        ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerLevel nmsWorld = nmsPlayer.level();\r\n        nmsPlayer.connection.send(new ClientboundRespawnPacket(nmsPlayer.createCommonSpawnInfo(nmsWorld), ClientboundRespawnPacket.KEEP_ALL_DATA));\r\n        nmsPlayer.connection.internalTeleport(PositionMoveRotation.of(nmsPlayer), Set.of());\r\n        if (nmsPlayer.isPassenger()) {\r\n           nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer.getVehicle()));\r\n        }\r\n        if (nmsPlayer.isVehicle()) {\r\n            nmsPlayer.connection.send(new ClientboundSetPassengersPacket(nmsPlayer));\r\n        }\r\n        AABB boundingBox = AABB.ofSize(nmsPlayer.getBoundingBox().getCenter(), 32, 32, 32);\r\n        for (net.minecraft.world.entity.Entity nmsEntity : nmsWorld.getEntitiesOfClass(net.minecraft.world.entity.Entity.class, boundingBox, nmsEntity -> nmsEntity instanceof Leashable nmsLeashable && nmsPlayer.equals(nmsLeashable.getLeashHolder()))) {\r\n            nmsPlayer.connection.send(new ClientboundSetEntityLinkPacket(nmsEntity, nmsPlayer));\r\n        }\r\n        if (!nmsPlayer.getCooldowns().cooldowns.isEmpty()) {\r\n            int tickCount = nmsPlayer.getCooldowns().tickCount;\r\n            for (Map.Entry<Identifier, ItemCooldowns.CooldownInstance> entry : nmsPlayer.getCooldowns().cooldowns.entrySet()) {\r\n                nmsPlayer.connection.send(new ClientboundCooldownPacket(entry.getKey(), entry.getValue().endTime - tickCount));\r\n            }\r\n        }\r\n        nmsPlayer.connection.send(new ClientboundSetExperiencePacket(nmsPlayer.experienceProgress, nmsPlayer.totalExperience, nmsPlayer.experienceLevel));\r\n        for (MobEffectInstance nmsEffect : nmsPlayer.getActiveEffects()) {\r\n            nmsPlayer.connection.send(new ClientboundUpdateMobEffectPacket(nmsPlayer.getId(), nmsEffect, false));\r\n        }\r\n        nmsPlayer.onUpdateAbilities();\r\n        PlayerList nmsPlayerList = MinecraftServer.getServer().getPlayerList();\r\n        nmsPlayerList.sendPlayerPermissionLevel(nmsPlayer);\r\n        nmsPlayerList.sendLevelInfo(nmsPlayer, nmsWorld);\r\n        nmsPlayerList.sendAllPlayerInfo(nmsPlayer);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/helpers/WorldHelperImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.helpers;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.WorldHelper;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.BiomeNMSImpl;\r\nimport com.denizenscript.denizen.objects.BiomeTag;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.mojang.datafixers.util.Pair;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.players.SleepStatus;\r\nimport net.minecraft.world.DifficultyInstance;\r\nimport net.minecraft.world.entity.LivingEntity;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.storage.PrimaryLevelData;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.event.world.TimeSkipEvent;\r\n\r\npublic class WorldHelperImpl implements WorldHelper {\r\n\r\n    @Override\r\n    public boolean isStatic(World world) {\r\n        return ((CraftWorld) world).getHandle().isClientSide();\r\n    }\r\n\r\n    @Override\r\n    public void setStatic(World world, boolean isStatic) {\r\n        ServerLevel worldServer = ((CraftWorld) world).getHandle();\r\n        ReflectionHelper.setFieldValue(net.minecraft.world.level.Level.class, \"isClientSide\", worldServer, isStatic);\r\n    }\r\n\r\n    @Override\r\n    public float getLocalDifficulty(Location location) {\r\n        BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());\r\n        DifficultyInstance scaler = ((CraftWorld) location.getWorld()).getHandle().getCurrentDifficultyAt(pos);\r\n        return scaler.getEffectiveDifficulty();\r\n    }\r\n\r\n    @Override\r\n    public Location getNearestBiomeLocation(Location start, BiomeTag biome) {\r\n        Pair<BlockPos, Holder<Biome>> result = ((CraftWorld) start.getWorld()).getHandle()\r\n                .findClosestBiome3d(b -> b.is(((BiomeNMSImpl) biome.getBiome()).biomeHolder.unwrapKey().get()), new BlockPos(start.getBlockX(), start.getBlockY(), start.getBlockZ()), 6400, 32, 64);\r\n        if (result == null || result.getFirst() == null) {\r\n            return null;\r\n        }\r\n        return new Location(start.getWorld(), result.getFirst().getX(), result.getFirst().getY(), result.getFirst().getZ());\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughSleeping(World world, int percentage) {\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, \"sleepStatus\", ((CraftWorld) world).getHandle());\r\n        return status.areEnoughSleeping(percentage);\r\n    }\r\n\r\n    @Override\r\n    public boolean areEnoughDeepSleeping(World world, int percentage) {\r\n        ServerLevel level = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, \"sleepStatus\", level);\r\n        return status.areEnoughDeepSleeping(percentage, level.players());\r\n    }\r\n\r\n    @Override\r\n    public int getSkyDarken(World world) {\r\n        return ((CraftWorld) world).getHandle().getSkyDarken();\r\n    }\r\n\r\n    @Override\r\n    public boolean isDay(World world) {\r\n        return ((CraftWorld) world).getHandle().isBrightOutside();\r\n    }\r\n\r\n    @Override\r\n    public boolean isNight(World world) {\r\n        return ((CraftWorld) world).getHandle().isDarkOutside();\r\n    }\r\n\r\n    @Override\r\n    public void setDayTime(World world, long time) {\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        nmsWorld.dimensionType().defaultClock().ifPresent((clock) -> nmsWorld.clockManager().setTotalTicks(clock, time, TimeSkipEvent.SkipReason.CUSTOM));\r\n    }\r\n\r\n    @Override\r\n    public void setGameTime(World world, long time) {\r\n        ((PrimaryLevelData) ((CraftWorld) world).getHandle().levelData).setGameTime(time);\r\n    }\r\n\r\n    // net.minecraft.server.level.ServerLevel#wakeUpAllPlayers()\r\n    @Override\r\n    public void wakeUpAllPlayers(World world) {\r\n        ServerLevel nmsWorld = ((CraftWorld) world).getHandle();\r\n        SleepStatus status = ReflectionHelper.getFieldValue(ServerLevel.class, \"sleepStatus\", nmsWorld);\r\n        status.removeAllSleepers();\r\n        nmsWorld.getPlayers(LivingEntity::isSleeping).forEach((player) -> player.stopSleepInBed(false, false));\r\n    }\r\n\r\n    @Override\r\n    public void clearWeather(World world) {\r\n        ((CraftWorld) world).getHandle().resetWeatherCycle();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/BiomeNMSImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BiomeNMS;\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.core.Holder;\r\nimport net.minecraft.core.MappedRegistry;\r\nimport net.minecraft.core.RegistrationInfo;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.resources.ResourceKey;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.util.random.Weighted;\r\nimport net.minecraft.util.random.WeightedList;\r\nimport net.minecraft.world.attribute.EnvironmentAttribute;\r\nimport net.minecraft.world.attribute.EnvironmentAttributeMap;\r\nimport net.minecraft.world.attribute.EnvironmentAttributes;\r\nimport net.minecraft.world.entity.MobCategory;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.BiomeSpecialEffects;\r\nimport net.minecraft.world.level.biome.MobSpawnSettings;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.NamespacedKey;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.entity.CraftEntityType;\r\nimport org.bukkit.craftbukkit.util.CraftNamespacedKey;\r\nimport org.bukkit.entity.EntityType;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.Optional;\r\n\r\npublic class BiomeNMSImpl extends BiomeNMS {\r\n\r\n    public static final MethodHandle BIOME_CLIMATESETTINGS_CONSTRUCTOR = ReflectionHelper.getConstructor(Biome.ClimateSettings.class, boolean.class, float.class, Biome.TemperatureModifier.class, float.class);\r\n    public static final MethodHandle MAPPED_REGISTRY_REGISTRATION_INFOS = ReflectionHelper.getFields(MappedRegistry.class).getGetter(\"registrationInfos\");\r\n    public static final MethodHandle BIOME_ATTRIBUTES_SETTER = ReflectionHelper.getFields(Biome.class).getSetter(\"attributes\");\r\n\r\n    public Holder.Reference<Biome> biomeHolder;\r\n    public ServerLevel world;\r\n\r\n    public BiomeNMSImpl(ServerLevel world, NamespacedKey key) {\r\n        super(world.getWorld(), key);\r\n        this.world = world;\r\n        this.biomeHolder = getBiomeRegistry().get(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(key))).orElse(null);\r\n    }\r\n\r\n    private MappedRegistry<Biome> getBiomeRegistry() {\r\n        return (MappedRegistry<Biome>) world.registryAccess().lookupOrThrow(Registries.BIOME);\r\n    }\r\n\r\n    @Override\r\n    public DownfallType getDownfallTypeAt(Location location) {\r\n        Biome.Precipitation precipitation = biomeHolder.value().getPrecipitationAt(Handler.toBlockPos(location), world.getSeaLevel());\r\n        return switch (precipitation) {\r\n            case RAIN -> DownfallType.RAIN;\r\n            case SNOW -> DownfallType.SNOW;\r\n            case NONE -> DownfallType.NONE;\r\n        };\r\n    }\r\n\r\n    @Override\r\n    public float getHumidity() {\r\n        return biomeHolder.value().climateSettings.downfall();\r\n    }\r\n\r\n    @Override\r\n    public float getBaseTemperature() {\r\n        return biomeHolder.value().getBaseTemperature();\r\n    }\r\n\r\n    @Override\r\n    public float getTemperatureAt(Location location) {\r\n        return biomeHolder.value().getTemperature(Handler.toBlockPos(location), world.getSeaLevel());\r\n    }\r\n\r\n    @Override\r\n    public boolean hasDownfall() {\r\n        return biomeHolder.value().hasPrecipitation();\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getAmbientEntities() {\r\n        return getSpawnableEntities(MobCategory.AMBIENT);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getCreatureEntities() {\r\n        return getSpawnableEntities(MobCategory.CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getMonsterEntities() {\r\n        return getSpawnableEntities(MobCategory.MONSTER);\r\n    }\r\n\r\n    @Override\r\n    public List<EntityType> getWaterEntities() {\r\n        return getSpawnableEntities(MobCategory.WATER_CREATURE);\r\n    }\r\n\r\n    @Override\r\n    public int getFoliageColor() {\r\n        // Check if the biome already has a default color\r\n        if (biomeHolder.value().getFoliageColor() != 0) {\r\n            return biomeHolder.value().getFoliageColor();\r\n        }\r\n        // Based on net.minecraft.world.level.biome.Biome#getFoliageColorFromTexture()\r\n        float temperature = clampColor(getBaseTemperature());\r\n        float humidity = clampColor(getHumidity());\r\n        // Based on net.minecraft.world.level.FoliageColor#get()\r\n        humidity *= temperature;\r\n        int humidityValue = (int)((1.0f - humidity) * 255.0f);\r\n        int temperatureValue = (int)((1.0f - temperature) * 255.0f);\r\n        int index = temperatureValue << 8 | humidityValue;\r\n        return index >= 65536 ? 4764952 : getColor(index / 256, index % 256).asRGB();\r\n    }\r\n\r\n    public void setClimate(boolean hasPrecipitation, float temperature, Biome.TemperatureModifier temperatureModifier, float downfall) {\r\n        try {\r\n            Object newClimate = BIOME_CLIMATESETTINGS_CONSTRUCTOR.invoke(hasPrecipitation, temperature, temperatureModifier, downfall);\r\n            ReflectionHelper.setFieldValue(Biome.class, \"climateSettings\", biomeHolder.value(), newClimate);\r\n            setNetworkedRegistrationInfo();\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setHumidity(float humidity) {\r\n        setClimate(hasDownfall(), getBaseTemperature(), getTemperatureModifier(), humidity);\r\n    }\r\n\r\n    @Override\r\n    public void setBaseTemperature(float baseTemperature) {\r\n        setClimate(hasDownfall(), baseTemperature, getTemperatureModifier(), getHumidity());\r\n    }\r\n\r\n    @Override\r\n    public void setHasDownfall(boolean hasDownfall) {\r\n        setClimate(hasDownfall, getBaseTemperature(), getTemperatureModifier(), getHumidity());\r\n    }\r\n\r\n    @Override\r\n    public void setFoliageColor(int color) {\r\n        BiomeSpecialEffects nmsCurrEffects = biomeHolder.value().getSpecialEffects();\r\n        BiomeSpecialEffects nmsNewEffects = new BiomeSpecialEffects(\r\n                nmsCurrEffects.waterColor(), Optional.of(color), nmsCurrEffects.dryFoliageColorOverride(), nmsCurrEffects.grassColorOverride(), nmsCurrEffects.grassColorModifier()\r\n        );\r\n        ReflectionHelper.setFieldValue(Biome.class, \"specialEffects\", biomeHolder.value(), nmsNewEffects);\r\n        setNetworkedRegistrationInfo();\r\n    }\r\n\r\n    @Override\r\n    public int getFogColor() {\r\n        return getEnvironmentAttribute(EnvironmentAttributes.FOG_COLOR);\r\n    }\r\n\r\n    @Override\r\n    public void setFogColor(int color) {\r\n        setEnvironmentAttribute(EnvironmentAttributes.FOG_COLOR, color);\r\n    }\r\n\r\n    @Override\r\n    public int getWaterFogColor() {\r\n        return getEnvironmentAttribute(EnvironmentAttributes.WATER_FOG_COLOR);\r\n    }\r\n\r\n    @Override\r\n    public void setWaterFogColor(int color) {\r\n        setEnvironmentAttribute(EnvironmentAttributes.WATER_FOG_COLOR, color);\r\n    }\r\n\r\n    public <T> T getEnvironmentAttribute(EnvironmentAttribute<T> attribute) {\r\n        return biomeHolder.value().getAttributes().applyModifier(attribute, attribute.defaultValue());\r\n    }\r\n\r\n    public <T> void setEnvironmentAttribute(EnvironmentAttribute<T> attribute, T value) {\r\n        Biome nmsBiome = biomeHolder.value();\r\n        EnvironmentAttributeMap newAttributeMap = EnvironmentAttributeMap.builder().putAll(nmsBiome.getAttributes()).set(attribute, value).build();\r\n        try {\r\n            BIOME_ATTRIBUTES_SETTER.invokeExact(nmsBiome, newAttributeMap);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n        }\r\n        setNetworkedRegistrationInfo();\r\n    }\r\n\r\n    private List<EntityType> getSpawnableEntities(MobCategory creatureType) {\r\n        MobSpawnSettings mobs = biomeHolder.value().getMobSettings();\r\n        WeightedList<MobSpawnSettings.SpawnerData> typeSettingList = mobs.getMobs(creatureType);\r\n        List<EntityType> entityTypes = new ArrayList<>();\r\n        if (typeSettingList == null) {\r\n            return entityTypes;\r\n        }\r\n        for (Weighted<MobSpawnSettings.SpawnerData> meta : typeSettingList.unwrap()) {\r\n            entityTypes.add(CraftEntityType.minecraftToBukkit(meta.value().type()));\r\n        }\r\n        return entityTypes;\r\n    }\r\n\r\n    @Override\r\n    public void setTo(Block block) {\r\n        if (((CraftWorld) block.getWorld()).getHandle() != this.world) {\r\n            NMSHandler.instance.getBiomeNMS(block.getWorld(), getKey()).setTo(block);\r\n            return;\r\n        }\r\n        // Based on CraftWorld source\r\n        BlockPos pos = new BlockPos(block.getX(), 0, block.getZ());\r\n        if (world.hasChunkAt(pos)) {\r\n            LevelChunk chunk = world.getChunkAt(pos);\r\n            if (chunk != null) {\r\n                chunk.setBiome(block.getX() >> 2, block.getY() >> 2, block.getZ() >> 2, biomeHolder);\r\n                chunk.markUnsaved();\r\n            }\r\n        }\r\n    }\r\n\r\n    public Biome.TemperatureModifier getTemperatureModifier() {\r\n        return biomeHolder.value().climateSettings.temperatureModifier();\r\n    }\r\n\r\n    private void setNetworkedRegistrationInfo() {\r\n        try {\r\n            Map<ResourceKey<Biome>, RegistrationInfo> registrationInfos = (Map<ResourceKey<Biome>, RegistrationInfo>) MAPPED_REGISTRY_REGISTRATION_INFOS.invokeExact(getBiomeRegistry());\r\n            registrationInfos.put(biomeHolder.key(), RegistrationInfo.BUILT_IN);\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(\"Failed to set biome registration info, changes may not be synced correctly.\");\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/ImprovedOfflinePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizen.nms.v26_1.helpers.NBTAdapter;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.kyori.adventure.nbt.CompoundBinaryTag;\r\nimport net.minecraft.nbt.NbtAccounter;\r\nimport net.minecraft.nbt.NbtIo;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ClientInformation;\r\nimport net.minecraft.server.level.ParticleStatus;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.world.ItemStackWithSlot;\r\nimport net.minecraft.world.entity.EntityEquipment;\r\nimport net.minecraft.world.entity.HumanoidArm;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeInstance;\r\nimport net.minecraft.world.entity.ai.attributes.AttributeMap;\r\nimport net.minecraft.world.entity.ai.attributes.Attributes;\r\nimport net.minecraft.world.entity.ai.attributes.DefaultAttributes;\r\nimport net.minecraft.world.entity.player.ChatVisiblity;\r\nimport net.minecraft.world.inventory.PlayerEnderChestContainer;\r\nimport net.minecraft.world.level.storage.ValueOutput;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.inventory.CraftInventory;\r\nimport org.bukkit.craftbukkit.inventory.CraftInventoryPlayer;\r\nimport org.bukkit.entity.HumanEntity;\r\nimport org.bukkit.inventory.Inventory;\r\nimport org.bukkit.inventory.InventoryHolder;\r\n\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.FileOutputStream;\r\nimport java.util.UUID;\r\nimport java.util.function.Consumer;\r\n\r\npublic class ImprovedOfflinePlayerImpl extends ImprovedOfflinePlayer {\r\n\r\n    public ImprovedOfflinePlayerImpl(UUID playeruuid) {\r\n        super(playeruuid);\r\n    }\r\n\r\n    public static class OfflinePlayerInventory extends net.minecraft.world.entity.player.Inventory {\r\n\r\n        public OfflinePlayerInventory(net.minecraft.world.entity.player.Player entityhuman) {\r\n            super(entityhuman, new EntityEquipment()); // TODO: 1.21.5: is the new Equipment right here?\r\n        }\r\n\r\n        @Override\r\n        public InventoryHolder getOwner() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static class OfflineCraftInventoryPlayer extends CraftInventoryPlayer {\r\n\r\n        public OfflineCraftInventoryPlayer(net.minecraft.world.entity.player.Inventory inventory) {\r\n            super(inventory);\r\n        }\r\n\r\n        @Override\r\n        public HumanEntity getHolder() {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    public static ServerPlayer fakeNmsPlayer;\r\n\r\n    public static ServerPlayer getFakeNmsPlayer() {\r\n        if (fakeNmsPlayer == null) {\r\n            MinecraftServer server = ((CraftServer)Bukkit.getServer()).getServer();\r\n            World world = Bukkit.getWorlds().getFirst();\r\n            GameProfile fakeProfile = new GameProfile(new UUID(0, 0xABC123), \"fakeplayer\");\r\n            ClientInformation fakeClientInfo = new ClientInformation(\"en\", 0, ChatVisiblity.HIDDEN, false, 0, HumanoidArm.LEFT, true, false, ParticleStatus.MINIMAL);\r\n            fakeNmsPlayer = new ServerPlayer(server, ((CraftWorld) world).getHandle(), fakeProfile, fakeClientInfo);\r\n        }\r\n        return fakeNmsPlayer;\r\n    }\r\n\r\n    public void editData(Consumer<ValueOutput> editor) {\r\n        this.compound = NBTAdapter.toAPI(Handler.useValueOutput(NBTAdapter.toNMS(this.compound), editor));\r\n        markModified();\r\n    }\r\n\r\n    @Override\r\n    public org.bukkit.inventory.PlayerInventory getInventory() {\r\n        if (inventory == null) {\r\n            net.minecraft.world.entity.player.Inventory newInv = new OfflinePlayerInventory(getFakeNmsPlayer());\r\n            Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> newInv.load(valueInput.listOrEmpty(\"Inventory\", ItemStackWithSlot.CODEC)));\r\n            inventory = new OfflineCraftInventoryPlayer(newInv);\r\n        }\r\n        return inventory;\r\n    }\r\n\r\n    @Override\r\n    public void setInventory(org.bukkit.inventory.PlayerInventory inventory) {\r\n        CraftInventoryPlayer inv = (CraftInventoryPlayer) inventory;\r\n        editData(valueOutput -> inv.getInventory().save(valueOutput.list(\"Inventory\", ItemStackWithSlot.CODEC)));\r\n    }\r\n\r\n    @Override\r\n    public Inventory getEnderChest() {\r\n        if (enderchest == null) {\r\n            PlayerEnderChestContainer nmsEnderChest = new PlayerEnderChestContainer(getFakeNmsPlayer());\r\n            Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> nmsEnderChest.fromSlots(valueInput.listOrEmpty(\"EnderItems\", ItemStackWithSlot.CODEC)));\r\n            enderchest = new CraftInventory(nmsEnderChest);\r\n        }\r\n        return enderchest;\r\n    }\r\n\r\n    @Override\r\n    public void setEnderChest(Inventory inventory) {\r\n        editData(valueOutput -> ((PlayerEnderChestContainer) ((CraftInventory) inventory).getInventory()).storeAsSlots(valueOutput.list(\"EnderItems\", ItemStackWithSlot.CODEC)));\r\n    }\r\n\r\n    @Override\r\n    public double getMaxHealth() {\r\n        AttributeInstance maxHealth = getAttributes().getInstance(Attributes.MAX_HEALTH);\r\n        return maxHealth == null ? Attributes.MAX_HEALTH.value().getDefaultValue() : maxHealth.getValue();\r\n    }\r\n\r\n    @Override\r\n    public void setMaxHealth(double input) {\r\n        AttributeMap attributes = getAttributes();\r\n        AttributeInstance maxHealth = attributes.getInstance(Attributes.MAX_HEALTH);\r\n        maxHealth.setBaseValue(input);\r\n        setAttributes(attributes);\r\n    }\r\n\r\n    private AttributeMap getAttributes() {\r\n        AttributeMap amb = new AttributeMap(DefaultAttributes.getSupplier(net.minecraft.world.entity.EntityType.PLAYER));\r\n        Handler.useValueInput(NBTAdapter.toNMS(this.compound), valueInput -> valueInput.read(\"attributes\", AttributeInstance.Packed.LIST_CODEC).ifPresent(amb::apply));\r\n        return amb;\r\n    }\r\n\r\n    public void setAttributes(AttributeMap attributes) {\r\n        editData(valueOutput -> valueOutput.store(\"attributes\", AttributeInstance.Packed.LIST_CODEC, attributes.pack()));\r\n    }\r\n\r\n    @Override\r\n    protected boolean loadPlayerData(UUID uuid) {\r\n        try {\r\n            this.player = uuid;\r\n            this.file = new File(MinecraftServer.getServer().playerDataStorage.getPlayerDir(), this.player + \".dat\");\r\n            if (this.file.exists()) {\r\n                this.compound = NBTAdapter.toAPI(NbtIo.readCompressed(new FileInputStream(this.file), NbtAccounter.unlimitedHeap()));\r\n                return true;\r\n            }\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void saveInternal(CompoundBinaryTag compound) {\r\n        try {\r\n            NbtIo.writeCompressed(NBTAdapter.toNMS(compound), new FileOutputStream(this.file));\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/ProfileEditorImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.ProfileEditor;\r\nimport com.denizenscript.denizen.nms.util.PlayerProfile;\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizen.nms.v26_1.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.collect.ImmutableMultimap;\r\nimport com.mojang.authlib.GameProfile;\r\nimport com.mojang.authlib.properties.Property;\r\nimport com.mojang.authlib.properties.PropertyMap;\r\nimport com.mojang.datafixers.util.Either;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.item.component.ResolvableProfile;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.entity.CraftPlayer;\r\nimport org.bukkit.entity.Player;\r\nimport org.bukkit.event.player.PlayerRespawnEvent;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.util.*;\r\n\r\npublic class ProfileEditorImpl extends ProfileEditor {\r\n\r\n    @Override\r\n    protected void updatePlayer(final Player player, final boolean isSkinChanging) {\r\n        final ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();\r\n        final UUID uuid = player.getUniqueId();\r\n        ClientboundPlayerInfoRemovePacket removePlayerInfoPacket = new ClientboundPlayerInfoRemovePacket(List.of(uuid));\r\n        ClientboundPlayerInfoUpdatePacket addPlayerInfoPacket = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(nmsPlayer));\r\n        for (Player otherPlayer : Bukkit.getServer().getOnlinePlayers()) {\r\n            PacketHelperImpl.send(otherPlayer, removePlayerInfoPacket);\r\n            PacketHelperImpl.send(otherPlayer, addPlayerInfoPacket);\r\n        }\r\n        for (Player otherPlayer : NMSHandler.entityHelper.getPlayersThatSee(player)) {\r\n            if (!otherPlayer.getUniqueId().equals(uuid)) {\r\n                PacketHelperImpl.forceRespawnPlayerEntity(player, otherPlayer);\r\n            }\r\n        }\r\n        if (isSkinChanging) {\r\n            ((CraftServer) Bukkit.getServer()).getHandle().respawn(nmsPlayer, true, Entity.RemovalReason.CHANGED_DIMENSION, PlayerRespawnEvent.RespawnReason.PLUGIN);\r\n        }\r\n        else {\r\n            NMSHandler.playerHelper.refreshPlayer(player);\r\n        }\r\n        player.updateInventory();\r\n    }\r\n\r\n    public static void registerHandlers() {\r\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoUpdatePacket.class, ProfileEditorImpl::processPlayerInfoUpdatePacket);\r\n    }\r\n\r\n    public static ClientboundPlayerInfoUpdatePacket processPlayerInfoUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket) {\r\n        if (ProfileEditor.mirrorUUIDs.isEmpty() && !RenameCommand.hasAnyDynamicRenames() && fakeProfiles.isEmpty()) {\r\n            return playerInfoUpdatePacket;\r\n        }\r\n        EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = playerInfoUpdatePacket.actions();\r\n        if (!actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) && !actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {\r\n            return playerInfoUpdatePacket;\r\n        }\r\n        boolean any = false;\r\n        for (ClientboundPlayerInfoUpdatePacket.Entry entry : playerInfoUpdatePacket.entries()) {\r\n            if (shouldChange(entry)) {\r\n                any = true;\r\n                break;\r\n            }\r\n        }\r\n        if (!any) {\r\n            return playerInfoUpdatePacket;\r\n        }\r\n        GameProfile ownProfile = networkManager.player.getGameProfile();\r\n        List<ClientboundPlayerInfoUpdatePacket.Entry> modifiedEntries = new ArrayList<>(playerInfoUpdatePacket.entries().size());\r\n        for (ClientboundPlayerInfoUpdatePacket.Entry entry : playerInfoUpdatePacket.entries()) {\r\n            if (!shouldChange(entry)) {\r\n                modifiedEntries.add(entry);\r\n                continue;\r\n            }\r\n            String rename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), false);\r\n            GameProfile baseProfile = fakeProfiles.containsKey(entry.profileId()) ? getGameProfile(fakeProfiles.get(entry.profileId())) : entry.profile();\r\n            PropertyMap modifiedProperties;\r\n            if (ProfileEditor.mirrorUUIDs.contains(entry.profileId())) {\r\n                modifiedProperties = ownProfile.properties();\r\n            }\r\n            else {\r\n                // On Paper 1.19+, we use Paper's PlayerProfile API instead of this system\r\n                modifiedProperties = Denizen.supportsPaper ? entry.profile().properties() : baseProfile.properties();\r\n            }\r\n            GameProfile modifiedProfile = new GameProfile(baseProfile.id(), rename != null ? (rename.length() > 16 ? rename.substring(0, 16) : rename) : baseProfile.name(), modifiedProperties);\r\n            String listRename = RenameCommand.getCustomNameFor(entry.profileId(), networkManager.player.getBukkitEntity(), true);\r\n            Component displayName = listRename != null ? Handler.componentToNMS(FormattedTextHelper.parse(listRename, ChatColor.WHITE)) : entry.displayName();\r\n            ClientboundPlayerInfoUpdatePacket.Entry modifiedEntry = new ClientboundPlayerInfoUpdatePacket.Entry(entry.profileId(), modifiedProfile, entry.listed(), entry.latency(), entry.gameMode(), displayName, entry.showHat(), entry.listOrder(), entry.chatSession());\r\n            modifiedEntries.add(modifiedEntry);\r\n        }\r\n        return createInfoPacket(actions, modifiedEntries);\r\n    }\r\n\r\n    public static boolean shouldChange(ClientboundPlayerInfoUpdatePacket.Entry entry) {\r\n        return ProfileEditor.mirrorUUIDs.contains(entry.profileId()) || RenameCommand.customNames.containsKey(entry.profileId()) || fakeProfiles.containsKey(entry.profileId());\r\n    }\r\n\r\n    public static final Field ClientboundPlayerInfoUpdatePacket_entries = ReflectionHelper.getFields(ClientboundPlayerInfoUpdatePacket.class).getFirstOfType(List.class);\r\n\r\n    public static ClientboundPlayerInfoUpdatePacket createInfoPacket(EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, List<ClientboundPlayerInfoUpdatePacket.Entry> entries) {\r\n        ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket = new ClientboundPlayerInfoUpdatePacket(actions, List.of());\r\n        try {\r\n            ClientboundPlayerInfoUpdatePacket_entries.set(playerInfoUpdatePacket, entries);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return playerInfoUpdatePacket;\r\n    }\r\n\r\n    public static GameProfile createGameProfile(UUID uuid, String name, String texture, String signature) {\r\n        return new GameProfile(\r\n                uuid != null ? uuid : NIL_UUID, name != null ? name : EMPTY_NAME,\r\n                texture != null ? new PropertyMap(ImmutableMultimap.of(\"textures\", new Property(\"textures\", texture, signature))) : PropertyMap.EMPTY\r\n        );\r\n    }\r\n\r\n    public static GameProfile getGameProfileNoProperties(PlayerProfile playerProfile) {\r\n        return createGameProfile(playerProfile.getUniqueId(), playerProfile.getName(), null, null);\r\n    }\r\n\r\n    public static GameProfile getGameProfile(PlayerProfile playerProfile) {\r\n        return createGameProfile(playerProfile.getUniqueId(), playerProfile.getName(), playerProfile.getTexture(), playerProfile.getTextureSignature());\r\n    }\r\n\r\n    public static final MethodHandle RESOLVABLEPROFILE_UNPACK = ReflectionHelper.getMethodHandle(ResolvableProfile.class, \"unpack\");\r\n    public static final MethodHandle RESOLVABLEPROFILE_PARTIAL_ID = ReflectionHelper\r\n            .getFields(ReflectionHelper.getClassOrThrow(\"net.minecraft.world.item.component.ResolvableProfile$Partial\"))\r\n            .getGetter(\"id\", Optional.class);\r\n\r\n    public static UUID getUUID(ResolvableProfile resolvableProfile) {\r\n        try {\r\n            Either<GameProfile, Object> unpacked = (Either<GameProfile, Object>) RESOLVABLEPROFILE_UNPACK.invokeExact(resolvableProfile);\r\n            return unpacked.map(GameProfile::id,\r\n                    partial -> {\r\n                        try {\r\n                            Optional<UUID> uuid = (Optional<UUID>) RESOLVABLEPROFILE_PARTIAL_ID.invoke(partial);\r\n                            return uuid.orElse(null);\r\n                        }\r\n                        catch (Throwable e) {\r\n                            Debug.echoError(e);\r\n                            return null;\r\n                        }\r\n                    }\r\n            );\r\n        }\r\n        catch (Throwable e) {\r\n            Debug.echoError(e);\r\n            return null;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/SidebarImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl;\r\n\r\nimport com.denizenscript.denizen.nms.abstracts.Sidebar;\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizen.nms.v26_1.helpers.PacketHelperImpl;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.md_5.bungee.api.ChatColor;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.chat.numbers.StyledFormat;\r\nimport net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSetScorePacket;\r\nimport net.minecraft.world.scores.DisplaySlot;\r\nimport net.minecraft.world.scores.Objective;\r\nimport net.minecraft.world.scores.PlayerTeam;\r\nimport net.minecraft.world.scores.Scoreboard;\r\nimport net.minecraft.world.scores.criteria.ObjectiveCriteria;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport java.lang.reflect.Constructor;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Optional;\r\n\r\npublic class SidebarImpl extends Sidebar {\r\n\r\n    // TODO: 1.20.3: the scoreboard system saw significant changes, there is likely a better way to do this now\r\n\r\n    public static Scoreboard dummyScoreboard = new Scoreboard();\r\n    public static ObjectiveCriteria dummyCriteria;\r\n\r\n    static {\r\n        try {\r\n            Constructor<ObjectiveCriteria> constructor = ObjectiveCriteria.class.getDeclaredConstructor(String.class);\r\n            constructor.setAccessible(true);\r\n            dummyCriteria = constructor.newInstance(\"dummy\");\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public Objective obj1;\r\n    public Objective obj2;\r\n\r\n    public SidebarImpl(Player player) {\r\n        super(player);\r\n        Component chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n        this.obj1 = new Objective(dummyScoreboard, \"dummy_1\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER, false, StyledFormat.SIDEBAR_DEFAULT);\r\n        this.obj2 = new Objective(dummyScoreboard, \"dummy_2\", dummyCriteria, chatComponentTitle, ObjectiveCriteria.RenderType.INTEGER, false, StyledFormat.SIDEBAR_DEFAULT);\r\n    }\r\n\r\n    @Override\r\n    protected void setDisplayName(String title) {\r\n        if (this.obj1 != null) {\r\n            Component chatComponentTitle = Handler.componentToNMS(FormattedTextHelper.parse(title, ChatColor.WHITE));\r\n            this.obj1.setDisplayName(chatComponentTitle);\r\n            this.obj2.setDisplayName(chatComponentTitle);\r\n        }\r\n    }\r\n\r\n    public List<PlayerTeam> generatedTeams = new ArrayList<>();\r\n\r\n    @Override\r\n    public void sendUpdate() {\r\n        List<PlayerTeam> oldTeams = generatedTeams;\r\n        generatedTeams = new ArrayList<>();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj1, 0));\r\n        String[] ids = getIds();\r\n        for (int i = 0; i < this.lines.length; i++) {\r\n            String line = this.lines[i];\r\n            if (line == null) {\r\n                break;\r\n            }\r\n            String lineId = ids[i];\r\n            PlayerTeam team = new PlayerTeam(dummyScoreboard, lineId);\r\n            team.getPlayers().add(lineId);\r\n            team.setPlayerPrefix(Handler.componentToNMS(FormattedTextHelper.parse(line, ChatColor.WHITE)));\r\n            generatedTeams.add(team);\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));\r\n            PacketHelperImpl.send(player, new ClientboundSetScorePacket(lineId, obj1.getName(), this.scores[i], Optional.empty(), Optional.of(StyledFormat.SIDEBAR_DEFAULT)));\r\n        }\r\n        PacketHelperImpl.send(player, new ClientboundSetDisplayObjectivePacket(DisplaySlot.SIDEBAR, this.obj1));\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n        Objective temp = this.obj2;\r\n        this.obj2 = this.obj1;\r\n        this.obj1 = temp;\r\n        for (PlayerTeam team : oldTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        for (PlayerTeam team : generatedTeams) {\r\n            PacketHelperImpl.send(player, ClientboundSetPlayerTeamPacket.createRemovePacket(team));\r\n        }\r\n        generatedTeams.clear();\r\n        PacketHelperImpl.send(player, new ClientboundSetObjectivePacket(this.obj2, 1));\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/blocks/BlockLightImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.blocks;\r\n\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\r\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;\r\nimport net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;\r\nimport net.minecraft.server.level.ThreadedLevelLightEngine;\r\nimport net.minecraft.world.level.ChunkPos;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.LightLayer;\r\nimport net.minecraft.world.level.chunk.ChunkAccess;\r\nimport net.minecraft.world.level.chunk.DataLayer;\r\nimport net.minecraft.world.level.chunk.LevelChunk;\r\nimport net.minecraft.world.level.chunk.status.ChunkStatus;\r\nimport net.minecraft.world.level.lighting.LayerLightEventListener;\r\nimport net.minecraft.world.level.lighting.LevelLightEngine;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.CraftChunk;\r\nimport org.bukkit.craftbukkit.block.CraftBlock;\r\nimport org.bukkit.util.Vector;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.BitSet;\r\nimport java.util.List;\r\n\r\npublic class BlockLightImpl extends BlockLight {\r\n\r\n    public static final Class LIGHTENGINETHREADED_TASKTYPE = Arrays.stream(ThreadedLevelLightEngine.class.getDeclaredClasses()).filter(c -> c.isEnum()).findFirst().get(); // TaskType\r\n    public static final Object LIGHTENGINETHREADED_TASKTYPE_PRE;\r\n\r\n    static {\r\n        Object preObj = null;\r\n        try {\r\n            preObj = ReflectionHelper.getFields(LIGHTENGINETHREADED_TASKTYPE).get(\"PRE_UPDATE\").get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            ex.printStackTrace();\r\n        }\r\n        LIGHTENGINETHREADED_TASKTYPE_PRE = preObj;\r\n    }\r\n\r\n    public static final MethodHandle LIGHTENGINETHREADED_QUEUERUNNABLE = ReflectionHelper.getMethodHandle(ThreadedLevelLightEngine.class, \"addTask\",\r\n            int.class, int.class,  LIGHTENGINETHREADED_TASKTYPE, Runnable.class);\r\n\r\n    public static void enqueueRunnable(LevelChunk chunk, Runnable runnable) {\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        if (lightEngine instanceof ThreadedLevelLightEngine) {\r\n            ChunkPos coord = chunk.getPos();\r\n            try {\r\n                LIGHTENGINETHREADED_QUEUERUNNABLE.invoke(lightEngine, coord.x(), coord.z(), LIGHTENGINETHREADED_TASKTYPE_PRE, runnable);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n        else {\r\n            runnable.run();\r\n        }\r\n    }\r\n\r\n    private BlockLightImpl(Location location, long ticks) {\r\n        super(location, ticks);\r\n    }\r\n\r\n    public static BlockLight createLight(Location location, int lightLevel, long ticks) {\r\n        location = location.getBlock().getLocation();\r\n        BlockLight blockLight;\r\n        if (lightsByLocation.containsKey(location)) {\r\n            blockLight = lightsByLocation.get(location);\r\n            if (blockLight.removeTask != null) {\r\n                blockLight.removeTask.cancel();\r\n                blockLight.removeTask = null;\r\n            }\r\n            if (blockLight.updateTask != null) {\r\n                blockLight.updateTask.cancel();\r\n                blockLight.updateTask = null;\r\n            }\r\n            blockLight.removeLater(ticks);\r\n        }\r\n        else {\r\n            blockLight = new BlockLightImpl(location, ticks);\r\n            lightsByLocation.put(location, blockLight);\r\n            if (!lightsByChunk.containsKey(blockLight.chunkCoord)) {\r\n                lightsByChunk.put(blockLight.chunkCoord, new ArrayList<>());\r\n            }\r\n            lightsByChunk.get(blockLight.chunkCoord).add(blockLight);\r\n        }\r\n        blockLight.intendedLevel = lightLevel;\r\n        blockLight.update(lightLevel, true);\r\n        return blockLight;\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundBlockUpdatePacket packet, Level world) {\r\n        try {\r\n            BlockPos pos = packet.getPos();\r\n            int chunkX = pos.getX() >> 4;\r\n            int chunkZ = pos.getZ() >> 4;\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                LevelChunk chunk = world.getChunk(chunkX, chunkZ);\r\n                boolean any = false;\r\n                for (Vector vec : RELATIVE_CHUNKS) {\r\n                    ChunkAccess other = world.getChunk(chunkX + vec.getBlockX(), chunkZ + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n                    if (other instanceof LevelChunk) {\r\n                        List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) other)));\r\n                        if (lights != null) {\r\n                            any = true;\r\n                            for (BlockLight light : lights) {\r\n                                Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates(chunk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static void checkIfLightsBrokenByPacket(ClientboundLightUpdatePacket packet, Level world) {\r\n        if (doNotCheck) {\r\n            return;\r\n        }\r\n        try {\r\n            int cX = packet.getX();\r\n            int cZ = packet.getZ();\r\n            BitSet bitMask = packet.getLightData().getBlockYMask();\r\n            List<byte[]> blockData = packet.getLightData().getBlockUpdates();\r\n            Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> {\r\n                ChunkAccess chk = world.getChunk(cX, cZ, ChunkStatus.FULL, false);\r\n                if (!(chk instanceof LevelChunk)) {\r\n                    return;\r\n                }\r\n                List<BlockLight> lights = lightsByChunk.get(new ChunkCoordinate(new CraftChunk((LevelChunk) chk)));\r\n                if (lights == null) {\r\n                    return;\r\n                }\r\n                boolean any = false;\r\n                for (BlockLight light : lights) {\r\n                    if (((BlockLightImpl) light).checkIfChangedBy(bitMask, blockData)) {\r\n                        Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> light.update(light.intendedLevel, false), 1);\r\n                        any = true;\r\n                    }\r\n                }\r\n                if (any) {\r\n                    Bukkit.getScheduler().scheduleSyncDelayedTask(NMSHandler.getJavaPlugin(), () -> sendNearbyChunkUpdates((LevelChunk) chk), 3);\r\n                }\r\n            }, 1);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static boolean doNotCheck = false;\r\n\r\n    public boolean checkIfChangedBy(BitSet bitmask, List<byte[]> data) {\r\n        Location blockLoc = block.getLocation();\r\n        int layer = (blockLoc.getBlockY() >> 4) + 1;\r\n        if (!bitmask.get(layer)) {\r\n            return false;\r\n        }\r\n        int found = 0;\r\n        for (int i = 0; i < 16; i++) {\r\n            if (bitmask.get(i)) {\r\n                if (i == layer) {\r\n                    byte[] blocks = data.get(found);\r\n                    DataLayer arr = new DataLayer(blocks);\r\n                    int x = blockLoc.getBlockX() - (chunkCoord.x << 4);\r\n                    int y = blockLoc.getBlockY() % 16;\r\n                    int z = blockLoc.getBlockZ() - (chunkCoord.z << 4);\r\n                    int level = arr.get(x, y, z);\r\n                    return intendedLevel != level;\r\n                }\r\n                found++;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static void runResetFor(final LevelChunk chunk, final BlockPos pos) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            engineBlock.checkBlock(pos);\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    public static void runSetFor(final LevelChunk chunk, final BlockPos pos, final int level) {\r\n        Runnable runnable = () -> {\r\n            LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n            LayerLightEventListener engineBlock = lightEngine.getLayerListener(LightLayer.BLOCK);\r\n            // engineBlock.onBlockEmissionIncrease(pos, level); // TODO: 1.20: ?\r\n        };\r\n        enqueueRunnable(chunk, runnable);\r\n    }\r\n\r\n    @Override\r\n    public void reset(boolean updateChunk) {\r\n        runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition());\r\n        if (updateChunk) {\r\n            // This runnable cast is necessary despite what your IDE may claim\r\n            updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void update(int lightLevel, boolean updateChunk) {\r\n        runResetFor((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition());\r\n        updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), () -> {\r\n            updateTask = null;\r\n            runSetFor((LevelChunk) ((CraftChunk) chunk).getHandle(ChunkStatus.FULL), ((CraftBlock) block).getPosition(), lightLevel);\r\n            if (updateChunk) {\r\n                // This runnable cast is necessary despite what your IDE may claim\r\n                updateTask = Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), (Runnable) this::sendNearbyChunkUpdates, 1);\r\n            }\r\n        }, 1);\r\n    }\r\n\r\n    public static final Vector[] RELATIVE_CHUNKS = new Vector[] {\r\n            new Vector(0, 0, 0),\r\n            new Vector(-1, 0, 0), new Vector(1, 0, 0), new Vector(0, 0, -1), new Vector(0, 0, 1),\r\n            new Vector(-1, 0, -1), new Vector(-1, 0, 1), new Vector(1, 0, -1), new Vector(1, 0, 1)\r\n    };\r\n\r\n    public void sendNearbyChunkUpdates() {\r\n        sendNearbyChunkUpdates((LevelChunk) ((CraftChunk) getChunk()).getHandle(ChunkStatus.FULL));\r\n    }\r\n\r\n    public static void sendNearbyChunkUpdates(LevelChunk chunk) {\r\n        ChunkPos pos = chunk.getPos();\r\n        for (Vector vec : RELATIVE_CHUNKS) {\r\n            ChunkAccess other = chunk.getLevel().getChunk(pos.x() + vec.getBlockX(), pos.z() + vec.getBlockZ(), ChunkStatus.FULL, false);\r\n            if (other instanceof LevelChunk) {\r\n                sendSingleChunkUpdate((LevelChunk) other);\r\n            }\r\n        }\r\n    }\r\n\r\n    public static void sendSingleChunkUpdate(LevelChunk chunk) {\r\n        // TODO: 1.20: ?\r\n        /*\r\n        doNotCheck = true;\r\n        LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();\r\n        ChunkPos pos = chunk.getPos();\r\n        //ClientboundLightUpdatePacket packet = new ClientboundLightUpdatePacket(pos, lightEngine, null, null, true); // TODO: 1.16: should 'trust edges' be true here?\r\n        ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(pos, false).forEach((player) -> {\r\n            player.connection.send(packet);\r\n        });\r\n        doNotCheck = false;*/\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakeArrow;\r\nimport net.minecraft.world.entity.projectile.arrow.AbstractArrow;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.entity.CraftAbstractArrow;\r\n\r\npublic class CraftFakeArrowImpl extends CraftAbstractArrow implements FakeArrow {\r\n\r\n    public CraftFakeArrowImpl(CraftServer craftServer, AbstractArrow entityArrow) {\r\n        super(craftServer, entityArrow);\r\n    }\r\n\r\n    @Override\r\n    public void remove() {\r\n        if (getPassenger() != null) {\r\n            return;\r\n        }\r\n        super.remove();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_ARROW\";\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.FakePlayer;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport org.bukkit.Material;\r\nimport org.bukkit.block.Block;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.entity.CraftPlayer;\r\nimport org.bukkit.metadata.FixedMetadataValue;\r\nimport org.bukkit.metadata.MetadataValue;\r\nimport org.bukkit.plugin.Plugin;\r\n\r\nimport java.util.List;\r\nimport java.util.Set;\r\n\r\npublic class CraftFakePlayerImpl extends CraftPlayer implements FakePlayer {\r\n\r\n    private final CraftServer server;\r\n    public String fullName;\r\n\r\n    public CraftFakePlayerImpl(CraftServer server, EntityFakePlayerImpl entity) {\r\n        super(server, entity);\r\n        this.server = server;\r\n        setMetadata(\"NPC\", new FixedMetadataValue(NMSHandler.getJavaPlugin(), true));\r\n    }\r\n\r\n    @Override\r\n    public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {\r\n        this.server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue);\r\n    }\r\n\r\n    @Override\r\n    public List<MetadataValue> getMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().getMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasMetadata(String metadataKey) {\r\n        return this.server.getEntityMetadata().hasMetadata(this, metadataKey);\r\n    }\r\n\r\n    @Override\r\n    public void removeMetadata(String metadataKey, Plugin owningPlugin) {\r\n        this.server.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin);\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return \"FAKE_PLAYER\";\r\n    }\r\n\r\n    @Override\r\n    public String getFullName() {\r\n        return fullName;\r\n    }\r\n\r\n    @Override\r\n    public Block getTargetBlock(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public List<Block> getLastTwoTargetBlocks(Set<Material> transparent, int maxDistance) {\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/CraftItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.ItemProjectile;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.entity.CraftEntity;\r\nimport org.bukkit.craftbukkit.inventory.CraftItemStack;\r\nimport org.bukkit.entity.EntityType;\r\nimport org.bukkit.inventory.ItemStack;\r\nimport org.bukkit.projectiles.ProjectileSource;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.util.UUID;\r\n\r\npublic class CraftItemProjectileImpl extends CraftEntity implements ItemProjectile {\r\n\r\n    private boolean doesBounce;\r\n\r\n    public CraftItemProjectileImpl(CraftServer server, EntityItemProjectileImpl entity) {\r\n        super(server, entity);\r\n        MethodHandle handle = ReflectionHelper.getFinalSetterForFirstOfType(CraftEntity.class, EntityType.class);\r\n        if (handle != null) {\r\n            try {\r\n                handle.invoke(this, EntityType.ITEM);\r\n            }\r\n            catch (Throwable ex) {\r\n                Debug.echoError(ex);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public EntityItemProjectileImpl getHandle() {\r\n        return (EntityItemProjectileImpl) super.getHandle();\r\n    }\r\n\r\n    @Override\r\n    public String getEntityTypeName() {\r\n        return getType().name();\r\n    }\r\n\r\n    @Override\r\n    public ItemStack getItemStack() {\r\n        return CraftItemStack.asBukkitCopy(getHandle().getItemStack());\r\n    }\r\n\r\n    @Override\r\n    public void setItemStack(ItemStack itemStack) {\r\n        getHandle().setItemStack(CraftItemStack.asNMSCopy(itemStack));\r\n    }\r\n\r\n    @Override\r\n    public int getPickupDelay() {\r\n        return 0;\r\n    }\r\n\r\n    @Override\r\n    public void setPickupDelay(int i) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public void setUnlimitedLifetime(boolean b) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public boolean isUnlimitedLifetime() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void setOwner(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getOwner() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public void setThrower(UUID uuid) {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    public UUID getThrower() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    public ProjectileSource getShooter() {\r\n        return getHandle().projectileSource;\r\n    }\r\n\r\n    @Override\r\n    public void setShooter(ProjectileSource projectileSource) {\r\n        if (projectileSource instanceof CraftEntity) {\r\n            getHandle().setOwner(((CraftEntity) projectileSource).getHandle());\r\n        }\r\n        else {\r\n            getHandle().projectileSource = projectileSource;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean doesBounce() {\r\n        return doesBounce;\r\n    }\r\n\r\n    @Override\r\n    public void setBounce(boolean doesBounce) {\r\n        this.doesBounce = doesBounce;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityFakeArrowImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.world.entity.projectile.arrow.SpectralArrow;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.item.Items;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakeArrowImpl extends SpectralArrow {\r\n\r\n    public EntityFakeArrowImpl(CraftWorld craftWorld, Location location) {\r\n        super(net.minecraft.world.entity.EntityType.SPECTRAL_ARROW, craftWorld.getHandle());\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakeArrowImpl((CraftServer) Bukkit.getServer(), this));\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        level().addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        // Do nothing\r\n    }\r\n\r\n    @Override\r\n    protected ItemStack getPickupItem() {\r\n        return new ItemStack(Items.ARROW);\r\n    }\r\n\r\n    @Override\r\n    public CraftFakeArrowImpl getBukkitEntity() {\r\n        return (CraftFakeArrowImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityFakePlayerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.entities;\r\n\r\nimport com.denizenscript.denizen.nms.v26_1.Handler;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.fakes.FakeNetworkManagerImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.fakes.FakePlayerConnectionImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ClientInformation;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.world.entity.player.Player;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.CraftServer;\r\nimport org.bukkit.event.entity.CreatureSpawnEvent;\r\n\r\npublic class EntityFakePlayerImpl extends ServerPlayer {\r\n\r\n    public EntityFakePlayerImpl(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile, ClientInformation clientInfo, boolean doAdd) {\r\n        super(minecraftserver, worldserver, gameprofile, clientInfo);\r\n        try {\r\n            Handler.ENTITY_BUKKITYENTITY.set(this, new CraftFakePlayerImpl((CraftServer) Bukkit.getServer(), this));\r\n            net.minecraft.network.Connection networkManager = new FakeNetworkManagerImpl(PacketFlow.CLIENTBOUND);\r\n            connection = new FakePlayerConnectionImpl(minecraftserver, networkManager, this, new CommonListenerCookie(gameprofile, 0, clientInfo, false));\r\n            DenizenNetworkManagerImpl.Connection_packetListener.set(networkManager, connection);\r\n        }\r\n        catch (Exception ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        getEntityData().set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) 127);\r\n        if (doAdd) {\r\n            worldserver.addFreshEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public CraftFakePlayerImpl getBukkitEntity() {\r\n        return (CraftFakePlayerImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/entities/EntityItemProjectileImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.entities;\r\n\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.google.common.base.Preconditions;\r\nimport net.minecraft.network.syncher.EntityDataAccessor;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ServerLevel;\r\nimport net.minecraft.world.entity.Entity;\r\nimport net.minecraft.world.entity.item.ItemEntity;\r\nimport net.minecraft.world.entity.projectile.ThrowableProjectile;\r\nimport net.minecraft.world.item.ItemStack;\r\nimport net.minecraft.world.level.Level;\r\nimport net.minecraft.world.level.storage.ValueInput;\r\nimport net.minecraft.world.level.storage.ValueOutput;\r\nimport net.minecraft.world.phys.BlockHitResult;\r\nimport org.bukkit.Location;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\n\r\npublic class EntityItemProjectileImpl extends ThrowableProjectile {\r\n\r\n    public static MethodHandle setBukkitEntityMethod = ReflectionHelper.getFinalSetter(Entity.class, \"bukkitEntity\");\r\n\r\n    public static final EntityDataAccessor<ItemStack> ITEM;\r\n\r\n    static {\r\n        EntityDataAccessor<ItemStack> watcher = null;\r\n        try {\r\n            watcher = (EntityDataAccessor<ItemStack>) ReflectionHelper.getFields(ItemEntity.class).get(\"DATA_ITEM\").get(null);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        ITEM = watcher;\r\n    }\r\n\r\n    public EntityItemProjectileImpl(Level world, Location location, ItemStack item) {\r\n        super((net.minecraft.world.entity.EntityType) net.minecraft.world.entity.EntityType.ITEM, world);\r\n        try {\r\n            setBukkitEntityMethod.invoke(this, new CraftItemProjectileImpl(((ServerLevel) world).getServer().server, this));\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        setPosRaw(location.getX(), location.getY(), location.getZ());\r\n        setRot(location.getYaw(), location.getPitch());\r\n        setItemStack(item);\r\n    }\r\n\r\n    @Override\r\n    protected void defineSynchedData(SynchedEntityData.Builder builder) {\r\n        builder.define(ITEM, ItemStack.EMPTY);\r\n    }\r\n\r\n    public ItemStack getItemStack() {\r\n        return this.getEntityData().get(ITEM);\r\n    }\r\n\r\n    public void setItemStack(ItemStack itemstack) {\r\n        Preconditions.checkArgument(!itemstack.isEmpty(), \"Cannot drop air\");\r\n        this.getEntityData().set(ITEM, itemstack);\r\n        this.getEntityData().markDirty(ITEM);\r\n    }\r\n\r\n    @Override\r\n    protected void onHitBlock(BlockHitResult movingobjectpositionblock) {\r\n        super.onHitBlock(movingobjectpositionblock);\r\n        remove(RemovalReason.KILLED);\r\n    }\r\n\r\n    @Override\r\n    public boolean save(ValueOutput nmsValueOutput) {\r\n        if (!this.getItemStack().isEmpty()) {\r\n            nmsValueOutput.store(\"Item\", ItemStack.CODEC, this.getItemStack());\r\n        }\r\n        super.save(nmsValueOutput);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public void load(ValueInput nmsValueInput) {\r\n        ItemStack nmsItemStack = nmsValueInput.read(\"Item\", ItemStack.CODEC).orElse(ItemStack.EMPTY);\r\n        if (nmsItemStack.isEmpty()) {\r\n            this.remove(RemovalReason.KILLED);\r\n        }\r\n        else {\r\n            this.setItemStack(nmsItemStack);\r\n        }\r\n        super.load(nmsValueInput);\r\n    }\r\n\r\n    @Override\r\n    public CraftItemProjectileImpl getBukkitEntity() {\r\n        return (CraftItemProjectileImpl) super.getBukkitEntity();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakeChannelImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.fakes;\r\n\r\nimport io.netty.channel.*;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeChannelImpl extends AbstractChannel {\r\n\r\n    private final ChannelConfig config = new DefaultChannelConfig(this);\r\n\r\n    protected FakeChannelImpl(Channel parent) {\r\n        super(parent);\r\n    }\r\n\r\n    @Override\r\n    public ChannelConfig config() {\r\n        config.setAutoRead(true);\r\n        return config;\r\n    }\r\n\r\n    @Override\r\n    protected AbstractUnsafe newUnsafe() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected boolean isCompatible(EventLoop eventLoop) {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress localAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected SocketAddress remoteAddress0() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected void doBind(SocketAddress socketAddress) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doDisconnect() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doClose() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doBeginRead() throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void doWrite(ChannelOutboundBuffer channelOutboundBuffer) throws Exception {\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean isOpen() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActive() {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public ChannelMetadata metadata() {\r\n        return new ChannelMetadata(true);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakeNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\n\r\nimport java.net.SocketAddress;\r\n\r\npublic class FakeNetworkManagerImpl extends Connection {\r\n\r\n    public FakeNetworkManagerImpl(PacketFlow enumprotocoldirection) {\r\n        super(enumprotocoldirection);\r\n        channel = new FakeChannelImpl(null);\r\n        address = new SocketAddress() {\r\n        };\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/fakes/FakePlayerConnectionImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.fakes;\r\n\r\nimport net.minecraft.network.Connection;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\n\r\npublic class FakePlayerConnectionImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public FakePlayerConnectionImpl(MinecraftServer minecraftserver, Connection networkmanager, ServerPlayer entityplayer, CommonListenerCookie cookie) {\r\n        super(minecraftserver, networkmanager, entityplayer, cookie);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet packet) {\r\n        // Do nothing\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/AbstractListenerPlayInImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerSendPacketScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport com.mojang.authlib.GameProfile;\r\nimport io.netty.channel.ChannelFutureListener;\r\nimport net.minecraft.CrashReport;\r\nimport net.minecraft.CrashReportCategory;\r\nimport net.minecraft.ReportedException;\r\nimport net.minecraft.network.ConnectionProtocol;\r\nimport net.minecraft.network.DisconnectionDetails;\r\nimport net.minecraft.network.chat.ChatType;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.chat.PlayerChatMessage;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.network.protocol.common.*;\r\nimport net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.protocol.ping.ServerboundPingRequestPacket;\r\nimport net.minecraft.server.MinecraftServer;\r\nimport net.minecraft.server.level.ClientInformation;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.CommonListenerCookie;\r\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.world.entity.PositionMoveRotation;\r\nimport net.minecraft.world.entity.Relative;\r\nimport net.minecraft.world.phys.Vec3;\r\nimport org.bukkit.Location;\r\nimport org.bukkit.craftbukkit.entity.CraftPlayer;\r\nimport org.bukkit.event.player.PlayerTeleportEvent;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.net.SocketAddress;\r\nimport java.util.Set;\r\n\r\npublic class AbstractListenerPlayInImpl extends ServerGamePacketListenerImpl {\r\n\r\n    public static final Field ServerGamePacketListenerImpl_chunkSender = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(\"chunkSender\");\r\n\r\n    public static final MethodHandle SERVER_COMMON_PACKET_LISTENER_IMPL_CREATE_COOKIE = ReflectionHelper.getMethodHandle(ServerCommonPacketListenerImpl.class, \"createCookie\", ClientInformation.class);\r\n\r\n    public static CommonListenerCookie createCookie(ServerPlayer nmsPlayer) {\r\n        try {\r\n            return (CommonListenerCookie) SERVER_COMMON_PACKET_LISTENER_IMPL_CREATE_COOKIE.invoke(nmsPlayer.connection, nmsPlayer.clientInformation());\r\n        }\r\n        catch (Throwable e) {\r\n            throw new RuntimeException(e);\r\n        }\r\n    }\r\n\r\n    public final ServerGamePacketListenerImpl oldListener;\r\n    public final DenizenNetworkManagerImpl denizenNetworkManager;\r\n\r\n    public AbstractListenerPlayInImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer, ServerGamePacketListenerImpl oldListener) {\r\n        super(MinecraftServer.getServer(), networkManager, entityPlayer, createCookie(entityPlayer));\r\n        this.oldListener = oldListener;\r\n        this.denizenNetworkManager = networkManager;\r\n        try {\r\n            ServerGamePacketListenerImpl_chunkSender.set(this, oldListener.chunkSender);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        oldListener.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(DisconnectionDetails disconnectiondetails) {\r\n        oldListener.disconnect(disconnectiondetails);\r\n    }\r\n\r\n    @Override\r\n    public void kickPlayer(Component reason) {\r\n        oldListener.kickPlayer(reason);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(double d0, double d1, double d2, float f, float f1) {\r\n        oldListener.teleport(d0, d1, d2, f, f1);\r\n    }\r\n\r\n    @Override\r\n    public boolean teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) {\r\n        return oldListener.teleport(d0, d1, d2, f, f1, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(PositionMoveRotation positionmoverotation, Set<Relative> set) {\r\n        oldListener.teleport(positionmoverotation, set);\r\n    }\r\n\r\n    @Override\r\n    public boolean teleport(PositionMoveRotation positionmoverotation, Set<Relative> set, PlayerTeleportEvent.TeleportCause cause) {\r\n        return oldListener.teleport(positionmoverotation, set, cause);\r\n    }\r\n\r\n    @Override\r\n    public void teleport(Location dest) {\r\n        oldListener.teleport(dest);\r\n    }\r\n\r\n    @Override\r\n    public void internalTeleport(PositionMoveRotation positionmoverotation, Set<Relative> set) {\r\n        oldListener.internalTeleport(positionmoverotation, set);\r\n    }\r\n\r\n    @Override\r\n    public CraftPlayer getCraftPlayer() {\r\n        return oldListener.getCraftPlayer();\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldListener.tick();\r\n    }\r\n\r\n    @Override\r\n    public void resetPosition() {\r\n        oldListener.resetPosition();\r\n    }\r\n\r\n    @Override\r\n    public boolean isAcceptingMessages() {\r\n        return oldListener.isAcceptingMessages();\r\n    }\r\n\r\n    @Override\r\n    public boolean shouldHandleMessage(Packet<?> packet) {\r\n        return oldListener.shouldHandleMessage(packet);\r\n    }\r\n\r\n    @Override\r\n    public void fillCrashReport(CrashReport var0) {\r\n        oldListener.fillCrashReport(var0);\r\n    }\r\n\r\n    @Override\r\n    public void fillListenerSpecificCrashDetails(CrashReport var0, CrashReportCategory var1) {\r\n        oldListener.fillListenerSpecificCrashDetails(var0, var1);\r\n    }\r\n\r\n    @Override\r\n    public GameProfile getOwner() {\r\n        return oldListener.getOwner();\r\n    }\r\n\r\n    @Override\r\n    public int latency() {\r\n        return oldListener.latency();\r\n    }\r\n\r\n    @Override\r\n    public void onDisconnect(DisconnectionDetails disconnectionDetails) {\r\n        oldListener.onDisconnect(disconnectionDetails);\r\n    }\r\n\r\n    @Override\r\n    public void ackBlockChangesUpTo(int i) {\r\n        oldListener.ackBlockChangesUpTo(i);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        oldListener.send(packet);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, ChannelFutureListener channelfuturelistener) {\r\n        oldListener.send(packet, channelfuturelistener);\r\n    }\r\n\r\n    public static Field AWAITING_POS_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(\"awaitingPositionFromClient\", Vec3.class);\r\n    public static Field AWAITING_TELEPORT_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(\"awaitingTeleport\", int.class);\r\n\r\n    public void debugPacketOutput(Packet<?> packet) {\r\n        try {\r\n            if (packet instanceof ServerboundMovePlayerPacket) {\r\n                ServerboundMovePlayerPacket movePacket = (ServerboundMovePlayerPacket) packet;\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet ServerboundMovePlayerPacket sent from \" + player.getScoreboardName() + \" with XYZ=\"\r\n                        + movePacket.x + \", \" + movePacket.y + \", \" + movePacket.z + \", yRot=\" + movePacket.yRot + \", xRot=\" + movePacket.xRot\r\n                        + \", onGround=\" + movePacket.isOnGround() + \", hasPos=\" + movePacket.hasPos + \", hasRot=\" + movePacket.hasRot);\r\n            }\r\n            else if (packet instanceof ServerboundAcceptTeleportationPacket) {\r\n                Vec3 awaitPos = (Vec3) AWAITING_POS_FIELD.get(oldListener);\r\n                int awaitTeleportId = AWAITING_TELEPORT_FIELD.getInt(oldListener);\r\n                ServerboundAcceptTeleportationPacket acceptPacket = (ServerboundAcceptTeleportationPacket) packet;\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet ServerboundAcceptTeleportationPacket sent from \" + player.getScoreboardName()\r\n                        + \" with ID=\" + acceptPacket.getId() + \", awaitingTeleport=\" + awaitTeleportId + \", awaitPos=\" + awaitPos);\r\n            }\r\n            else {\r\n                DenizenNetworkManagerImpl.doPacketOutput(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent from \" + player.getScoreboardName());\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public boolean handlePacketIn(Packet<?> packet) {\r\n        denizenNetworkManager.packetsReceived++;\r\n        if (NMSHandler.debugPackets) {\r\n            debugPacketOutput(packet);\r\n        }\r\n        if (PlayerSendPacketScriptEvent.instance.eventData.isEnabled) {\r\n            if (PlayerSendPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) {\r\n                if (NMSHandler.debugPackets) {\r\n                    DenizenNetworkManagerImpl.doPacketOutput(\"Denied packet-in \" + packet.getClass().getCanonicalName() + \" from \" + player.getScoreboardName() + \" due to event\");\r\n                }\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public void handleChatAck(ServerboundChatAckPacket serverboundchatackpacket) {\r\n        oldListener.handleChatAck(serverboundchatackpacket);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(ServerboundPlayerInputPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerInput(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleMoveVehicle(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleAcceptTeleportPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookSeenRecipePacket(ServerboundRecipeBookSeenRecipePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRecipeBookSeenRecipePacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRecipeBookChangeSettingsPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSeenAdvancements(ServerboundSeenAdvancementsPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSeenAdvancements(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCustomCommandSuggestions(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandBlock(ServerboundSetCommandBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCommandBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCommandMinecart(ServerboundSetCommandMinecartPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCommandMinecart(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAcceptPlayerLoad(ServerboundPlayerLoadedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleAcceptPlayerLoad(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePickItemFromBlock(ServerboundPickItemFromBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePickItemFromBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePickItemFromEntity(ServerboundPickItemFromEntityPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePickItemFromEntity(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleRenameItem(ServerboundRenameItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleRenameItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetBeaconPacket(ServerboundSetBeaconPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetBeaconPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetStructureBlock(ServerboundSetStructureBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetStructureBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetTestBlock(ServerboundSetTestBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetTestBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleTestInstanceBlockAction(ServerboundTestInstanceBlockActionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleTestInstanceBlockAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetJigsawBlock(ServerboundSetJigsawBlockPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetJigsawBlock(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleJigsawGenerate(ServerboundJigsawGeneratePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleJigsawGenerate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSelectTrade(ServerboundSelectTradePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSelectTrade(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEditBook(ServerboundEditBookPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleEditBook(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleMovePlayer(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItemOn(ServerboundUseItemOnPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleUseItemOn(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleTeleportToEntityPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void suspendFlushing() {\r\n        oldListener.suspendFlushing();\r\n    }\r\n\r\n    @Override\r\n    public void resumeFlushing() {\r\n        oldListener.resumeFlushing();\r\n    }\r\n\r\n    @Override\r\n    public void handlePaddleBoat(ServerboundPaddleBoatPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePaddleBoat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePong(ServerboundPongPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePong(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChat(ServerboundChatPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChat(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChatCommand(ServerboundChatCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChatCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void chat(String s, PlayerChatMessage original, boolean async) {\r\n        oldListener.chat(s, original, async);\r\n    }\r\n\r\n    @Override\r\n    public ConnectionProtocol protocol() {\r\n        return oldListener == null ? ConnectionProtocol.PLAY : oldListener.protocol();\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void sendPlayerChatMessage(PlayerChatMessage playerchatmessage, ChatType.Bound chatmessagetype_a) {\r\n        oldListener.sendPlayerChatMessage(playerchatmessage, chatmessagetype_a);\r\n    }\r\n\r\n    @Override\r\n    public void sendDisguisedChatMessage(Component ichatbasecomponent, ChatType.Bound chatmessagetype_a) {\r\n        oldListener.sendDisguisedChatMessage(ichatbasecomponent, chatmessagetype_a);\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRemoteAddress() {\r\n        return oldListener.getRemoteAddress();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRawAddress() {\r\n        return oldListener.getRawAddress();\r\n    }\r\n\r\n    @Override\r\n    public void switchToConfig() {\r\n        oldListener.switchToConfig();\r\n    }\r\n\r\n    @Override\r\n    public void handleInteract(ServerboundInteractPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleInteract(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientCommand(ServerboundClientCommandPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleClientCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClose(ServerboundContainerClosePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerClose(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlaceRecipe(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerButtonClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCreativeModeSlot(ServerboundSetCreativeModeSlotPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSetCreativeModeSlot(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleKeepAlive(ServerboundKeepAlivePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleKeepAlive(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePlayerAbilities(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientInformation(ServerboundClientInformationPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleClientInformation(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChangeDifficulty(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleLockDifficulty(ServerboundLockDifficultyPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleLockDifficulty(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChatSessionUpdate(ServerboundChatSessionUpdatePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChatSessionUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleConfigurationAcknowledged(ServerboundConfigurationAcknowledgedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleConfigurationAcknowledged(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChunkBatchReceived(ServerboundChunkBatchReceivedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChunkBatchReceived(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleBlockEntityTagQuery(ServerboundBlockEntityTagQueryPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleBlockEntityTagQuery(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleBundleItemSelectedPacket(ServerboundSelectBundleItemPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleBundleItemSelectedPacket(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleClientTickEnd(ServerboundClientTickEndPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleClientTickEnd(packet);\r\n    }\r\n\r\n    @Override\r\n    public boolean hasInfiniteMaterials() {\r\n        return oldListener.hasInfiniteMaterials();\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerSlotStateChanged(ServerboundContainerSlotStateChangedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleContainerSlotStateChanged(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleDebugSubscriptionRequest(ServerboundDebugSubscriptionRequestPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleDebugSubscriptionRequest(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleEntityTagQuery(ServerboundEntityTagQueryPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleEntityTagQuery(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePingRequest(ServerboundPingRequestPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handlePingRequest(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignedChatCommand(ServerboundChatCommandSignedPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleSignedChatCommand(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleChangeGameMode(ServerboundChangeGameModePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleChangeGameMode(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomClickAction(ServerboundCustomClickActionPacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCustomClickAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCookieResponse(ServerboundCookieResponsePacket packet) {\r\n        if (handlePacketIn(packet)) { return; }\r\n        oldListener.handleCookieResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public ServerPlayer getPlayer() {\r\n        return oldListener.getPlayer();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow flow() {\r\n        return oldListener == null ? PacketFlow.SERVERBOUND : oldListener.flow();\r\n    }\r\n\r\n    @Override\r\n    public void onPacketError(Packet var0, Exception var1) throws ReportedException {\r\n        oldListener.onPacketError(var0, var1);\r\n    }\r\n\r\n    @Override\r\n    public DisconnectionDetails createDisconnectionInfo(Component var0, Throwable var1) {\r\n        return oldListener.createDisconnectionInfo(var0, var1);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/DenizenNetworkManagerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerReceivesPacketScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet.*;\r\nimport com.denizenscript.denizen.utilities.Settings;\r\nimport com.denizenscript.denizen.utilities.packets.NetworkInterceptCodeGen;\r\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport io.netty.buffer.Unpooled;\r\nimport io.netty.channel.ChannelFutureListener;\r\nimport io.netty.channel.ChannelHandlerContext;\r\nimport io.netty.channel.ChannelPipeline;\r\nimport net.minecraft.network.*;\r\nimport net.minecraft.network.chat.Component;\r\nimport net.minecraft.network.codec.StreamCodec;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.PacketFlow;\r\nimport net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.network.protocol.login.ClientLoginPacketListener;\r\nimport net.minecraft.network.protocol.status.ClientStatusPacketListener;\r\nimport net.minecraft.network.syncher.SynchedEntityData;\r\nimport net.minecraft.server.level.ChunkMap;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport net.minecraft.server.network.ServerCommonPacketListenerImpl;\r\nimport net.minecraft.server.network.ServerGamePacketListenerImpl;\r\nimport net.minecraft.server.network.ServerPlayerConnection;\r\nimport net.minecraft.util.debugchart.LocalSampleLogger;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.CraftRegistry;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.entity.CraftPlayer;\r\nimport org.bukkit.entity.Player;\r\n\r\nimport javax.annotation.Nullable;\r\nimport javax.crypto.Cipher;\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Field;\r\nimport java.net.SocketAddress;\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.function.BiConsumer;\r\nimport java.util.function.Consumer;\r\nimport java.util.stream.Collectors;\r\n\r\npublic class DenizenNetworkManagerImpl extends Connection {\r\n\r\n    public static <T extends Packet<?>> T copyPacket(T original, StreamCodec<? super RegistryFriendlyByteBuf, T> packetCodec) {\r\n        try {\r\n            RegistryFriendlyByteBuf copier = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry());\r\n            packetCodec.encode(copier, original);\r\n            return packetCodec.decode(copier);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @FunctionalInterface\r\n    public interface PacketHandler<T extends Packet<ClientGamePacketListener>> {\r\n        Packet<ClientGamePacketListener> handlePacket(DenizenNetworkManagerImpl networkManager, T packet) throws Exception;\r\n    }\r\n\r\n    public static final Map<Class<? extends Packet<ClientGamePacketListener>>, List<PacketHandler<?>>> packetHandlers = new HashMap<>();\r\n\r\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetClass, PacketHandler<T> handler) {\r\n        packetHandlers.computeIfAbsent(packetClass, k -> new ArrayList<>()).add(handler);\r\n    }\r\n\r\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetClass, BiConsumer<DenizenNetworkManagerImpl, T> handler) {\r\n        registerPacketHandler(packetClass, (networkManager, packet) -> {\r\n            handler.accept(networkManager, packet);\r\n            return packet;\r\n        });\r\n    }\r\n\r\n    public final Connection oldManager;\r\n    public final DenizenPacketListenerImpl packetListener;\r\n    public final ServerPlayer player;\r\n    public int packetsSent, packetsReceived;\r\n\r\n    public DenizenNetworkManagerImpl(ServerPlayer entityPlayer, Connection oldManager) {\r\n        super(getProtocolDirection(oldManager));\r\n        this.oldManager = oldManager;\r\n        this.channel = oldManager.channel;\r\n        this.player = entityPlayer;\r\n        packetListener = (DenizenPacketListenerImpl) NetworkInterceptCodeGen.generateAppropriateInterceptor(this, entityPlayer, DenizenPacketListenerImpl.class, AbstractListenerPlayInImpl.class, ServerGamePacketListenerImpl.class);\r\n        if (!(oldManager.getPacketListener() instanceof ServerConfigurationPacketListener)) {\r\n            setListener(packetListener);\r\n        }\r\n    }\r\n\r\n    public void setListener(PacketListener listener) {\r\n        try {\r\n            Connection_packetListener.set(oldManager, listener);\r\n        }\r\n        catch (IllegalAccessException e) {\r\n            Debug.echoError(e);\r\n            throw new RuntimeException(\"Failed to set packet listener due to reflection error\", e);\r\n        }\r\n    }\r\n\r\n    public static Connection getConnection(ServerPlayer player) {\r\n        try {\r\n            return (Connection) ServerGamePacketListener_ConnectionField.get(player.connection);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n            throw new RuntimeException(\"Failed to get connection from player due to reflection error\", ex);\r\n        }\r\n    }\r\n\r\n    public static Connection getConnection(Player player) {\r\n        return getConnection(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    public static DenizenNetworkManagerImpl getNetworkManager(ServerPlayer player) {\r\n        return (DenizenNetworkManagerImpl) getConnection(player);\r\n    }\r\n\r\n    public static DenizenNetworkManagerImpl getNetworkManager(Player player) {\r\n        return getNetworkManager(((CraftPlayer) player).getHandle());\r\n    }\r\n\r\n    public static void setNetworkManager(Player player) {\r\n        ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle();\r\n        ServerGamePacketListenerImpl playerConnection = entityPlayer.connection;\r\n        setNetworkManager(playerConnection, new DenizenNetworkManagerImpl(entityPlayer, getConnection(entityPlayer)));\r\n    }\r\n\r\n    public static void enableNetworkManager() {\r\n        for (World w : Bukkit.getWorlds()) {\r\n            for (ChunkMap.TrackedEntity tracker : ((CraftWorld) w).getHandle().getChunkSource().chunkMap.entityMap.values()) {\r\n                ArrayList<ServerPlayerConnection> connections = new ArrayList<>(tracker.seenBy);\r\n                tracker.seenBy.clear();\r\n                for (ServerPlayerConnection connection : connections) {\r\n                    tracker.seenBy.add(connection.getPlayer().connection);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public int hashCode() {\r\n        return oldManager.hashCode();\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object c2) {\r\n        return oldManager.equals(c2);\r\n    }\r\n\r\n    @Override\r\n    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelRegistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelUnregistered(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception {\r\n        oldManager.channelActive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public void channelInactive(ChannelHandlerContext channelhandlercontext) {\r\n        oldManager.channelInactive(channelhandlercontext);\r\n    }\r\n\r\n    @Override\r\n    public boolean isSharable() {\r\n        return oldManager.isSharable();\r\n    }\r\n\r\n    @Override\r\n    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerAdded(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.handlerRemoved(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) {\r\n        oldManager.exceptionCaught(channelhandlercontext, throwable);\r\n    }\r\n\r\n    @Override\r\n    public <T extends PacketListener> void setupInboundProtocol(ProtocolInfo<T> protocolinfo, T t0) {\r\n        oldManager.setupInboundProtocol(protocolinfo, t0);\r\n    }\r\n\r\n    @Override\r\n    public void setupOutboundProtocol(ProtocolInfo<?> protocolinfo) {\r\n        oldManager.setupOutboundProtocol(protocolinfo);\r\n    }\r\n\r\n    @Override\r\n    protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) {\r\n        if (oldManager.channel.isOpen()) {\r\n            try {\r\n                packet.handle(this.packetListener);\r\n            }\r\n            catch (Exception e) {\r\n                // Do nothing\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void setListenerForServerboundHandshake(PacketListener packetlistener) {\r\n        oldManager.setListenerForServerboundHandshake(packetlistener);\r\n    }\r\n\r\n    @Override\r\n    public void initiateServerboundStatusConnection(String s, int i, ClientStatusPacketListener packetstatusoutlistener) {\r\n        oldManager.initiateServerboundStatusConnection(s, i, packetstatusoutlistener);\r\n    }\r\n\r\n    @Override\r\n    public void initiateServerboundPlayConnection(String s, int i, ClientLoginPacketListener packetloginoutlistener) {\r\n        oldManager.initiateServerboundPlayConnection(s, i, packetloginoutlistener);\r\n    }\r\n\r\n    @Override\r\n    public <S extends ServerboundPacketListener, C extends ClientboundPacketListener> void initiateServerboundPlayConnection(String s, int i, ProtocolInfo<S> protocolinfo, ProtocolInfo<C> protocolinfo1, C c0, boolean flag) {\r\n        oldManager.initiateServerboundPlayConnection(s, i, protocolinfo, protocolinfo1, c0, flag);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        send(packet, null);\r\n    }\r\n\r\n    public static void doPacketOutput(String text) {\r\n        if (!NMSHandler.debugPackets) {\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPacketFilter == null || NMSHandler.debugPacketFilter.trim().isEmpty()\r\n                || CoreUtilities.toLowerCase(text).contains(NMSHandler.debugPacketFilter)) {\r\n            Debug.log(text);\r\n        }\r\n    }\r\n\r\n    public void debugOutputPacket(Packet<?> packet) {\r\n        if (packet instanceof ClientboundSetEntityDataPacket) {\r\n            StringBuilder output = new StringBuilder(128);\r\n            output.append(\"Packet: ClientboundSetEntityDataPacket sent to \").append(player.getScoreboardName()).append(\" for entity ID: \").append(((ClientboundSetEntityDataPacket) packet).id()).append(\": \");\r\n            List<SynchedEntityData.DataValue<?>> list = ((ClientboundSetEntityDataPacket) packet).packedItems();\r\n            if (list == null) {\r\n                output.append(\"None\");\r\n            }\r\n            else {\r\n                for (SynchedEntityData.DataValue<?> data : list) {\r\n                    output.append('[').append(data.id()).append(\": \").append(data.value()).append(\"], \");\r\n                }\r\n            }\r\n            doPacketOutput(output.toString());\r\n        }\r\n        else if (packet instanceof ClientboundSetEntityMotionPacket) {\r\n            ClientboundSetEntityMotionPacket velPacket = (ClientboundSetEntityMotionPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundSetEntityMotionPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + velPacket.id() + \": \" + velPacket.movement());\r\n        }\r\n        else if (packet instanceof ClientboundAddEntityPacket) {\r\n            ClientboundAddEntityPacket addEntityPacket = (ClientboundAddEntityPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundAddEntityPacket sent to \" + player.getScoreboardName() + \" for entity ID: \" + addEntityPacket.getId() + \": \" + \"uuid: \" + addEntityPacket.getUUID()\r\n                    + \", type: \" + addEntityPacket.getType() + \", at: \" + addEntityPacket.getX() + \",\" + addEntityPacket.getY() + \",\" + addEntityPacket.getZ() + \", data: \" + addEntityPacket.getData());\r\n        }\r\n        else if (packet instanceof ClientboundMapItemDataPacket) {\r\n            ClientboundMapItemDataPacket mapPacket = (ClientboundMapItemDataPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundMapItemDataPacket sent to \" + player.getScoreboardName() + \" for map ID: \" + mapPacket.mapId() + \", scale: \" + mapPacket.scale() + \", locked: \" + mapPacket.locked());\r\n        }\r\n        else if (packet instanceof ClientboundRemoveEntitiesPacket) {\r\n            ClientboundRemoveEntitiesPacket removePacket = (ClientboundRemoveEntitiesPacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundRemoveEntitiesPacket sent to \" + player.getScoreboardName() + \" for entities: \" + removePacket.getEntityIds().stream().map(Object::toString).collect(Collectors.joining(\", \")));\r\n        }\r\n        else if (packet instanceof ClientboundPlayerInfoUpdatePacket) {\r\n            ClientboundPlayerInfoUpdatePacket playerInfoPacket = (ClientboundPlayerInfoUpdatePacket) packet;\r\n            doPacketOutput(\"Packet: ClientboundPlayerInfoPacket sent to \" + player.getScoreboardName() + \" of types \" + playerInfoPacket.actions() + \" for player profiles: \" +\r\n                    playerInfoPacket.entries().stream().map(p -> \"mode=\" + p.gameMode() + \"/latency=\" + p.latency() + \"/display=\" + p.displayName() + \"/name=\" + p.profile().name() + \"/id=\" + p.profile().id() + \"/\"\r\n                            + p.profile().properties().asMap().entrySet().stream().map(e -> e.getKey() + \"=\" + e.getValue().stream().map(v -> v.value() + \";\" + v.signature()).collect(Collectors.joining(\";;;\"))).collect(Collectors.joining(\"/\"))).collect(Collectors.joining(\", \")));\r\n        }\r\n        else {\r\n            doPacketOutput(\"Packet: \" + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, ChannelFutureListener channelFutureListener) {\r\n        send(packet, channelFutureListener, true);\r\n    }\r\n\r\n    @Override\r\n    public void send(Packet<?> packet, @Nullable ChannelFutureListener channelFutureListener, boolean flush) {\r\n        if (!Bukkit.isPrimaryThread()) {\r\n            if (Settings.cache_warnOnAsyncPackets\r\n                    && !(packet instanceof ClientboundSystemChatPacket) && !(packet instanceof ClientboundPlayerChatPacket) // Vanilla supports an async chat system, though it's normally disabled, some plugins use this as justification for sending messages async\r\n                    && !(packet instanceof ClientboundCommandSuggestionsPacket)) { // Async tab complete is wholly unsupported in Spigot (and will cause an exception), however Paper explicitly adds async support (for unclear reasons), so let it through too\r\n                Debug.echoError(\"Warning: packet sent off main thread! This is completely unsupported behavior! Denizen network interceptor ignoring packet to avoid crash. Packet class: \"\r\n                        + packet.getClass().getCanonicalName() + \" sent to \" + player.getScoreboardName() + \" identify the sender of the packet from the stack trace:\");\r\n                try {\r\n                    throw new RuntimeException(\"Trace\");\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(ex);\r\n                }\r\n            }\r\n            oldManager.send(packet, channelFutureListener, flush);\r\n            return;\r\n        }\r\n        if (NMSHandler.debugPackets) {\r\n            debugOutputPacket(packet);\r\n        }\r\n        packetsSent++;\r\n        if (packet instanceof ClientboundBundlePacket bundlePacket) {\r\n            List<Packet<? super ClientGamePacketListener>> processedPackets = new ArrayList<>();\r\n            boolean anyChange = false;\r\n            for (Packet<? super ClientGamePacketListener> _subPacket : bundlePacket.subPackets()) {\r\n                // Bundle packets with non-game packets shouldn't ever be sent while the Denizen interceptor is active\r\n                Packet<ClientGamePacketListener> subPacket = (Packet<ClientGamePacketListener>) _subPacket;\r\n                Packet<ClientGamePacketListener> processed = processPacketHandlersFor(subPacket);\r\n                anyChange = anyChange || processed != subPacket;\r\n                if (processed != null) {\r\n                    processedPackets.add(processed);\r\n                }\r\n            }\r\n            if (processedPackets.isEmpty()) {\r\n                return;\r\n            }\r\n            if (anyChange) {\r\n                packet = new ClientboundBundlePacket(processedPackets);\r\n            }\r\n        }\r\n        else {\r\n            Packet<?> processed = processPacketHandlersFor((Packet<ClientGamePacketListener>) packet);\r\n            if (processed == null) {\r\n                return;\r\n            }\r\n            packet = processed;\r\n        }\r\n        oldManager.send(packet, channelFutureListener, flush);\r\n    }\r\n\r\n    @Override\r\n    public void runOnceConnected(Consumer<Connection> consumer) {\r\n        oldManager.runOnceConnected(consumer);\r\n    }\r\n\r\n    @Override\r\n    public void flushChannel() {\r\n        oldManager.flushChannel();\r\n    }\r\n\r\n    public Packet<ClientGamePacketListener> processPacketHandlersFor(Packet<ClientGamePacketListener> packet) {\r\n        if (packet == null) {\r\n            return null;\r\n        }\r\n        List<PacketHandler<?>> packetHandlers = DenizenNetworkManagerImpl.packetHandlers.get(packet.getClass());\r\n        if (packetHandlers != null) {\r\n            for (PacketHandler<?> _packetHandler : packetHandlers) {\r\n                PacketHandler<Packet<ClientGamePacketListener>> packetHandler = (PacketHandler<Packet<ClientGamePacketListener>>) _packetHandler;\r\n                Packet<ClientGamePacketListener> processed;\r\n                try {\r\n                    processed = packetHandler.handlePacket(this, packet);\r\n                }\r\n                catch (Exception ex) {\r\n                    Debug.echoError(\"Packet handler for \" + packet.getClass().getCanonicalName() + \" threw an exception:\");\r\n                    Debug.echoError(ex);\r\n                    continue;\r\n                }\r\n                if (processed == null) {\r\n                    if (NMSHandler.debugPackets) {\r\n                        doPacketOutput(\"DENIED PACKET - \" + packet.getClass().getCanonicalName() + \" DENIED FROM SEND TO \" + player.getScoreboardName());\r\n                    }\r\n                    return null;\r\n                }\r\n                packet = processed;\r\n            }\r\n        }\r\n        if (PlayerReceivesPacketScriptEvent.instance.eventData.isEnabled & PlayerReceivesPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) {\r\n            if (NMSHandler.debugPackets) {\r\n                doPacketOutput(\"DENIED PACKET - \" + packet.getClass().getCanonicalName() + \" DENIED FROM SEND TO \" + player.getScoreboardName() + \" due to event\");\r\n            }\r\n            return null;\r\n        }\r\n        return packet;\r\n    }\r\n\r\n    static {\r\n        ActionBarEventPacketHandlers.registerHandlers();\r\n        AttachPacketHandlers.registerHandlers();\r\n        BlockLightPacketHandlers.registerHandlers();\r\n        DenizenPacketHandlerPacketHandlers.registerHandlers();\r\n        EntityMetadataPacketHandlers.registerHandlers();\r\n        DisguisePacketHandlers.registerHandlers();\r\n        FakeBlocksPacketHandlers.registerHandlers();\r\n        FakeEquipmentPacketHandlers.registerHandlers();\r\n        FakePlayerPacketHandlers.registerHandlers();\r\n        HiddenEntitiesPacketHandlers.registerHandlers();\r\n        HideParticlesPacketHandlers.registerHandlers();\r\n        PlayerHearsSoundEventPacketHandlers.registerHandlers();\r\n        ProfileEditorImpl.registerHandlers();\r\n        TablistUpdateEventPacketHandlers.registerHandlers();\r\n    }\r\n\r\n    @Override\r\n    public void tick() {\r\n        oldManager.tick();\r\n    }\r\n\r\n    @Override\r\n    public SocketAddress getRemoteAddress() {\r\n        return oldManager.getRemoteAddress();\r\n    }\r\n\r\n    @Override\r\n    public String getLoggableAddress(boolean flag) {\r\n        return oldManager.getLoggableAddress(flag);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(Component ichatbasecomponent) {\r\n        if (!player.getBukkitEntity().isOnline()) { // Workaround Paper duplicate quit event issue\r\n            return;\r\n        }\r\n        oldManager.disconnect(ichatbasecomponent);\r\n    }\r\n\r\n    @Override\r\n    public void disconnect(DisconnectionDetails disconnectiondetails) {\r\n        oldManager.disconnect(disconnectiondetails);\r\n    }\r\n\r\n    @Override\r\n    public boolean isMemoryConnection() {\r\n        return oldManager != null && oldManager.isMemoryConnection();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getReceiving() {\r\n        return oldManager.getReceiving();\r\n    }\r\n\r\n    @Override\r\n    public PacketFlow getSending() {\r\n        return oldManager.getSending();\r\n    }\r\n\r\n    @Override\r\n    public void configurePacketHandler(ChannelPipeline channelpipeline) {\r\n        oldManager.configurePacketHandler(channelpipeline);\r\n    }\r\n\r\n    @Override\r\n    public void setEncryptionKey(Cipher cipher, Cipher cipher1) {\r\n        oldManager.setEncryptionKey(cipher, cipher1);\r\n    }\r\n\r\n    @Override\r\n    public boolean isEncrypted() {\r\n        return oldManager.isEncrypted();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnected() {\r\n        return oldManager.isConnected();\r\n    }\r\n\r\n    @Override\r\n    public boolean isConnecting() {\r\n        return oldManager.isConnecting();\r\n    }\r\n\r\n    @Override\r\n    public PacketListener getPacketListener() {\r\n        return oldManager.getPacketListener();\r\n    }\r\n\r\n    @Override\r\n    public DisconnectionDetails getDisconnectionDetails() {\r\n        return oldManager.getDisconnectionDetails();\r\n    }\r\n\r\n    @Override\r\n    public void setReadOnly() {\r\n        oldManager.setReadOnly();\r\n    }\r\n\r\n    @Override\r\n    public void setupCompression(int i, boolean b) {\r\n        oldManager.setupCompression(i, b);\r\n    }\r\n\r\n    @Override\r\n    public void handleDisconnection() {\r\n        oldManager.handleDisconnection();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageReceivedPackets() {\r\n        return oldManager.getAverageReceivedPackets();\r\n    }\r\n\r\n    @Override\r\n    public float getAverageSentPackets() {\r\n        return oldManager.getAverageSentPackets();\r\n    }\r\n\r\n    @Override\r\n    public void setBandwidthLogger(LocalSampleLogger localsamplelogger) {\r\n        oldManager.setBandwidthLogger(localsamplelogger);\r\n    }\r\n\r\n    //////////////////////////////////\r\n    //// Reflection Methods/Fields\r\n    ///////////\r\n\r\n    private static final Field protocolDirectionField = ReflectionHelper.getFields(Connection.class).get(\"receiving\", PacketFlow.class);\r\n    public static final Field Connection_packetListener = ReflectionHelper.getFields(Connection.class).get(\"packetListener\", PacketListener.class);\r\n    private static final Field ServerGamePacketListener_ConnectionField = ReflectionHelper.getFields(ServerCommonPacketListenerImpl.class).get(\"connection\");\r\n    private static final MethodHandle ServerGamePacketListener_ConnectionSetter = ReflectionHelper.getFinalSetter(ServerCommonPacketListenerImpl.class, \"connection\");\r\n\r\n    private static PacketFlow getProtocolDirection(Connection networkManager) {\r\n        PacketFlow direction = null;\r\n        try {\r\n            direction = (PacketFlow) protocolDirectionField.get(networkManager);\r\n        }\r\n        catch (Exception e) {\r\n            Debug.echoError(e);\r\n        }\r\n        return direction;\r\n    }\r\n\r\n    private static void setNetworkManager(ServerGamePacketListenerImpl playerConnection, Connection networkManager) {\r\n        try {\r\n            ServerGamePacketListener_ConnectionSetter.invoke(playerConnection, networkManager);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public boolean acceptInboundMessage(Object msg) throws Exception {\r\n        return oldManager.acceptInboundMessage(msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\r\n        oldManager.channelRead(ctx, msg);\r\n    }\r\n\r\n    @Override\r\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelReadComplete(ctx);\r\n    }\r\n\r\n    @Override\r\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\r\n        oldManager.userEventTriggered(ctx, evt);\r\n    }\r\n\r\n    @Override\r\n    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {\r\n        oldManager.channelWritabilityChanged(ctx);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/DenizenPacketListenerImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.events.player.PlayerChangesSignScriptEvent;\r\nimport com.denizenscript.denizen.events.player.PlayerSteersEntityScriptEvent;\r\nimport com.denizenscript.denizen.nms.NMSHandler;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.packets.PacketInResourcePackStatusImpl;\r\nimport com.denizenscript.denizen.nms.v26_1.impl.network.packets.PacketInSteerVehicleImpl;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.objects.MaterialTag;\r\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\r\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport net.minecraft.core.BlockPos;\r\nimport net.minecraft.network.protocol.Packet;\r\nimport net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;\r\nimport net.minecraft.network.protocol.common.ServerboundResourcePackPacket;\r\nimport net.minecraft.network.protocol.game.*;\r\nimport net.minecraft.server.level.ServerPlayer;\r\nimport org.bukkit.Bukkit;\r\nimport org.bukkit.craftbukkit.block.CraftBlock;\r\nimport org.bukkit.event.block.SignChangeEvent;\r\n\r\npublic class DenizenPacketListenerImpl extends AbstractListenerPlayInImpl {\r\n\r\n    public BlockPos fakeSignExpected;\r\n\r\n    public DenizenPacketListenerImpl(DenizenNetworkManagerImpl networkManager, ServerPlayer entityPlayer) {\r\n        super(networkManager, entityPlayer, entityPlayer.connection);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerInput(final ServerboundPlayerInputPacket packet) {\r\n        if (!PlayerSteersEntityScriptEvent.instance.eventData.isEnabled) {\r\n            super.handlePlayerInput(packet);\r\n            return;\r\n        }\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInSteerVehicleImpl(packet), () -> super.handlePlayerInput(packet));\r\n    }\r\n\r\n    @Override\r\n    public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {\r\n        DenizenPacketHandler.instance.receivePacket(player.getBukkitEntity(), new PacketInResourcePackStatusImpl(packet));\r\n        super.handleResourcePackResponse(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleUseItem(ServerboundUseItemPacket packet) {\r\n        DenizenPacketHandler.instance.receivePlacePacket(player.getBukkitEntity());\r\n        super.handleUseItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handlePlayerAction(ServerboundPlayerActionPacket packet) {\r\n        DenizenPacketHandler.instance.receiveDigPacket(player.getBukkitEntity());\r\n        super.handlePlayerAction(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleAnimate(ServerboundSwingPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && (override.hand != null || override.offhand != null)) {\r\n            player.getBukkitEntity().updateInventory();\r\n        }\r\n        super.handleAnimate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && override.hand != null) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 2);\r\n        }\r\n        super.handleSetCarriedItem(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleContainerClick(ServerboundContainerClickPacket packet) {\r\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(player.getUUID(), getCraftPlayer());\r\n        if (override != null && packet.containerId() == 0) {\r\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(), player.getBukkitEntity()::updateInventory, 1);\r\n        }\r\n        super.handleContainerClick(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {\r\n        if (NMSHandler.debugPackets) {\r\n            Debug.log(\"Custom packet payload: \" + packet.payload().type().id().toString() + \" sent from \" + player.getScoreboardName());\r\n        }\r\n        super.handleCustomPayload(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleSignUpdate(ServerboundSignUpdatePacket packet) {\r\n        if (fakeSignExpected != null && packet.getPos().equals(fakeSignExpected)) {\r\n            LocationTag loc = new LocationTag(player.getBukkitEntity().getWorld(), fakeSignExpected.getX(), fakeSignExpected.getY(), fakeSignExpected.getZ());\r\n            this.connection.send(new ClientboundBlockUpdatePacket(player.level(), fakeSignExpected));\r\n            PlayerChangesSignScriptEvent evt = (PlayerChangesSignScriptEvent) PlayerChangesSignScriptEvent.instance.clone();\r\n            evt.material = new MaterialTag(org.bukkit.Material.OAK_WALL_SIGN);\r\n            evt.location = new LocationTag(player.getBukkitEntity().getLocation());\r\n            evt.event = new SignChangeEvent(CraftBlock.at(player.level(), fakeSignExpected), player.getBukkitEntity(), packet.getLines());\r\n            fakeSignExpected = null;\r\n            evt.fire(evt.event);\r\n        }\r\n        super.handleSignUpdate(packet);\r\n    }\r\n\r\n    @Override\r\n    public void handleMovePlayer(ServerboundMovePlayerPacket packet) {\r\n        if (DenizenPacketHandler.forceNoclip.contains(player.getUUID())) {\r\n            player.noPhysics = true;\r\n        }\r\n        super.handleMovePlayer(packet);\r\n    }\r\n\r\n    // For compatibility with other plugins using Reflection weirdly...\r\n    @Override\r\n    public void send(Packet<?> packet) {\r\n        super.send(packet);\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/FakeBlockHelper.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers;\r\n\r\nimport com.denizenscript.denizen.Denizen;\r\nimport com.denizenscript.denizen.objects.LocationTag;\r\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\r\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\r\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\r\nimport io.netty.buffer.Unpooled;\r\nimport net.minecraft.core.Registry;\r\nimport net.minecraft.core.registries.Registries;\r\nimport net.minecraft.nbt.CompoundTag;\r\nimport net.minecraft.network.FriendlyByteBuf;\r\nimport net.minecraft.network.RegistryFriendlyByteBuf;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;\r\nimport net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;\r\nimport net.minecraft.world.level.biome.Biome;\r\nimport net.minecraft.world.level.biome.Biomes;\r\nimport net.minecraft.world.level.block.Block;\r\nimport net.minecraft.world.level.block.Blocks;\r\nimport net.minecraft.world.level.block.entity.BlockEntity;\r\nimport net.minecraft.world.level.block.entity.BlockEntityType;\r\nimport net.minecraft.world.level.block.state.BlockState;\r\nimport net.minecraft.world.level.chunk.PalettedContainer;\r\nimport net.minecraft.world.level.chunk.Strategy;\r\nimport org.bukkit.World;\r\nimport org.bukkit.craftbukkit.CraftRegistry;\r\nimport org.bukkit.craftbukkit.CraftWorld;\r\nimport org.bukkit.craftbukkit.block.CraftBlockStates;\r\nimport org.bukkit.craftbukkit.block.data.CraftBlockData;\r\n\r\nimport java.lang.invoke.MethodHandle;\r\nimport java.lang.reflect.Constructor;\r\nimport java.lang.reflect.Field;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\n\r\npublic class FakeBlockHelper {\r\n\r\n    public static Field CHUNKDATA_BLOCK_ENTITIES = ReflectionHelper.getFields(ClientboundLevelChunkPacketData.class).getFirstOfType(List.class);\r\n    public static MethodHandle CHUNKDATA_BLOCK_ENTITY_CONSTRUCTOR = ReflectionHelper.getConstructor(ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0], int.class, int.class, BlockEntityType.class, CompoundTag.class);\r\n    public static MethodHandle CHUNKDATA_BUFFER_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkPacketData.class, byte[].class);\r\n    public static Class CHUNKDATA_BLOCKENTITYINFO_CLASS = ClientboundLevelChunkPacketData.class.getDeclaredClasses()[0];\r\n    public static Field CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get(\"packedXZ\");\r\n    public static Field CHUNKDATA_BLOCKENTITYINFO_Y = ReflectionHelper.getFields(CHUNKDATA_BLOCKENTITYINFO_CLASS).get(\"y\");\r\n    public static MethodHandle CHUNKPACKET_CHUNKDATA_SETTER = ReflectionHelper.getFinalSetterForFirstOfType(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class);\r\n    public static Constructor<?> PALETTEDCONTAINER_CTOR = Arrays.stream(PalettedContainer.class.getConstructors()).filter(c -> c.getParameterCount() == 2).findFirst().get();\r\n\r\n    public static BlockState getNMSState(FakeBlock block) {\r\n        return ((CraftBlockData) block.material.getModernData()).getState();\r\n    }\r\n\r\n    public static boolean anyBlocksInSection(List<FakeBlock> blocks, int y) {\r\n        int minY = y << 4;\r\n        int maxY = (y << 4) + 16;\r\n        for (FakeBlock block : blocks) {\r\n            int blockY = block.location.getBlockY();\r\n            if (blockY >= minY && blockY < maxY) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public static Field PAPER_CHUNK_READY;\r\n    public static boolean tryPaperPatch = true;\r\n\r\n    public static void copyPacketPaperPatch(ClientboundLevelChunkWithLightPacket newPacket, ClientboundLevelChunkWithLightPacket oldPacket) {\r\n        if (!Denizen.supportsPaper || !tryPaperPatch) {\r\n            return;\r\n        }\r\n        try {\r\n            if (PAPER_CHUNK_READY == null) {\r\n                PAPER_CHUNK_READY = ReflectionHelper.getFields(ClientboundLevelChunkWithLightPacket.class).get(\"ready\");\r\n            }\r\n        }\r\n        catch (Throwable ex) {\r\n            tryPaperPatch = false;\r\n            Debug.echoError(\"Paper packet patch failed:\");\r\n            Debug.echoError(ex);\r\n            return;\r\n        }\r\n        try {\r\n            PAPER_CHUNK_READY.setBoolean(newPacket, true);\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n    }\r\n\r\n    public static ClientboundLevelChunkWithLightPacket handleMapChunkPacket(World world, ClientboundLevelChunkWithLightPacket originalPacket, int chunkX, int chunkZ, List<FakeBlock> blocks) {\r\n        try {\r\n            ClientboundLevelChunkWithLightPacket duplicateCorePacket = DenizenNetworkManagerImpl.copyPacket(originalPacket, ClientboundLevelChunkWithLightPacket.STREAM_CODEC);\r\n            copyPacketPaperPatch(duplicateCorePacket, originalPacket);\r\n            RegistryFriendlyByteBuf copier = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry());\r\n            originalPacket.getChunkData().write(copier);\r\n            ClientboundLevelChunkPacketData packet = new ClientboundLevelChunkPacketData(copier, chunkX, chunkZ);\r\n            FriendlyByteBuf serial = originalPacket.getChunkData().getReadBuffer();\r\n            FriendlyByteBuf outputSerial = new FriendlyByteBuf(Unpooled.buffer(serial.readableBytes()));\r\n            List blockEntities = new ArrayList((List) CHUNKDATA_BLOCK_ENTITIES.get(originalPacket.getChunkData()));\r\n            CHUNKDATA_BLOCK_ENTITIES.set(packet, blockEntities);\r\n            for (int i = 0; i < blockEntities.size(); i++) {\r\n                Object blockEnt = blockEntities.get(i);\r\n                int xz = CHUNKDATA_BLOCKENTITYINFO_PACKEDXZ.getInt(blockEnt);\r\n                int y = CHUNKDATA_BLOCKENTITYINFO_Y.getInt(blockEnt);\r\n                int x = (chunkX << 4) + ((xz >> 4) & 15);\r\n                int z = (chunkZ << 4) + (xz & 15);\r\n                for (FakeBlock block : blocks) {\r\n                    LocationTag loc = block.location;\r\n                    if (loc.getBlockX() == x && loc.getBlockY() == y && loc.getBlockZ() == z && block.material != null) {\r\n                        BlockEntity newBlockEnt = CraftBlockStates.createNewTileEntity(block.material.getMaterial());\r\n                        Object newData = CHUNKDATA_BLOCK_ENTITY_CONSTRUCTOR.invoke(xz, y, newBlockEnt.getType(), newBlockEnt.getUpdateTag(CraftRegistry.getMinecraftRegistry()));\r\n                        blockEntities.set(i, newData);\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            int worldMinY = world.getMinHeight();\r\n            int worldMaxY = world.getMaxHeight();\r\n            int minChunkY = worldMinY >> 4;\r\n            int maxChunkY = worldMaxY >> 4;\r\n            Registry<Biome> biomeRegistry = ((CraftWorld) world).getHandle().registryAccess().lookupOrThrow(Registries.BIOME);\r\n            for (int y = minChunkY; y < maxChunkY; y++) {\r\n                int blockCount = serial.readShort();\r\n                // reflected constructors as workaround for spigot remapper bug - Mojang \"IdMap\" became Spigot \"IRegistry\" but should be \"Registry\"\r\n                PalettedContainer<BlockState> states = (PalettedContainer<BlockState>) PALETTEDCONTAINER_CTOR.newInstance(Blocks.AIR.defaultBlockState(), Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY));\r\n                states.read(serial);\r\n                PalettedContainer<Biome> biomes = (PalettedContainer<Biome>) PALETTEDCONTAINER_CTOR.newInstance(biomeRegistry.getOrThrow(Biomes.PLAINS), Strategy.createForBiomes(biomeRegistry));\r\n                biomes.read(serial);\r\n                if (anyBlocksInSection(blocks, y)) {\r\n                    int minY = y << 4;\r\n                    int maxY = (y << 4) + 16;\r\n                    for (FakeBlock block : blocks) {\r\n                        int blockY = block.location.getBlockY();\r\n                        if (blockY >= minY && blockY < maxY && block.material != null) {\r\n                            int blockX = block.location.getBlockX();\r\n                            int blockZ = block.location.getBlockZ();\r\n                            blockX -= (blockX >> 4) * 16;\r\n                            blockY -= (blockY >> 4) * 16;\r\n                            blockZ -= (blockZ >> 4) * 16;\r\n                            BlockState oldState = states.get(blockX, blockY, blockZ);\r\n                            BlockState newState = getNMSState(block);\r\n                            if (oldState.isAir() && !newState.isAir()) {\r\n                                blockCount++;\r\n                            }\r\n                            else if (newState.isAir() && !oldState.isAir()) {\r\n                                blockCount--;\r\n                            }\r\n                            states.set(blockX, blockY, blockZ, newState);\r\n                        }\r\n                    }\r\n                }\r\n                outputSerial.writeShort(blockCount);\r\n                states.write(outputSerial);\r\n                biomes.write(outputSerial);\r\n            }\r\n            byte[] outputBytes = outputSerial.array();\r\n            CHUNKDATA_BUFFER_SETTER.invoke(packet, outputBytes);\r\n            CHUNKPACKET_CHUNKDATA_SETTER.invoke(duplicateCorePacket, packet);\r\n            return duplicateCorePacket;\r\n        }\r\n        catch (Throwable ex) {\r\n            Debug.echoError(ex);\r\n        }\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/ActionBarEventPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerReceivesActionbarScriptEvent;\nimport com.denizenscript.denizen.nms.v26_1.Handler;\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.objects.core.ElementTag;\nimport net.minecraft.network.chat.Component;\nimport net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket;\nimport org.bukkit.craftbukkit.util.CraftChatMessage;\n\npublic class ActionBarEventPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetActionBarTextPacket.class, ActionBarEventPacketHandlers::processActionbarPacket);\n    }\n\n    public static ClientboundSetActionBarTextPacket processActionbarPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetActionBarTextPacket actionbarPacket) {\n        PlayerReceivesActionbarScriptEvent event = PlayerReceivesActionbarScriptEvent.instance;\n        if (!event.loaded) {\n            return actionbarPacket;\n        }\n        event.reset();\n        Component actionbarText = actionbarPacket.text();\n        event.message = new ElementTag(FormattedTextHelper.stringify(Handler.componentToSpigot(actionbarText)), true);\n        event.rawJson = new ElementTag(CraftChatMessage.toJSON(actionbarText), true);\n        event.system = new ElementTag(false);\n        event.player = PlayerTag.mirrorBukkitPlayer(networkManager.player.getBukkitEntity());\n        event = (PlayerReceivesActionbarScriptEvent) event.triggerNow();\n        if (event.cancelled) {\n            return null;\n        }\n        if (event.modified) {\n            return new ClientboundSetActionBarTextPacket(Handler.componentToNMS(event.altMessageDetermination));\n        }\n        return actionbarPacket;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/AttachPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\nimport com.denizenscript.denizencore.utilities.CoreConfiguration;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.PositionMoveRotation;\nimport net.minecraft.world.phys.Vec3;\nimport org.bukkit.craftbukkit.entity.CraftEntity;\nimport org.bukkit.craftbukkit.util.CraftVector;\nimport org.bukkit.util.Vector;\n\nimport java.lang.reflect.Field;\nimport java.util.Set;\n\npublic class AttachPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundRotateHeadPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityMotionPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundTeleportEntityPacket.class, AttachPacketHandlers::processAttachToForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundRemoveEntitiesPacket.class, AttachPacketHandlers::processAttachToForPacket);\n    }\n\n    public static Field POS_X_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(\"xa\", short.class);\n    public static Field POS_Y_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(\"ya\", short.class);\n    public static Field POS_Z_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(\"za\", short.class);\n    public static Field YAW_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(\"yRot\", byte.class);\n    public static Field PITCH_PACKENT = ReflectionHelper.getFields(ClientboundMoveEntityPacket.class).get(\"xRot\", byte.class);\n\n    public static Vector VECTOR_ZERO = new Vector(0, 0, 0);\n\n    public static void tryProcessMovePacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket packet, Entity e) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    ClientboundMoveEntityPacket pNew;\n                    int newId = att.attached.getBukkitEntity().getEntityId();\n                    if (packet instanceof ClientboundMoveEntityPacket.Pos) {\n                        pNew = new ClientboundMoveEntityPacket.Pos(newId, packet.getXa(), packet.getYa(), packet.getZa(), packet.isOnGround());\n                    }\n                    else if (packet instanceof ClientboundMoveEntityPacket.Rot) {\n                        pNew = new ClientboundMoveEntityPacket.Rot(newId, Mth.packDegrees(packet.getYRot()), Mth.packDegrees(packet.getXRot()), packet.isOnGround());\n                    }\n                    else if (packet instanceof ClientboundMoveEntityPacket.PosRot) {\n                        pNew = new ClientboundMoveEntityPacket.PosRot(newId, packet.getXa(), packet.getYa(), packet.getZa(), Mth.packDegrees(packet.getYRot()), Mth.packDegrees(packet.getXRot()), packet.isOnGround());\n                    }\n                    else {\n                        if (CoreConfiguration.debugVerbose) {\n                            Debug.echoError(\"Impossible move-entity packet class: \" + packet.getClass().getCanonicalName());\n                        }\n                        return;\n                    }\n                    if (att.positionalOffset != null) {\n                        boolean isRotate = packet instanceof ClientboundMoveEntityPacket.PosRot || packet instanceof ClientboundMoveEntityPacket.Rot;\n                        float yaw, pitch;\n                        if (att.noRotate) {\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                            yaw = attachedEntity.getYRot();\n                            pitch = attachedEntity.getXRot();\n                        }\n                        else if (isRotate) {\n                            yaw = packet.getYRot();\n                            pitch = packet.getXRot();\n                        }\n                        else {\n                            yaw = e.getYRot();\n                            pitch = e.getXRot();\n                        }\n                        if (att.noPitch) {\n                            pitch = ((CraftEntity) att.attached.getBukkitEntity()).getHandle().getXRot();\n                        }\n                        float newYaw = yaw;\n                        if (isRotate) {\n                            newYaw = EntityAttachmentHelper.normalizeAngle(newYaw + att.positionalOffset.getYaw());\n                            pitch = EntityAttachmentHelper.normalizeAngle(pitch + att.positionalOffset.getPitch());\n                        }\n                        Vector goalPosition = att.fixedForOffset(new Vector(e.getX(), e.getY(), e.getZ()), e.getYRot(), e.getXRot());\n                        Vector oldPos = att.visiblePositions.get(networkManager.player.getUUID());\n                        boolean forceTele = false;\n                        if (oldPos == null) {\n                            oldPos = att.attached.getLocation().toVector();\n                            forceTele = true;\n                        }\n                        Vector moveNeeded = goalPosition.clone().subtract(oldPos);\n                        att.visiblePositions.put(networkManager.player.getUUID(), goalPosition.clone());\n                        int offX = (int) (moveNeeded.getX() * (32 * 128));\n                        int offY = (int) (moveNeeded.getY() * (32 * 128));\n                        int offZ = (int) (moveNeeded.getZ() * (32 * 128));\n                        if ((isRotate && att.offsetRelative) || forceTele || offX < Short.MIN_VALUE || offX > Short.MAX_VALUE\n                                || offY < Short.MIN_VALUE || offY > Short.MAX_VALUE\n                                || offZ < Short.MIN_VALUE || offZ > Short.MAX_VALUE) {\n                            ClientboundTeleportEntityPacket newTeleportPacket = new ClientboundTeleportEntityPacket(\n                                    att.attached.getBukkitEntity().getEntityId(),\n                                    new PositionMoveRotation(CraftVector.toNMS(goalPosition), Vec3.ZERO, newYaw, pitch),\n                                    Set.of(),\n                                    e.onGround()\n                            );\n                            if (NMSHandler.debugPackets) {\n                                DenizenNetworkManagerImpl.doPacketOutput(\"Attach Move-Tele Packet: \" + newTeleportPacket.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\n                            }\n                            networkManager.oldManager.send(newTeleportPacket);\n                        }\n                        else {\n                            POS_X_PACKENT.setShort(pNew, (short) Mth.clamp(offX, Short.MIN_VALUE, Short.MAX_VALUE));\n                            POS_Y_PACKENT.setShort(pNew, (short) Mth.clamp(offY, Short.MIN_VALUE, Short.MAX_VALUE));\n                            POS_Z_PACKENT.setShort(pNew, (short) Mth.clamp(offZ, Short.MIN_VALUE, Short.MAX_VALUE));\n                            if (isRotate) {\n                                YAW_PACKENT.setByte(pNew, EntityAttachmentHelper.compressAngle(yaw));\n                                PITCH_PACKENT.setByte(pNew, EntityAttachmentHelper.compressAngle(pitch));\n                            }\n                            if (NMSHandler.debugPackets) {\n                                DenizenNetworkManagerImpl.doPacketOutput(\"Attach Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName() + \" with original yaw \" + yaw + \" adapted to \" + newYaw);\n                            }\n                            networkManager.oldManager.send(pNew);\n                        }\n                    }\n                    else {\n                        if (NMSHandler.debugPackets) {\n                            DenizenNetworkManagerImpl.doPacketOutput(\"Attach Replica-Move Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName());\n                        }\n                        networkManager.oldManager.send(pNew);\n                    }\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessMovePacketForAttach(networkManager, packet, ent);\n            }\n        }\n    }\n\n    public static void tryProcessRotateHeadPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundRotateHeadPacket packet, Entity e) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    float yaw = packet.getYHeadRot();\n                    Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                    if (att.positionalOffset != null) {\n                        if (att.noRotate) {\n                            yaw = attachedEntity.getYRot();\n                        }\n                        yaw = EntityAttachmentHelper.normalizeAngle(yaw + att.positionalOffset.getYaw());\n                    }\n                    ClientboundRotateHeadPacket pNew = new ClientboundRotateHeadPacket(attachedEntity, EntityAttachmentHelper.compressAngle(yaw));\n                    if (NMSHandler.debugPackets) {\n                        DenizenNetworkManagerImpl.doPacketOutput(\"Head Rotation Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName());\n                    }\n                    networkManager.oldManager.send(pNew);\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessRotateHeadPacketForAttach(networkManager, packet, ent);\n            }\n        }\n    }\n\n    public static void tryProcessVelocityPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityMotionPacket packet, Entity e) {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    ClientboundSetEntityMotionPacket pNew = new ClientboundSetEntityMotionPacket(att.attached.getBukkitEntity().getEntityId(), packet.movement());\n                    if (NMSHandler.debugPackets) {\n                        DenizenNetworkManagerImpl.doPacketOutput(\"Attach Velocity Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID() + \" sent to \" + networkManager.player.getScoreboardName());\n                    }\n                    networkManager.oldManager.send(pNew);\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessVelocityPacketForAttach(networkManager, packet, ent);\n            }\n        }\n    }\n\n    public static void tryProcessTeleportPacketForAttach(DenizenNetworkManagerImpl networkManager, ClientboundTeleportEntityPacket packet, Entity e, Vector relative) throws IllegalAccessException {\n        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n        if (attList != null) {\n            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                if (attMap.attached.isValid() && att != null) {\n                    ClientboundTeleportEntityPacket pNew;\n                    Vector resultPos = CraftVector.toBukkit(packet.change().position()).add(relative);\n                    if (att.positionalOffset != null) {\n                        resultPos = att.fixedForOffset(resultPos, e.getYRot(), e.getXRot());\n                        float yaw, pitch;\n                        if (att.noRotate) {\n                            Entity attachedEntity = ((CraftEntity) att.attached.getBukkitEntity()).getHandle();\n                            yaw = attachedEntity.getYRot();\n                            pitch = attachedEntity.getXRot();\n                        }\n                        else {\n                            yaw = packet.change().yRot();\n                            pitch = packet.change().xRot();\n                        }\n                        if (att.noPitch) {\n                            pitch = ((CraftEntity) att.attached.getBukkitEntity()).getHandle().getXRot();\n                        }\n                        float newYaw = EntityAttachmentHelper.normalizeAngle(yaw + att.positionalOffset.getYaw());\n                        pitch = EntityAttachmentHelper.normalizeAngle(pitch + att.positionalOffset.getPitch());\n                        pNew = new ClientboundTeleportEntityPacket(\n                                att.attached.getBukkitEntity().getEntityId(),\n                                new PositionMoveRotation(CraftVector.toNMS(resultPos), packet.change().deltaMovement(), newYaw, pitch),\n                                packet.relatives(),\n                                packet.onGround()\n                        );\n                        if (NMSHandler.debugPackets) {\n                            DenizenNetworkManagerImpl.doPacketOutput(\"Attach Teleport Packet: \" + pNew.getClass().getCanonicalName() + \" for \" + att.attached.getUUID()\n                                    + \" sent to \" + networkManager.player.getScoreboardName() + \" with raw yaw \" + yaw + \" adapted to \" + newYaw);\n                        }\n                    }\n                    else {\n                        pNew = new ClientboundTeleportEntityPacket(att.attached.getBukkitEntity().getEntityId(), packet.change(), packet.relatives(), packet.onGround());\n                    }\n                    att.visiblePositions.put(networkManager.player.getUUID(), resultPos.clone());\n                    networkManager.oldManager.send(pNew);\n                }\n            }\n        }\n        if (e.passengers != null && !e.passengers.isEmpty()) {\n            for (Entity ent : e.passengers) {\n                tryProcessTeleportPacketForAttach(networkManager, packet, ent, new Vector(ent.getX() - e.getX(), ent.getY() - e.getY(), ent.getZ() - e.getZ()));\n            }\n        }\n    }\n\n    public static Packet<ClientGamePacketListener> processAttachToForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (EntityAttachmentHelper.toEntityToData.isEmpty()) {\n            return packet;\n        }\n        try {\n            if (packet instanceof ClientboundMoveEntityPacket moveEntityPacket) {\n                Entity e = moveEntityPacket.getEntity(networkManager.player.level());\n                if (e == null) {\n                    return packet;\n                }\n                if (!e.isPassenger()) {\n                    tryProcessMovePacketForAttach(networkManager, moveEntityPacket, e);\n                }\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundRotateHeadPacket rotateHeadPacket) {\n                Entity e = rotateHeadPacket.getEntity(networkManager.player.level());\n                if (e == null) {\n                    return packet;\n                }\n                tryProcessRotateHeadPacketForAttach(networkManager, rotateHeadPacket, e);\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundSetEntityMotionPacket setEntityMotionPacket) {\n                int ider = setEntityMotionPacket.id();\n                Entity e = networkManager.player.level().getEntity(ider);\n                if (e == null) {\n                    return packet;\n                }\n                tryProcessVelocityPacketForAttach(networkManager, setEntityMotionPacket, e);\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundTeleportEntityPacket teleportEntityPacket) {\n                int ider = teleportEntityPacket.id();\n                Entity e = networkManager.player.level().getEntity(ider);\n                if (e == null) {\n                    return packet;\n                }\n                tryProcessTeleportPacketForAttach(networkManager, teleportEntityPacket, e, VECTOR_ZERO);\n                return EntityAttachmentHelper.denyOriginalPacketSend(networkManager.player.getUUID(), e.getUUID()) ? null : packet;\n            }\n            else if (packet instanceof ClientboundRemoveEntitiesPacket removeEntitiesPacket) {\n                for (int id : removeEntitiesPacket.getEntityIds()) {\n                    Entity e = networkManager.player.level().getEntity(id);\n                    if (e != null) {\n                        EntityAttachmentHelper.EntityAttachedToMap attList = EntityAttachmentHelper.toEntityToData.get(e.getUUID());\n                        if (attList != null) {\n                            for (EntityAttachmentHelper.PlayerAttachMap attMap : attList.attachedToMap.values()) {\n                                EntityAttachmentHelper.AttachmentData att = attMap.getAttachment(networkManager.player.getUUID());\n                                if (attMap.attached.isValid() && att != null) {\n                                    att.visiblePositions.remove(networkManager.player.getUUID());\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        catch (Exception ex) {\n            Debug.echoError(ex);\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/BlockLightPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.abstracts.BlockLight;\nimport com.denizenscript.denizen.nms.v26_1.impl.blocks.BlockLightImpl;\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;\nimport net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;\n\npublic class BlockLightPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLightUpdatePacket.class, BlockLightPacketHandlers::processLightUpdatePacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, BlockLightPacketHandlers::processBlockUpdatePacket);\n    }\n\n    public static void processLightUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundLightUpdatePacket lightUpdatePacket) {\n        if (!BlockLight.lightsByChunk.isEmpty()) {\n            BlockLightImpl.checkIfLightsBrokenByPacket(lightUpdatePacket, networkManager.player.level());\n        }\n    }\n\n    public static void processBlockUpdatePacket(DenizenNetworkManagerImpl networkManager, ClientboundBlockUpdatePacket blockUpdatePacket) {\n        if (!BlockLight.lightsByChunk.isEmpty()) {\n            BlockLightImpl.checkIfLightsBrokenByPacket(blockUpdatePacket, networkManager.player.level());\n        }\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/DenizenPacketHandlerPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerReceivesMessageScriptEvent;\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.nms.v26_1.impl.network.packets.PacketOutChatImpl;\nimport com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;\nimport net.minecraft.network.protocol.game.ClientboundSystemChatPacket;\n\npublic class DenizenPacketHandlerPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSystemChatPacket.class, DenizenPacketHandlerPacketHandlers::processPacketHandlerForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerChatPacket.class, DenizenPacketHandlerPacketHandlers::processPacketHandlerForPacket);\n    }\n\n    public static Packet<ClientGamePacketListener> processPacketHandlerForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (DenizenPacketHandler.instance.shouldInterceptChatPacket()) {\n            PacketOutChatImpl packetHelper = null;\n            boolean isActionbar = false;\n            if (packet instanceof ClientboundSystemChatPacket chatPacket) {\n                isActionbar = chatPacket.overlay();\n                packetHelper = new PacketOutChatImpl(chatPacket);\n                if (packetHelper.rawJson == null) { // Makes no sense but this can be null in weird edge cases\n                    return packet;\n                }\n            }\n            else if (packet instanceof ClientboundPlayerChatPacket playerChatPacket) {\n                packetHelper = new PacketOutChatImpl(playerChatPacket);\n            }\n            if (packetHelper != null) {\n                PlayerReceivesMessageScriptEvent result = DenizenPacketHandler.instance.sendPacket(networkManager.player.getBukkitEntity(), packetHelper);\n                if (result != null) {\n                    if (result.cancelled) {\n                        return null;\n                    }\n                    if (result.modified) {\n                        return new ClientboundSystemChatPacket(result.altMessageDetermination, isActionbar);\n                    }\n                }\n            }\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/DisguisePacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.v26_1.helpers.PacketHelperImpl;\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.objects.PlayerTag;\nimport com.denizenscript.denizen.scripts.commands.player.DisguiseCommand;\nimport com.denizenscript.denizen.utilities.entity.EntityAttachmentHelper;\nimport com.denizenscript.denizen.utilities.entity.FakeEntity;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.network.syncher.SynchedEntityData;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.PositionMoveRotation;\nimport net.minecraft.world.level.Level;\nimport org.bukkit.craftbukkit.entity.CraftEntity;\nimport org.bukkit.entity.EntityType;\nimport org.bukkit.entity.LivingEntity;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.function.BiFunction;\nimport java.util.function.ToIntFunction;\n\npublic class DisguisePacketHandlers {\n\n    public static void registerHandlers() {\n        registerPacketHandler(ClientboundSetEntityDataPacket.class, ClientboundSetEntityDataPacket::id, DisguisePacketHandlers::processEntityDataPacket);\n        registerPacketHandler(ClientboundUpdateAttributesPacket.class, ClientboundUpdateAttributesPacket::getEntityId, DisguisePacketHandlers::processAttributesPacket);\n        registerPacketHandler(ClientboundAddEntityPacket.class, ClientboundAddEntityPacket::getId, DisguisePacketHandlers::sendDisguiseForPacket);\n        registerPacketHandler(ClientboundTeleportEntityPacket.class, ClientboundTeleportEntityPacket::id, DisguisePacketHandlers::processTeleportPacket);\n        registerPacketHandler(ClientboundMoveEntityPacket.Rot.class, ClientboundMoveEntityPacket::getEntity, DisguisePacketHandlers::processMoveEntityRotPacket);\n        registerPacketHandler(ClientboundMoveEntityPacket.PosRot.class, ClientboundMoveEntityPacket::getEntity, DisguisePacketHandlers::processMoveEntityPosRotPacket);\n    }\n\n    private static boolean antiDuplicate = false;\n\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetType, ToIntFunction<T> idGetter, DisguisePacketHandler<T> handler) {\n        registerPacketHandler(packetType, (packet, level) -> level.getEntity(idGetter.applyAsInt(packet)), handler);\n    }\n\n    public static <T extends Packet<ClientGamePacketListener>> void registerPacketHandler(Class<T> packetType, BiFunction<T, Level, Entity> entityGetter, DisguisePacketHandler<T> handler) {\n        DenizenNetworkManagerImpl.registerPacketHandler(packetType, (networkManager, packet) -> {\n            if (DisguiseCommand.disguises.isEmpty() || antiDuplicate) {\n                return packet;\n            }\n            Entity entity = entityGetter.apply(packet, networkManager.player.level());\n            if (entity == null) {\n                return packet;\n            }\n            Map<UUID, DisguiseCommand.TrackedDisguise> playerMap = DisguiseCommand.disguises.get(entity.getUUID());\n            if (playerMap == null) {\n                return packet;\n            }\n            DisguiseCommand.TrackedDisguise disguise = playerMap.get(networkManager.player.getUUID());\n            if (disguise == null) {\n                disguise = playerMap.get(null);\n            }\n            if (disguise == null || !disguise.isActive) {\n                return packet;\n            }\n            if (NMSHandler.debugPackets) {\n                DenizenNetworkManagerImpl.doPacketOutput(\"DISGUISED packet \" + packet.getClass().getName() + \" for entity \" + entity.getId() + \" to player \" + networkManager.player.getScoreboardName());\n            }\n            try {\n                return handler.handle(networkManager, packet, disguise);\n            }\n            catch (Exception e) {\n                antiDuplicate = false;\n                throw e; // \"pass it\" to the generic exception handling\n            }\n        });\n    }\n\n    @FunctionalInterface\n    public interface DisguisePacketHandler<T extends Packet<ClientGamePacketListener>> {\n\n        T handle(DenizenNetworkManagerImpl networkManager, T packet, DisguiseCommand.TrackedDisguise disguise) throws Exception;\n    }\n\n    public static ClientboundSetEntityDataPacket processEntityDataPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityDataPacket entityDataPacket, DisguiseCommand.TrackedDisguise disguise) {\n        if (entityDataPacket.id() == networkManager.player.getId()) {\n            if (!disguise.shouldFake) {\n                return entityDataPacket;\n            }\n            for (SynchedEntityData.DataValue<?> dataValue : entityDataPacket.packedItems()) {\n                if (dataValue.id() == 0) { // Entity flags\n                    List<SynchedEntityData.DataValue<?>> newData = new ArrayList<>(entityDataPacket.packedItems());\n                    newData.remove(dataValue);\n                    byte flags = (byte) dataValue.value();\n                    flags |= 0x20; // Invisible flag\n                    newData.add(PacketHelperImpl.createEntityData(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags));\n                    return new ClientboundSetEntityDataPacket(entityDataPacket.id(), newData);\n                }\n            }\n        }\n        else {\n            List<SynchedEntityData.DataValue<?>> data = ((CraftEntity) disguise.toOthers.entity.entity).getHandle().getEntityData().getNonDefaultValues();\n            return data != null ? new ClientboundSetEntityDataPacket(entityDataPacket.id(), data) : null;\n        }\n        return entityDataPacket;\n    }\n\n    public static ClientboundUpdateAttributesPacket processAttributesPacket(DenizenNetworkManagerImpl networkManager, ClientboundUpdateAttributesPacket attributesPacket, DisguiseCommand.TrackedDisguise disguise) {\n        FakeEntity fake = attributesPacket.getEntityId() == networkManager.player.getId() ? disguise.fakeToSelf : disguise.toOthers;\n        return fake == null || fake.entity.entity instanceof LivingEntity ? attributesPacket : null; // Non-living entities don't have attributes\n    }\n\n    public static ClientboundTeleportEntityPacket processTeleportPacket(DenizenNetworkManagerImpl networkManager, ClientboundTeleportEntityPacket teleportEntityPacket, DisguiseCommand.TrackedDisguise disguise) throws IllegalAccessException {\n        if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\n            PositionMoveRotation oldChange = teleportEntityPacket.change();\n            return new ClientboundTeleportEntityPacket(\n                    teleportEntityPacket.id(),\n                    new PositionMoveRotation(oldChange.position(), oldChange.deltaMovement(), EntityAttachmentHelper.normalizeAngle(oldChange.yRot() + 180), oldChange.xRot()),\n                    teleportEntityPacket.relatives(),\n                    teleportEntityPacket.onGround()\n            );\n        }\n        return sendDisguiseForPacket(networkManager, teleportEntityPacket, disguise);\n    }\n\n\n    public static ClientboundMoveEntityPacket.Rot processMoveEntityRotPacket(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket.Rot rotPacket, DisguiseCommand.TrackedDisguise disguise) {\n        if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\n            return new ClientboundMoveEntityPacket.Rot(\n                    disguise.entity.getBukkitEntity().getEntityId(),\n                    EntityAttachmentHelper.compressAngle(rotPacket.getYRot() + 180),\n                    Mth.packDegrees(rotPacket.getXRot()),\n                    rotPacket.isOnGround()\n            );\n        }\n        return sendDisguiseForPacket(networkManager, rotPacket, disguise);\n    }\n\n\n    public static ClientboundMoveEntityPacket.PosRot processMoveEntityPosRotPacket(DenizenNetworkManagerImpl networkManager, ClientboundMoveEntityPacket.PosRot posRotPacket, DisguiseCommand.TrackedDisguise disguise) {\n        if (disguise.as.getBukkitEntityType() == EntityType.ENDER_DRAGON) {\n            return new ClientboundMoveEntityPacket.PosRot(\n                    disguise.entity.getBukkitEntity().getEntityId(),\n                    posRotPacket.getXa(),\n                    posRotPacket.getYa(),\n                    posRotPacket.getZa(),\n                    EntityAttachmentHelper.compressAngle(posRotPacket.getYRot() + 180),\n                    Mth.packDegrees(posRotPacket.getXRot()),\n                    posRotPacket.isOnGround()\n            );\n        }\n        return sendDisguiseForPacket(networkManager, posRotPacket, disguise);\n    }\n\n    public static <T extends Packet<ClientGamePacketListener>> T sendDisguiseForPacket(DenizenNetworkManagerImpl networkManager, T packet, DisguiseCommand.TrackedDisguise disguise) {\n        antiDuplicate = true;\n        disguise.sendTo(List.of(new PlayerTag(networkManager.player.getUUID())));\n        antiDuplicate = false;\n        return null;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/EntityMetadataPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v26_1.Handler;\nimport com.denizenscript.denizen.nms.v26_1.helpers.PacketHelperImpl;\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.scripts.commands.entity.GlowCommand;\nimport com.denizenscript.denizen.scripts.commands.entity.InvisibleCommand;\nimport com.denizenscript.denizen.scripts.commands.entity.RenameCommand;\nimport com.denizenscript.denizen.scripts.commands.entity.SneakCommand;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.md_5.bungee.api.ChatColor;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;\nimport net.minecraft.network.syncher.SynchedEntityData;\nimport net.minecraft.world.entity.Entity;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\n\npublic class EntityMetadataPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityDataPacket.class, EntityMetadataPacketHandlers::processMetadataChangesForPacket);\n    }\n\n    public static ClientboundSetEntityDataPacket getModifiedMetadataFor(DenizenNetworkManagerImpl networkManager, ClientboundSetEntityDataPacket metadataPacket) {\n        if (!RenameCommand.hasAnyDynamicRenames() && SneakCommand.forceSetSneak.isEmpty() && InvisibleCommand.helper.noOverrides() && GlowCommand.helper.noOverrides()) {\n            return null;\n        }\n        try {\n            Entity entity = networkManager.player.level().getEntity(metadataPacket.id());\n            if (entity == null) {\n                return null; // If it doesn't exist on-server, it's definitely not relevant, so move on\n            }\n            String nameToApply = RenameCommand.getCustomNameFor(entity.getUUID(), networkManager.player.getBukkitEntity(), false);\n            Boolean forceSneak = SneakCommand.shouldSneak(entity.getUUID(), networkManager.player.getUUID());\n            Boolean isInvisible = InvisibleCommand.helper.getState(entity.getBukkitEntity(), networkManager.player.getUUID(), true);\n            Boolean isGlowing = GlowCommand.helper.getState(entity.getBukkitEntity(), networkManager.player.getUUID(), true);\n            boolean shouldModifyFlags = isInvisible != null || forceSneak != null || isGlowing != null;\n            if (nameToApply == null && !shouldModifyFlags) {\n                return null;\n            }\n            List<SynchedEntityData.DataValue<?>> data = new ArrayList<>(metadataPacket.packedItems().size());\n            Byte currentFlags = null;\n            for (SynchedEntityData.DataValue<?> dataValue : metadataPacket.packedItems()) {\n                if (dataValue.id() == 0 && shouldModifyFlags) { // 0: Entity Flags\n                    currentFlags = (Byte) dataValue.value();\n                }\n                else if (nameToApply == null || (dataValue.id() != 2 && dataValue.id() != 3)) { // 2 and 3: Custom name and custom name visible\n                    data.add(dataValue);\n                }\n            }\n            if (shouldModifyFlags) {\n                byte flags = currentFlags == null ? entity.getEntityData().get(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS) : currentFlags;\n                flags = applyEntityDataFlag(flags, forceSneak, 0x02);\n                flags = applyEntityDataFlag(flags, isInvisible, 0x20);\n                flags = applyEntityDataFlag(flags, isGlowing, 0x40);\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_FLAGS, flags));\n            }\n            if (nameToApply != null) {\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME, Optional.of(Handler.componentToNMS(FormattedTextHelper.parse(nameToApply, ChatColor.WHITE)))));\n                data.add(SynchedEntityData.DataValue.create(PacketHelperImpl.ENTITY_DATA_ACCESSOR_CUSTOM_NAME_VISIBLE, true));\n            }\n            return new ClientboundSetEntityDataPacket(metadataPacket.id(), data);\n        }\n        catch (Throwable ex) {\n            Debug.echoError(ex);\n            return null;\n        }\n    }\n\n    public static byte applyEntityDataFlag(byte currentFlags, Boolean value, int flag) {\n        if (value == null) {\n            return currentFlags;\n        }\n        return (byte) (value ? currentFlags | flag : currentFlags & ~flag);\n    }\n\n    public static Packet<ClientGamePacketListener> processMetadataChangesForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!(packet instanceof ClientboundSetEntityDataPacket entityDataPacket)) {\n            return packet;\n        }\n        ClientboundSetEntityDataPacket altPacket = getModifiedMetadataFor(networkManager, entityDataPacket);\n        if (altPacket == null) {\n            return packet;\n        }\n        return altPacket;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakeBlocksPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.FakeBlockHelper;\nimport com.denizenscript.denizen.objects.LocationTag;\nimport com.denizenscript.denizen.utilities.blocks.ChunkCoordinate;\nimport com.denizenscript.denizen.utilities.blocks.FakeBlock;\nimport com.denizenscript.denizencore.utilities.ReflectionHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.SectionPos;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.world.level.block.state.BlockState;\n\nimport java.lang.reflect.Field;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class FakeBlocksPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelChunkWithLightPacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSectionBlocksUpdatePacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundBlockUpdatePacket.class, FakeBlocksPacketHandlers::processShowFakeForPacket);\n    }\n\n    public static Field SECTIONPOS_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(\"sectionPos\", SectionPos.class);\n    public static Field OFFSETARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(\"positions\", short[].class);\n    public static Field BLOCKARRAY_MULTIBLOCKCHANGE = ReflectionHelper.getFields(ClientboundSectionBlocksUpdatePacket.class).get(\"states\", BlockState[].class);\n\n    public static Packet<ClientGamePacketListener> processShowFakeForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (FakeBlock.blocks.isEmpty()) {\n            return packet;\n        }\n        try {\n            if (packet instanceof ClientboundLevelChunkWithLightPacket) {\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID());\n                if (map == null) {\n                    return packet;\n                }\n                int chunkX = ((ClientboundLevelChunkWithLightPacket) packet).getX();\n                int chunkZ = ((ClientboundLevelChunkWithLightPacket) packet).getZ();\n                ChunkCoordinate chunkCoord = new ChunkCoordinate(chunkX, chunkZ, networkManager.player.level().getWorld().getName());\n                List<FakeBlock> blocks = FakeBlock.getFakeBlocksFor(networkManager.player.getUUID(), chunkCoord);\n                if (blocks == null || blocks.isEmpty()) {\n                    return packet;\n                }\n                ClientboundLevelChunkWithLightPacket newPacket = FakeBlockHelper.handleMapChunkPacket(networkManager.player.getBukkitEntity().getWorld(), (ClientboundLevelChunkWithLightPacket) packet, chunkX, chunkZ, blocks);\n                return newPacket;\n            }\n            else if (packet instanceof ClientboundSectionBlocksUpdatePacket sectionBlocksUpdatePacket) {\n                FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(networkManager.player.getUUID());\n                if (map == null) {\n                    return sectionBlocksUpdatePacket;\n                }\n                SectionPos coord = (SectionPos) SECTIONPOS_MULTIBLOCKCHANGE.get(sectionBlocksUpdatePacket);\n                ChunkCoordinate coordinateDenizen = new ChunkCoordinate(coord.getX(), coord.getZ(), networkManager.player.level().getWorld().getName());\n                if (!map.byChunk.containsKey(coordinateDenizen)) {\n                    return sectionBlocksUpdatePacket;\n                }\n                ClientboundSectionBlocksUpdatePacket newPacket = DenizenNetworkManagerImpl.copyPacket(sectionBlocksUpdatePacket, ClientboundSectionBlocksUpdatePacket.STREAM_CODEC);\n                LocationTag location = new LocationTag(networkManager.player.level().getWorld(), 0, 0, 0);\n                short[] originalOffsetArray = (short[])OFFSETARRAY_MULTIBLOCKCHANGE.get(newPacket);\n                BlockState[] originalDataArray = (BlockState[])BLOCKARRAY_MULTIBLOCKCHANGE.get(newPacket);\n                short[] offsetArray = Arrays.copyOf(originalOffsetArray, originalOffsetArray.length);\n                BlockState[] dataArray = Arrays.copyOf(originalDataArray, originalDataArray.length);\n                OFFSETARRAY_MULTIBLOCKCHANGE.set(newPacket, offsetArray);\n                BLOCKARRAY_MULTIBLOCKCHANGE.set(newPacket, dataArray);\n                for (int i = 0; i < offsetArray.length; i++) {\n                    short offset = offsetArray[i];\n                    BlockPos pos = coord.relativeToBlockPos(offset);\n                    location.setX(pos.getX());\n                    location.setY(pos.getY());\n                    location.setZ(pos.getZ());\n                    FakeBlock block = map.byLocation.get(location);\n                    if (block != null) {\n                        dataArray[i] = FakeBlockHelper.getNMSState(block);\n                    }\n                }\n                return newPacket;\n            }\n            else if (packet instanceof ClientboundBlockUpdatePacket) {\n                BlockPos pos = ((ClientboundBlockUpdatePacket) packet).getPos();\n                LocationTag loc = new LocationTag(networkManager.player.level().getWorld(), pos.getX(), pos.getY(), pos.getZ());\n                FakeBlock block = FakeBlock.getFakeBlockFor(networkManager.player.getUUID(), loc);\n                if (block != null) {\n                    ClientboundBlockUpdatePacket newPacket = new ClientboundBlockUpdatePacket(((ClientboundBlockUpdatePacket) packet).getPos(), FakeBlockHelper.getNMSState(block));\n                    return newPacket;\n                }\n            }\n            else if (packet instanceof ClientboundBlockChangedAckPacket) {\n                // TODO: 1.19: Can no longer determine what block this packet is for. Would have to track separately? Possibly from the inbound packet rather than the outbound one.\n                /*\n                ClientboundBlockChangedAckPacket origPack = (ClientboundBlockChangedAckPacket) packet;\n                BlockPos pos = origPack.pos();\n                LocationTag loc = new LocationTag(player.getLevel().getWorld(), pos.getX(), pos.getY(), pos.getZ());\n                FakeBlock block = FakeBlock.getFakeBlockFor(player.getUUID(), loc);\n                if (block != null) {\n                    ClientboundBlockChangedAckPacket newPacket = new ClientboundBlockChangedAckPacket(origPack.pos(), FakeBlockHelper.getNMSState(block), origPack.action(), false);\n                    oldManager.send(newPacket, genericfuturelistener);\n                    return true;\n                }*/\n            }\n        }\n        catch (Throwable ex) {\n            Debug.echoError(ex);\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakeEquipmentPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.objects.ItemTag;\nimport com.denizenscript.denizen.scripts.commands.entity.FakeEquipCommand;\nimport com.mojang.datafixers.util.Pair;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.EquipmentSlot;\nimport net.minecraft.world.entity.LivingEntity;\nimport net.minecraft.world.item.ItemStack;\nimport org.bukkit.craftbukkit.inventory.CraftItemStack;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class FakeEquipmentPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEquipmentPacket.class, FakeEquipmentPacketHandlers::processSetEquipmentPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundEntityEventPacket.class, FakeEquipmentPacketHandlers::processEntityEventPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundContainerSetContentPacket.class, FakeEquipmentPacketHandlers::processContainerSetContentPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundContainerSetSlotPacket.class, FakeEquipmentPacketHandlers::processContainerSetSlotPacket);\n    }\n\n    public static ClientboundSetEquipmentPacket processSetEquipmentPacket(DenizenNetworkManagerImpl networkManager, ClientboundSetEquipmentPacket setEquipmentPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return setEquipmentPacket;\n        }\n        Entity entity = networkManager.player.level().getEntity(setEquipmentPacket.getEntity());\n        if (entity == null) {\n            return setEquipmentPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(entity.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null) {\n            return setEquipmentPacket;\n        }\n        List<Pair<EquipmentSlot, ItemStack>> equipment = new ArrayList<>(setEquipmentPacket.getSlots());\n        for (int i = 0; i < equipment.size(); i++) {\n            Pair<EquipmentSlot, ItemStack> pair = equipment.get(i);\n            ItemStack use = switch (pair.getFirst()) {\n                case MAINHAND -> override.hand == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.hand.getItemStack());\n                case OFFHAND -> override.offhand == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.offhand.getItemStack());\n                case CHEST -> override.chest == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.chest.getItemStack());\n                case HEAD -> override.head == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.head.getItemStack());\n                case LEGS -> override.legs == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.legs.getItemStack());\n                case FEET -> override.boots == null ? pair.getSecond() : CraftItemStack.asNMSCopy(override.boots.getItemStack());\n                case BODY -> pair.getSecond(); // TODO: 1.20.6: is this actually used here? do we want to allow overriding it?\n                case SADDLE -> pair.getSecond(); // TODO: 1.21.5: same as above\n            };\n            equipment.set(i, new Pair<>(pair.getFirst(), use));\n        }\n        return new ClientboundSetEquipmentPacket(setEquipmentPacket.getEntity(), equipment);\n    }\n\n    public static Packet<ClientGamePacketListener> processEntityEventPacket(DenizenNetworkManagerImpl networkManager, ClientboundEntityEventPacket entityEventPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return entityEventPacket;\n        }\n        if (entityEventPacket.getEventId() != 55) {\n            return entityEventPacket;\n        }\n        if (!(entityEventPacket.getEntity(networkManager.player.level()) instanceof LivingEntity livingEntity)) {\n            return entityEventPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(livingEntity.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null || (override.hand == null && override.offhand == null)) {\n            return entityEventPacket;\n        }\n        ItemStack hand = override.hand != null ? CraftItemStack.asNMSCopy(override.hand.getItemStack()) : livingEntity.getMainHandItem();\n        ItemStack offhand = override.offhand != null ? CraftItemStack.asNMSCopy(override.offhand.getItemStack()) : livingEntity.getOffhandItem();\n        return new ClientboundSetEquipmentPacket(livingEntity.getId(), List.of(new Pair<>(EquipmentSlot.MAINHAND, hand), new Pair<>(EquipmentSlot.OFFHAND, offhand)));\n    }\n\n    public static ClientboundContainerSetContentPacket processContainerSetContentPacket(DenizenNetworkManagerImpl networkManager, ClientboundContainerSetContentPacket setContentPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return setContentPacket;\n        }\n        if (setContentPacket.containerId() != 0) {\n            return setContentPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(networkManager.player.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null) {\n            return setContentPacket;\n        }\n        List<ItemStack> items = setContentPacket.items();\n        if (override.head != null) {\n            items.set(5, CraftItemStack.asNMSCopy(override.head.getItemStack()));\n        }\n        if (override.chest != null) {\n            items.set(6, CraftItemStack.asNMSCopy(override.chest.getItemStack()));\n        }\n        if (override.legs != null) {\n            items.set(7, CraftItemStack.asNMSCopy(override.legs.getItemStack()));\n        }\n        if (override.boots != null) {\n            items.set(8, CraftItemStack.asNMSCopy(override.boots.getItemStack()));\n        }\n        if (override.offhand != null) {\n            items.set(45, CraftItemStack.asNMSCopy(override.offhand.getItemStack()));\n        }\n        if (override.hand != null) {\n            items.set(getMainHandSlot(networkManager.player), CraftItemStack.asNMSCopy(override.hand.getItemStack()));\n        }\n        return new ClientboundContainerSetContentPacket(setContentPacket.containerId(), setContentPacket.stateId(), items, setContentPacket.carriedItem());\n    }\n\n    public static ClientboundContainerSetSlotPacket processContainerSetSlotPacket(DenizenNetworkManagerImpl networkManager, ClientboundContainerSetSlotPacket setSlotPacket) {\n        if (FakeEquipCommand.overrides.isEmpty()) {\n            return setSlotPacket;\n        }\n        if (setSlotPacket.getContainerId() != 0) {\n            return setSlotPacket;\n        }\n        FakeEquipCommand.EquipmentOverride override = FakeEquipCommand.getOverrideFor(networkManager.player.getUUID(), networkManager.player.getBukkitEntity());\n        if (override == null) {\n            return setSlotPacket;\n        }\n        ItemTag item = switch (setSlotPacket.getSlot()) {\n            case 5 -> override.head;\n            case 6 -> override.chest;\n            case 7 -> override.legs;\n            case 8 -> override.boots;\n            case 45 -> override.offhand;\n            default -> setSlotPacket.getSlot() == getMainHandSlot(networkManager.player) ? override.hand : null;\n        };\n        if (item == null) {\n            return setSlotPacket;\n        }\n        return new ClientboundContainerSetSlotPacket(setSlotPacket.getContainerId(), setSlotPacket.getStateId(), setSlotPacket.getSlot(), CraftItemStack.asNMSCopy(item.getItemStack()));\n    }\n\n    public static int getMainHandSlot(ServerPlayer player) {\n        return player.getInventory().getSelectedSlot() + 36;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/FakePlayerPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\n/*\nimport com.denizenscript.denizen.nms.NMSHandler;\nimport com.denizenscript.denizen.nms.v1_20.impl.entities.EntityFakePlayerImpl;\nimport com.denizenscript.denizen.nms.v1_20.impl.network.handlers.DenizenNetworkManagerImpl;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;\nimport org.bukkit.Bukkit;\n\nimport java.util.List;\n*/\n\npublic class FakePlayerPacketHandlers {\n\n    public static void registerHandlers() {\n        // TODO: 1.20.2: Replace this.\n        //DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddPlayerPacket.class, FakePlayerPacketHandlers::processAddPlayerPacket);\n    }\n\n    /*\n    public static void processAddPlayerPacket(DenizenNetworkManagerImpl networkManager, ClientboundAddPlayerPacket addPlayerPacket) {\n        if (networkManager.player.level().getEntity(addPlayerPacket.getEntityId()) instanceof EntityFakePlayerImpl fakePlayer) {\n            networkManager.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, fakePlayer));\n            Bukkit.getScheduler().runTaskLater(NMSHandler.getJavaPlugin(),\n                    () -> networkManager.send(new ClientboundPlayerInfoRemovePacket(List.of(fakePlayer.getUUID()))), 5);\n        }\n    }*/\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/HiddenEntitiesPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.entity.HideEntitiesHelper;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.*;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.world.entity.Entity;\n\npublic class HiddenEntitiesPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddEntityPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        //DenizenNetworkManagerImpl.registerPacketHandler(ClientboundAddExperienceOrbPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.Rot.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.Pos.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundMoveEntityPacket.PosRot.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityDataPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSetEntityMotionPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundTeleportEntityPacket.class, HiddenEntitiesPacketHandlers::processHiddenEntitiesForPacket);\n    }\n\n    public static boolean isHidden(ServerPlayer player, Entity entity) {\n        return entity != null && HideEntitiesHelper.playerShouldHide(player.getBukkitEntity().getUniqueId(), entity.getBukkitEntity());\n    }\n\n    public static Packet<ClientGamePacketListener> processHiddenEntitiesForPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!HideEntitiesHelper.hasAnyHides()) {\n            return packet;\n        }\n        try {\n            int ider = -1;\n            Entity e = null;\n            if (packet instanceof ClientboundAddEntityPacket) {\n                ider = ((ClientboundAddEntityPacket) packet).getId();\n            }\n            //TODO: 1.21.5: check this packet list\n            /*\n            else if (packet instanceof ClientboundAddExperienceOrbPacket) {\n                ider = ((ClientboundAddExperienceOrbPacket) packet).getId();\n            }*/\n            else if (packet instanceof ClientboundMoveEntityPacket) {\n                e = ((ClientboundMoveEntityPacket) packet).getEntity(networkManager.player.level());\n            }\n            else if (packet instanceof ClientboundSetEntityDataPacket) {\n                ider = ((ClientboundSetEntityDataPacket) packet).id();\n            }\n            else if (packet instanceof ClientboundSetEntityMotionPacket) {\n                ider = ((ClientboundSetEntityMotionPacket) packet).id();\n            }\n            else if (packet instanceof ClientboundTeleportEntityPacket) {\n                ider = ((ClientboundTeleportEntityPacket) packet).id();\n            }\n            if (e == null && ider != -1) {\n                e = networkManager.player.level().getEntity(ider);\n            }\n            if (e != null) {\n                if (isHidden(networkManager.player, e)) {\n                    return null;\n                }\n            }\n        }\n        catch (Exception ex) {\n            Debug.echoError(ex);\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/HideParticlesPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.packets.HideParticles;\nimport net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;\nimport org.bukkit.Particle;\nimport org.bukkit.craftbukkit.CraftParticle;\n\nimport java.util.Set;\n\npublic class HideParticlesPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundLevelParticlesPacket.class, HideParticlesPacketHandlers::processParticlesPacket);\n    }\n\n    public static ClientboundLevelParticlesPacket processParticlesPacket(DenizenNetworkManagerImpl networkManager, ClientboundLevelParticlesPacket particlesPacket) {\n        if (HideParticles.hidden.isEmpty()) {\n            return particlesPacket;\n        }\n        Set<Particle> hidden = HideParticles.hidden.get(networkManager.player.getUUID());\n        if (hidden == null) {\n            return particlesPacket;\n        }\n        Particle bukkitParticle = CraftParticle.minecraftToBukkit(particlesPacket.getParticle().getType());\n        if (hidden.contains(bukkitParticle)) {\n            return null;\n        }\n        return particlesPacket;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/PlayerHearsSoundEventPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerHearsSoundScriptEvent;\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundSoundEntityPacket;\nimport net.minecraft.network.protocol.game.ClientboundSoundPacket;\nimport net.minecraft.world.entity.Entity;\nimport org.bukkit.Location;\n\npublic class PlayerHearsSoundEventPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSoundPacket.class, PlayerHearsSoundEventPacketHandlers::processSoundPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundSoundEntityPacket.class, PlayerHearsSoundEventPacketHandlers::processSoundPacket);\n    }\n\n    public static Packet<ClientGamePacketListener> processSoundPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!PlayerHearsSoundScriptEvent.instance.eventData.isEnabled) {\n            return packet;\n        }\n        if (packet instanceof ClientboundSoundPacket) {\n            ClientboundSoundPacket spacket = (ClientboundSoundPacket) packet;\n            return PlayerHearsSoundScriptEvent.instance.run(networkManager.player.getBukkitEntity(), spacket.getSound().value().location().getPath(), spacket.getSource().name(),\n                    false, null, new Location(networkManager.player.getBukkitEntity().getWorld(), spacket.getX(), spacket.getY(), spacket.getZ()), spacket.getVolume(), spacket.getPitch()) ? null : packet;\n        }\n        else if (packet instanceof ClientboundSoundEntityPacket) {\n            ClientboundSoundEntityPacket spacket = (ClientboundSoundEntityPacket) packet;\n            Entity entity = networkManager.player.level().getEntity(spacket.getId());\n            if (entity == null) {\n                return packet;\n            }\n            return PlayerHearsSoundScriptEvent.instance.run(networkManager.player.getBukkitEntity(), spacket.getSound().value().location().getPath(), spacket.getSource().name(),\n                    false, entity.getBukkitEntity(), null, spacket.getVolume(), spacket.getPitch()) ? null : packet;\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/handlers/packet/TablistUpdateEventPacketHandlers.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.handlers.packet;\n\nimport com.denizenscript.denizen.events.player.PlayerReceivesTablistUpdateScriptEvent;\nimport com.denizenscript.denizen.nms.v26_1.Handler;\nimport com.denizenscript.denizen.nms.v26_1.impl.ProfileEditorImpl;\nimport com.denizenscript.denizen.nms.v26_1.impl.network.handlers.DenizenNetworkManagerImpl;\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\nimport com.denizenscript.denizencore.utilities.CoreUtilities;\nimport com.denizenscript.denizencore.utilities.debugging.Debug;\nimport com.google.common.base.Joiner;\nimport com.mojang.authlib.GameProfile;\nimport com.mojang.authlib.properties.Property;\nimport net.md_5.bungee.api.ChatColor;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientGamePacketListener;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;\nimport net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;\nimport net.minecraft.world.level.GameType;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.UUID;\n\npublic class TablistUpdateEventPacketHandlers {\n\n    public static void registerHandlers() {\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoUpdatePacket.class, TablistUpdateEventPacketHandlers::processTablistPacket);\n        DenizenNetworkManagerImpl.registerPacketHandler(ClientboundPlayerInfoRemovePacket.class, TablistUpdateEventPacketHandlers::processTablistPacket);\n    }\n\n    public static boolean tablistBreakOnlyOnce = false;\n\n    // TODO: properly rebundle the packet instead of splitting it up\n    public static Packet<ClientGamePacketListener> processTablistPacket(DenizenNetworkManagerImpl networkManager, Packet<ClientGamePacketListener> packet) {\n        if (!PlayerReceivesTablistUpdateScriptEvent.instance.eventData.isEnabled) {\n            return packet;\n        }\n        if (packet instanceof ClientboundPlayerInfoUpdatePacket) {\n            ClientboundPlayerInfoUpdatePacket infoPacket = (ClientboundPlayerInfoUpdatePacket) packet;\n            String mode = \"\";\n            for (ClientboundPlayerInfoUpdatePacket.Action action : infoPacket.actions()) {\n                switch (action) {\n                    case ADD_PLAYER:\n                        mode = \"add\";\n                        break;\n                    case UPDATE_LATENCY:\n                        mode = mode.isEmpty() ? \"update_latency\" : mode + \"|update_latency\";\n                        break;\n                    case UPDATE_GAME_MODE:\n                        mode = mode.isEmpty() ? \"update_gamemode\" : mode + \"|update_gamemode\";\n                        break;\n                    case UPDATE_DISPLAY_NAME:\n                        mode = mode.isEmpty() ? \"update_display\" : mode + \"|update_display\";\n                        break;\n                    case UPDATE_LISTED:\n                        mode = mode.isEmpty() ? \"update_listed\" : mode + \"|update_listed\";\n                        break;\n                    case INITIALIZE_CHAT:\n                        mode = mode.isEmpty() ? \"initialize_chat\" : mode + \"|initialize_chat\";\n                    default:\n                        break;\n                }\n            }\n            if (mode.isEmpty()) {\n                if (!tablistBreakOnlyOnce) {\n                    tablistBreakOnlyOnce = true;\n                    Debug.echoError(\"Tablist packet processing failed: unknown action \" + Joiner.on(\", \").join(infoPacket.actions()));\n                }\n                return packet;\n            }\n            boolean isOverriding = false;\n            for (ClientboundPlayerInfoUpdatePacket.Entry update : infoPacket.entries()) {\n                GameProfile profile = update.profile();\n                String texture = null, signature = null;\n                if (profile.properties().containsKey(\"textures\")) {\n                    Property property = profile.properties().get(\"textures\").stream().findFirst().get();\n                    texture = property.value();\n                    signature = property.signature();\n                }\n                String modeText = update.gameMode() == null ? null : update.gameMode().name();\n                PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(mode, profile.id(), update.listed(), profile.name(),\n                        update.displayName() == null ? null : FormattedTextHelper.stringify(Handler.componentToSpigot(update.displayName())), modeText, texture, signature, update.latency());\n                PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data);\n                if (data.modified) {\n                    if (!isOverriding) {\n                        isOverriding = true;\n                        for (ClientboundPlayerInfoUpdatePacket.Entry priorUpdate : infoPacket.entries()) {\n                            if (priorUpdate == update) {\n                                break;\n                            }\n                            networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(priorUpdate)));\n                        }\n                    }\n                    if (!data.cancelled) {\n                        GameProfile newProfile = ProfileEditorImpl.createGameProfile(data.id, data.name, data.texture, data.signature);\n                        ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(newProfile.id(), newProfile, data.isListed, data.latency, data.gamemode == null ? null : GameType.byName(CoreUtilities.toLowerCase(data.gamemode)),\n                                data.display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(data.display, ChatColor.WHITE)), update.showHat(), update.listOrder(), update.chatSession());\n                        networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(entry)));\n                    }\n                }\n                else if (isOverriding) {\n                    networkManager.oldManager.send(ProfileEditorImpl.createInfoPacket(infoPacket.actions(), Collections.singletonList(update)));\n                }\n            }\n            return isOverriding ? null : packet;\n        }\n        else if (packet instanceof ClientboundPlayerInfoRemovePacket) {\n            ClientboundPlayerInfoRemovePacket removePacket = (ClientboundPlayerInfoRemovePacket) packet;\n            boolean modified = false;\n            List<UUID> altIds = new ArrayList<>(((ClientboundPlayerInfoRemovePacket) packet).profileIds());\n            for (UUID id : ((ClientboundPlayerInfoRemovePacket) packet).profileIds()) {\n                PlayerReceivesTablistUpdateScriptEvent.TabPacketData data = new PlayerReceivesTablistUpdateScriptEvent.TabPacketData(\"remove\", id, false, null, null, null, null, null, 0);\n                PlayerReceivesTablistUpdateScriptEvent.fire(networkManager.player.getBukkitEntity(), data);\n                if (data.modified && data.cancelled) {\n                    modified = true;\n                    altIds.remove(id);\n                }\n            }\n            if (modified) {\n                return new ClientboundPlayerInfoRemovePacket(altIds);\n            }\n        }\n        return packet;\n    }\n}\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketInResourcePackStatusImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInResourcePackStatus;\r\nimport net.minecraft.network.protocol.common.ServerboundResourcePackPacket;\r\n\r\npublic class PacketInResourcePackStatusImpl implements PacketInResourcePackStatus {\r\n\r\n    private ServerboundResourcePackPacket internal;\r\n\r\n    public PacketInResourcePackStatusImpl(ServerboundResourcePackPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public String getStatus() {\r\n        return internal.action().name();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketInSteerVehicleImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketInSteerVehicle;\r\nimport net.minecraft.network.protocol.game.ServerboundPlayerInputPacket;\r\n\r\npublic class PacketInSteerVehicleImpl implements PacketInSteerVehicle {\r\n\r\n    private ServerboundPlayerInputPacket internal;\r\n\r\n    public PacketInSteerVehicleImpl(ServerboundPlayerInputPacket internal) {\r\n        this.internal = internal;\r\n    }\r\n\r\n    @Override\r\n    public float getLeftwardInput() {\r\n        return internal.input().left() ? 1 : 0;\r\n    }\r\n\r\n    @Override\r\n    public float getForwardInput() {\r\n        return internal.input().forward() ? 1 : 0;\r\n    }\r\n\r\n    @Override\r\n    public boolean getJumpInput() {\r\n        return internal.input().jump();\r\n    }\r\n\r\n    @Override\r\n    public boolean getDismountInput() {\r\n        return internal.input().shift();\r\n    }\r\n}\r\n"
  },
  {
    "path": "v26_1/src/main/java/com/denizenscript/denizen/nms/v26_1/impl/network/packets/PacketOutChatImpl.java",
    "content": "package com.denizenscript.denizen.nms.v26_1.impl.network.packets;\r\n\r\nimport com.denizenscript.denizen.nms.interfaces.packets.PacketOutChat;\r\nimport com.denizenscript.denizen.utilities.FormattedTextHelper;\r\nimport net.md_5.bungee.chat.ComponentSerializer;\r\nimport net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;\r\nimport net.minecraft.network.protocol.game.ClientboundSystemChatPacket;\r\nimport org.bukkit.craftbukkit.util.CraftChatMessage;\r\n\r\npublic class PacketOutChatImpl extends PacketOutChat {\r\n\r\n    public ClientboundPlayerChatPacket playerPacket;\r\n    public ClientboundSystemChatPacket systemPacket;\r\n    public String message;\r\n    public String rawJson;\r\n    public boolean isOverlayActionbar;\r\n\r\n    public PacketOutChatImpl(ClientboundSystemChatPacket internal) {\r\n        systemPacket = internal;\r\n        rawJson = CraftChatMessage.toJSON(internal.content());\r\n        message = FormattedTextHelper.stringify(FormattedTextHelper.parseJson(rawJson));\r\n        isOverlayActionbar = internal.overlay();\r\n    }\r\n\r\n    public PacketOutChatImpl(ClientboundPlayerChatPacket internal) {\r\n        playerPacket = internal;\r\n        rawJson = ComponentSerializer.toString(internal.body().content());\r\n        message = FormattedTextHelper.stringify(FormattedTextHelper.parseJson(rawJson));\r\n    }\r\n\r\n    @Override\r\n    public boolean isSystem() {\r\n        return systemPacket != null;\r\n    }\r\n\r\n    @Override\r\n    public boolean isActionbar() {\r\n        return isOverlayActionbar;\r\n    }\r\n\r\n    @Override\r\n    public String getMessage() {\r\n        return message;\r\n    }\r\n\r\n    @Override\r\n    public String getRawJson() {\r\n        return rawJson;\r\n    }\r\n}\r\n"
  }
]