diff --git a/.editorconfig b/.editorconfig
index ba49e3c234..81e0f98639 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -3,3 +3,6 @@ root = true
[*]
indent_style = tab
indent_size = 4
+
+[docssrc/**]
+indent_style = space
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 16f70bdab4..d43fec2ad4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -4,7 +4,10 @@
# against bad commits.
name: build
-on: [push]
+on:
+ push:
+ pull_request:
+ types: [opened, synchronize, reopened]
jobs:
build:
@@ -19,42 +22,150 @@ jobs:
os: [ubuntu-20.04]
runs-on: ${{ matrix.os }}
steps:
- - name: checkout repository
+
+ ### Set up ###
+
+ - name: Checkout the CommandAPI repository
uses: actions/checkout@v3
- - name: setup jdk ${{ matrix.java }}
+
+ - name: Setup JDK ${{ matrix.java }}
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven
- - name: build CommandAPI using maven
- run: mvn clean install --batch-mode
- - name: CommandAPI plugin artifact
+
+ ### Compilation ###
+
+ - name: Build the CommandAPI (Bukkit+Velocity) using maven
+ run: mvn clean install --batch-mode -P Platform.Bukkit,Platform.Velocity
+
+ - name: Check NMS_Common compiles against all compatible Minecraft versions
+ run: |
+ mvn clean package -pl :commandapi-bukkit-nms-common -P Platform.Bukkit,Spigot_1_17_R1
+ mvn clean package -pl :commandapi-bukkit-nms-common -P Platform.Bukkit,Spigot_1_18_R1
+ mvn clean package -pl :commandapi-bukkit-nms-common -P Platform.Bukkit,Spigot_1_18_2_R2
+ mvn clean package -pl :commandapi-bukkit-nms-common -P Platform.Bukkit,Spigot_1_19_R1
+ mvn clean package -pl :commandapi-bukkit-nms-common -P Platform.Bukkit,Spigot_1_19_3_R2;
+
+ # - name: validate version-specific dependencies (nms-common)
+ # run: |
+ # nms_common_path="commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/target/classes/dev/jorel/commandapi/nms/NMS_Common.class"
+ # if ! command -v javap &> /dev/null
+ # then
+ # echo "javap could not be found, skipping NMS Common validation check"
+ # exit
+ # else
+ # javapcommand=$(javap -v $nms_common_path | grep -E '#[0-9]+ = Class ' | cut -c 46- | sort | grep 'org/bukkit/craftbukkit')
+ # if [[ -n "$javapcommand" ]]; then
+ # echo "NMS Common has version-specific dependencies!"
+ # echo $javapcommand
+ # exit 1
+ # else
+ # echo "NMS Common has no version-specific dependencies :)"
+ # fi
+ # fi
+
+ ### Bukkit tests across all NMS versions ###
+ # Dev note: Yes, I know I could put this under one run section, but it's a million times easier
+ # to see which version failed when it's got the version name and subsection in GitHub Actions
+
+ - name: Run Bukkit unit tests across all versions (1.19.4)
+ continue-on-error: true
+ run: mvn clean package -pl :commandapi-bukkit-test-tests -P Platform.Bukkit -Dmaven.javadoc.skip=true -P Minecraft_1_19_4
+
+ - name: Run Bukkit unit tests across all versions (1.19.2)
+ continue-on-error: true
+ run: |
+ mvn clean package -pl :commandapi-bukkit-test-tests -P Platform.Bukkit -Dmaven.javadoc.skip=true -P Minecraft_1_19_2
+ mvn clean package -pl :commandapi-bukkit-test-tests-1.18 -P Platform.Bukkit -Dmaven.javadoc.skip=true -P Minecraft_1_19_2
+
+ - name: Run Bukkit unit tests across all versions (1.18)
+ continue-on-error: true
+ run: mvn clean package -pl :commandapi-bukkit-test-tests -P Platform.Bukkit -Dmaven.javadoc.skip=true -P Minecraft_1_18
+
+ - name: Run Bukkit unit tests across all versions (1.17)
+ continue-on-error: true
+ run: mvn clean package -pl :commandapi-bukkit-test-tests -P Platform.Bukkit -Dmaven.javadoc.skip=true -P Minecraft_1_17
+
+ - name: Run Bukkit unit tests across all versions (1.16.5)
+ continue-on-error: true
+ run: mvn clean package -pl :commandapi-bukkit-test-tests -P Platform.Bukkit -Dmaven.javadoc.skip=true -P Minecraft_1_16_5
+
+ # It doesn't matter which version the Kotlin DSL uses. It should be version independent
+ - name: Run Bukkit Kotlin DSL unit tests
+ continue-on-error: true
+ run: mvn clean package -pl :commandapi-bukkit-kotlin-test -P Platform.Bukkit -Dmaven.javadoc.skip=true -P Minecraft_1_19_2
+
+ ### Upload .jar artifacts ###
+
+ - name: Upload CommandAPI (Bukkit) plugin artifact
if: ${{ runner.os == 'Linux' && matrix.java == '17' }} # Only upload artifacts built from latest java on one OS
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: CommandAPI (plugin)
path: |
- commandapi-plugin/target/CommandAPI*.jar
- !commandapi-plugin/target/*sources.jar
- !commandapi-plugin/target/*javadoc.jar
- - name: CommandAPI core artifact
- if: ${{ runner.os == 'Linux' && matrix.java == '17' }} # Only upload artifacts built from latest java on one OS
- uses: actions/upload-artifact@v2
+ commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/target/CommandAPI*.jar
+ !commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/target/*sources.jar
+ !commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/target/*javadoc.jar
+
+ ### Examples ###
+
+ - name: Build CommandAPI example Bukkit plugins in examples/ folder
+ run: cd ./examples; ./build.sh;
+
+ ### Documentation ###
+
+ - name: Run markdownlint on all documentation Markdown documents
+ uses: DavidAnson/markdownlint-cli2-action@v8
with:
- name: CommandAPI (core)
- path: |
- commandapi-core/target/CommandAPI*.jar
- !commandapi-core/target/*sources.jar
- !commandapi-core/target/*javadoc.jar
- - name: CommandAPI shade artifact
- if: ${{ runner.os == 'Linux' && matrix.java == '17' }} # Only upload artifacts built from latest java on one OS
- uses: actions/upload-artifact@v2
+ globs: 'docssrc/src/*.md'
+
+ - name: Build the documentation with mdBook
+ run: |
+ cd docssrc
+ curl -LJO https://github.com/JorelAli/mdBook/releases/download/v0.4.21-fa6/mdbook-fa6 && chmod +x mdbook-fa6
+ mkdir -p mdbook-linkcheck && cd "$_" && curl -L https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/latest/download/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -o mdbook-linkcheck.zip && unzip "$_" && chmod +x mdbook-linkcheck && export PATH=$PWD:$PATH && cd ..
+ ./mdbook-fa6 build
+
+ ### Save PR information for 'SonarAnalyze'
+ - name: Save PR number to file
+ if: github.event_name == 'pull_request' && ${{ matrix.os }} == 'ubuntu-latest' && ${{ matrix.java_version }} == '17'
+ run: echo ${{ github.event.number }} > PR_NUMBER.txt
+ - name: Archive PR number
+ if: github.event_name == 'pull_request' && ${{ matrix.os }} == 'ubuntu-latest' && ${{ matrix.java_version }} == '17'
+ uses: actions/upload-artifact@v3
with:
- name: CommandAPI (shade)
- path: |
- commandapi-shade/target/CommandAPI*.jar
- !commandapi-shade/target/*sources.jar
- !commandapi-shade/target/*javadoc.jar
- - name: build CommandAPI examples in examples/
- run: for folder in examples/*/; do ( cd $folder; mvn clean package ); done
+ name: PR_NUMBER
+ path: PR_NUMBER.txt
+
+ snapshot-deploy:
+ needs: build # Only run if "build" succeeds
+ runs-on: ubuntu-latest
+ if: github.repository == 'JorelAli/CommandAPI' && github.ref == 'refs/heads/dev/dev'
+
+ steps:
+ - name: Checkout the CommandAPI repository
+ uses: actions/checkout@v3
+
+ - name: Setup JDK 17
+ uses: actions/setup-java@v3
+ with:
+ distribution: temurin
+ server-id: ossrh # Needs to match the id in the main pom.xml file
+ server-username: MAVEN_USERNAME
+ server-password: MAVEN_PASSWORD
+ java-version: 17
+ cache: maven
+
+ - name: Deploy snapshot version of the CommandAPI (Bukkit+Velocity) to the Sonatype snapshot repository
+ run: |
+ if mvn help:evaluate -Dexpression=project.version -q -DforceStdout | grep -E -q '\-SNAPSHOT$'; then
+ echo "SNAPSHOT version detected: $(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)"
+ mvn deploy --batch-mode -P Platform.Bukkit,Platform.Velocity -DskipTests=true
+ else
+ echo "Version is not a SNAPSHOT version, not deploying to Sonatype Snapshot repo"
+ fi
+ env:
+ MAVEN_USERNAME: ${{ secrets.OSS_SONATYPE_USERNAME }}
+ MAVEN_PASSWORD: ${{ secrets.OSS_SONATYPE_PASSWORD }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index df84a7bc72..c5cead236c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-.idea/**
+**/.idea/
*.iml
build-works/
libs/
@@ -7,9 +7,10 @@ bin
.project
.settings
target
-logs/latest.log
-commandapi-plugin/dependency-reduced-pom.xml
-commandapi-shade/dependency-reduced-pom.xml
+build
+**/logs/*.log
+**/logs/*.log.gz
.vscode
-commandapi-plugin-test/command_registration.json
-commandapi-plugin-test/logs/*
\ No newline at end of file
+.DS_Store
+docssrc/src/.markdownlint-cli2.yaml
+.gradle
\ No newline at end of file
diff --git a/Doxyfile b/Doxyfile
index 411d2b2a5f..97866a27e5 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -38,7 +38,7 @@ PROJECT_NAME = CommandAPI
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = 8.8.0
+PROJECT_NUMBER = 9.0.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -877,7 +877,8 @@ WARN_LOGFILE =
INPUT = README.md \
commandapi-core\src\main\java \
..\I-Al-Istannen\brigadier\src\main\java \
- commandapi-annotations\src\main\java
+ commandapi-annotations\src\main\java \
+ commandapi-platforms\commandapi-bukkit\commandapi-bukkit-core\src\main\java
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/README.md b/README.md
index 57836058bc..c9d7a86133 100644
--- a/README.md
+++ b/README.md
@@ -47,21 +47,21 @@ The list of what version of the CommandAPI you'll need to run on a specific vers
| **1.13.x** | v1.0 - 5.12, 8.3.0 - 8.8.0 | 8.8.0 | 16 |
| **1.14.1, 1.14.2** | v2.0 - 5.12, 8.3.0 - 8.8.0 | 8.8.0 | 16 |
| **1.14.3, 1.14.4** | v2.1 - 5.12, 8.3.0 - 8.8.0 | 8.8.0 | 16 |
-| **1.15.x** | v2.3a - 5.12, 8.3.0 - 8.8.0 | 8.8.0 | 16 |
-| **1.16.1** | v3.0 - 5.12, 8.3.0 - 8.8.0 | 8.8.0 | 16 |
-| **1.16.2** | v4.0 - 5.12, 8.3.0 - 8.8.0 | 8.8.0 | 16 |
-| **1.16.3** | v4.2 - 5.12, 8.3.0 - 8.8.0 | 8.8.0 | 16 |
-| **1.16.4** | v5.2 - 5.12, 8.3.0 - 8.8.0 | 8.8.0 | 16 |
-| **1.16.5** | v5.7 - 7.0.0, 8.3.0 - 8.8.0 | 8.8.0 | 16 |
-| **1.17** | 6.0.x - 8.8.0 | 8.8.0 | 16 |
-| **1.17.1** | 6.1.x - 8.8.0 | 8.8.0 | 16 |
-| **1.18, 1.18.1** | 6.5.2 - 8.8.0 | 8.8.0 | 16 |
-| **1.18.2** | 6.5.4 - 8.8.0 | 8.8.0 | 16 |
-| **1.19** | 8.3.0 - 8.8.0 | 8.8.0 | 16 |
-| **1.19.1** | 8.5.0 - 8.8.0 | 8.8.0 | 16 |
-| **1.19.2** | 8.5.1 - 8.8.0 | 8.8.0 | 16 |
-| **1.19.3** | 8.7.0 - 8.8.0 | 8.8.0 | 16 |
-| **1.19.4** | 8.8.0 | 8.8.0 | 16 |
+| **1.15.x** | v2.3a - 5.12, 8.3.0 - 9.0.0 | 9.0.0 | 16 |
+| **1.16.1** | v3.0 - 5.12, 8.3.0 - 9.0.0 | 9.0.0 | 16 |
+| **1.16.2** | v4.0 - 5.12, 8.3.0 - 9.0.0 | 9.0.0 | 16 |
+| **1.16.3** | v4.2 - 5.12, 8.3.0 - 9.0.0 | 9.0.0 | 16 |
+| **1.16.4** | v5.2 - 5.12, 8.3.0 - 9.0.0 | 9.0.0 | 16 |
+| **1.16.5** | v5.7 - 7.0.0, 8.3.0 - 9.0.0 | 9.0.0 | 16 |
+| **1.17** | 6.0.x - 9.0.0 | 9.0.0 | 16 |
+| **1.17.1** | 6.1.x - 9.0.0 | 9.0.0 | 16 |
+| **1.18, 1.18.1** | 6.5.2 - 9.0.0 | 9.0.0 | 16 |
+| **1.18.2** | 6.5.4 - 9.0.0 | 9.0.0 | 16 |
+| **1.19** | 8.3.0 - 9.0.0 | 9.0.0 | 16 |
+| **1.19.1** | 8.5.0 - 9.0.0 | 9.0.0 | 16 |
+| **1.19.2** | 8.5.1 - 9.0.0 | 9.0.0 | 16 |
+| **1.19.3** | 8.7.0 - 9.0.0 | 9.0.0 | 16 |
+| **1.19.4** | 8.8.0 - 9.0.0 | 9.0.0 | 16 |
-----
@@ -98,8 +98,8 @@ This project provides an API to help Bukkit/Spigot developers use the Minecraft
Still not convinced? In addition to all of the above, the CommandAPI also provides:
- **Built-in command converter** - Convert other plugin commands into `/execute`-compatible ones - no code required!
-- **Compile-time annotation framework** - Don't like writing lots of code with builders? You don't have to!
- **Tree-structure command registration** - Like Brigadier's code format? We've got you covered with `CommandTree`
+- **Kotlin DSL** - Prefer writing plugins in Kotlin? The CommandAPI has an optional Kotlin DSL just for you
- **Powerful suggestion generation** - Generate new suggestions for each argument, or add to existing suggestions
- **Safe suggestion generation** - The CommandAPI offers compile-time type safety for specific arguments
- **Precise permission support** - Apply permissions to specific arguments - you need perms to even _see_ the argument
@@ -345,13 +345,9 @@ This is the current roadmap for the CommandAPI (as of 2nd November 2022):
- **Future:**
- **Optional arguments**
-
- One of the most requested features: adding support for optional arguments! This isn't a simple task, but development has begun into looking at how optional arguments can be incorporated into the CommandAPI.
-
**Argument conflict detection**
- The CommandAPI simply uses the Brigadier system under the hood. This system is prone to _argument conflicts_, which is where certain arguments are given priority over other arguments. (For example "hello" and "123" are both valid string arguments, but if you have a command that has a string argument or an integer argument, Brigadier may ignore the integer argument). In this update, the CommandAPI will try to spot potential conflicts and add a warning in the console. The research required for this is also required in order to implement optional arguments (which is not coming out in this release).
+ The CommandAPI simply uses the Brigadier system under the hood. This system is prone to _argument conflicts_, which is where certain arguments are given priority over other arguments. (For example "hello" and "123" are both valid string arguments, but if you have a command that has a string argument or an integer argument, Brigadier may ignore the integer argument). In this update, the CommandAPI will try to spot potential conflicts and add a warning in the console.
**'True' custom arguments and server-side argument implementations**
@@ -370,6 +366,87 @@ This is the current roadmap for the CommandAPI (as of 2nd November 2022):
+
+
9.0.0
+
April 2023
+
+ ⚠️ This version is incompatible with any plugin that used the CommandAPI version 8.X.X or below! (See documentation for more information)
+ New features:
+
+
https://github.com/JorelAli/CommandAPI/issues/360, https://github.com/JorelAli/CommandAPI/pull/369 Made executor methods now use CommandArguments to allow for accessing arguments by name instead of index
https://github.com/JorelAli/CommandAPI/pull/388 Added new hook-paper-reload config option to toggle whether the CommandAPI hooks into /minecraft:reload
+
Reworked the implementation of ItemArgument, so the ItemStack count is correctly reflected and ItemMeta is properly assigned
+
Made the TeamArgument return a Team instead of a String
+
Made the ObjectiveArgument return a Objective instead of a String
+
https://github.com/JorelAli/CommandAPI/pull/391 Made the CommandAPI only complain about commands registered in a plugin.yml if this plugin.yml belongs to the plugin calling the CommandAPI
+
https://github.com/JorelAli/CommandAPI/issues/422 Added a way to access the raw command a player typed from the executor
+
https://github.com/JorelAli/CommandAPI/issues/431 Added a way to access more info to construct lists for the ListArgumentBuilder
+
Added support for sidebar team colors using an enum for ScoreboardSlot
+
+ Kotlin DSL changes:
+
+
Implemented resulting executors
+
Implemented the FunctionArgument
+
Several improvements for the CommandAPICommand DSL
+
+ Bug fixes:
+
+
Fixed commandapi-preprocessor appearing in the plugin and shaded jar file
+
https://github.com/JorelAli/CommandAPI/issues/390 Fixed .executesNative()'s CommandSender's getLocation() method returning the wrong pitch and yaw
+
Fixed tags showing up in the BiomeArgument when they shouldn't have been doing so
+
Fixed LocationArgument with BLOCK_POSITION not returning locations in unloaded chunks
+
+ Testing and validation:
+
+
Created the testing matrix to perform multi-Minecraft-version testing
+ Bugs found (and fixed) as a result of the testing matrix:
+
+
Fixed IntegerRangeArgument and FloatRangeArgument not working on Minecraft 1.16.4 and 1.16.5
+
Fixed RecipeArgument not working on Minecraft 1.17
+
Fixed TeamArgument not working on Minecraft 1.17
+
Fixed AdventureChatArgument not working on Minecraft 1.17
+
Fixed commands with no executors not being caught by the CommandAPI
+
Fixed ParticleArgument producing "Invalid particle data type" warnings on Minecraft 1.16.5 and below
+
Fixed FunctionArgument not working on Minecraft 1.17.x and 1.18.x
+
Fixed NamespacedKeyArgument not working on Minecraft 1.18
+
+
Integrated the CommandAPI repository with SonarCloud to identify bugs and improve the internal code
+ Bugs found (and fixed) as a result of using SonarCloud:
+
+
Fixed the FunctionArgument not correctly retrieving datapack (function) tags in 1.17+
+
+
Added some code coverage reports to identify how well tested the CommandAPI is, and what code paths need attention to during development
+ Issues found (and fixed) as a result of using code coverage reports:
+
+
Removed some redundant vibration particle handling code that would never be run under any circumstances
+
+
+ Documentation changes:
+
+
https://github.com/JorelAli/CommandAPI/issues/384 Fixed various particle data not being documented for the ParticleArgument documentation page
Improved mobile support for the CommandAPI home page
+
Added the CommandAPI's Modrinth link to the CommandAPI home page
+
Dropped support for Minecraft 1.13 - 1.14.4. Please use an older version of the CommandAPI, or raise an issue on GitHub to bring back support for these versions
+
+ GitHub Actions changes:
+
+
Fixed NodeJS 12 deprecation warnings
+
Added markdownlint to verify that the documentation adheres to suitable Markdown standards
+
Fixed building the CommandAPI example projects not failing if they failed to compile
+
Added the CommandAPI documentation to GitHub Actions
+
Added deployment of snapshot builds to GitHub Actions
+
+
8.8.0
March 2023
diff --git a/annotations_spec_doc.md b/annotations_spec_doc.md
new file mode 100644
index 0000000000..4d8dc52ac0
--- /dev/null
+++ b/annotations_spec_doc.md
@@ -0,0 +1,371 @@
+# Annotations API specification
+
+This document outlines the new (post 9.0.0) annotations API specification. This describes what will be implemented, what features will be present, how the new annotations will look and other important stuff
+
+## Motivation
+
+The current annotations API was a proof-of-concept showcasing how compile-time command declaration is a feasible task that will work for the CommandAPI. Since the introduction of the annotations API, the CommandAPI has added more and more increasingly more complex features that have not been ported to the annotations API. As such, the annotations API is lacking in important features that make the CommandAPI what it is today.
+
+-----
+
+## An example of a simple command (old annotation system)
+
+Notably, this annotation system suffers from requiring everything to be static!
+
+```java
+@Command("warp")
+public class WarpCommand {
+ // List of warp names and their locations
+ static Map warps = new HashMap<>();
+
+ @Default
+ public static void warp(CommandSender sender) {
+ sender.sendMessage("--- Warp help ---");
+ sender.sendMessage("/warp - Show this help");
+ sender.sendMessage("/warp - Teleport to ");
+ sender.sendMessage("/warp create - Creates a warp at your current location");
+ }
+
+ @Default
+ public static void warp(Player player, @AStringArgument String warpName) {
+ player.teleport(warps.get(warpName));
+ }
+
+ @Subcommand("create")
+ @Permission("warps.create")
+ public static void createWarp(Player player, @AStringArgument String warpName) {
+ warps.put(warpName, player.getLocation());
+ }
+}
+```
+
+-----
+
+## Features
+
+Features that have been implemented so far:
+
+- Declaring a command in a file (one command per file)
+- Declaring a 'default' implementation (e.g. `/warp`)
+- Declaring subcommands, such as `/warp create `
+- Permissions with permission nodes
+- Permissions for operators (via `@NeedsOp`)
+- Help, via `@Help`
+
+Features that exist in the CommandAPI and haven't been implemented so far:
+
+- Requirements (more powerful permissions)
+- Suggestions:
+ - With tooltips
+ - Asynchronous
+- Custom arguments
+- Other argument properties, including:
+ - Argument listing
+ - Optional arguments
+
+-----
+
+## Known changes from 9.0.0
+
+- TeamArgument, ObjectiveArgument class changes
+
+-----
+
+## Issues
+
+- https://github.com/JorelAli/CommandAPI/issues/318
+- https://github.com/JorelAli/CommandAPI/issues/292
+- https://github.com/JorelAli/CommandAPI/issues/263
+- https://github.com/JorelAli/CommandAPI/issues/201
+- https://github.com/JorelAli/CommandAPI/issues/163
+
+## Issue draft requirements
+
+### 292 - multiple command executors
+
+Issue https://github.com/JorelAli/CommandAPI/issues/292 requests multiple command executors. The CommandAPI can support multiple command executors via the various `.executesPlayer`, `.executesConsole` etc. methods.
+
+The annotation API could accommodate that using overloading using a different parameter type:
+
+```patch
+@Command("warp")
+public class WarpCommand {
+ // List of warp names and their locations
+ Map warps = new HashMap<>();
+
+ @Default
+ public void warp(Player player, @AStringArgument String warpName) {
+ player.teleport(warps.get(warpName));
+ }
+
++ @Default
++ public void warp(ConsoleCommandSender console, @AStringArgument String warpName) {
++ console.sendMessage("This command can't be run from the console!");
++ }
+}
+```
+
+For multiple executors, it may be possible to use the `ExecutorType` like this:
+
+```patch
+@Command("warp")
+public class WarpCommand {
+ // List of warp names and their locations
+ Map warps = new HashMap<>();
+
+ @Default
++ public void warp(@Senders({ ExecutorType.PLAYER, ExecutorType.ENTITY }) CommandSender sender, @AStringArgument String warpName) {
++ ((Entity) sender).teleport(warps.get(warpName));
+ }
+}
+```
+
+-----
+
+## Must have requirements
+
+- Commands that don't need the `static` modifier on their methods
+
+## Main spec
+
+- An example of a new command using this system can be found [here](https://github.com/JorelAli/CommandAPI/blob/dev/annotations/commandapi-annotations/src/test/java/HordeCommand2.java).
+
+### Commands
+
+Commands consist of an (ideally) public class with the `@Command` annotation. The `@Command` annotation specifies the command's name. We'll call this the "command class".
+
+### Command class "global" arguments
+
+Inside the command class, class field members can be specified with argument annotations, for example:
+
+```java
+@Command("mycommand")
+public class MyCommand {
+
+ @AStringArgument
+ String name;
+
+ // ...
+
+}
+```
+
+These arguments are arguments that are present for all commands in the class. Arguments are accessed in declaration order, so the following will only allow `/mycommand `, and _not_ `/mycommand `:
+
+```java
+@Command("mycommand")
+public class MyCommand {
+
+ @AStringArgument
+ String name;
+
+ @AIntegerArgument
+ int value;
+
+ // ...
+
+}
+```
+
+Due to the nature of command class arguments, the declaration location does not matter when it comes to subcommands. The following two code blocks will yield the same command `/mycommand subcommand`:
+
+```java
+@Command("mycommand")
+public class MyCommand {
+
+ @AStringArgument
+ String name;
+
+ @AIntegerArgument
+ int value;
+
+ @Subcommand("subcommand")
+ class Subcommand {
+
+ // Executor
+
+ }
+}
+```
+
+```java
+@Command("mycommand")
+public class MyCommand {
+
+ @AStringArgument
+ String name;
+
+ @Subcommand("subcommand")
+ class Subcommand {
+
+ // Executor
+
+ }
+
+ @AIntegerArgument
+ int value;
+}
+```
+
+### Subcommand executors
+
+In the `dev/annotations` example, subcommand executors were implemented using `@Subcommand` on the method itself. I think it would be better to use a different annotation to avoid misleading syntax. For example, the following allows `/mycommand subcommand`:
+
+```java
+@Command("mycommand")
+public class MyCommand {
+
+ @AStringArgument
+ String name;
+
+ @Subcommand("subcommand")
+ class Subcommand {
+
+ @Executes
+ public void myMethod(Player player) {
+ // ...
+ }
+
+ }
+}
+```
+
+Per-method arguments are still valid, allowing `/mycommand subcommand `:
+
+```java
+@Command("mycommand")
+public class MyCommand {
+
+ @AStringArgument
+ String name;
+
+ @Subcommand("subcommand")
+ class Subcommand {
+
+ @Executes
+ public void myMethod(Player player, @AIntegerArgument int value) {
+ // ...
+ }
+
+ }
+}
+```
+
+### Subcommand non-class subcommands
+
+It may be desirable to add subcommands without creating a giant tree. This can be done by applying (a repeatable) `@Subcommand` annotation to the executable method. This example shows `/mycommand subcommand subsubcommand `:
+
+```java
+@Command("mycommand")
+public class MyCommand {
+
+ @AStringArgument
+ String name;
+
+ @Subcommand("subcommand")
+ class Subcommand {
+
+ @Subcommand("subsubcommand")
+ @Executes
+ public void myMethod(Player player, @AIntegerArgument int value) {
+ // ...
+ }
+
+ }
+}
+```
+
+This could also be implemented on the class-level:
+
+```java
+@Command("mycommand")
+public class MyCommand {
+
+ @AStringArgument
+ String name;
+
+ @Subcommand("subcommand")
+ @Subcommand("subsubcommand")
+ class Subcommand {
+
+ @Executes
+ public void myMethod(Player player, @AIntegerArgument int value) {
+ // ...
+ }
+
+ }
+}
+```
+
+### Arguments with different names to their parameters
+
+**Currently not planned unless demand requires this feature.**
+
+### Suggestions
+
+Since suggestions are implemented as functional interfaces, we can simply bind functional interface implementations to corresponding arguments via class references.
+
+Arguments with suggestions requires `@Suggests`, and classes that implement suggestions requires `@Suggestion`. These "pairs" will be checked at compile time. Since we can observe all matching pairs (each `@Suggests` should have a corresponding `@Suggestion`), we can also provide compiler warnings for unused suggestions.
+
+The compiler should error if `@Suggestion` does not implement the correct type.
+
+This command represents `/mycommand `, and the suggestions for `` will be `Player1`, `Player2` and `Player3`:
+
+```java
+@Command("mycommand")
+public class MyCommand {
+
+ @AStringArgument
+ @Suggests(ListOfNames.class)
+ String name;
+
+ @Executes
+ public void myExecutor(Player player) {
+ // ...
+ }
+
+ @Suggestion
+ class ListOfNames implements Supplier {
+
+ @Override
+ public ArgumentSuggestions get() {
+ return ArgumentSuggestions.strings("Player1", "Player2", "Player3");
+ }
+
+ }
+}
+```
+
+### Subcommands from another class
+
+See https://github.com/JorelAli/CommandAPI/issues/318#issuecomment-1205781476.
+
+Subcommands need not be declared in the command class. They can be declared in their own class. To link these together, the CommandAPI can use a `@ExternalSubcommand` annotation (actual name TBD) which takes in the class reference of the subcommand. The external subcommand must have its corresponding `@Subcommand` declaration on the class level. The external subcommand class _must_ inherit the parent command in order to inherit global class variables. For example, `Subcommand` can access `name` because it extends `MyCommand`:
+
+```java
+@Command("mycommand")
+public class MyCommand {
+
+ @AStringArgument
+ String name;
+
+ @ExternalSubcommand(Subcommand.class)
+ Subcommand subcommand;
+}
+```
+
+```java
+@Subcommand("subcommand")
+public class Subcommand extends MyCommand {
+
+ @Executes
+ public void myMethod(Player player, @AIntegerArgument int value) {
+ // ...
+ }
+
+}
+```
+
+### Aliases
+
+Aliases should no longer be provided using the `@Alias` annotation - it gets too messy. Consider adding it as a parameter to the `@Command` annotation.
diff --git a/build b/build
deleted file mode 100644
index 76d52b1d74..0000000000
--- a/build
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-mvn clean install -DcreateChecksum=true
\ No newline at end of file
diff --git a/commandapi-annotations/pom.xml b/commandapi-annotations/pom.xml
index f8390feeeb..8367bdf02b 100644
--- a/commandapi-annotations/pom.xml
+++ b/commandapi-annotations/pom.xml
@@ -18,7 +18,7 @@
dev.jorelcommandapi
- 8.8.0
+ 9.0.0commandapi-annotations
@@ -33,7 +33,7 @@
dev.jorel
- commandapi-core
+ commandapi-bukkit-core${project.version}
diff --git a/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/Annotations.java b/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/Annotations.java
index 45e2cc5016..52ae792456 100644
--- a/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/Annotations.java
+++ b/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/Annotations.java
@@ -66,7 +66,6 @@
import dev.jorel.commandapi.annotations.arguments.AEnchantmentArgument;
import dev.jorel.commandapi.annotations.arguments.AEntitySelectorArgument;
import dev.jorel.commandapi.annotations.arguments.AEntityTypeArgument;
-import dev.jorel.commandapi.annotations.arguments.AEnvironmentArgument;
import dev.jorel.commandapi.annotations.arguments.AFloatArgument;
import dev.jorel.commandapi.annotations.arguments.AFloatRangeArgument;
import dev.jorel.commandapi.annotations.arguments.AFunctionArgument;
@@ -102,10 +101,8 @@
import dev.jorel.commandapi.annotations.arguments.AUUIDArgument;
import dev.jorel.commandapi.annotations.arguments.AWorldArgument;
import dev.jorel.commandapi.annotations.arguments.Primitive;
-import dev.jorel.commandapi.arguments.EntitySelector;
import dev.jorel.commandapi.arguments.LocationType;
import dev.jorel.commandapi.arguments.MultiLiteralArgument;
-import dev.jorel.commandapi.arguments.ScoreHolderArgument.ScoreHolderType;
/**
* The main annotation processor for annotation-based arguments
@@ -113,20 +110,22 @@
@AutoService(Processor.class)
public class Annotations extends AbstractProcessor {
- private final Class>[] ARGUMENT_ANNOTATIONS = new Class>[] { AAdvancementArgument.class,
- AAdventureChatArgument.class, AAdventureChatComponentArgument.class, AAngleArgument.class,
- AAxisArgument.class, ABiomeArgument.class, ABlockPredicateArgument.class, ABlockStateArgument.class,
- ABooleanArgument.class, AChatArgument.class, AChatColorArgument.class, AChatComponentArgument.class,
- ADoubleArgument.class, AEnchantmentArgument.class, AEntitySelectorArgument.class, AEntityTypeArgument.class,
- AEnvironmentArgument.class, AFloatArgument.class, AFloatRangeArgument.class, AFunctionArgument.class,
- AGreedyStringArgument.class, AIntegerArgument.class, AIntegerRangeArgument.class, AItemStackArgument.class,
- AItemStackPredicateArgument.class, ALiteralArgument.class, ALocation2DArgument.class,
- ALocationArgument.class, ALongArgument.class, ALootTableArgument.class, AMathOperationArgument.class,
- AMultiLiteralArgument.class, ANamespacedKeyArgument.class, ANBTCompoundArgument.class, AObjectiveArgument.class,
- AObjectiveCriteriaArgument.class, AOfflinePlayerArgument.class, AParticleArgument.class, APlayerArgument.class,
- APotionEffectArgument.class, ARecipeArgument.class, ARotationArgument.class, AScoreboardSlotArgument.class,
- AScoreHolderArgument.class, ASoundArgument.class, AStringArgument.class, ATeamArgument.class,
- ATextArgument.class, ATimeArgument.class, AUUIDArgument.class, AWorldArgument.class};
+ private static final Class>[] ARGUMENT_ANNOTATIONS = new Class>[] { AAdvancementArgument.class,
+ AAdventureChatArgument.class, AAdventureChatComponentArgument.class, AAngleArgument.class,
+ AAxisArgument.class, ABiomeArgument.class, ABlockPredicateArgument.class, ABlockStateArgument.class,
+ ABooleanArgument.class, AChatArgument.class, AChatColorArgument.class, AChatComponentArgument.class,
+ ADoubleArgument.class, AEnchantmentArgument.class, AEntitySelectorArgument.ManyEntities.class,
+ AEntitySelectorArgument.ManyPlayers.class, AEntitySelectorArgument.OneEntity.class,
+ AEntitySelectorArgument.OnePlayer.class, AEntityTypeArgument.class,
+ AFloatArgument.class, AFloatRangeArgument.class, AFunctionArgument.class,
+ AGreedyStringArgument.class, AIntegerArgument.class, AIntegerRangeArgument.class, AItemStackArgument.class,
+ AItemStackPredicateArgument.class, ALiteralArgument.class, ALocation2DArgument.class,
+ ALocationArgument.class, ALongArgument.class, ALootTableArgument.class, AMathOperationArgument.class,
+ AMultiLiteralArgument.class, ANamespacedKeyArgument.class, ANBTCompoundArgument.class, AObjectiveArgument.class,
+ AObjectiveCriteriaArgument.class, AOfflinePlayerArgument.class, AParticleArgument.class, APlayerArgument.class,
+ APotionEffectArgument.class, ARecipeArgument.class, ARotationArgument.class, AScoreboardSlotArgument.class,
+ AScoreHolderArgument.Single.class, AScoreHolderArgument.Multiple.class, ASoundArgument.class, AStringArgument.class, ATeamArgument.class,
+ ATextArgument.class, ATimeArgument.class, AUUIDArgument.class, AWorldArgument.class};
// List of stuff we can deal with
@Override
@@ -146,7 +145,7 @@ public SourceVersion getSupportedSourceVersion() {
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(Command.class)) {
try {
- processCommand(roundEnv, element);
+ processCommand(element);
} catch (IOException e) {
e.printStackTrace();
}
@@ -185,18 +184,22 @@ private SortedSet calculateImports(Element classElement) {
imports.add(CommandPermission.class.getCanonicalName());
}
- if(methodElement instanceof ExecutableElement) {
- ExecutableElement method = (ExecutableElement) methodElement;
+ if(methodElement instanceof ExecutableElement method) {
for(VariableElement parameter : method.getParameters()) {
- if(getArgument(parameter) != null) {
- imports.addAll(Arrays.asList(getPrimitive(getArgument(parameter)).value()));
- imports.add("dev.jorel.commandapi.arguments." + getArgument(parameter).annotationType().getSimpleName().substring(1));
- if(getArgument(parameter) instanceof ALocationArgument || getArgument(parameter) instanceof ALocation2DArgument) {
+ Annotation argument = getArgument(parameter);
+ if(argument != null) {
+ imports.addAll(Arrays.asList(getPrimitive(argument).value()));
+
+ if(argument.annotationType().getEnclosingClass() == null) {
+ // Normal arguments
+ imports.add("dev.jorel.commandapi.arguments." + argument.annotationType().getSimpleName().substring(1));
+ } else {
+ // Nested arguments, like EntitySelectorArgument
+ imports.add("dev.jorel.commandapi.arguments." + argument.annotationType().getEnclosingClass().getSimpleName().substring(1));
+ }
+
+ if(argument instanceof ALocationArgument || argument instanceof ALocation2DArgument) {
imports.add(LocationType.class.getCanonicalName());
- } else if(getArgument(parameter) instanceof AScoreHolderArgument) {
- imports.add(ScoreHolderType.class.getCanonicalName());
- } else if(getArgument(parameter) instanceof AEntitySelectorArgument) {
- imports.add(EntitySelector.class.getCanonicalName());
}
}
@@ -218,10 +221,9 @@ private void emitImports(PrintWriter out, Element classElement) {
String previousImport = "";
for(String import_ : calculateImports(classElement)) {
// Separate different packages
- if(previousImport.contains(".") && import_.contains(".")) {
- if(!previousImport.substring(0, previousImport.indexOf(".")).equals(import_.substring(0, import_.indexOf(".")))) {
+ if(previousImport.contains(".") && import_.contains(".") &&
+ !previousImport.substring(0, previousImport.indexOf(".")).equals(import_.substring(0, import_.indexOf(".")))) {
out.println();
- }
}
// Don't import stuff like "String"
if(!import_.contains(".") || import_.contains("<")) {
@@ -304,22 +306,6 @@ private Map emitArgumentsAndGenerateArgu
Primitive primitive = getPrimitive(argumentAnnotation);
if(primitive.value().length == 1) {
argumentMapping.put(i - 1, primitive.value()[0]);
- } else {
- if(argumentAnnotation instanceof AEntitySelectorArgument argument) {
- switch(argument.value()) {
- case MANY_ENTITIES -> argumentMapping.put(i - 1, primitive.value()[0]);
- case MANY_PLAYERS -> argumentMapping.put(i - 1, primitive.value()[1]);
- case ONE_ENTITY -> argumentMapping.put(i - 1, primitive.value()[2]);
- case ONE_PLAYER -> argumentMapping.put(i - 1, primitive.value()[3]);
- default -> throw new IllegalArgumentException("Unexpected value: " + argument.value());
- }
- } else if (argumentAnnotation instanceof AScoreHolderArgument argument) {
- switch(argument.value()) {
- case MULTIPLE -> argumentMapping.put(i - 1, primitive.value()[0]);
- case SINGLE -> argumentMapping.put(i - 1, primitive.value()[1]);
- default -> throw new IllegalArgumentException("Unexpected value: " + argument.value());
- }
- }
}
}
@@ -366,9 +352,9 @@ private int emitExecutes(PrintWriter out, Map argumentMapping,
} else {
out.print(simpleFromQualified(fromArgumentMap));
}
- out.print(") args[");
+ out.print(") args.get(");
out.print(i);
- out.print("]");
+ out.print(")");
}
//populate stuff here
@@ -430,7 +416,7 @@ private void emitHelp(PrintWriter out, Element classElement, int indent) {
}
}
- private void processCommand(RoundEnvironment roundEnv, Element classElement) throws IOException {
+ private void processCommand(Element classElement) throws IOException {
TypeElement commandClass = (TypeElement) classElement;
JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(commandClass.getQualifiedName() + "$Command");
int indent = 0;
@@ -493,7 +479,15 @@ private void processCommand(RoundEnvironment roundEnv, El
private void emitArgument(PrintWriter out, T argumentAnnotation, VariableElement parameter, int indent) {
out.print(indent(indent) + ".withArguments(new ");
// We're assuming that the name of the argument MUST be "A" + the same name
- out.print(argumentAnnotation.annotationType().getSimpleName().substring(1));
+ if(argumentAnnotation.annotationType().getEnclosingClass() == null) {
+ // Normal arguments
+ out.print(argumentAnnotation.annotationType().getSimpleName().substring(1));
+ } else {
+ // Nested arguments, like EntitySelectorArgument
+ out.print(argumentAnnotation.annotationType().getEnclosingClass().getSimpleName().substring(1));
+ out.print(".");
+ out.print(argumentAnnotation.annotationType().getSimpleName());
+ }
// Node name
if(argumentAnnotation instanceof AMultiLiteralArgument || argumentAnnotation instanceof ALiteralArgument) {
@@ -522,10 +516,6 @@ else if(argumentAnnotation instanceof ALocation2DArgument argument) {
out.print(", " + LocationType.class.getSimpleName() + "." + argument.value().toString());
} else if(argumentAnnotation instanceof ALocationArgument argument) {
out.print(", " + LocationType.class.getSimpleName() + "." + argument.value().toString());
- } else if(argumentAnnotation instanceof AEntitySelectorArgument argument) {
- out.print(", " + EntitySelector.class.getSimpleName() + "." + argument.value().toString());
- } else if(argumentAnnotation instanceof AScoreHolderArgument argument) {
- out.print(", " + ScoreHolderType.class.getSimpleName() + "." + argument.value().toString());
} else if(argumentAnnotation instanceof AMultiLiteralArgument argument) {
out.print(Arrays.stream(argument.value()).map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
} else if(argumentAnnotation instanceof ALiteralArgument argument) {
@@ -558,11 +548,22 @@ private Primitive getPrimitive(T annotation) {
private T getArgument(VariableElement tMirror) {
for(AnnotationMirror mirror : tMirror.getAnnotationMirrors()) {
if(isArgument(mirror)) {
+ T argumentAnnotation = null;
+ String mirrorCanonicalName = mirror.getAnnotationType().toString();
try {
- return tMirror.getAnnotationsByType((Class) Class.forName(mirror.getAnnotationType().toString()))[0];
+ argumentAnnotation = tMirror.getAnnotationsByType((Class) Class.forName(mirrorCanonicalName))[0];
} catch (ClassNotFoundException e) {
- e.printStackTrace();
+ // We might be in a nested class. Let's try accessing that
+ try {
+ // Replace final . with $
+ mirrorCanonicalName = mirrorCanonicalName.substring(0, mirrorCanonicalName.lastIndexOf(".")) + "$" + mirrorCanonicalName.substring(mirrorCanonicalName.lastIndexOf(".") + 1);
+ argumentAnnotation = tMirror.getAnnotationsByType((Class) Class.forName(mirrorCanonicalName))[0];
+ } catch (ClassNotFoundException e1) {
+ e1.printStackTrace();
+ }
}
+
+ return argumentAnnotation;
}
}
return null;
diff --git a/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AEntitySelectorArgument.java b/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AEntitySelectorArgument.java
index f458c55eca..29599d2bff 100644
--- a/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AEntitySelectorArgument.java
+++ b/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AEntitySelectorArgument.java
@@ -25,24 +25,41 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import dev.jorel.commandapi.arguments.EntitySelector;
import dev.jorel.commandapi.arguments.EntitySelectorArgument;
-/**
- * Annotation equivalent of the {@link EntitySelectorArgument}
- */
-@Primitive({ "java.util.Collection", // MANY_ENTITIES
- "java.util.Collection", // MANY_PLAYERS
- "org.bukkit.entity.Entity", // ONE_ENTITY
- "org.bukkit.entity.Player" // ONE_PLAYER
-})
-@Retention(RetentionPolicy.SOURCE)
-@Target(ElementType.PARAMETER)
-public @interface AEntitySelectorArgument {
+public interface AEntitySelectorArgument {
+
+ /**
+ * Annotation equivalent of the {@link EntitySelectorArgument} for one entity
+ */
+ @Primitive("org.bukkit.entity.Entity")
+ @Retention(RetentionPolicy.SOURCE)
+ @Target(ElementType.PARAMETER)
+ public @interface OneEntity { }
+
+ /**
+ * Annotation equivalent of the {@link EntitySelectorArgument} for one player
+ */
+ @Primitive("org.bukkit.entity.Player")
+ @Retention(RetentionPolicy.SOURCE)
+ @Target(ElementType.PARAMETER)
+ public @interface OnePlayer { }
/**
- * @return the entity selector for this argument
+ * Annotation equivalent of the {@link EntitySelectorArgument} for many entities
*/
- EntitySelector value() default EntitySelector.ONE_ENTITY;
+ @Primitive("java.util.Collection")
+ @Retention(RetentionPolicy.SOURCE)
+ @Target(ElementType.PARAMETER)
+ public @interface ManyEntities { }
+
+ /**
+ * Annotation equivalent of the {@link EntitySelectorArgument} for many players
+ */
+ @Primitive("java.util.Collection")
+ @Retention(RetentionPolicy.SOURCE)
+ @Target(ElementType.PARAMETER)
+ public @interface ManyPlayers { }
+
}
diff --git a/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AEnvironmentArgument.java b/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AEnvironmentArgument.java
deleted file mode 100644
index 10f362366c..0000000000
--- a/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AEnvironmentArgument.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*******************************************************************************
- * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
- * the Software, and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
- * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
- * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
- * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- *******************************************************************************/
-package dev.jorel.commandapi.annotations.arguments;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import dev.jorel.commandapi.arguments.EnvironmentArgument;
-
-/**
- * Annotation equivalent of the {@link EnvironmentArgument}
- */
-@Primitive("org.bukkit.World.Environment")
-@Retention(RetentionPolicy.SOURCE)
-@Target(ElementType.PARAMETER)
-public @interface AEnvironmentArgument {
-}
diff --git a/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AScoreHolderArgument.java b/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AScoreHolderArgument.java
index d9b1962a96..ece0483464 100644
--- a/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AScoreHolderArgument.java
+++ b/commandapi-annotations/src/main/java/dev/jorel/commandapi/annotations/arguments/AScoreHolderArgument.java
@@ -26,22 +26,26 @@
import java.lang.annotation.Target;
import dev.jorel.commandapi.arguments.ScoreHolderArgument;
-import dev.jorel.commandapi.arguments.ScoreHolderArgument.ScoreHolderType;
/**
* Annotation equivalent of the {@link ScoreHolderArgument}
*/
-@Primitive({ "java.util.Collection", // ScoreHolderType.MULTIPLE
- "String" // ScoreHolderType.SINGLE
-})
-@Retention(RetentionPolicy.SOURCE)
-@Target(ElementType.PARAMETER)
-public @interface AScoreHolderArgument {
+public interface AScoreHolderArgument {
/**
- * @return whether this argument represents a single score holder or a
- * collection of score holders
+ * Annotation equivalent of the {@link ScoreHolderArgument} for a single score holder
*/
- ScoreHolderType value() default ScoreHolderType.SINGLE;
+ @Primitive("String")
+ @Retention(RetentionPolicy.SOURCE)
+ @Target(ElementType.PARAMETER)
+ public @interface Single { }
+
+ /**
+ * Annotation equivalent of the {@link ScoreHolderArgument} for multiple score holders
+ */
+ @Primitive("java.util.Collection")
+ @Retention(RetentionPolicy.SOURCE)
+ @Target(ElementType.PARAMETER)
+ public @interface Multiple { }
}
diff --git a/commandapi-annotations/src/test/java/Test2Command.java b/commandapi-annotations/src/test/java/Test2Command.java
index c0e3d39c24..09ad907f26 100644
--- a/commandapi-annotations/src/test/java/Test2Command.java
+++ b/commandapi-annotations/src/test/java/Test2Command.java
@@ -40,9 +40,7 @@
import dev.jorel.commandapi.annotations.arguments.ALongArgument;
import dev.jorel.commandapi.annotations.arguments.AMultiLiteralArgument;
import dev.jorel.commandapi.annotations.arguments.AScoreHolderArgument;
-import dev.jorel.commandapi.arguments.EntitySelector;
import dev.jorel.commandapi.arguments.LocationType;
-import dev.jorel.commandapi.arguments.ScoreHolderArgument.ScoreHolderType;
/* ANCHOR: teleport_command */
@Command("teleport")
@@ -112,13 +110,14 @@ public static void command(CommandSender sender,
/* ANCHOR_END: literal_arguments */
+// TODO: EntitySelectorArgument and ScoreHolder argument have changed - these need updating in the documentation
/* ANCHOR: other_arguments */
@Default
public static void command(CommandSender sender,
@ALocationArgument(LocationType.BLOCK_POSITION) Location location,
@ALocation2DArgument(LocationType.PRECISE_POSITION) Location location2d,
- @AEntitySelectorArgument(EntitySelector.MANY_ENTITIES) Collection entities,
- @AScoreHolderArgument(ScoreHolderType.MULTIPLE) Collection scoreHolders
+ @AEntitySelectorArgument.ManyEntities Collection entities,
+ @AScoreHolderArgument.Multiple Collection scoreHolders
) {
// Command implementation here
}
diff --git a/commandapi-annotations/src/test/java/WarpCommand.java b/commandapi-annotations/src/test/java/WarpCommand.java
index 8a71cd7a44..eba3b10538 100644
--- a/commandapi-annotations/src/test/java/WarpCommand.java
+++ b/commandapi-annotations/src/test/java/WarpCommand.java
@@ -124,7 +124,7 @@ class Examples {
warps.keySet().toArray(new String[0])
)))
.executesPlayer((player, args) -> {
- player.teleport(warps.get((String) args[0]));
+ player.teleport(warps.get((String) args.get(0)));
})
.register();
@@ -135,7 +135,7 @@ class Examples {
.withPermission("warps.create")
.withArguments(new StringArgument("warpname"))
.executesPlayer((player, args) -> {
- warps.put((String) args[0], player.getLocation());
+ warps.put((String) args.get(0), player.getLocation());
})
)
.register();
diff --git a/commandapi-codecov/pom.xml b/commandapi-codecov/pom.xml
new file mode 100644
index 0000000000..97c6570f52
--- /dev/null
+++ b/commandapi-codecov/pom.xml
@@ -0,0 +1,170 @@
+
+
+ 4.0.0
+
+ dev.jorel
+ commandapi
+ 9.0.0
+
+
+ commandapi-codecov
+
+
+ true
+
+
+
+
+ dev.jorel
+ commandapi-core
+ ${project.version}
+
+
+ dev.jorel
+ commandapi-bukkit-core
+ ${project.version}
+
+
+
+
+ dev.jorel
+ commandapi-bukkit-test-tests
+ ${project.version}
+ test
+
+
+
+
+
+
+ dev.jorel
+ commandapi-bukkit-nms-common
+ ${project.version}
+ mojang-mapped
+
+
+ dev.jorel
+ commandapi-bukkit-1.19-common
+ ${project.version}
+ mojang-mapped
+
+
+ dev.jorel
+ commandapi-bukkit-1.17-common
+ ${project.version}
+ mojang-mapped
+
+
+
+
+ dev.jorel
+ commandapi-bukkit-1.19.4
+ ${project.version}
+ mojang-mapped
+
+
+ dev.jorel
+ commandapi-bukkit-1.19.3
+ ${project.version}
+ mojang-mapped
+
+
+ dev.jorel
+ commandapi-bukkit-1.19.1
+ ${project.version}
+ mojang-mapped
+
+
+ dev.jorel
+ commandapi-bukkit-1.19
+ ${project.version}
+ mojang-mapped
+
+
+ dev.jorel
+ commandapi-bukkit-1.18.2
+ ${project.version}
+ mojang-mapped
+
+
+ dev.jorel
+ commandapi-bukkit-1.18
+ ${project.version}
+ mojang-mapped
+
+
+ dev.jorel
+ commandapi-bukkit-1.17
+ ${project.version}
+ mojang-mapped
+
+
+ dev.jorel
+ commandapi-bukkit-1.17.1
+ ${project.version}
+ mojang-mapped
+
+
+
+
+ dev.jorel
+ commandapi-bukkit-1.16.5
+ ${project.version}
+
+
+ dev.jorel
+ commandapi-bukkit-1.16.4
+ ${project.version}
+
+
+ dev.jorel
+ commandapi-bukkit-1.16.2
+ ${project.version}
+
+
+ dev.jorel
+ commandapi-bukkit-1.16.1
+ ${project.version}
+
+
+ dev.jorel
+ commandapi-bukkit-1.15
+ ${project.version}
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ report-aggregate
+ verify
+
+ report-aggregate
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/commandapi-core/pom.xml b/commandapi-core/pom.xml
index 8be641dc6b..e747e718b1 100644
--- a/commandapi-core/pom.xml
+++ b/commandapi-core/pom.xml
@@ -19,7 +19,7 @@
commandapidev.jorel
- 8.8.0
+ 9.0.04.0.0
@@ -35,25 +35,10 @@
https://repo.codemc.org/repository/maven-public/default
-
- papermc
- https://papermc.io/repo/repository/maven-public/
-
-
- net.kyori
- adventure-platform-bukkit
- 4.1.0
- test
-
-
- io.papermc.paper
- paper-api
- ${paper.version}
- provided
-
+
com.mojangbrigadier
@@ -67,28 +52,25 @@
provided
- dev.jorel
- commandapi-preprocessor
- ${project.version}
+ org.apache.logging.log4j
+ log4j-api
+ 2.19.0
+ provided
-
+
- de.tr7zw
- item-nbt-api
- 2.10.0
- test
-
-
- org.jetbrains.kotlin
- kotlin-stdlib
- 1.7.10
- test
+ dev.jorel
+ commandapi-preprocessor
+ ${project.version}
+ provided
+
+
org.apache.maven.pluginsmaven-compiler-plugin
@@ -102,20 +84,23 @@
- org.jetbrains.kotlin
- kotlin-maven-plugin
- 1.7.10
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ default-prepare-agent
+
+ prepare-agent
+
+
+
- test-compile
+ report
+ verify
- test-compile
+ report
-
-
- ${project.basedir}/src/test/kotlin
-
-
diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java
new file mode 100644
index 0000000000..579f7301b6
--- /dev/null
+++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java
@@ -0,0 +1,71 @@
+package dev.jorel.commandapi;
+
+import dev.jorel.commandapi.arguments.AbstractArgument;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a base class for arguments, allowing them to behave as tree nodes in
+ * a {@link AbstractCommandTree}
+ * @param The class extending this class, used as the return type for chain calls
+ * @param The CommandSender class used by the class extending this class
+ */
+public abstract class AbstractArgumentTree,
+ Argument extends AbstractArgument, ?, Argument, CommandSender>, CommandSender> extends Executable {
+
+ final List> arguments = new ArrayList<>();
+ final Argument argument;
+
+ /**
+ * Instantiates an {@link AbstractArgumentTree}. This can only be called if the class
+ * that extends this is an {@link AbstractArgument}
+ */
+ @SuppressWarnings("unchecked")
+ protected AbstractArgumentTree() {
+ if (this instanceof AbstractArgument, ?, Argument, CommandSender>) {
+ this.argument = (Argument) this;
+ } else {
+ throw new IllegalArgumentException("Implicit inherited constructor must be from Argument");
+ }
+ }
+
+ /**
+ * Instantiates an {@link AbstractArgumentTree} with an underlying argument.
+ *
+ * @param argument the argument to use as the underlying argument for this
+ * argument tree
+ */
+ protected AbstractArgumentTree(final Argument argument) {
+ this.argument = argument;
+ // Copy the executor in case any executions were defined on the argument
+ this.executor = argument.executor;
+ }
+
+ /**
+ * Create a child branch on this node
+ *
+ * @param tree The child branch
+ * @return this tree node
+ */
+ public Impl then(final AbstractArgumentTree, Argument, CommandSender> tree) {
+ this.arguments.add(tree);
+ return instance();
+ }
+
+ List> getExecutions() {
+ List> executions = new ArrayList<>();
+ // If this is executable, add its execution
+ if (this.executor.hasAnyExecutors()) {
+ executions.add(new Execution<>(List.of(this.argument), this.executor));
+ }
+ // Add all executions from all arguments
+ for (AbstractArgumentTree, Argument, CommandSender> tree : arguments) {
+ for (Execution execution : tree.getExecutions()) {
+ // Prepend this argument to the arguments of the executions
+ executions.add(execution.prependedBy(this.argument));
+ }
+ }
+ return executions;
+ }
+}
diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java
new file mode 100644
index 0000000000..401626597a
--- /dev/null
+++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java
@@ -0,0 +1,372 @@
+/*******************************************************************************
+ * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *******************************************************************************/
+package dev.jorel.commandapi;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Predicate;
+
+import dev.jorel.commandapi.arguments.AbstractArgument;
+import dev.jorel.commandapi.arguments.GreedyArgument;
+import dev.jorel.commandapi.exceptions.GreedyArgumentException;
+import dev.jorel.commandapi.exceptions.MissingCommandExecutorException;
+import dev.jorel.commandapi.exceptions.OptionalArgumentException;
+
+/**
+ * A builder used to create commands to be registered by the CommandAPI.
+ *
+ * @param The class extending this class, used as the return type for chain calls
+ * @param The implementation of AbstractArgument used by the class extending this class
+ * @param The CommandSender class used by the class extending this class
+ */
+public abstract class AbstractCommandAPICommand,
+ Argument extends AbstractArgument, ?, Argument, CommandSender>, CommandSender> extends ExecutableCommand {
+
+ protected List arguments = new ArrayList<>();
+ protected List subcommands = new ArrayList<>();
+ protected boolean isConverted;
+
+ /**
+ * Creates a new command builder
+ *
+ * @param commandName The name of the command to create
+ */
+ protected AbstractCommandAPICommand(String commandName) {
+ super(commandName);
+ this.isConverted = false;
+ }
+
+ /**
+ * Creates a new Command builder
+ *
+ * @param metaData The metadata of the command to create
+ */
+ protected AbstractCommandAPICommand(CommandMetaData metaData) {
+ super(metaData);
+ this.isConverted = false;
+ }
+
+ /**
+ * Appends the arguments to the current command builder
+ *
+ * @param args A List that represents the arguments that this
+ * command can accept
+ * @return this command builder
+ */
+ public Impl withArguments(List args) {
+ this.arguments.addAll(args);
+ return instance();
+ }
+
+ /**
+ * Appends the argument(s) to the current command builder
+ *
+ * @param args Arguments that this command can accept
+ * @return this command builder
+ */
+ @SafeVarargs
+ public final Impl withArguments(Argument... args) {
+ this.arguments.addAll(Arrays.asList(args));
+ return instance();
+ }
+
+ /**
+ * Appends the optional arguments to the current command builder.
+ *
+ * This also calls {@link AbstractArgument#setOptional(boolean)} on each argument to make sure they are optional
+ *
+ * @param args A List that represents the arguments that this
+ * command can accept
+ * @return this command builder
+ */
+ public Impl withOptionalArguments(List args) {
+ for (Argument argument : args) {
+ argument.setOptional(true);
+ this.arguments.add(argument);
+ }
+ return instance();
+ }
+
+ /**
+ * Appends the optional arguments to the current command builder.
+ *
+ * This also calls {@link AbstractArgument#setOptional(boolean)} on each argument to make sure they are optional
+ *
+ * @param args Arguments that this command can accept
+ * @return this command builder
+ */
+ @SafeVarargs
+ public final Impl withOptionalArguments(Argument... args) {
+ for (Argument argument : args) {
+ argument.setOptional(true);
+ this.arguments.add(argument);
+ }
+ return instance();
+ }
+
+ /**
+ * Adds a subcommand to this command builder
+ *
+ * @param subcommand the subcommand to add as a child of this command
+ * @return this command builder
+ */
+ public Impl withSubcommand(Impl subcommand) {
+ this.subcommands.add(subcommand);
+ return instance();
+ }
+
+ /**
+ * Adds subcommands to this command builder
+ *
+ * @param subcommands the subcommands to add as children of this command
+ * @return this command builder
+ */
+ public Impl withSubcommands(@SuppressWarnings("unchecked") Impl... subcommands) {
+ this.subcommands.addAll(Arrays.asList(subcommands));
+ return instance();
+ }
+
+ /**
+ * Returns the list of arguments that this command has
+ *
+ * @return the list of arguments that this command has
+ */
+ public List getArguments() {
+ return arguments;
+ }
+
+ /**
+ * Sets the arguments that this command has
+ *
+ * @param args the arguments that this command has
+ */
+ public void setArguments(List args) {
+ this.arguments = args;
+ }
+
+ /**
+ * Returns the list of subcommands that this command has
+ *
+ * @return the list of subcommands that this command has
+ */
+ public List getSubcommands() {
+ return subcommands;
+ }
+
+ /**
+ * Sets the list of subcommands that this command has
+ *
+ * @param subcommands the list of subcommands that this command has
+ */
+ public void setSubcommands(List subcommands) {
+ this.subcommands = subcommands;
+ }
+
+ /**
+ * Returns whether this command is an automatically converted command
+ *
+ * @return whether this command is an automatically converted command
+ */
+ public boolean isConverted() {
+ return isConverted;
+ }
+
+ /**
+ * Sets a command as "converted". This tells the CommandAPI that this command
+ * was converted by the CommandAPI's Converter. This should not be used outside
+ * of the CommandAPI's internal API
+ *
+ * @param isConverted whether this command is converted or not
+ * @return this command builder
+ */
+ Impl setConverted(boolean isConverted) {
+ this.isConverted = isConverted;
+ return instance();
+ }
+
+ // Expands subcommands into arguments. This method should be static (it
+ // shouldn't be accessing/depending on any of the contents of the current class instance)
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private static , Argument extends AbstractArgument, ?, Argument, CommandSender>, CommandSender>
+ void flatten(Impl rootCommand, List prevArguments, Impl subcommand) {
+ // Get the list of literals represented by the current subcommand. This
+ // includes the subcommand's name and any aliases for this subcommand
+ String[] literals = new String[subcommand.meta.aliases.length + 1];
+ literals[0] = subcommand.meta.commandName;
+ System.arraycopy(subcommand.meta.aliases, 0, literals, 1, subcommand.meta.aliases.length);
+
+ // Create a MultiLiteralArgument using the subcommand information
+ Argument literal = (Argument) CommandAPIHandler.getInstance().getPlatform().newConcreteMultiLiteralArgument(literals);
+
+ literal.withPermission(subcommand.meta.permission)
+ .withRequirement((Predicate) subcommand.meta.requirements)
+ .setListed(false);
+
+ prevArguments.add(literal);
+
+ if (subcommand.executor.hasAnyExecutors()) {
+ // Create the new command. The new command:
+ // - starts at the root command node
+ // - has all of the previously declared arguments (i.e. not itself)
+ // - uses the subcommand's executor
+ // - has no subcommands(?)
+ // Honestly, if you're asking how or why any of this works, I don't
+ // know because I just trialled random code until it started working
+ rootCommand.arguments = prevArguments;
+ rootCommand.withArguments(subcommand.arguments);
+ rootCommand.executor = subcommand.executor;
+ rootCommand.subcommands = new ArrayList<>();
+ rootCommand.register();
+ }
+
+ for (Impl subsubcommand : subcommand.getSubcommands()) {
+ flatten(rootCommand, new ArrayList<>(prevArguments), subsubcommand);
+ }
+ }
+
+ boolean hasAnyExecutors() {
+ if (this.executor.hasAnyExecutors()) {
+ return true;
+ } else {
+ for(Impl subcommand : this.subcommands) {
+ if (subcommand.hasAnyExecutors()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void checkHasExecutors() {
+ if(!hasAnyExecutors()) {
+ throw new MissingCommandExecutorException(this.meta.commandName);
+ }
+ }
+
+ @Override
+ public void register() {
+ if (!CommandAPI.canRegister()) {
+ CommandAPI.logWarning("Command /" + meta.commandName + " is being registered after the server had loaded. Undefined behavior ahead!");
+ }
+
+ @SuppressWarnings("unchecked")
+ Argument[] argumentsArray = (Argument[]) (arguments == null ? new AbstractArgument[0] : arguments.toArray(AbstractArgument[]::new));
+
+ // Check GreedyArgument constraints
+ checkGreedyArgumentConstraints(argumentsArray);
+ checkHasExecutors();
+
+ // Assign the command's permissions to arguments if the arguments don't already
+ // have one
+ for (Argument argument : argumentsArray) {
+ if (argument.getArgumentPermission() == null) {
+ argument.withPermission(meta.permission);
+ }
+ }
+
+ if (executor.hasAnyExecutors()) {
+ // Need to cast handler to the right CommandSender type so that argumentsArray and executor are accepted
+ @SuppressWarnings("unchecked")
+ CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance();
+
+ // Create a List that is used to register optional arguments
+ for (Argument[] args : getArgumentsToRegister(argumentsArray)) {
+ handler.register(meta, args, executor, isConverted);
+ }
+ }
+
+ // Convert subcommands into multiliteral arguments
+ for (Impl subcommand : this.subcommands) {
+ flatten(this.copy(), new ArrayList<>(), subcommand);
+ }
+ }
+
+ // Checks that greedy arguments don't have any other arguments at the end,
+ // and only zero or one greedy argument is present in an array of arguments
+ private void checkGreedyArgumentConstraints(Argument[] argumentsArray) {
+ for (int i = 0; i < argumentsArray.length; i++) {
+ // If we've seen a greedy argument that isn't at the end, then that
+ // also covers the case of seeing more than one greedy argument, as
+ // if there are more than one greedy arguments, one of them must not
+ // be at the end!
+ if (argumentsArray[i] instanceof GreedyArgument && i != argumentsArray.length - 1) {
+ throw new GreedyArgumentException(argumentsArray);
+ }
+ }
+ }
+
+ public Impl copy() {
+ Impl command = newConcreteCommandAPICommand(new CommandMetaData<>(this.meta));
+ command.arguments = new ArrayList<>(this.arguments);
+ command.subcommands = new ArrayList<>(this.subcommands);
+ command.isConverted = this.isConverted;
+ return command;
+ }
+
+ protected abstract Impl newConcreteCommandAPICommand(CommandMetaData metaData);
+
+ private List getArgumentsToRegister(Argument[] argumentsArray) {
+ List argumentsToRegister = new ArrayList<>();
+ List currentCommand = new ArrayList<>();
+
+ Iterator argumentIterator = List.of(argumentsArray).iterator();
+
+ // Collect all required arguments, adding them as a command once finding the first optional
+ while(argumentIterator.hasNext()) {
+ Argument next = argumentIterator.next();
+ if(next.isOptional()) {
+ argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0]));
+ currentCommand.addAll(unpackCombinedArguments(next));
+ break;
+ }
+ currentCommand.addAll(unpackCombinedArguments(next));
+ }
+
+ // Collect the optional arguments, adding each one as a valid command
+ while (argumentIterator.hasNext()) {
+ Argument next = argumentIterator.next();
+ if(!next.isOptional()) {
+ throw new OptionalArgumentException(meta.commandName); // non-optional argument after optional
+ }
+ argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0]));
+ currentCommand.addAll(unpackCombinedArguments(next));
+ }
+
+ // All the arguments expanded, also handles when there are no optional arguments
+ argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0]));
+ return argumentsToRegister;
+ }
+
+ private List unpackCombinedArguments(Argument argument) {
+ if (!argument.hasCombinedArguments()) {
+ return List.of(argument);
+ }
+ List combinedArguments = new ArrayList<>();
+ combinedArguments.add(argument);
+ for (Argument subArgument : argument.getCombinedArguments()) {
+ subArgument.copyPermissionsAndRequirements(argument);
+ combinedArguments.addAll(unpackCombinedArguments(subArgument));
+ }
+ return combinedArguments;
+ }
+}
diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java
new file mode 100644
index 0000000000..4e7da91431
--- /dev/null
+++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java
@@ -0,0 +1,54 @@
+package dev.jorel.commandapi;
+
+import dev.jorel.commandapi.arguments.AbstractArgument;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is the root node for creating a command as a tree
+ * @param The class extending this class, used as the return type for chain calls
+ * @param The implementation of AbstractArgument being used by this class
+ * @param The CommandSender class used by the class extending this class
+ */
+public abstract class AbstractCommandTree,
+ Argument extends AbstractArgument, ?, Argument, CommandSender>, CommandSender> extends ExecutableCommand {
+
+ private final List> arguments = new ArrayList<>();
+
+ /**
+ * Creates a main root node for a command tree with a given command name
+ *
+ * @param commandName The name of the command to create
+ */
+ protected AbstractCommandTree(final String commandName) {
+ super(commandName);
+ }
+
+ /**
+ * Create a child branch on the tree
+ *
+ * @param tree the child node
+ * @return this root node
+ */
+ public Impl then(final AbstractArgumentTree, Argument, CommandSender> tree) {
+ this.arguments.add(tree);
+ return instance();
+ }
+
+ /**
+ * Registers the command
+ */
+ public void register() {
+ List> executions = new ArrayList<>();
+ if (this.executor.hasAnyExecutors()) {
+ executions.add(new Execution<>(List.of(), this.executor));
+ }
+ for (AbstractArgumentTree, Argument, CommandSender> tree : arguments) {
+ executions.addAll(tree.getExecutions());
+ }
+ for (Execution execution : executions) {
+ execution.register(this.meta);
+ }
+ }
+}
diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/ArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/ArgumentTree.java
deleted file mode 100644
index ef7c84f318..0000000000
--- a/commandapi-core/src/main/java/dev/jorel/commandapi/ArgumentTree.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package dev.jorel.commandapi;
-
-import dev.jorel.commandapi.arguments.Argument;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * This is a base class for arguments, allowing them to behave as tree nodes in
- * a {@link CommandTree}
- */
-public class ArgumentTree extends Executable {
-
- final List arguments = new ArrayList<>();
- final Argument> argument;
-
- /**
- * Instantiates an {@link ArgumentTree}. This can only be called if the class
- * that extends this is an {@link Argument}
- */
- protected ArgumentTree() {
- if (!(this instanceof Argument> argument)) {
- throw new IllegalArgumentException("Implicit inherited constructor must be from Argument");
- }
- this.argument = argument;
- }
-
- /**
- * Instantiates an {@link ArgumentTree} with an underlying argument.
- *
- * @param argument the argument to use as the underlying argument for this
- * argument tree
- */
- public ArgumentTree(final Argument> argument) {
- this.argument = argument;
- // Copy the executor in case any executions were defined on the argument
- this.executor = argument.executor;
- }
-
- /**
- * Create a child branch on this node
- *
- * @param tree The child branch
- * @return this tree node
- */
- public ArgumentTree then(final ArgumentTree tree) {
- this.arguments.add(tree);
- return this;
- }
-
- List getExecutions() {
- List executions = new ArrayList<>();
- // If this is executable, add its execution
- if (this.executor.hasAnyExecutors()) {
- executions.add(new Execution(Arrays.asList(this.argument), this.executor));
- }
- // Add all executions from all arguments
- for (ArgumentTree tree : arguments) {
- for (Execution execution : tree.getExecutions()) {
- // Prepend this argument to the arguments of the executions
- executions.add(execution.prependedBy(this.argument));
- }
- }
- return executions;
- }
-
-}
diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java b/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java
index a677d00d46..92b89151d6 100644
--- a/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java
+++ b/commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java
@@ -20,12 +20,6 @@
*******************************************************************************/
package dev.jorel.commandapi;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.BiPredicate;
-
-import org.bukkit.command.CommandSender;
-
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.RedirectModifier;
@@ -35,9 +29,13 @@
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.tree.RootCommandNode;
+import dev.jorel.commandapi.arguments.AbstractArgument;
+import dev.jorel.commandapi.arguments.Literal;
+import dev.jorel.commandapi.commandsenders.AbstractCommandSender;
-import dev.jorel.commandapi.arguments.Argument;
-import dev.jorel.commandapi.arguments.LiteralArgument;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiPredicate;
/**
* The Brigadier class is used to access some of the internals of the CommandAPI
@@ -59,7 +57,7 @@ private Brigadier() {
* @return The CommandAPI's internal CommandDispatcher instance
*/
public static CommandDispatcher getCommandDispatcher() {
- return CommandAPIHandler.getInstance().DISPATCHER;
+ return CommandAPIHandler.getInstance().getPlatform().getBrigadierDispatcher();
}
/**
@@ -82,9 +80,10 @@ public static RootCommandNode getRootNode() {
* @param literalArgument the LiteralArgument to convert from
* @return a LiteralArgumentBuilder that represents the literal
*/
- public static LiteralArgumentBuilder fromLiteralArgument(LiteralArgument literalArgument) {
- return CommandAPIHandler.getInstance().getLiteralArgumentBuilderArgument(literalArgument.getLiteral(),
- literalArgument.getArgumentPermission(), literalArgument.getRequirements());
+ public static >
+ LiteralArgumentBuilder fromLiteralArgument(Literal literalArgument) {
+ CommandAPIHandler, CommandSender, ?> handler = (CommandAPIHandler, CommandSender, ?>) CommandAPIHandler.getInstance();
+ return handler.getLiteralArgumentBuilderArgument(literalArgument.getLiteral(), literalArgument.instance().getArgumentPermission(), literalArgument.instance().getRequirements());
}
/**
@@ -103,9 +102,10 @@ public static LiteralArgumentBuilder fromLiteralArgument(LiteralArgument literal
* @param args the arguments that the sender has filled in
* @return a RedirectModifier that encapsulates the provided predicate
*/
- public static RedirectModifier fromPredicate(BiPredicate predicate, List args) {
+ public static >
+ RedirectModifier fromPredicate(BiPredicate predicate, List args) {
return cmdCtx -> {
- if (predicate.test(getBukkitCommandSenderFromContext(cmdCtx), parseArguments(cmdCtx, args))) {
+ if (predicate.test(getCommandSenderFromContext(cmdCtx), parseArguments(cmdCtx, args))) {
return Collections.singleton(cmdCtx.getSource());
} else {
return Collections.emptyList();
@@ -119,16 +119,11 @@ public static RedirectModifier fromPredicate(BiPredicate, CommandSender>
+ Command fromCommand(AbstractCommandAPICommand, Argument, CommandSender> command) {
+ // Need to cast base handler to make it realize we're using the same CommandSender class
+ CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance();
+ return handler.generateCommand((Argument[]) command.getArguments().toArray(AbstractArgument[]::new), command.getExecutor(), command.isConverted());
}
/**
@@ -147,8 +142,9 @@ public static Command fromCommand(CommandAPICommand command) {
* @param argument the argument you want to specify
* @return a RequiredArgumentBuilder that represents the provided argument
*/
- public static RequiredArgumentBuilder fromArgument(List args, Argument> argument) {
- return CommandAPIHandler.getInstance().getRequiredArgumentBuilderDynamic(args.toArray(new Argument[0]), argument);
+ public static > RequiredArgumentBuilder fromArgument(List args, Argument argument) {
+ CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance();
+ return handler.getRequiredArgumentBuilderDynamic((Argument[]) args.toArray(AbstractArgument[]::new), argument);
}
/**
@@ -157,8 +153,9 @@ public static RequiredArgumentBuilder fromArgument(List args, Argument
* @param argument the argument to create a RequiredArgumentBuilder from
* @return a RequiredArgumentBuilder that represents the provided argument
*/
- public static RequiredArgumentBuilder fromArgument(Argument argument) {
- return CommandAPIHandler.getInstance().getRequiredArgumentBuilderDynamic(new Argument[] { argument }, argument);
+ public static > RequiredArgumentBuilder fromArgument(Argument argument) {
+ CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance();
+ return handler.getRequiredArgumentBuilderDynamic((Argument[]) new AbstractArgument[] { argument }, argument);
}
/**
@@ -170,8 +167,9 @@ public static RequiredArgumentBuilder fromArgument(Argument argument) {
* @return a SuggestionProvider that suggests the overridden suggestions for the
* specified argument
*/
- public static SuggestionProvider toSuggestions(Argument> argument, List args) {
- return CommandAPIHandler.getInstance().toSuggestions(argument, args.toArray(new Argument[0]), true);
+ public static > SuggestionProvider toSuggestions(Argument argument, List args) {
+ CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance();
+ return handler.toSuggestions(argument, (Argument[]) args.toArray(AbstractArgument[]::new), true);
}
/**
@@ -186,8 +184,9 @@ public static SuggestionProvider toSuggestions(Argument> argument, List args) throws CommandSyntaxException {
- return CommandAPIHandler.getInstance().argsToObjectArr(cmdCtx, args.toArray(new Argument[0]));
+ public static > Object[] parseArguments(CommandContext cmdCtx, List args) throws CommandSyntaxException {
+ CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance();
+ return handler.argsToCommandArgs(cmdCtx, (Argument[]) args.toArray(AbstractArgument[]::new)).args();
}
/**
@@ -200,8 +199,9 @@ public static Object[] parseArguments(CommandContext cmdCtx, List args
* @return a Brigadier source object representing the provided Bukkit
* CommandSender
*/
- public static Object getBrigadierSourceFromCommandSender(CommandSender sender) {
- return CommandAPIHandler.getInstance().getNMS().getCLWFromCommandSender(sender);
+ public static Object getBrigadierSourceFromCommandSender(CommandSender sender) {
+ CommandAPIPlatform, CommandSender, ?> platform = (CommandAPIPlatform, CommandSender, ?>) CommandAPIHandler.getInstance().getPlatform();
+ return platform.getBrigadierSourceFromCommandSender(platform.wrapCommandSender(sender));
}
@@ -211,7 +211,10 @@ public static Object getBrigadierSourceFromCommandSender(CommandSender sender) {
* @param cmdCtx the command context to get the CommandSender from
* @return a Bukkit CommandSender from the provided Brigadier CommandContext
*/
- public static CommandSender getBukkitCommandSenderFromContext(CommandContext cmdCtx) {
- return CommandAPIHandler.getInstance().getNMS().getSenderForCommand(cmdCtx, false);
+ public static CommandSender getCommandSenderFromContext(CommandContext cmdCtx) {
+ CommandAPIPlatform, CommandSender, ?> platform = (CommandAPIPlatform, CommandSender, ?>) CommandAPIHandler.getInstance().getPlatform();
+ // For some reason putting this on one line doesn't work - very weird
+ AbstractCommandSender abstractSender = platform.getSenderForCommand(cmdCtx, false);
+ return abstractSender.getSource();
}
}
\ No newline at end of file
diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/ChainableBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/ChainableBuilder.java
new file mode 100644
index 0000000000..67197841ec
--- /dev/null
+++ b/commandapi-core/src/main/java/dev/jorel/commandapi/ChainableBuilder.java
@@ -0,0 +1,12 @@
+package dev.jorel.commandapi;
+
+/**
+ *
+ * @param The class extending this class, used as the return type for chain calls
+ */
+public interface ChainableBuilder {
+ /**
+ * Returns the instance of this class with the class Impl. Used for chaining builder methods.
+ */
+ Impl instance();
+}
diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java
index aa596e5fc4..759b7f3f47 100644
--- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java
+++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java
@@ -1,105 +1,142 @@
-/*******************************************************************************
- * Copyright 2018, 2021 Jorel Ali (Skepter) - MIT License
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
- * the Software, and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
- * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
- * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
- * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- *******************************************************************************/
package dev.jorel.commandapi;
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
import com.mojang.brigadier.Message;
-import net.kyori.adventure.text.Component;
-import net.md_5.bungee.api.chat.BaseComponent;
-import org.bukkit.Bukkit;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.EventPriority;
-import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.event.player.PlayerQuitEvent;
-import org.bukkit.plugin.Plugin;
-import org.bukkit.plugin.java.JavaPlugin;
-
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
-
+import dev.jorel.commandapi.commandsenders.AbstractPlayer;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
/**
* Class to register commands with the 1.13 command UI
*
*/
-public final class CommandAPI {
-
+public class CommandAPI {
// Cannot be instantiated
private CommandAPI() {
}
-
+
static {
onDisable();
}
private static boolean canRegister;
- static InternalConfig config;
- static Logger logger;
private static boolean loaded;
+ private static InternalConfig config;
+ private static CommandAPILogger logger;
+
+ // Accessing static variables
/**
* Returns whether the CommandAPI is currently loaded. This should be true when
* {@link CommandAPI#onLoad(CommandAPIConfig)} is called. If the CommandAPI is
* loaded, commands are available to register.
- *
+ *
* @return whether the CommandAPI has been loaded properly
*/
public static boolean isLoaded() {
return loaded;
}
+ /**
+ * Flag that commands should no longer be registered. After running this,
+ * {@link CommandAPI#canRegister()} will return false.
+ */
+ public static void stopCommandRegistration() {
+ CommandAPI.canRegister = false;
+ }
+
+ /**
+ * Determines whether command registration is permitted via the CommandAPI
+ *
+ * @return true if commands can still be registered
+ */
+ public static boolean canRegister() {
+ return canRegister;
+ }
+
/**
* Returns the internal configuration used to manage the CommandAPI
- *
+ *
* @return the internal configuration used to manage the CommandAPI
*/
public static InternalConfig getConfiguration() {
- if (config == null) {
- CommandAPI.onLoad(new CommandAPIConfig());
- logWarning(
- "Could not find any configuration for the CommandAPI. Loading basic built-in configuration. Did you forget to call CommandAPI.onLoad(config)?");
+ if(config != null) {
+ return config;
+ } else {
+ throw new IllegalStateException("Tried to access InternalConfig, but it was null! Are you using CommandAPI features before calling CommandAPI#onLoad?");
}
- return config;
}
- private static class CommandAPILogger extends Logger {
+ public static void setLogger(CommandAPILogger logger) {
+ CommandAPI.logger = logger;
+ }
- protected CommandAPILogger() {
- super("CommandAPI", null);
- setParent(Bukkit.getServer().getLogger());
- setLevel(Level.ALL);
+ /**
+ * @return the CommandAPI's logger
+ */
+ public static CommandAPILogger getLogger() {
+ if (logger == null) {
+ logger = CommandAPIHandler.getInstance().getPlatform().getLogger();
}
+ return logger;
+ }
+ // Loading, enabling, and disabling
+
+ /**
+ * Initializes the CommandAPI for loading. This should be placed at the start of
+ * your onLoad() method.
+ *
+ * @param config the configuration to use for the CommandAPI. This should be a {@link CommandAPIConfig}
+ * subclass corresponding to the active platform.
+ */
+ public static void onLoad(CommandAPIConfig> config) {
+ if (!loaded) {
+ // Setup variables
+ CommandAPI.config = new InternalConfig(config);
+
+ // Initialize handlers
+ CommandAPIPlatform, ?, ?> platform = CommandAPIVersionHandler.getPlatform();
+ new CommandAPIHandler<>(platform);
+
+ // Log platform load
+ final String platformClassHierarchy;
+ {
+ List platformClassHierarchyList = new ArrayList<>();
+ Class> platformClass = platform.getClass();
+ // Goes up through class inheritance only (ending at Object, but we don't want to include that)
+ // CommandAPIPlatform is an interface, so it is not included
+ while (platformClass != null && platformClass != Object.class) {
+ platformClassHierarchyList.add(platformClass.getSimpleName());
+ platformClass = platformClass.getSuperclass();
+ }
+ platformClassHierarchy = String.join(" > ", platformClassHierarchyList);
+ }
+ logNormal("Loaded platform " + platformClassHierarchy);
+
+ // Finish loading
+ CommandAPIHandler.getInstance().onLoad(config);
+
+ loaded = true;
+ } else {
+ getLogger().severe("You've tried to call the CommandAPI's onLoad() method more than once!");
+ }
+ }
+
+ /**
+ * Enables the CommandAPI. This should be placed at the start of your
+ * onEnable() method.
+ */
+ public static void onEnable() {
+ CommandAPIHandler.getInstance().onEnable();
}
-
+
/**
- * Unloads the CommandAPI. This should go in your plugin's
- * {@link JavaPlugin#onDisable} method.
+ * Unloads the CommandAPI.
*/
public static void onDisable() {
CommandAPI.canRegister = true;
@@ -107,25 +144,23 @@ public static void onDisable() {
CommandAPI.logger = null;
CommandAPI.loaded = false;
- CommandAPIHandler.onDisable();
- }
-
- /**
- * Returns the CommandAPI's logger
- *
- * @return the CommandAPI's logger
- */
- public static Logger getLogger() {
- if (logger == null) {
- logger = new CommandAPILogger();
+ // This method is called automatically when the class loads to set up variables, in which case
+ // CommandAPIHandler will not have been initialized
+ CommandAPIHandler, ?, ?> handler = null;
+ try {
+ handler = CommandAPIHandler.getInstance();
+ } catch (IllegalStateException ignored) {
+ // Not an error, CommandAPIHandler is not in loaded state anyway
}
- return logger;
+ if (handler != null) handler.onDisable();
}
+ // Logging
+
/**
* Logs a message to the console using Logger.info() if the configuration has
* verbose logging enabled
- *
+ *
* @param message the message to log to the console
*/
public static void logInfo(String message) {
@@ -137,7 +172,7 @@ public static void logInfo(String message) {
/**
* Logs a message from the CommandAPI. If silent logs are enabled, this message
* is not logged.
- *
+ *
* @param message the message to log
*/
public static void logNormal(String message) {
@@ -149,7 +184,7 @@ public static void logNormal(String message) {
/**
* Logs a warning from the CommandAPI. If silent logs are enabled, this warning
* is not logged.
- *
+ *
* @param message the message to log as a warning
*/
public static void logWarning(String message) {
@@ -161,7 +196,7 @@ public static void logWarning(String message) {
/**
* Logs an error from the CommandAPI. This always gets logged, even if silent
* logs are enabled.
- *
+ *
* @param message the message to log as an error
*/
public static void logError(String message) {
@@ -169,114 +204,33 @@ public static void logError(String message) {
}
/**
- * Initializes the CommandAPI for loading. This should be placed at the start of
- * your onLoad() method.
- *
- * @param config the configuration to use for the CommandAPI
- */
- public static void onLoad(CommandAPIConfig config) {
- if (!loaded) {
- CommandAPI.config = new InternalConfig(config);
- CommandAPIHandler.getInstance().checkDependencies();
- loaded = true;
- } else {
- getLogger().severe("You've tried to call the CommandAPI's onLoad() method more than once!");
- }
- }
-
- /**
- * Enables the CommandAPI. This should be placed at the start of your
- * onEnable() method.
- *
- * @param plugin the plugin that this onEnable method is called from
- */
- public static void onEnable(Plugin plugin) {
- // Prevent command registration after server has loaded
- Bukkit.getScheduler().runTaskLater(plugin, () -> {
- canRegister = false;
-
- // Sort out permissions after the server has finished registering them all
- CommandAPIHandler.getInstance().fixPermissions();
- CommandAPIHandler.getInstance().getNMS().reloadDataPacks();
- CommandAPIHandler.getInstance().updateHelpForCommands();
- }, 0L);
-
- // (Re)send command graph packet to players when they join
- Bukkit.getServer().getPluginManager().registerEvents(new Listener() {
-
- // For some reason, any other priority doesn't work
- @EventHandler(priority = EventPriority.MONITOR)
- public void onPlayerJoin(PlayerJoinEvent e) {
- CommandAPIHandler.getInstance().getNMS().resendPackets(e.getPlayer());
- }
-
- }, plugin);
-
- // On 1.19+, enable chat preview if the server allows it
- if(CommandAPIHandler.getInstance().getNMS().canUseChatPreview()) {
- Bukkit.getServer().getPluginManager().registerEvents(new Listener() {
-
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent e) {
- if(Bukkit.shouldSendChatPreviews()) {
- CommandAPIHandler.getInstance().getNMS().hookChatPreview(plugin, e.getPlayer());
- }
- }
-
- @EventHandler
- public void onPlayerQuit(PlayerQuitEvent e) {
- if(Bukkit.shouldSendChatPreviews()) {
- CommandAPIHandler.getInstance().getNMS().unhookChatPreview(e.getPlayer());
- }
- }
-
- }, plugin);
- logNormal("Chat preview enabled");
- } else {
- logNormal("Chat preview is not available");
- }
-
- CommandAPIHandler.getInstance().getPaper().registerReloadHandler(plugin);
- }
-
- /**
- * Updates the requirements required for a given player to execute a command.
- *
- * @param player the player whos requirements to update
- */
- public static void updateRequirements(Player player) {
- CommandAPIHandler.getInstance().getNMS().resendPackets(player);
- }
-
- /**
- * Reloads all of the datapacks that are on the server. This should be used if
+ * Reloads all the datapacks that are on the server. This should be used if
* you change a datapack and want to reload a server. Execute this method after
* running /minecraft:reload, NOT before.
*/
public static void reloadDatapacks() {
- CommandAPIHandler.getInstance().getNMS().reloadDataPacks();
+ CommandAPIHandler.getInstance().getPlatform().reloadDataPacks();
}
/**
- * Forces a command to return a success value of 0
- *
- * @param message Description of the error message
- * @return a {@link WrapperCommandSyntaxException} that wraps Brigadier's
- * {@link CommandSyntaxException}
+ * Updates the requirements required for a given player to execute a command.
*
- * @deprecated Please use {@link CommandAPI#failWithString(String)} instead
+ * @param player the player whose requirements should be updated
*/
- @Deprecated
- public static WrapperCommandSyntaxException fail(String message) {
- return failWithString(message);
+ public static void updateRequirements(Player player) {
+ @SuppressWarnings("unchecked")
+ CommandAPIPlatform, CommandSender, ?> platform = (CommandAPIPlatform, CommandSender, ?>) CommandAPIHandler.getInstance().getPlatform();
+ platform.updateRequirements((AbstractPlayer>) platform.wrapCommandSender(player));
}
+ // Produce WrapperCommandSyntaxException
+
/**
* Forces a command to return a success value of 0
*
* @param message Description of the error message
* @return a {@link WrapperCommandSyntaxException} that wraps Brigadier's
- * {@link CommandSyntaxException}
+ * {@link CommandSyntaxException}
*/
public static WrapperCommandSyntaxException failWithString(String message) {
return failWithMessage(Tooltip.messageFromString(message));
@@ -287,70 +241,41 @@ public static WrapperCommandSyntaxException failWithString(String message) {
*
* @param message Description of the error message, formatted as a brigadier message
* @return a {@link WrapperCommandSyntaxException} that wraps Brigadier's
- * {@link CommandSyntaxException}
+ * {@link CommandSyntaxException}
*/
public static WrapperCommandSyntaxException failWithMessage(Message message) {
return new WrapperCommandSyntaxException(new SimpleCommandExceptionType(message).create());
}
- /**
- * Forces a command to return a success value of 0
- *
- * @param message Description of the error message, formatted as an array of base components
- * @return a {@link WrapperCommandSyntaxException} that wraps Brigadier's
- * {@link CommandSyntaxException}
- */
- public static WrapperCommandSyntaxException failWithBaseComponents(BaseComponent... message) {
- return failWithMessage(Tooltip.messageFromBaseComponents(message));
- }
-
- /**
- * Forces a command to return a success value of 0
- *
- * @param message Description of the error message, formatted as an adventure chat component
- * @return a {@link WrapperCommandSyntaxException} that wraps Brigadier's
- * {@link CommandSyntaxException}
- */
- public static WrapperCommandSyntaxException failWithAdventureComponent(Component message) {
- return failWithMessage(Tooltip.messageFromAdventureComponent(message));
- }
-
- /**
- * Determines whether command registration is permitted via the CommandAPI
- *
- * @return true if commands can still be registered
- */
- public static boolean canRegister() {
- return canRegister;
- }
+ // Command registration and unregistration
/**
* Unregisters a command
- *
+ *
* @param command the name of the command to unregister
*/
public static void unregister(String command) {
- CommandAPIHandler.getInstance().unregister(command, false);
+ CommandAPIHandler.getInstance().getPlatform().unregister(command, false);
}
/**
* Unregisters a command, by force (removes all instances of that command)
- *
+ *
* @param command the name of the command to unregister
* @param force if true, attempt to unregister all instances of the command
* across all plugins as well as minecraft, bukkit and spigot
*/
public static void unregister(String command, boolean force) {
- if (!canRegister()) {
+ if (!canRegister) {
getLogger().warning("Unexpected unregistering of /" + command
- + ", as server is loaded! Unregistering anyway, but this can lead to unstable results!");
+ + ", as server is loaded! Unregistering anyway, but this can lead to unstable results!");
}
- CommandAPIHandler.getInstance().unregister(command, force);
+ CommandAPIHandler.getInstance().getPlatform().unregister(command, force);
}
/**
* Registers a command. Used with the CommandAPI's Annotation API.
- *
+ *
* @param commandClass the class to register
*/
public static void registerCommand(Class> commandClass) {
@@ -363,7 +288,7 @@ public static void registerCommand(Class> commandClass) {
/**
* @return A list of all {@link RegisteredCommand}{@code s} that have been
- * registered by the CommandAPI so far. The returned list is immutable.
+ * registered by the CommandAPI so far. The returned list is immutable.
*/
public static List getRegisteredCommands() {
return Collections.unmodifiableList(CommandAPIHandler.getInstance().registeredCommands);
diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java
deleted file mode 100644
index 51f69aa827..0000000000
--- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*******************************************************************************
- * Copyright 2018, 2020 Jorel Ali (Skepter) - MIT License
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
- * the Software, and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
- * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
- * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
- * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- *******************************************************************************/
-package dev.jorel.commandapi;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import com.mojang.brigadier.exceptions.CommandSyntaxException;
-
-import dev.jorel.commandapi.arguments.Argument;
-import dev.jorel.commandapi.arguments.IGreedyArgument;
-import dev.jorel.commandapi.arguments.MultiLiteralArgument;
-import dev.jorel.commandapi.exceptions.GreedyArgumentException;
-
-/**
- * A builder used to create commands to be registered by the CommandAPI.
- */
-public class CommandAPICommand extends ExecutableCommand {
-
- private List> args = new ArrayList<>();
- private List subcommands = new ArrayList<>();
- private boolean isConverted;
-
- /**
- * Creates a new command builder
- *
- * @param commandName The name of the command to create
- */
- public CommandAPICommand(String commandName) {
- super(commandName);
- this.isConverted = false;
- }
-
- /**
- * Creates a new Command builder
- *
- * @param metaData The metadata of the command to create
- */
- protected CommandAPICommand(CommandMetaData metaData) {
- super(metaData);
- this.isConverted = false;
- }
-
- /**
- * Appends the arguments to the current command builder
- *
- * @param args A List that represents the arguments that this
- * command can accept
- * @return this command builder
- */
- public CommandAPICommand withArguments(List> args) {
- this.args.addAll(args);
- return this;
- }
-
- /**
- * Appends the argument(s) to the current command builder
- *
- * @param args Arguments that this command can accept
- * @return this command builder
- */
- public CommandAPICommand withArguments(Argument>... args) {
- this.args.addAll(Arrays.asList(args));
- return this;
- }
-
- /**
- * Adds a subcommand to this command builder
- *
- * @param subcommand the subcommand to add as a child of this command
- * @return this command builder
- */
- public CommandAPICommand withSubcommand(CommandAPICommand subcommand) {
- this.subcommands.add(subcommand);
- return this;
- }
-
- /**
- * Adds subcommands to this command builder
- *
- * @param subcommands the subcommands to add as children of this command
- * @return this command builder
- */
- public CommandAPICommand withSubcommands(CommandAPICommand... subcommands) {
- this.subcommands.addAll(Arrays.asList(subcommands));
- return this;
- }
-
- /**
- * Returns the list of arguments that this command has
- *
- * @return the list of arguments that this command has
- */
- public List> getArguments() {
- return args;
- }
-
- /**
- * Sets the arguments that this command has
- *
- * @param args the arguments that this command has
- */
- public void setArguments(List> args) {
- this.args = args;
- }
-
- /**
- * Returns the list of subcommands that this command has
- *
- * @return the list of subcommands that this command has
- */
- public List getSubcommands() {
- return subcommands;
- }
-
- /**
- * Sets the list of subcommands that this command has
- *
- * @param subcommands the list of subcommands that this command has
- */
- public void setSubcommands(List subcommands) {
- this.subcommands = subcommands;
- }
-
- /**
- * Returns whether this command is an automatically converted command
- *
- * @return whether this command is an automatically converted command
- */
- public boolean isConverted() {
- return isConverted;
- }
-
- /**
- * Sets a command as "converted". This tells the CommandAPI that this command
- * was converted by the CommandAPI's Converter. This should not be used outside
- * of the CommandAPI's internal API
- *
- * @param isConverted whether this command is converted or not
- * @return this command builder
- */
- CommandAPICommand setConverted(boolean isConverted) {
- this.isConverted = isConverted;
- return this;
- }
-
- // Expands subcommands into arguments. This method should be static (it
- // shouldn't
- // be accessing/depending on any of the contents of the current class instance)
- private static void flatten(CommandAPICommand rootCommand, List> prevArguments, CommandAPICommand subcommand) {
- // Get the list of literals represented by the current subcommand. This
- // includes the subcommand's name and any aliases for this subcommand
- String[] literals = new String[subcommand.meta.aliases.length + 1];
- literals[0] = subcommand.meta.commandName;
- System.arraycopy(subcommand.meta.aliases, 0, literals, 1, subcommand.meta.aliases.length);
-
- // Create a MultiLiteralArgument using the subcommand information
- MultiLiteralArgument literal = (MultiLiteralArgument) new MultiLiteralArgument(literals)
- .withPermission(subcommand.meta.permission)
- .withRequirement(subcommand.meta.requirements)
- .setListed(false);
-
- prevArguments.add(literal);
-
- if (subcommand.executor.hasAnyExecutors()) {
- // Create the new command. The new command:
- // - starts at the root command node
- // - has all of the previously declared arguments (i.e. not itself)
- // - uses the subcommand's executor
- // - has no subcommands(?)
- // Honestly, if you're asking how or why any of this works, I don't
- // know because I just trialled random code until it started working
- rootCommand.args = prevArguments;
- rootCommand.withArguments(subcommand.args);
- rootCommand.executor = subcommand.executor;
- rootCommand.subcommands = new ArrayList<>();
- rootCommand.register();
- }
-
- for (CommandAPICommand subsubcommand : new ArrayList<>(subcommand.subcommands)) {
- flatten(rootCommand, new ArrayList<>(prevArguments), subsubcommand);
- }
- }
-
- @Override
- public void register() {
- if (!CommandAPI.canRegister()) {
- CommandAPI.logWarning("Command /" + meta.commandName + " is being registered after the server had loaded. Undefined behavior ahead!");
- }
- try {
- Argument>[] argumentsArr = args == null ? new Argument>[0] : args.toArray(new Argument>[0]);
-
- // Check IGreedyArgument constraints
- for (int i = 0, numGreedyArgs = 0; i < argumentsArr.length; i++) {
- if (argumentsArr[i] instanceof IGreedyArgument) {
- if (++numGreedyArgs > 1 || i != argumentsArr.length - 1) {
- throw new GreedyArgumentException(argumentsArr);
- }
- }
- }
-
- // Assign the command's permissions to arguments if the arguments don't already
- // have one
- for (Argument> argument : argumentsArr) {
- if (argument.getArgumentPermission() == null) {
- argument.withPermission(meta.permission);
- }
- }
-
- if (executor.hasAnyExecutors()) {
- CommandAPIHandler.getInstance().register(meta, argumentsArr, executor, isConverted);
- }
-
- // Convert subcommands into multiliteral arguments
- for (CommandAPICommand subcommand : new ArrayList<>(this.subcommands)) {
- flatten(this.copy(), new ArrayList<>(), subcommand);
- }
- } catch (CommandSyntaxException | IOException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Copies this Command builder
- *
- * @return a copy of the current CommandAPICommand
- */
- public CommandAPICommand copy() {
- CommandAPICommand command = new CommandAPICommand(new CommandMetaData(this.meta));
- command.args = new ArrayList<>(this.args);
- command.subcommands = new ArrayList<>(this.subcommands);
- command.isConverted = this.isConverted;
- return command;
- }
-
-}
diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIConfig.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIConfig.java
index 71284a02bd..e37373bce8 100644
--- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIConfig.java
+++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIConfig.java
@@ -21,51 +21,51 @@
package dev.jorel.commandapi;
import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Function;
-import dev.jorel.commandapi.nms.NMS;
-
/**
- * A class to contain information about how to configure the CommandAPI during
- * its loading step.
+ * A class to contain information about how to configure the CommandAPI during its loading step.
+ * You shouldn't use this class directly. Instead, use an appropriate subclass that corresponds to
+ * the platform you are developing for.
*/
-public class CommandAPIConfig {
-
- // The default configuration. This should mirror the commandapi-plugin
- // config.yml file.
+public abstract class CommandAPIConfig> implements ChainableBuilder {
+ // The default configuration. This should mirror the commandapi-plugin config.yml file.
boolean verboseOutput = false;
boolean silentLogs = false;
boolean useLatestNMSVersion = false;
String missingExecutorImplementationMessage = "This command has no implementations for %s";
+ File dispatcherFile = null;
+
+ List skipSenderProxy = new ArrayList<>();
+
// NBT API
Class> nbtContainerClass = null;
Function