Skip to content

Commit b09584d

Browse files
committed
Rework commands
1 parent 428dc40 commit b09584d

File tree

78 files changed

+3910
-1922
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+3910
-1922
lines changed

buildSrc/src/main/kotlin/bluemap.base.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ repositories {
1212
maven ("https://repo.bluecolored.de/releases") {
1313
content { includeGroupByRegex ("de\\.bluecolored.*") }
1414
}
15+
maven ("https://repo.bluecolored.de/snapshots") {
16+
content { includeGroupByRegex ("de\\.bluecolored.*") }
17+
}
1518
maven ("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") {
1619
content { includeGroup ("org.spigotmc") }
1720
}

common/build.gradle.kts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ plugins {
99
dependencies {
1010
api ( project( ":core" ) )
1111

12-
api ( libs.brigadier )
12+
api ( libs.adventure.api )
13+
api ( libs.bluecommands.core )
14+
15+
compileOnly ( libs.bluecommands.brigadier )
16+
compileOnly ( libs.brigadier )
1317

1418
compileOnly ( libs.jetbrains.annotations )
1519
compileOnly ( libs.lombok )

common/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ public synchronized Storage getOrLoadStorage(String storageId) throws Configurat
313313
return storage;
314314
}
315315

316+
public Map<String, Storage> getLoadedStorages() {
317+
return Collections.unmodifiableMap(storages);
318+
}
319+
316320
public @Nullable ResourcePack getResourcePack() {
317321
return resourcePack;
318322
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* This file is part of BlueMap, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package de.bluecolored.bluemap.common.commands;
26+
27+
import com.mojang.brigadier.exceptions.CommandSyntaxException;
28+
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
29+
import de.bluecolored.bluecommands.ParseFailure;
30+
import de.bluecolored.bluecommands.ParseResult;
31+
import de.bluecolored.bluecommands.brigadier.CommandExecutionHandler;
32+
import de.bluecolored.bluemap.common.plugin.Plugin;
33+
import de.bluecolored.bluemap.common.serverinterface.CommandSource;
34+
35+
import java.util.Comparator;
36+
37+
public class BrigadierExecutionHandler extends CommandExecutor implements CommandExecutionHandler<CommandSource, Object> {
38+
39+
public BrigadierExecutionHandler(Plugin plugin) {
40+
super(plugin);
41+
}
42+
43+
@Override
44+
public int handle(ParseResult<CommandSource, Object> parseResult) throws CommandSyntaxException {
45+
ExecutionResult executionResult = this.execute(parseResult);
46+
if (executionResult.parseFailure())
47+
return parseFailure(parseResult);
48+
return executionResult.resultCode();
49+
}
50+
51+
private int parseFailure(ParseResult<CommandSource, Object> result) throws CommandSyntaxException {
52+
ParseFailure<CommandSource, Object> failure = result.getFailures().stream()
53+
.max(Comparator.comparing(ParseFailure::getPosition))
54+
.orElseThrow(IllegalAccessError::new);
55+
throw new CommandSyntaxException(
56+
new SimpleCommandExceptionType(failure::getReason),
57+
failure::getReason,
58+
result.getInput(),
59+
failure.getPosition()
60+
);
61+
}
62+
63+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* This file is part of BlueMap, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package de.bluecolored.bluemap.common.commands;
26+
27+
import de.bluecolored.bluecommands.ParseMatch;
28+
import de.bluecolored.bluecommands.ParseResult;
29+
import de.bluecolored.bluemap.common.plugin.Plugin;
30+
import de.bluecolored.bluemap.common.serverinterface.CommandSource;
31+
import de.bluecolored.bluemap.core.BlueMap;
32+
import de.bluecolored.bluemap.core.logger.Logger;
33+
import lombok.RequiredArgsConstructor;
34+
import net.kyori.adventure.text.ComponentLike;
35+
36+
import java.util.Comparator;
37+
import java.util.concurrent.CompletableFuture;
38+
import java.util.concurrent.TimeUnit;
39+
40+
import static de.bluecolored.bluemap.common.commands.TextFormat.NEGATIVE_COLOR;
41+
import static net.kyori.adventure.text.Component.text;
42+
43+
@RequiredArgsConstructor
44+
public class CommandExecutor {
45+
46+
private final Plugin plugin;
47+
48+
public ExecutionResult execute(ParseResult<CommandSource, Object> parseResult) {
49+
if (parseResult.getMatches().isEmpty()) {
50+
51+
// check if the plugin is not loaded first
52+
if (!Commands.checkPluginLoaded(plugin, parseResult.getContext()))
53+
return new ExecutionResult(0, false);
54+
55+
return new ExecutionResult(0, true);
56+
}
57+
58+
ParseMatch<CommandSource, Object> match = parseResult.getMatches().stream()
59+
.max(Comparator.comparing(ParseMatch::getPriority))
60+
.orElseThrow(IllegalStateException::new);
61+
62+
if (!Commands.checkExecutablePreconditions(plugin, match.getContext(), match.getExecutable()))
63+
return new ExecutionResult(0, false);
64+
65+
return CompletableFuture.supplyAsync(match::execute, BlueMap.THREAD_POOL)
66+
.thenApply(result -> switch (result) {
67+
case Number n -> n.intValue();
68+
case ComponentLike c -> {
69+
match.getContext().sendMessage(c.asComponent());
70+
yield 1;
71+
}
72+
case Boolean b -> b ? 1 : 0;
73+
case null, default -> 1;
74+
})
75+
.exceptionally(e -> {
76+
Logger.global.logError("Command execution for '%s' failed".formatted(parseResult.getInput()), e);
77+
parseResult.getContext().sendMessage(text("There was an error executing this command! See logs or console for details.")
78+
.color(NEGATIVE_COLOR));
79+
return 0;
80+
})
81+
.completeOnTimeout(1, 100, TimeUnit.MILLISECONDS)
82+
.thenApply(code -> new ExecutionResult(code, false))
83+
.join();
84+
}
85+
86+
public record ExecutionResult (
87+
int resultCode,
88+
boolean parseFailure
89+
) {}
90+
91+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* This file is part of BlueMap, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package de.bluecolored.bluemap.common.commands;
26+
27+
import com.flowpowered.math.vector.Vector3d;
28+
import com.github.benmanes.caffeine.cache.Cache;
29+
import com.github.benmanes.caffeine.cache.Caffeine;
30+
import com.github.benmanes.caffeine.cache.LoadingCache;
31+
import de.bluecolored.bluecommands.*;
32+
import de.bluecolored.bluemap.common.commands.arguments.MapBackedArgumentParser;
33+
import de.bluecolored.bluemap.common.commands.arguments.StringSetArgumentParser;
34+
import de.bluecolored.bluemap.common.commands.commands.*;
35+
import de.bluecolored.bluemap.common.config.storage.StorageConfig;
36+
import de.bluecolored.bluemap.common.plugin.Plugin;
37+
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
38+
import de.bluecolored.bluemap.common.rendermanager.TileUpdateStrategy;
39+
import de.bluecolored.bluemap.common.serverinterface.CommandSource;
40+
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
41+
import de.bluecolored.bluemap.core.map.BmMap;
42+
import de.bluecolored.bluemap.core.world.World;
43+
import net.kyori.adventure.text.event.ClickEvent;
44+
import org.jetbrains.annotations.Nullable;
45+
46+
import java.util.Map;
47+
import java.util.Random;
48+
import java.util.Set;
49+
import java.util.stream.Stream;
50+
51+
import static de.bluecolored.bluemap.common.commands.TextFormat.*;
52+
import static net.kyori.adventure.text.Component.text;
53+
54+
public class Commands {
55+
56+
private static final Cache<String, RenderTask> REF_TO_RENDERTASK = Caffeine.newBuilder()
57+
.weakValues()
58+
.build();
59+
private static final LoadingCache<RenderTask, String> RENDERTASK_TO_REF = Caffeine.newBuilder()
60+
.weakKeys()
61+
.build(Commands::safeRandomRef);
62+
63+
public static de.bluecolored.bluecommands.Command<CommandSource, Object> create(Plugin plugin) {
64+
BlueCommands<CommandSource> builder = new BlueCommands<>();
65+
66+
builder.setArgumentParserForArgumentType(BmMap.class, new MapBackedArgumentParser<>("map", () ->
67+
plugin.isLoaded() ? plugin.getBlueMap().getMaps() : Map.of()));
68+
builder.setArgumentParserForArgumentType(StorageConfig.class, new MapBackedArgumentParser<>("storage", () ->
69+
plugin.isLoaded() ? plugin.getBlueMap().getConfig().getStorageConfigs() : Map.of()));
70+
builder.setArgumentParserForArgumentType(RenderTask.class, new MapBackedArgumentParser<>("render-task", REF_TO_RENDERTASK.asMap()));
71+
72+
builder.setArgumentParserForId("storage-id", new StringSetArgumentParser("storage", () ->
73+
plugin.isLoaded() ? plugin.getBlueMap().getConfig().getStorageConfigs().keySet() : Set.of()));
74+
75+
builder.setContextResolverForType(ServerWorld.class, c -> c.getWorld().orElse(null));
76+
builder.setContextResolverForType(World.class, c -> plugin.isLoaded() ? c.getWorld().map(plugin::getWorld).orElse(null) : null);
77+
builder.setContextResolverForType(Vector3d.class, c -> c.getPosition().orElse(null));
78+
79+
builder.setAnnotationContextPredicate(Permission.class, (permission, commandSource) ->
80+
permission == null || commandSource.hasPermission(permission.value())
81+
);
82+
builder.setAnnotationContextPredicate(WithWorld.class, (withWorld, commandSource) ->
83+
withWorld == null || plugin.isLoaded() && commandSource.getWorld().map(plugin::getWorld).isPresent()
84+
);
85+
builder.setAnnotationContextPredicate(WithPosition.class, (withPosition, commandSource) ->
86+
withPosition == null || commandSource.getPosition().isPresent()
87+
);
88+
89+
de.bluecolored.bluecommands.Command<CommandSource, Object> commands = new LiteralCommand<>("bluemap");
90+
91+
// register commands
92+
Stream.of(
93+
new DebugCommand(plugin),
94+
new FreezeCommand(plugin),
95+
new HelpCommand(plugin),
96+
new MapListCommand(plugin),
97+
new PurgeCommand(plugin),
98+
new ReloadCommand(plugin),
99+
new StartCommand(plugin),
100+
new StatusCommand(plugin),
101+
new StopCommand(plugin),
102+
new StoragesCommand(plugin),
103+
new TasksCommand(plugin),
104+
new TroubleshootCommand(plugin),
105+
new UnfreezeCommand(plugin),
106+
new VersionCommand(plugin)
107+
)
108+
.map(builder::createCommand)
109+
.forEach(commands::addSubCommand);
110+
111+
// register an update-command for each update-strategy
112+
Map.of(
113+
"update", TileUpdateStrategy.FORCE_NONE,
114+
"fix-edges", TileUpdateStrategy.FORCE_EDGE,
115+
"force-update", TileUpdateStrategy.FORCE_ALL
116+
).forEach((updateLiteral, strategy) -> {
117+
Command<CommandSource, Object> updateCommand = new LiteralCommand<>(updateLiteral);
118+
updateCommand.addSubCommand(builder.createCommand(new UpdateCommand(plugin, strategy)));
119+
commands.addSubCommand(updateCommand);
120+
});
121+
122+
return commands;
123+
}
124+
125+
public static String getRefForTask(RenderTask task) {
126+
return RENDERTASK_TO_REF.get(task);
127+
}
128+
129+
public static @Nullable RenderTask getTaskForRef(String ref) {
130+
return REF_TO_RENDERTASK.getIfPresent(ref);
131+
}
132+
133+
public static boolean checkExecutablePreconditions(Plugin plugin, CommandSource context, CommandExecutable<CommandSource, Object> executable) {
134+
if (executable instanceof MethodCommandExecutable<CommandSource> methodExecutable) {
135+
136+
// check if plugin needs to be loaded
137+
if (methodExecutable.getMethod().getAnnotation(Unloaded.class) == null) {
138+
return Commands.checkPluginLoaded(plugin, context);
139+
}
140+
141+
}
142+
143+
return true;
144+
}
145+
146+
public static boolean checkPluginLoaded(Plugin plugin, CommandSource context){
147+
if (!plugin.isLoaded()) {
148+
if (plugin.isLoading()) {
149+
context.sendMessage(lines(
150+
text("⌛ BlueMap is still loading!").color(INFO_COLOR),
151+
text("Please try again in a few seconds.").color(BASE_COLOR)
152+
));
153+
} else {
154+
context.sendMessage(lines(
155+
text("❌ BlueMap is not loaded!").color(NEGATIVE_COLOR),
156+
format("Check your server-console for errors or warnings and try using %.",
157+
command("/bluemap reload").color(HIGHLIGHT_COLOR)
158+
).color(BASE_COLOR)
159+
));
160+
}
161+
return false;
162+
}
163+
164+
return true;
165+
}
166+
167+
private static synchronized String safeRandomRef(RenderTask task) {
168+
String ref = randomRef();
169+
while (REF_TO_RENDERTASK.asMap().putIfAbsent(ref, task) != null) ref = randomRef();
170+
return ref;
171+
}
172+
173+
private static String randomRef() {
174+
StringBuilder ref = new StringBuilder(Integer.toString(Math.abs(new Random().nextInt()), 16));
175+
while (ref.length() < 4) ref.insert(0, "0");
176+
return ref.subSequence(0, 4).toString();
177+
}
178+
179+
}

0 commit comments

Comments
 (0)