Skip to content

Commit d9fdfd8

Browse files
committed
1.1 - Adding subcommand methods
1 parent 0b45ff6 commit d9fdfd8

4 files changed

Lines changed: 131 additions & 113 deletions

File tree

README.md

Lines changed: 35 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -4,92 +4,26 @@
44
</a>
55
</p>
66

7-
# Command Framework
7+
# Command Framework
88

9-
Command Framework is a flexible and extensible command framework designed for Bukkit/Spigot and especially Paper forks. It simplifies the process of creating, registering, and managing commands dynamically at runtime—a task that can be challenging due to inherent differences and limitations between Bukkit/Spigot and Paper.
9+
A modern, flexible command framework for Bukkit/Spigot and especially Paper Minecraft servers.
10+
Easily create, register, and manage commands at runtime—even on Paper, where this is usually difficult.
1011

1112
---
1213

13-
## Overview
14+
## ✨ Features
1415

15-
The `me.croabeast.command` package offers a modular command system that provides:
16-
17-
- **Robust Registration/Unregistration:**
18-
Seamlessly register and unregister commands at runtime even on Paper servers, where many traditional Bukkit/Spigot implementations can break. Command Framework addresses the runtime registration challenges posed by Paper.
19-
20-
- **Sub-Commands Support:**
21-
Easily implement command hierarchies with sub-commands. Each sub-command automatically derives its permission node from its parent and can be configured with aliases.
22-
23-
- **Dynamic Tab Completion:**
24-
Build context-sensitive tab completions with the included `TabBuilder` class. You can define both static and dynamic suggestions, and even use predicates and functions to tailor completions based on the command sender or input.
25-
26-
- **Permission Handling:**
27-
Leverage the `Permissible` and `DefaultPermissible` interfaces for robust permission checking. The framework supports wildcard permissions and integrates neatly with the Bukkit permission system.
28-
29-
- **Fluent Command Creation:**
30-
Use the `CommandBuilder` class for a fluent API to quickly create and configure commands, setting properties like overriding behavior, error handling, and custom tab completion strategies.
31-
32-
- **Integration with Bukkit/Spigot/Paper:**
33-
Built on top of Bukkit’s command system, Command Framework seamlessly integrates with the server’s command map and permission system. It is particularly tuned for Paper servers where runtime command management is notoriously challenging.
34-
35-
---
36-
37-
## Key Components
38-
39-
### Permissible & DefaultPermissible
40-
- **Purpose:** Define basic permission requirements for commands.
41-
- **Usage:** Implement these interfaces to ensure that only authorized users execute commands.
42-
43-
### Executable
44-
- **Purpose:** Represent the command action that is executed.
45-
- **Usage:** Return a result using a {@code State} enum to indicate success or failure.
46-
47-
### BaseCommand
48-
- **Purpose:** Establish the core structure of commands, including their names, aliases, and executable actions.
49-
- **Usage:** Use these interfaces as the foundation for building more complex commands with sub-commands and permissions.
50-
51-
### Command
52-
- **Purpose:** Extend BaseCommand and Completable to provide a complete command interface with tab completion support.
53-
- **Usage:** Manage sub-commands and wildcard permissions automatically.
54-
55-
### SubCommand
56-
- **Purpose:** Create sub-commands that are automatically linked to a parent command.
57-
- **Usage:** Easily extend commands with additional functionalities (e.g., a "reload" sub-command) while inheriting permission settings.
58-
59-
### TabBuilder
60-
- **Purpose:** Build and manage tab-completion suggestions.
61-
- **Usage:** Configure suggestions for specific command arguments with flexible filtering and predicate-based selection.
62-
63-
### BukkitCommand
64-
- **Purpose:** An abstract base class that integrates with Bukkit’s command system.
65-
- **Usage:** Extend this class for full control over command execution, including runtime registration/unregistration—a common challenge on Paper servers.
66-
67-
### CommandBuilder
68-
- **Purpose:** Provide a fluent API for constructing commands.
69-
- **Usage:** Use CommandBuilder to quickly create commands with tab completions and error handling, perfect for Paper forks with dynamic runtime command management.
70-
71-
---
72-
73-
## Why Command Framework Works on Paper Forks
74-
75-
Paper forks of Bukkit/Spigot often break traditional command registration methods, especially when attempting dynamic registration and unregistration at runtime. Command Framework was built with these challenges in mind. It:
76-
77-
- **Supports Runtime Changes:**
78-
Enables commands to be registered and unregistered at runtime without issues on Paper.
79-
80-
- **Handles Compatibility Issues:**
81-
Includes workarounds and specialized implementations (e.g., in `BukkitCommand` and `CommandBuilder`) that account for Paper’s modifications to the command system.
82-
83-
- **Ensures Stability:**
84-
Provides robust error handling and permission management even when underlying APIs change between Bukkit/Spigot and Paper.
16+
- 🔄 **Dynamic Command Registration:** Register/unregister commands at runtime, even on Paper.
17+
- 🌳 **Sub-Commands:** Build command hierarchies with permissions and aliases.
18+
-**Tab Completion:** Context-aware suggestions with `TabBuilder`.
19+
- 🔐 **Permissions:** Wildcard and custom permission checks.
20+
- 🧑‍💻 **Fluent API:** Quickly build commands with `CommandBuilder`.
8521

8622
---
8723

88-
## Usage Examples
89-
90-
### Example: Custom Command by Extending BukkitCommand
24+
## 🛠️ Quick Example
9125

92-
Create a custom command by extending `BukkitCommand`:
26+
Create and register a simple command with a sub-command:
9327

9428
```java
9529
package com.example.myplugin.command;
@@ -125,7 +59,7 @@ public class GreetCommand extends BukkitCommand implements DefaultPermissible {
12559
sender.sendMessage("GreetCommand configuration reloaded.");
12660
return true;
12761
});
128-
registerSubCommand(reloadSub);
62+
addSubCommand(reloadSub);
12963
}
13064

13165
/**
@@ -155,7 +89,7 @@ public class GreetCommand extends BukkitCommand implements DefaultPermissible {
15589
}
15690
```
15791

158-
#### Registering the Command
92+
#### 📝 Registering the Command
15993

16094
In your plugin’s main class (extending `JavaPlugin`), register the command:
16195

@@ -181,14 +115,9 @@ public class MyPlugin extends JavaPlugin {
181115

182116
---
183117

184-
## Maven / Gradle Installation
185-
186-
To include Command Framework to the project, add the following repository and dependency to your build configuration. Replace `${version}` with the desired version tag.
187-
188-
### Maven
189-
190-
Add the repository and dependency to your `pom.xml`:
118+
## ⚙️ Installation
191119

120+
**Maven:**
192121
```xml
193122
<repositories>
194123
<repository>
@@ -207,10 +136,7 @@ Add the repository and dependency to your `pom.xml`:
207136
</dependencies>
208137
```
209138

210-
### Gradle
211-
212-
Add the repository and dependency to your `build.gradle`:
213-
139+
**Gradle:**
214140
```groovy
215141
repositories {
216142
maven {
@@ -223,16 +149,29 @@ dependencies {
223149
}
224150
```
225151

226-
Replace `${version}` with the appropriate module version.
152+
Replace `${version}` with the latest version.
227153

228154
---
229155

230-
## Conclusion
156+
## ❓ Why Use This?
231157

232-
**Command Framework** (the collection of classes in the `me.croabeast.command` package) is designed to streamline command development for Minecraft plugins, particularly on Paper forks where runtime registration can be challenging. Its modular design, support for sub-commands, dynamic tab completion, and robust permission checks make it an ideal choice for modern plugin development.
158+
- Works reliably on Paper forks where other frameworks fail.
159+
- Makes complex command trees and tab completions easy.
160+
- Handles permissions and registration for you.
161+
162+
---
163+
164+
## 🚀 Get Started
165+
166+
1. Add the dependency.
167+
2. Extend `BukkitCommand` or create your own command class.
168+
3. Register your command in your plugin's `onEnable()`.
169+
170+
---
233171

234-
With Command Framework, you can build sophisticated command hierarchies, provide context-sensitive tab completions, and manage command registration and unregistration at runtime—ensuring compatibility and stability even on the latest Paper servers.
172+
## 🎉 Happy Coding!
235173

236-
Happy coding and enjoy building powerful commands with Command Framework!
174+
Build powerful, modern commands for your Minecraft plugin with ease!
175+
Questions? Join our [Discord](https://discord.com/invite/gzzhVqgy3b) 💬
237176

238-
*CroaBeast*
177+
*CroaBeast*

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>me.croabeast</groupId>
88
<artifactId>CommandFramework</artifactId>
9-
<version>1.0</version>
9+
<version>1.1</version>
1010
<packaging>jar</packaging>
1111

1212
<properties>

src/main/java/me/croabeast/command/BukkitCommand.java

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.bukkit.permissions.Permission;
1616
import org.bukkit.plugin.Plugin;
1717
import org.jetbrains.annotations.NotNull;
18+
import org.jetbrains.annotations.Nullable;
1819

1920
import java.lang.reflect.Field;
2021
import java.lang.reflect.Method;
@@ -62,7 +63,7 @@ public abstract class BukkitCommand extends org.bukkit.command.defaults.BukkitCo
6263
/**
6364
* The set of sub-commands registered under this command.
6465
*/
65-
final Set<BaseCommand> subCommands = new LinkedHashSet<>();
66+
final Map<String, BaseCommand> subCommands = new LinkedHashMap<>();
6667

6768
/**
6869
* Flag indicating whether this command is currently registered.
@@ -378,31 +379,83 @@ public CommandPredicate getPredicate() {
378379
return Objects.requireNonNull(predicate, "Executable predicate is not set");
379380
}
380381

382+
/**
383+
* Retrieves a sub-command by its name or alias.
384+
* <p>
385+
* If no sub-command matches the provided name, {@code null} is returned.
386+
* </p>
387+
*
388+
* @param name the name or alias of the sub-command.
389+
* @return the sub-command if found; {@code null} otherwise.
390+
*/
391+
@Nullable
392+
public BaseCommand getSubCommand(String name) {
393+
BaseCommand command = subCommands.get(name);
394+
if (command != null) return command;
395+
396+
for (BaseCommand sub : subCommands.values())
397+
if (sub.getAliases().contains(name))
398+
return sub;
399+
400+
return null;
401+
}
402+
381403
/**
382404
* Retrieves an unmodifiable set of all sub-commands registered under this command.
383405
*
384406
* @return a set of sub-commands.
385407
*/
386408
@NotNull
387409
public Set<BaseCommand> getSubCommands() {
388-
return Collections.unmodifiableSet(subCommands);
410+
return Collections.unmodifiableSet(new HashSet<>(subCommands.values()));
389411
}
390412

391413
/**
392-
* Registers a sub-command with this command.
414+
* Adds a sub-command to this command.
393415
* <p>
394-
* If a sub-command with the same name already exists, the new sub-command is ignored.
416+
* If the sub-command's name is already registered, it will not be added again.
395417
* </p>
396418
*
397-
* @param sub the sub-command to register.
419+
* @param sub the sub-command to add; must not be {@code null}.
420+
* @throws NullPointerException if the sub-command is {@code null}.
398421
*/
399422
@Override
400-
public void registerSubCommand(@NotNull BaseCommand sub) {
423+
public void addSubCommand(@NotNull BaseCommand sub) {
401424
Objects.requireNonNull(sub);
402-
for (BaseCommand command : subCommands)
403-
if (command.getName().equals(sub.getName()))
404-
return;
405-
subCommands.add(sub);
425+
426+
if (!subCommands.containsValue(sub))
427+
subCommands.put(sub.getName(), sub);
428+
}
429+
430+
/**
431+
* Removes a sub-command by its name.
432+
* <p>
433+
* If the name is blank or null, an {@link IllegalArgumentException} is thrown.
434+
* </p>
435+
*
436+
* @param name the name of the sub-command to remove; must not be {@code null} or empty.
437+
* @throws IllegalArgumentException if the name is blank or null.
438+
*/
439+
@Override
440+
public void removeSubCommand(@NotNull String name) {
441+
if (StringUtils.isBlank(name))
442+
throw new IllegalArgumentException("Sub-command name cannot be null or empty");
443+
444+
subCommands.remove(name);
445+
}
446+
447+
/**
448+
* Registers a sub-command under this command.
449+
* <p>
450+
* This method is deprecated; use {@link #addSubCommand(BaseCommand)} instead.
451+
* </p>
452+
*
453+
* @param sub the sub-command to register; must not be {@code null}.
454+
* @throws NullPointerException if the sub-command is {@code null}.
455+
*/
456+
@Deprecated
457+
public void registerSubCommand(@NotNull BaseCommand sub) {
458+
addSubCommand(sub);
406459
}
407460

408461
/**
@@ -442,17 +495,22 @@ private static void removePerm(String perm) {
442495
* @param loaded if {@code true}, permissions are removed; if {@code false}, they are added back.
443496
*/
444497
private void loadCommandPermissions(boolean loaded) {
498+
Collection<BaseCommand> subs = subCommands.values();
499+
445500
if (loaded) {
446501
removePerm(getPermission());
502+
447503
if (!subCommands.isEmpty()) {
448-
subCommands.forEach(s -> removePerm(s.getPermission()));
504+
subs.forEach(s -> removePerm(s.getPermission()));
449505
removePerm(getWildcardPermission());
450506
}
451507
return;
452508
}
509+
453510
addPerm(getPermission());
454511
if (subCommands.isEmpty()) return;
455-
subCommands.forEach(s -> addPerm(s.getPermission()));
512+
513+
subs.forEach(s -> addPerm(s.getPermission()));
456514
addPerm(getWildcardPermission());
457515
}
458516

src/main/java/me/croabeast/command/Command.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,17 @@
2929
* Example usage:
3030
* <pre><code>
3131
* public class MyCommand implements Command {
32+
*
3233
* private final Set<BaseCommand> subCommands = new HashSet<>();
3334
*
3435
* {@literal @}Override
3536
* public String getName() {
36-
* return "mycommand";
37+
* return "my-command";
3738
* }
3839
*
3940
* {@literal @}Override
4041
* public List&lt;String&gt; getAliases() {
41-
* return Arrays.asList("mc", "mycmd");
42+
* return Arrays.asList("mc", "my-cmd");
4243
* }
4344
*
4445
* {@literal @}Override
@@ -69,6 +70,16 @@
6970
* subCommands.add(sub);
7071
* }
7172
*
73+
* {@literal @}Override
74+
* public void removeSubCommand(@NotNull String name) {
75+
* for (BaseCommand sub : subCommands) {
76+
* if (!sub.getName().equalsIgnoreCase(name)) return;
77+
*
78+
* subCommands.remove(sub);
79+
* break;
80+
* }
81+
* }
82+
*
7283
* // Other methods from Completable, PluginIdentifiableCommand, Keyed, and Registrable...
7384
* }</code></pre></p>
7485
*
@@ -103,11 +114,21 @@ public interface Command extends BaseCommand, Completable, PluginIdentifiableCom
103114
Set<BaseCommand> getSubCommands();
104115

105116
/**
106-
* Registers a sub-command under this command.
117+
* Adds a sub-command under this command.
107118
*
108119
* @param sub the sub-command to register (must not be {@code null}).
109120
*/
110-
void registerSubCommand(@NotNull BaseCommand sub);
121+
void addSubCommand(@NotNull BaseCommand sub);
122+
123+
/**
124+
* Removes a sub-command by its name.
125+
* <p>
126+
* The method searches for a sub-command with the specified name and removes it if found.
127+
* </p>
128+
*
129+
* @param name the name of the sub-command to remove (must not be {@code null} or empty).
130+
*/
131+
void removeSubCommand(@NotNull String name);
111132

112133
/**
113134
* Retrieves a sub-command matching the given name.

0 commit comments

Comments
 (0)