Skip to content

Commit 2e05d6d

Browse files
committed
Add SurfCanvas config
1 parent 4459f20 commit 2e05d6d

3 files changed

Lines changed: 296 additions & 0 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package dev.slne.surf;
2+
3+
import io.canvasmc.canvas.configuration.*;
4+
import org.slf4j.*;
5+
6+
import java.nio.file.*;
7+
8+
public class SurfCanvasGlobalConfiguration extends Part {
9+
private static final Path CONFIG_PATH = Path.of("config", "surf-canvas-server.yml").toAbsolutePath().normalize();
10+
11+
protected static final int CHAR_LIM = 90;
12+
13+
public static final Logger LOGGER = LoggerFactory.getLogger("SurfCanvas");
14+
15+
public static final int INFO = 0;
16+
public static final int WARN = 1;
17+
public static final int ERROR = 2;
18+
19+
private static SurfCanvasGlobalConfiguration instance;
20+
21+
static {
22+
reload();
23+
}
24+
25+
public static void reload() {
26+
LOGGER.info("Loading SurfCanvas server configuration...");
27+
ConfigurationProvider.buildSolidConfiguration(
28+
CONFIG_PATH,
29+
SurfCanvasGlobalConfiguration::new,
30+
CHAR_LIM,
31+
new io.canvasmc.canvas.configuration.Resolver<SurfCanvasGlobalConfiguration>() {
32+
@Override
33+
public void onDiffAdd(String fullyQualifiedName) {
34+
LOGGER.info("Added new server-wide configuration option: \"{}\"", fullyQualifiedName);
35+
}
36+
37+
@Override
38+
public void onDiffRemove(String fullyQualifiedName) {
39+
LOGGER.info("Server-wide configuration option \"{}\" no longer exists and is now removed.", fullyQualifiedName);
40+
}
41+
42+
@Override
43+
public void onFinishLoad(dev.slne.surf.SurfCanvasGlobalConfiguration instance) {
44+
postLoad(instance);
45+
}
46+
},
47+
Style.create()
48+
.literal("Global configuration for SurfCanvas").endLine()
49+
.blank()
50+
.wordWrap(
51+
"This is the server-wide configuration file provided by SurfCanvas. This config holds options",
52+
"that are set across the entire server, and cannot be overridden per-world. You are free to modify,",
53+
"add, or remove comments as you please."
54+
).endLine()
55+
.blank()
56+
.wordWrap(
57+
"You may refresh this configuration at runtime using the \"/surfcanvas reload\" command, however",
58+
"it is not recommended to do this during production, as this can cause issues like unexpected crashes",
59+
"or unintended behavior."
60+
).endLine()
61+
.blank()
62+
.wordWrap(
63+
"If you have questions about certain configuration options please think for yourself"
64+
).endLine()
65+
.compile(60)
66+
);
67+
}
68+
69+
private static void postLoad(SurfCanvasGlobalConfiguration instance) {
70+
SurfCanvasGlobalConfiguration.instance = instance;
71+
Validator.validateObject(instance);
72+
}
73+
74+
public static SurfCanvasGlobalConfiguration getInstance() {
75+
return instance;
76+
}
77+
78+
79+
public PluginConfiguration plugin = new PluginConfiguration();
80+
public static class PluginConfiguration extends Part {
81+
{
82+
option("shutdown").docs("Shutdown related configuration for plugin shutdown process");
83+
}
84+
85+
public ShutdownConfiguration shutdown = new ShutdownConfiguration();
86+
87+
public static class ShutdownConfiguration extends Part {
88+
{
89+
option("timeout")
90+
.docs("The maximum amount of time (in seconds) to wait for a single plugin to shutdown before giving up.")
91+
.greaterThanOrEqualTo(1);
92+
}
93+
94+
public int timeout = 60;
95+
}
96+
}
97+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package dev.slne.surf.command;
2+
3+
import com.mojang.brigadier.CommandDispatcher;
4+
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
5+
import dev.slne.surf.command.sub.SurfCanvasReloadCommand;
6+
import io.canvasmc.canvas.command.*;
7+
import net.kyori.adventure.text.*;
8+
import net.kyori.adventure.text.event.ClickEvent;
9+
import net.kyori.adventure.text.event.HoverEvent;
10+
import net.kyori.adventure.text.format.*;
11+
import net.minecraft.commands.CommandSourceStack;
12+
import org.bukkit.command.CommandSender;
13+
import org.jspecify.annotations.*;
14+
15+
import java.util.*;
16+
17+
import static net.minecraft.commands.Commands.literal;
18+
19+
@NullMarked
20+
public class SurfCanvasRootCommandTree {
21+
22+
private static final TextColor HEADER = TextColor.color(47, 140, 94);
23+
private static final TextColor PRIMARY = TextColor.color(32, 143, 89);
24+
private static final TextColor SECONDARY = TextColor.color(20, 123, 116);
25+
private static final TextColor INFORMATION = TextColor.color(79, 143, 47);
26+
private static final TextColor LIST = TextColor.color(21, 96, 68);
27+
private static final TextColor ACCENT = TextColor.color(47, 140, 94);
28+
private static final TextColor MUTED = TextColor.color(80, 120, 100);
29+
30+
public static final SurfCanvasRootCommandTree INSTANCE;
31+
32+
static {
33+
INSTANCE = new SurfCanvasRootCommandTree();
34+
INSTANCE.register(SurfCanvasReloadCommand.class);
35+
}
36+
37+
private final List<Command> subCommands = new LinkedList<>();
38+
39+
private Component buildDetailComponent(Command subCommand) {
40+
String name = subCommand.getName();
41+
String description = subCommand.getDescription();
42+
boolean selfCmd = subCommand.isAllowedSelfCommand();
43+
44+
TextComponent.Builder builder = Component.text()
45+
.append(Component.text("----", SECONDARY))
46+
.append(Component.text("/surfcanvas " + name, HEADER).decorate(TextDecoration.BOLD))
47+
.append(Component.text("----", SECONDARY))
48+
.appendNewline()
49+
.appendNewline();
50+
51+
builder.append(Component.text(" Description ", MUTED).decorate(TextDecoration.BOLD))
52+
.append(Component.text(description != null ? description : "No description provided.", ACCENT))
53+
.appendNewline();
54+
55+
builder.append(Component.text(" Permission ", MUTED).decorate(TextDecoration.BOLD))
56+
.append(Component.text("surfcanvas.command." + name, ACCENT))
57+
.appendNewline();
58+
59+
builder.append(Component.text(" Standalone ", MUTED).decorate(TextDecoration.BOLD))
60+
.append(selfCmd
61+
? Component.text("Yes ", TextColor.color(100, 220, 140)).append(Component.text("(/" + name + ", /surfcanvas:" + name + ")", INFORMATION))
62+
: Component.text("No", TextColor.color(220, 100, 100)))
63+
.appendNewline()
64+
.appendNewline();
65+
66+
builder.append(Component.text("-----------------------", SECONDARY));
67+
68+
return builder.build();
69+
}
70+
71+
public void build(CommandDispatcher<CommandSourceStack> dispatcher) {
72+
LiteralArgumentBuilder<CommandSourceStack> root = literal("surfcanvas")
73+
.requires(source -> source.getSender().isOp() || source.getSender().hasPermission("surfcanvas.command"));
74+
75+
for (Command subCommand : subCommands) {
76+
String name = subCommand.getName();
77+
78+
root.then(subCommand.construct(literal(name)
79+
.requires(source -> source.getSender().isOp() || source.getSender().hasPermission("surfcanvas.command." + name))));
80+
81+
if (subCommand.isAllowedSelfCommand()) {
82+
dispatcher.register(subCommand.construct(literal(name)
83+
.requires(source -> source.getSender().isOp() || source.getSender().hasPermission("surfcanvas.command." + name))));
84+
85+
dispatcher.register(subCommand.construct(literal("surfcanvas:" + name)
86+
.requires(source -> source.getSender().isOp() || source.getSender().hasPermission("surfcanvas.command." + name))));
87+
}
88+
}
89+
90+
root.then(literal("help")
91+
.requires(source -> source.getSender().isOp() || source.getSender().hasPermission("surfcanvas.command.help"))
92+
.executes(context -> {
93+
CommandSender bukkitSender = context.getSource().getBukkitSender();
94+
95+
TextComponent.Builder builder = Component.text()
96+
.append(Component.text("----", SECONDARY))
97+
.append(Component.text("SurfCanvas Commands", HEADER).decorate(TextDecoration.BOLD))
98+
.append(Component.text("----", SECONDARY))
99+
.appendNewline();
100+
101+
for (Command subCommand : subCommands) {
102+
String name = subCommand.getName();
103+
if (!bukkitSender.hasPermission("surfcanvas.command." + name)) {
104+
continue;
105+
}
106+
107+
Component hoverText = Component.text()
108+
.append(Component.text("Click to view further details", INFORMATION))
109+
.build();
110+
111+
Component detailComponent = buildDetailComponent(subCommand);
112+
113+
Component entry = Component.text()
114+
.append(Component.text("- ").color(LIST))
115+
.append(Component.text("/").color(SECONDARY))
116+
.append(Component.text(name, PRIMARY)
117+
.decorate(TextDecoration.UNDERLINED)
118+
.hoverEvent(HoverEvent.showText(hoverText))
119+
.clickEvent(ClickEvent.callback((audience) -> audience.sendMessage(detailComponent))))
120+
.appendNewline()
121+
.build();
122+
123+
builder.append(entry);
124+
}
125+
126+
builder.append(Component.text("-----------------------", SECONDARY));
127+
128+
bukkitSender.sendMessage(builder.build());
129+
return 1;
130+
}));
131+
dispatcher.register(root);
132+
}
133+
134+
public void register(Class<? extends Command> command) {
135+
try {
136+
if (command.getDeclaredConstructor().getParameterCount() != 0) {
137+
throw new IllegalArgumentException("Command must have no-arg constructor");
138+
}
139+
this.subCommands.add(
140+
command.getDeclaredConstructor().newInstance()
141+
);
142+
} catch (InstantiationException | IllegalAccessException | java.lang.reflect.InvocationTargetException |
143+
NoSuchMethodException e) {
144+
throw new RuntimeException(e);
145+
}
146+
}
147+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package dev.slne.surf.command.sub;
2+
3+
import com.mojang.brigadier.builder.*;
4+
import dev.slne.surf.*;
5+
import io.canvasmc.canvas.command.*;
6+
import net.minecraft.commands.*;
7+
import net.minecraft.network.chat.*;
8+
import net.minecraft.util.*;
9+
import org.jspecify.annotations.*;
10+
11+
@NullMarked
12+
public class SurfCanvasReloadCommand implements Command {
13+
@Override
14+
public String getName() {
15+
return "reload";
16+
}
17+
18+
@Override
19+
public @Nullable String getDescription() {
20+
return "Reloads SurfCanvas configuration";
21+
}
22+
23+
@Override
24+
public boolean isAllowedSelfCommand() {
25+
return false;
26+
}
27+
28+
@Override
29+
public LiteralArgumentBuilder<CommandSourceStack> construct(LiteralArgumentBuilder<CommandSourceStack> base) {
30+
return base.executes(context -> {
31+
context.getSource().sendSystemMessage(
32+
Component.literal("Some configuration options cannot be changed at runtime or may work incorrectly after reloading.")
33+
.withColor(CommonColors.RED)
34+
);
35+
context.getSource().sendSystemMessage(
36+
Component.literal("This command is unsupported. If you encounter issues, please run /stop")
37+
.withColor(CommonColors.RED)
38+
);
39+
40+
long start = System.nanoTime();
41+
SurfCanvasGlobalConfiguration.reload();
42+
43+
context.getSource()
44+
.sendSystemMessage(
45+
Component.literal("Reloaded all SurfCanvas solid and patch configurations in " + String.format("%.2f", ((System.nanoTime() - start) / 1e+6)) + "ms")
46+
.withColor(CommonColors.GREEN)
47+
);
48+
49+
return 1;
50+
});
51+
}
52+
}

0 commit comments

Comments
 (0)