Skip to content

Commit 5f25199

Browse files
authored
GH-990 Add custom command feature (#1064)
* Add custom command feature Signed-off-by: vLuckyyy <[email protected]> * Add comment. * move to okaeri configs. * Refactor custom commands to improve maintainability * fix. --------- Signed-off-by: vLuckyyy <[email protected]>
1 parent e4eca41 commit 5f25199

File tree

4 files changed

+274
-0
lines changed

4 files changed

+274
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.eternalcode.core.feature.customcommand;
2+
3+
import com.eternalcode.multification.notice.Notice;
4+
import java.io.Serializable;
5+
import java.util.List;
6+
7+
public class CustomCommand implements Serializable {
8+
9+
private String name;
10+
private List<String> aliases;
11+
private Notice message;
12+
13+
public CustomCommand() {}
14+
15+
public CustomCommand(String name, List<String> aliases, Notice message) {
16+
this.name = name;
17+
this.aliases = aliases;
18+
this.message = message;
19+
}
20+
21+
public static CustomCommand of(String commandName, List<String> aliases, Notice message) {
22+
return new CustomCommand(commandName, aliases, message);
23+
}
24+
25+
public String getName() {
26+
return name;
27+
}
28+
29+
public List<String> getAliases() {
30+
return aliases;
31+
}
32+
33+
public Notice getMessage() {
34+
return message;
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.eternalcode.core.feature.customcommand;
2+
3+
import com.eternalcode.core.notice.NoticeService;
4+
import com.eternalcode.multification.notice.Notice;
5+
import dev.rollczi.litecommands.util.StringUtil;
6+
import java.util.List;
7+
import org.bukkit.command.Command;
8+
import org.bukkit.command.CommandSender;
9+
import org.jetbrains.annotations.NotNull;
10+
11+
public class CustomCommandBukkitWrapper extends Command {
12+
13+
private static final String EMPTY_USAGE_MESSAGE = StringUtil.EMPTY;
14+
private static final String EMPTY_DESCRIPTION_MESSAGE = StringUtil.EMPTY;
15+
16+
private final NoticeService noticeService;
17+
private final Notice message;
18+
19+
protected CustomCommandBukkitWrapper(
20+
@NotNull String name,
21+
@NotNull List<String> aliases,
22+
NoticeService noticeService,
23+
Notice message
24+
) {
25+
super(name, EMPTY_DESCRIPTION_MESSAGE, EMPTY_USAGE_MESSAGE, aliases);
26+
27+
this.noticeService = noticeService;
28+
this.message = message;
29+
}
30+
31+
@Override
32+
public boolean execute(@NotNull CommandSender commandSender, @NotNull String s, @NotNull String[] strings) {
33+
this.noticeService.create()
34+
.notice(this.message)
35+
.sender(commandSender)
36+
.send();
37+
38+
return true;
39+
}
40+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package com.eternalcode.core.feature.customcommand;
2+
3+
import com.eternalcode.core.configuration.AbstractConfigurationFile;
4+
import com.eternalcode.core.injector.annotations.component.ConfigurationFile;
5+
import com.eternalcode.multification.notice.Notice;
6+
import eu.okaeri.configs.annotation.Comment;
7+
import eu.okaeri.configs.annotation.Header;
8+
import java.io.File;
9+
import java.time.Duration;
10+
import java.util.List;
11+
import net.kyori.adventure.bossbar.BossBar.Color;
12+
import net.kyori.adventure.bossbar.BossBar.Overlay;
13+
14+
@ConfigurationFile
15+
@Header({
16+
" ",
17+
"Custom commands configuration",
18+
"",
19+
"This file allows you to define your own commands using simple YAML configuration.",
20+
"Each command entry supports multiple display formats: chat, title, subtitle, action bar, bossbar and sound.",
21+
"",
22+
"Example usage:",
23+
"- Display your Discord link with a title and chat message (/discord)",
24+
"- Promote your store using a bossbar (/store)",
25+
"- Show server rules with a subtitle and timed title (/rules)",
26+
"",
27+
"You can test and generate these easily using:",
28+
"https://www.eternalcode.pl/notification-generator"
29+
})
30+
public class CustomCommandConfig extends AbstractConfigurationFile {
31+
32+
@Comment({
33+
" ",
34+
"# Custom commands",
35+
"#",
36+
"# Each command defines its name, aliases and message configuration.",
37+
"#",
38+
"# WARNING: You must restart the server after editing this file for changes to take effect.",
39+
})
40+
public List<CustomCommand> commands = List.of(
41+
CustomCommand.of(
42+
"help",
43+
List.of("info"),
44+
Notice.builder()
45+
.title("<rainbow>Welcome to EternalCore!</rainbow>")
46+
.subtitle("<gray>Use /help to get started</gray>")
47+
.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500))
48+
.actionBar("<green>Need support? Try /discord</green>")
49+
.chat(
50+
" ",
51+
"<yellow>To add your own commands, edit <aqua>custom-commands.yml</aqua> and restart the server.",
52+
"<gray>Use our generator to create nice looking messages:</gray>",
53+
"<aqua>https://www.eternalcode.pl/notification-generator</aqua>"
54+
).build()
55+
),
56+
CustomCommand.of(
57+
"discord",
58+
List.of("dc"),
59+
Notice.builder()
60+
.title("<blue>🎧 Discord</blue>")
61+
.subtitle("<gray>Join the community!</gray>")
62+
.times(Duration.ofMillis(300), Duration.ofSeconds(2), Duration.ofMillis(300))
63+
.chat(
64+
" ",
65+
"<blue>🎧 Join our Discord server:",
66+
"<aqua>https://discord.gg/yourserver</aqua>",
67+
" ",
68+
"<gray>Talk with the team, suggest features, get help.</gray>"
69+
).build()
70+
),
71+
CustomCommand.of(
72+
"store",
73+
List.of("shop"),
74+
Notice.builder()
75+
.bossBar(
76+
Color.YELLOW, Overlay.NOTCHED_10, Duration.ofSeconds(5), 1.0,
77+
"<gold>🛒 Visit our store – support the server & get cool perks!")
78+
.chat(
79+
" ",
80+
"<gold>🛒 Visit our server store:",
81+
"<aqua>https://store.yourserver.com</aqua>",
82+
" ",
83+
"<gray>Your support keeps us alive ❤</gray>"
84+
).build()
85+
),
86+
CustomCommand.of(
87+
"vote",
88+
List.of("votes"),
89+
Notice.builder()
90+
.actionBar("<yellow>★ Vote daily and earn rewards!</yellow>")
91+
.chat(
92+
" ",
93+
"<yellow>★ Vote for our server and receive daily rewards!",
94+
"<aqua>https://yourserver.com/vote</aqua>",
95+
" ",
96+
"<gray>Each vote helps us grow!</gray>"
97+
).build()
98+
),
99+
CustomCommand.of(
100+
"rules",
101+
List.of(),
102+
Notice.builder()
103+
.title("<red>📜 Server Rules</red>")
104+
.subtitle("<gray>Read before playing!</gray>")
105+
.times(Duration.ofMillis(300), Duration.ofSeconds(3), Duration.ofMillis(300))
106+
.actionBar("<red>No cheating, griefing or toxicity – be respectful!</red>")
107+
.chat(
108+
" ",
109+
"<red>📜 Server Rules:",
110+
"<aqua>https://yourserver.com/rules</aqua>",
111+
" ",
112+
"<gray>Breaking rules may result in a ban.</gray>"
113+
).build()
114+
),
115+
CustomCommand.of(
116+
"map",
117+
List.of("dynmap"),
118+
Notice.builder()
119+
.chat(
120+
" ",
121+
"<green>🗺 Live Server Map:",
122+
"<aqua>https://map.yourserver.com</aqua>",
123+
" ",
124+
"<gray>See what others are building in real time!</gray>"
125+
).build()
126+
)
127+
);
128+
129+
@Override
130+
public File getConfigFile(File dataFolder) {
131+
return new File(dataFolder, "custom-commands.yml");
132+
}
133+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.eternalcode.core.feature.customcommand;
2+
3+
import com.eternalcode.core.injector.annotations.Inject;
4+
import com.eternalcode.core.injector.annotations.component.Service;
5+
import com.eternalcode.core.notice.NoticeService;
6+
import dev.rollczi.litecommands.util.StringUtil;
7+
import java.lang.reflect.Field;
8+
import org.bukkit.Server;
9+
import org.bukkit.command.CommandMap;
10+
11+
@Service
12+
public class CustomCommandRegistry {
13+
14+
private static final String FALLBACK_PREFIX = "eternalcore";
15+
16+
private final CustomCommandConfig customCommandConfig;
17+
private final NoticeService noticeService;
18+
private final Server server;
19+
20+
private CommandMap commandMap;
21+
22+
@Inject
23+
public CustomCommandRegistry(CustomCommandConfig customCommandConfig, NoticeService noticeService, Server server) {
24+
this.customCommandConfig = customCommandConfig;
25+
this.noticeService = noticeService;
26+
this.server = server;
27+
28+
this.registerCustomCommands();
29+
}
30+
31+
public void registerCustomCommands() {
32+
for (CustomCommand customCommand : this.customCommandConfig.commands) {
33+
this.registerCustomCommand(customCommand);
34+
}
35+
}
36+
37+
private void registerCustomCommand(CustomCommand customCommand) {
38+
CustomCommandBukkitWrapper customCommandBukkitWrapper = new CustomCommandBukkitWrapper(
39+
customCommand.getName(),
40+
customCommand.getAliases(),
41+
this.noticeService,
42+
customCommand.getMessage()
43+
);
44+
45+
this.commandMap().register(FALLBACK_PREFIX, customCommandBukkitWrapper);
46+
}
47+
48+
CommandMap commandMap() {
49+
if (this.commandMap == null) {
50+
try {
51+
Field commandMapField = this.server.getClass().getDeclaredField("commandMap");
52+
commandMapField.setAccessible(true);
53+
54+
this.commandMap = (CommandMap) commandMapField.get(this.server);
55+
}
56+
catch (NoSuchFieldException | IllegalAccessException exception) {
57+
throw new RuntimeException(
58+
"Failed to get CommandMap from the server, this might be due to a server version incompatibility.",
59+
exception);
60+
}
61+
}
62+
63+
return this.commandMap;
64+
}
65+
}

0 commit comments

Comments
 (0)