Skip to content

Commit e4a0780

Browse files
author
Francisco Solis
committed
Features, Updates and Fixes
* Now arguments are global! * Added Bungee API * Added Bungee/Bukkit Argument Processors * Arguments are now just one class (Instead of indexed and named arguments) * Added permission check for commands * Added tab completer * Added custom completions for tab commands * Fixed register process for commands
1 parent 7885a2b commit e4a0780

File tree

16 files changed

+577
-227
lines changed

16 files changed

+577
-227
lines changed

.github/workflows/gradle-build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
steps:
1717
# Checkout the Code
1818
- name: Checkout Code
19-
uses: actions/checkout@v2
19+
uses: actions/checkout@v3
2020
# Set up git hashes environment variables
2121
- name: Git Hashes
2222
uses: Im-Fran/[email protected]
@@ -27,7 +27,7 @@ jobs:
2727
remove-first-character: 'v'
2828
# Set up the JDK
2929
- name: Set up JDK 11
30-
uses: actions/setup-java@v2
30+
uses: actions/setup-java@v3
3131
with:
3232
distribution: adopt
3333
java-version: 11
@@ -39,13 +39,13 @@ jobs:
3939
run: ./gradlew clean test publish shadow dokkaHtml
4040
# Now we store the artifact in the action
4141
- name: Upload the artifact
42-
uses: actions/upload-artifact@v2
42+
uses: actions/upload-artifact@v3
4343
with:
4444
name: CommandsModule
4545
path: ./build/libs/CommandsModule-${{ env.VERSION }}.jar
4646
# Now we deploy the documents to GitHub pages
4747
- name: Deploy Dokka
48-
uses: JamesIves/github-pages-deploy-action@4.1.7
48+
uses: JamesIves/github-pages-deploy-action@4.3.0
4949
with:
5050
branch: gh-pages
5151
folder: build/dokka

build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import org.apache.tools.ant.filters.ReplaceTokens
22

33
plugins {
4-
id 'org.jetbrains.kotlin.jvm' version '1.6.10'
4+
id 'org.jetbrains.kotlin.jvm' version '1.6.20'
55
id 'maven-publish'
66
id 'com.github.johnrengelman.shadow' version '7.1.2'
7-
id 'org.jetbrains.dokka' version '1.6.0'
7+
id 'org.jetbrains.dokka' version '1.6.10'
88
}
99

1010
def projectVersion = (System.getenv("VERSION") ?: '0.1.0-SNAPSHOT').replaceFirst("v", "").replace('/', '')
@@ -28,10 +28,10 @@ repositories {
2828
}
2929

3030
dependencies {
31-
compileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.6.10'
31+
compileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.6.20'
3232
compileOnly 'org.spigotmc:spigot-api:1.18.1-R0.1-SNAPSHOT'
3333
compileOnly 'net.md-5:bungeecord-api:1.18-R0.1-SNAPSHOT'
34-
compileOnly 'xyz.theprogramsrc:simplecoreapi:0.3.1-SNAPSHOT'
34+
compileOnly 'xyz.theprogramsrc:simplecoreapi:0.3.4-SNAPSHOT'
3535
compileOnly 'xyz.theprogramsrc:translationsmodule:0.1.5-SNAPSHOT'
3636
compileOnly 'xyz.theprogramsrc:tasksmodule:0.1.0-SNAPSHOT'
3737
compileOnly 'xyz.theprogramsrc:filesmodule:0.1.1-SNAPSHOT'

src/main/kotlin/xyz/theprogramsrc/commandsmodule/Main.kt

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package xyz.theprogramsrc.commandsmodule
22

3+
import xyz.theprogramsrc.commandsmodule.bungee.arguments.BungeeArgumentProcessors
4+
import xyz.theprogramsrc.commandsmodule.spigot.arguments.BukkitArgumentProcessors
35
import xyz.theprogramsrc.filesmodule.config.JsonConfig
46
import xyz.theprogramsrc.filesmodule.utils.folder
7+
import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI
58
import xyz.theprogramsrc.simplecoreapi.global.module.Module
9+
import xyz.theprogramsrc.simplecoreapi.global.utils.SoftwareType
610
import xyz.theprogramsrc.translationsmodule.Translation
711
import java.io.File
812
import java.time.format.DateTimeFormatter
@@ -48,22 +52,27 @@ class Main: Module() {
4852
colors = arrayOf("&e&b")
4953
)
5054

51-
private val config = JsonConfig(
52-
File(
53-
File("plugins/SimpleCoreAPI/modules/CommandsModule").folder(),
54-
"config.json"
55-
)
56-
)
57-
58-
fun getDefaultDateFormatter(): DateTimeFormatter {
59-
config.add("dateFormat", "dd-MM-yyyy HH:mm:ss")
60-
return DateTimeFormatter.ofPattern(config.getString("dateFormat") ?: "dd-MM-yyyy HH:mm:ss")
55+
private val config = JsonConfig(File(
56+
File("plugins/SimpleCoreAPI/modules/CommandsModule").folder(),
57+
"config.json"
58+
)).apply {
59+
add("dateFormat", "dd-MM-yyyy HH:mm:ss")
6160
}
6261

62+
fun getDefaultDateFormatter(): DateTimeFormatter = DateTimeFormatter.ofPattern(config.getString("dateFormat") ?: "dd-MM-yyyy HH:mm:ss")
63+
6364
}
6465

65-
override fun onEnable() {
66+
override fun onLoad() {
67+
if(SimpleCoreAPI.instance.softwareType == SoftwareType.UNKNOWN) {
68+
error("You're currently running on an unsupported server software! Please contact us at https://go.theprogramsrc.xyz/discord so we can help you further.")
69+
}
6670

71+
if(SimpleCoreAPI.instance.softwareType in arrayOf(SoftwareType.BUNGEE, SoftwareType.WATERFALL)) { // Proxies!
72+
BungeeArgumentProcessors.load()
73+
}else{
74+
BukkitArgumentProcessors.load()
75+
}
6776
}
6877

6978
}
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
package xyz.theprogramsrc.commandsmodule.bungee
2+
3+
import net.md_5.bungee.api.CommandSender
4+
import net.md_5.bungee.api.chat.TextComponent
5+
import net.md_5.bungee.api.connection.ProxiedPlayer
6+
import net.md_5.bungee.api.plugin.TabExecutor
7+
import xyz.theprogramsrc.commandsmodule.Main
8+
import xyz.theprogramsrc.commandsmodule.objects.CommandRequirement
9+
import xyz.theprogramsrc.commandsmodule.objects.CommandType
10+
import xyz.theprogramsrc.commandsmodule.objects.arguments.Arguments
11+
import xyz.theprogramsrc.simplecoreapi.bungee.BungeeLoader
12+
import xyz.theprogramsrc.tasksmodule.bungee.BungeeTasks
13+
14+
/**
15+
* Representation of a command
16+
* @param name The name of the command
17+
* @param onExecute The function to execute when the command is executed.
18+
*/
19+
class Command(val name: String, val onExecute: (CommandExecutor) -> Unit = {}) {
20+
21+
var permission: String? = null
22+
private set
23+
var commandType: CommandType = CommandType.PLAYER_AND_CONSOLE
24+
private set
25+
var tabCompleter: (CommandSender, Array<String>) -> Collection<String>
26+
private set
27+
val aliases: MutableList<String> = mutableListOf()
28+
val subCommands: MutableList<SubCommand> = mutableListOf()
29+
val requirements: MutableList<CommandRequirement<CommandSender>> = mutableListOf()
30+
private var registered = false
31+
32+
init {
33+
tabCompleter = { sender, args ->
34+
if(commandType == CommandType.PLAYER && sender !is ProxiedPlayer) {
35+
emptyList()
36+
} else if(commandType == CommandType.CONSOLE && sender is ProxiedPlayer) {
37+
emptyList()
38+
} else {
39+
if(args.isNotEmpty()){ // If we have at least 1 argument
40+
val subCommands = subCommands.filter { it.name.lowercase().startsWith(args[0].lowercase()) } // Get the sub commands to show
41+
if(args.size > 1){ // If there are more than 1 arguments
42+
subCommands.flatMap {
43+
val argument = it.arguments.getOrNull(args.size-2) // Get the argument. We remove 2 because the first argument is the sub command and the second is the argument
44+
val completions = it.tabCompletions(sender)
45+
if(argument != null && completions.isNotEmpty() && completions.keys.any { key -> key.lowercase() == argument.lowercase() }){ // Get the available completions for the current argument if any
46+
completions.filter { entry -> entry.key.lowercase() == argument.lowercase() }.values.flatten() // Get the completions
47+
} else if(argument != null) {
48+
listOf("<$argument>") // If there are no completions show the argument with <>
49+
} else {
50+
emptyList() // If there are no completions nor arguments show nothing
51+
}
52+
}.filter {
53+
it.lowercase().startsWith(args[args.size - 1].lowercase())
54+
} // Show the arguments of the sub command that matches the argument depth
55+
} else { // If there is only 1 argument
56+
subCommands.map { it.name } // Show the sub commands
57+
}
58+
} else {
59+
emptyList()
60+
}
61+
}
62+
}
63+
register()
64+
}
65+
66+
/**
67+
* Sets the command permission to the given permission
68+
* @param permission The permission to set
69+
* @return this command
70+
*/
71+
fun withPermission(permission: String): Command = this.apply {
72+
this.permission = permission
73+
}
74+
75+
/**
76+
* Adds the given aliases to the command
77+
* @param aliases the aliases to add
78+
* @return this command
79+
*/
80+
fun withAlias(vararg aliases: String): Command = this.apply {
81+
this.aliases.addAll(aliases)
82+
}
83+
84+
/**
85+
* Makes this command to be executable only by players
86+
* @return this command
87+
*/
88+
fun playersOnly(): Command = this.apply {
89+
this.commandType = CommandType.PLAYER
90+
}
91+
92+
/**
93+
* Makes this command to be executable only through console
94+
* @return this command
95+
*/
96+
fun consoleOnly(): Command = this.apply {
97+
this.commandType = CommandType.CONSOLE
98+
}
99+
100+
/**
101+
* Adds a new requirement to the command
102+
* @param message The message to send to the command sender if the requirement is not met
103+
* @param check The function to check if the requirement is met
104+
*/
105+
fun require(message: String, check: (CommandSender) -> Boolean): Command = this.apply {
106+
requirements.add(CommandRequirement(message, check))
107+
}
108+
109+
/**
110+
* Replaces the default tab completer for this command
111+
* @param tabCompleter The tab completer to set
112+
* @return this command
113+
*/
114+
fun withTabCompleter(tabCompleter: (CommandSender, Array<String>) -> Collection<String>): Command = this.apply {
115+
this.tabCompleter = tabCompleter
116+
}
117+
118+
/**
119+
* Registers an argument to this command
120+
* @param signature The name and signature of the command
121+
* @param executor The function to execute when the argument is executed
122+
* @return this command
123+
*/
124+
fun subCommand(signature: String, tabCompletions: (CommandSender) -> Map<String, Collection<String>> = { emptyMap() }, executor: (CommandExecutor) -> Unit): Command = this.apply {
125+
subCommands.add(SubCommand(
126+
name = signature.split(" ")[0],
127+
arguments = signature.split(" ").drop(1).map {
128+
it.dropWhile { char -> char == '{' }.dropLastWhile { char -> char == '}' }
129+
},
130+
tabCompletions = tabCompletions,
131+
executor = executor
132+
))
133+
}
134+
135+
/**
136+
* Registers this command in the server
137+
* @return true if the command was registered, false otherwise
138+
*/
139+
private fun register(): Boolean {
140+
check(!registered) { "Command $name is already registered" }
141+
try {
142+
val command = BungeeCommandHandler(name = this.name, permission = this.permission ?: "", aliases = this.aliases.toTypedArray(), tabCompleter = this.tabCompleter) { sender, args ->
143+
if(commandType == CommandType.PLAYER && sender !is ProxiedPlayer) {
144+
sender.sendMessage(TextComponent(Main.PLAYER_COMMAND.translate()))
145+
return@BungeeCommandHandler
146+
} else if(commandType == CommandType.CONSOLE && sender is ProxiedPlayer) {
147+
sender.sendMessage(TextComponent(Main.CONSOLE_COMMAND.translate()))
148+
return@BungeeCommandHandler
149+
}
150+
151+
val cmd = this@Command
152+
val failedRequirements = cmd.requirements.filter { !it.check(sender) }
153+
// First we check the requirements
154+
if(failedRequirements.isNotEmpty()){
155+
BungeeTasks.instance.runAsync { // Send async to prevent lag
156+
sender.sendMessage(TextComponent(
157+
Main.MISSING_REQUIREMENTS
158+
.translate()
159+
.plus("\n")
160+
.plus(failedRequirements.joinToString("\n") { "&7- &c${it.message}" })
161+
.replace("&", "§")
162+
))
163+
}
164+
return@BungeeCommandHandler
165+
}
166+
167+
if(sender is ProxiedPlayer && cmd.permission != null && cmd.permission != "none" && !sender.hasPermission(cmd.permission!!)) {
168+
sender.sendMessage(TextComponent(Main.NO_PERMISSION.translate()))
169+
return@BungeeCommandHandler
170+
}
171+
172+
if(args.isNotEmpty() && cmd.subCommands.isNotEmpty()){ // Now that we have sub commands, let's find one!
173+
val subCommandName = args.first() // Here we get the subcommand name
174+
val rawArguments = args.drop(1) // Arguments provided by bukkit
175+
val subCommand = cmd.subCommands.find { it.name.lowercase() == subCommandName.lowercase() && it.arguments.size == rawArguments.size } // Let's look for a sub command. And because we can specify multiple sub commands with the same name, we need to check the amount of arguments too
176+
if(subCommand == null) { // No sub commands? No worries, let's run the main action
177+
cmd.onExecute(CommandExecutor(sender, Arguments(indexedArguments = args.toList())))
178+
return@BungeeCommandHandler
179+
}
180+
181+
val inputArguments = mutableMapOf<String, String>() // The arguments that the API will provide to the executor
182+
if(subCommand.arguments.isNotEmpty()){ // Only generate named arguments if there is at least one
183+
for(index in 0 until subCommand.arguments.size) { // Here we fill the map with the arguments
184+
val name = subCommand.arguments[index]
185+
if(rawArguments.getOrNull(index) == null){ // Should not be null because all the arguments are required
186+
BungeeTasks.instance.runAsync { // If we need to send a message, let's do it async!
187+
sender.sendMessage(TextComponent(Main.MISSING_ARGUMENT.translate(placeholders = mapOf(
188+
"argument" to name // Apply the placeholder to let know the user which argument is missing
189+
))))
190+
}
191+
return@BungeeCommandHandler
192+
}
193+
inputArguments[name] = rawArguments[index] // Here we add the argument to the map
194+
}
195+
}
196+
val arguments = Arguments(indexedArguments = args.toList(), namedArguments = inputArguments) // The sub commands always uses indexed and named arguments
197+
subCommand.executor(CommandExecutor(sender, arguments)) // Now we execute it!
198+
} else {
199+
cmd.onExecute(CommandExecutor(sender, Arguments(indexedArguments = args.toList())))
200+
}
201+
}
202+
BungeeLoader.instance.proxy.pluginManager.registerCommand(BungeeLoader.instance, command)
203+
registered = true
204+
return true
205+
} catch (e: Exception) {
206+
e.printStackTrace()
207+
return false
208+
}
209+
}
210+
}
211+
212+
213+
214+
/**
215+
* Representation of a SubCommand
216+
* @param name The name of the argument
217+
* @param arguments The arguments of the subCommand
218+
* @param tabCompletions The tab completions of the arguments mapped by name and their respective completions
219+
* @param executor The function to execute when the argument is executed
220+
*
221+
* Signature format:
222+
* {argument1} {argument2}...
223+
*
224+
* Examples:
225+
* - ban {player} -> The name is ban, the argument is player.
226+
* - ban {uuid} -> The name is ban, the argument is uuid.
227+
*/
228+
data class SubCommand(val name: String, val arguments: List<String> = emptyList(), val tabCompletions: (CommandSender) -> Map<String, Collection<String>>, val executor: (CommandExecutor) -> Unit = {})
229+
230+
private class BungeeCommandHandler(
231+
name: String,
232+
permission: String = "",
233+
aliases: Array<String> = emptyArray(),
234+
val tabCompleter: (CommandSender, Array<String>) -> Collection<String> = { _, _ -> emptyList() },
235+
val commandExecutor: (CommandSender, Array<String>) -> Unit = { _, _ -> },
236+
): net.md_5.bungee.api.plugin.Command(name, permission, *aliases), TabExecutor {
237+
238+
override fun execute(sender: CommandSender, args: Array<String>) = commandExecutor(sender, args)
239+
240+
override fun onTabComplete(sender: CommandSender, args: Array<String>): MutableIterable<String> = tabCompleter(sender, args).toMutableList()
241+
242+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package xyz.theprogramsrc.commandsmodule.bungee
2+
3+
import net.md_5.bungee.api.CommandSender
4+
import net.md_5.bungee.api.chat.TextComponent
5+
import net.md_5.bungee.api.connection.ProxiedPlayer
6+
import xyz.theprogramsrc.commandsmodule.objects.arguments.Arguments
7+
8+
/**
9+
* Represents a command executor
10+
* @param commandSender The sender of the command
11+
* @param args The arguments of the command
12+
*/
13+
class CommandExecutor(val commandSender: CommandSender, val args: Arguments) {
14+
15+
/**
16+
* Gets a player from the command sender
17+
*/
18+
val player = commandSender as? ProxiedPlayer
19+
20+
/**
21+
* Sends a message to the command sender
22+
* @param message The message to send
23+
*/
24+
fun sendMessage(message: String) = commandSender.sendMessage(TextComponent(message.replace("&", "§")))
25+
}

0 commit comments

Comments
 (0)