This commit is contained in:
kento2 2026-08-10 00:31:28 +02:00
parent 13ced30434
commit a9b7f47494
35 changed files with 1024 additions and 123 deletions

View file

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="run dev server" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="bazel run //dev-server:dev" />
<option name="SCRIPT_TEXT" value="bazel run //build:dev" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />

View file

@ -27,12 +27,14 @@ maven.install(
"org.postgresql:postgresql:42.7.12",
"org.mongodb:bson:5.8.0",
"io.nats:jnats:2.25.2",
"de.kentoj.scrow:kencommandapi-core:0.36-SNAPSHOT",
"de.kentoj.scrow:kencommandapi-bukkit:0.36-SNAPSHOT",
"de.kentoj.scrow:kencommandapi-velocity:0.36-SNAPSHOT",
"de.kentoj.scrow:kencommandapi-core:0.39-SNAPSHOT",
"de.kentoj.scrow:kencommandapi-bukkit:0.39-SNAPSHOT",
"de.kentoj.scrow:kencommandapi-velocity:0.39-SNAPSHOT",
"io.papermc.paper:paper-api:[26.2.build,)",
"net.luckperms:api:5.5",
"com.google.code.gson:gson:2.14.0",
],
fetch_sources = True,
repositories = [
"https://repo.papermc.io/repository/maven-public/",
"https://git.lab0x13.site/api/packages/scrow/maven",

0
buildserver/BUILD Normal file
View file

View file

@ -0,0 +1,26 @@
load("@rules_jvm_external//:defs.bzl", "artifact")
load("@rules_java//java:java_binary.bzl", "java_binary")
java_binary(
name = "_plugin",
srcs = glob(["main/java/**/*.java"]),
create_executable = False,
resources = glob(["main/resources/**"]),
resource_strip_prefix = "buildserver/configurator/main/resources",
deps = [
"//core/bukkit:api",
artifact("com.google.code.gson:gson"),
artifact("io.papermc.paper:paper-api"),
],
add_exports = [
artifact("com.google.code.gson:gson"),
],
)
genrule(
name = "plugin",
srcs = [":_plugin_deploy.jar"],
outs = ["plugin.jar"],
cmd = "cp $< $@",
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,40 @@
# configurator
helper for generating json configs interactively.
Plugins that hook into this plugin can register configuration nodes like "lobby.spawnpoint" with type Location.
server operators can then go ingame and do `/cfg key lobby.spawnpoint` to set a spawn location and then
`/cfg export` ig
## TODO
- lists
- exporting
- proper namespaces
- split into api and impl
- more types(region pickers especially; also itemstacks)
- type variants like location without yaw/pitch
## design
in json we have primitives(like "hi", 1, false"), structures(like {"foo":"bar}) and lists(like []).
everything consists of primitives eventually so we can go from
```json
{
"arenas": [
{
"name": "skyfall",
"minPlayers": 3,
"maxPlayers": 8
}
]
}
```
to
```text
arenas.0.name = Primitive("skyfall")
arenas.0.minPlayers = Primitive(3)
arenas.0.maxPlayers = Primitive(8)
```
We can define a complex type as "type has a string field called name" and it will just use

View file

@ -0,0 +1,18 @@
package site.lab0x13.scrow.configurator;
import de.kentoj.scrow.bukkit.ScrowAPI;
import org.bukkit.plugin.java.JavaPlugin;
import site.lab0x13.scrow.configurator.command.ConfigCommand;
import site.lab0x13.scrow.configurator.node.ConfigRootNode;
import site.lab0x13.scrow.configurator.node.ConfigValueNode;
import site.lab0x13.scrow.configurator.type.location.LocationConfigType;
public class ConfiguratorPlugin extends JavaPlugin {
@Override
public void onEnable() {
ConfigRootNode nodeRegistry = new ConfigRootNode();
nodeRegistry.register("spawnLocation", new ConfigValueNode<>(LocationConfigType.INSTANCE));
ScrowAPI.playerCommands().register(new ConfigCommand(nodeRegistry).rootLiteral());
}
}

View file

@ -0,0 +1,26 @@
package site.lab0x13.scrow.configurator.action;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import org.bukkit.entity.Player;
import java.util.List;
public interface ConfigOptionAction<T> {
/* FIXME not too clean */
List<ConfigOptionAction<?>> GLOBAL_ACTIONS = List.of(new GlobalShowAction<>());
String name();
List<CommandArgumentSpec<Player, ?>> arguments();
boolean requiresValue();
void execute(
ScrowMessageStyle style,
T node,
CommandContext<Player> ctx
);
}

View file

@ -0,0 +1,43 @@
package site.lab0x13.scrow.configurator.action;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import org.bukkit.entity.Player;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import java.util.List;
public final class GlobalShowAction<S extends ConfigNode<S>> implements ConfigOptionAction<S> {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
@Override
public String name() {
return "show";
}
@Override
public List<CommandArgumentSpec<Player, ?>> arguments() {
return List.of();
}
@Override
public boolean requiresValue() {
return true;
}
@Override
public void execute(ScrowMessageStyle style, S node, CommandContext<Player> ctx) {
var jsonElement = node.type().serialize(node.value());
var prettyJson = GSON.toJson(jsonElement, JsonElement.class);
var component = Component.text(prettyJson + " [click to copy]")
.clickEvent(ClickEvent.copyToClipboard(prettyJson));
ctx.sender().sendMessage(style.ok(component));
}
}

View file

@ -0,0 +1,10 @@
package site.lab0x13.scrow.configurator.action;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public interface ValuePicker<T> {
CompletableFuture<T> pick(Player player);
}

View file

@ -0,0 +1,25 @@
package site.lab0x13.scrow.configurator.command;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.literal.RootLiteral;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.entity.Player;
import site.lab0x13.scrow.configurator.node.ConfigRootNode;
import site.lab0x13.scrow.configurator.noderegistry.ConfigNodeWalker;
public final class ConfigCommand {
private final RootLiteral<Player> rootLiteral;
public ConfigCommand(ConfigRootNode nodeRegistry) {
var style = new ScrowMessageStyle("config", TextColor.color(0x28B088));
rootLiteral = Literal.<Player>builder("config", "cfg")
.withPermission("command.config")
.withLiteral(new KeyLiteral(style, new ConfigNodeWalker(nodeRegistry)).literal())
.asRootLiteral(style);
}
public RootLiteral<Player> rootLiteral() {
return rootLiteral;
}
}

View file

@ -0,0 +1,53 @@
package site.lab0x13.scrow.configurator.command;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.ArgumentType;
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
import org.bukkit.entity.Player;
import site.lab0x13.scrow.configurator.node.ConfigListNode;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import site.lab0x13.scrow.configurator.node.ConfigRootNode;
import site.lab0x13.scrow.configurator.node.SubFielded;
import site.lab0x13.scrow.configurator.noderegistry.ConfigNodeWalker;
import java.util.Collections;
import java.util.stream.IntStream;
public class ConfigNodeArgumentType implements ArgumentType<Player, ConfigNode<?>> {
private final ConfigNodeWalker nodeWalker;
public ConfigNodeArgumentType(ConfigRootNode rootNode) {
nodeWalker = new ConfigNodeWalker(rootNode);
}
@Override
public Result<ConfigNode<?>, String> parseInputUnchecked(String input) throws IllegalArgumentException {
assert input != null;
var res = nodeWalker.walk(input);
if (res.error() != null)
Results.failure(res.error());
assert res.node() != null;
return Results.success(res.node());
}
@Override
public SuggestionProvider<Player> getDefaultSuggestionProvider() {
return (_, input) -> {
var res = nodeWalker.walk(input);
switch (res.type()) {
case NO_FIELDS, SUCCESS -> Collections.emptyList();
case NO_SUCH_FIELD, INDEX_OUT_OF_RANGE, INDEX_EXPECTED -> {
if (res.node() instanceof ConfigListNode<?> list)
return IntStream.range(0, list.size()).mapToObj(Integer::toString).toList();
if (res.node() instanceof SubFielded subFielded)
return subFielded.fields().keySet().stream()
.map(it -> res.parentKey() + "." + it)
.toList();
}
}
return Collections.emptyList();
};
}
}

View file

@ -0,0 +1,58 @@
package site.lab0x13.scrow.configurator.command;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.literal.Literal;
import de.kentoj.kencommandapi.api.literal.LiteralBuilder;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import org.bukkit.entity.Player;
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import site.lab0x13.scrow.configurator.node.ConfigValueNode;
import site.lab0x13.scrow.configurator.noderegistry.ConfigNodeWalker;
import java.util.ArrayList;
import java.util.List;
class KeyLiteral {
private final ScrowMessageStyle style;
private final Literal<Player> literal;
KeyLiteral(ScrowMessageStyle style, ConfigNodeWalker nodeWalker) {
this.style = style;
var builder = Literal.<Player>builder("key");
nodeWalker.getAllNodes().forEach((key, node) ->
addNodeLiteral(builder, key, node));
literal = builder.build();
}
private void addNodeLiteral(LiteralBuilder<Player> builder, String key, ConfigNode<?> node) {
var nodeLiteralBuilder = Literal.<Player>builder(key);
addNodeActionLiteral(nodeLiteralBuilder, (ConfigNode<Object>) node, new ArrayList<>(node.type().actions()));
addNodeActionLiteral(nodeLiteralBuilder, (ConfigNode<Object>) node, ConfigOptionAction.GLOBAL_ACTIONS);
builder.withLiteral(nodeLiteralBuilder.build());
}
private void addNodeActionLiteral(LiteralBuilder<Player> builder, ConfigNode<Object> node, List<ConfigOptionAction<?>> actions) {
for (var _action : actions) {
var action = (ConfigOptionAction<ConfigNode<Object>>) _action;
var actionLiteralBuilder = Literal.<Player>builder(action.name());
for (var argument : action.arguments())
actionLiteralBuilder.withArgument(argument);
actionLiteralBuilder.withSyncExecutor(ctx -> {
if (action.requiresValue() && node.value() == null) {
ctx.sender().sendMessage(style.err("No value set."));
return Results.success(new Object());
}
action.execute(style, node, ctx);
return Results.success(new Object());
});
builder.withLiteral(actionLiteralBuilder.build());
}
}
public Literal<Player> literal() {
return literal;
}
}

View file

@ -0,0 +1,54 @@
package site.lab0x13.scrow.configurator.node;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import site.lab0x13.scrow.configurator.type.ConfigType;
import java.util.ArrayList;
import java.util.List;
public final class ConfigListNode<T>
extends ArrayList<ConfigNode<T>>
implements ConfigNode<List<ConfigNode<T>>>, List<ConfigNode<T>> {
private final ConfigType<ConfigNode<T>> elementType;
public ConfigListNode(ConfigType<ConfigNode<T>> elementType) {
this.elementType = elementType;
}
public ConfigType<ConfigNode<T>> elementType() {
return this.elementType;
}
@Override
public ConfigType<List<ConfigNode<T>>> type() {
return new ConfigType<>() {
@Override
public JsonElement serialize(List<ConfigNode<T>> value) {
var list = new JsonArray();
forEach(n -> list.add(n.type().serialize(n.value())));
return list;
}
@Override
public List<ConfigNode<T>> deserialize(JsonElement json) {
var res = new ArrayList<ConfigNode<T>>();
var list = json.getAsJsonArray();
list.forEach(e -> res.add(elementType.deserialize(e)));
return res;
}
};
}
@Override
public List<ConfigNode<T>> value() {
return this;
}
@Override
public void value(List<ConfigNode<T>> value) {
this.clear();
this.addAll(value);
}
}

View file

@ -0,0 +1,11 @@
package site.lab0x13.scrow.configurator.node;
import site.lab0x13.scrow.configurator.type.ConfigType;
public sealed interface ConfigNode<T> permits ConfigValueNode, ConfigListNode, ConfigRootNode {
ConfigType<T> type();
T value();
void value(T value);
}

View file

@ -0,0 +1,43 @@
package site.lab0x13.scrow.configurator.node;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.type.ConfigMapType;
import site.lab0x13.scrow.configurator.type.ConfigType;
import java.util.HashMap;
import java.util.Map;
public final class ConfigRootNode implements ConfigNode<Map<String, ConfigNode<?>>>, SubFielded {
private final ConfigMapType<ConfigNode<?>> type = new ConfigMapType<>(new HashMap<>());
private Map<String, ConfigNode<?>> value = new HashMap<>();
public void register(String name, ConfigNode<?> node) {
value.put(name, node);
}
@Override
public ConfigType<Map<String, ConfigNode<?>>> type() {
return type;
}
@Override
public Map<String, ConfigNode<?>> value() {
return value;
}
@Override
public void value(Map<String, ConfigNode<?>> value) {
this.value = value;
}
@Override
public Map<String, ConfigNode<?>> fields() {
return value;
}
@Override
public @Nullable ConfigNode<?> getField(String name) {
return value.get(name);
}
}

View file

@ -0,0 +1,43 @@
package site.lab0x13.scrow.configurator.node;
import site.lab0x13.scrow.configurator.type.ConfigType;
public final class ConfigValueNode<T> implements ConfigNode<T> {
private final ConfigType<T> type;
private final T defaultValue;
private T value;
public ConfigValueNode(ConfigType<T> type, T defaultValue) {
this.type = type;
this.defaultValue = defaultValue;
this.value = defaultValue;
}
public ConfigValueNode(ConfigType<T> type) {
this(type, null);
}
public void reset() {
this.value = defaultValue;
}
public T defaultValue() {
return defaultValue;
}
public T value() {
return value;
}
public void value(T value) {
this.value = value;
}
public boolean required() {
return defaultValue == null;
}
public ConfigType<T> type() {
return type;
}
}

View file

@ -0,0 +1,11 @@
package site.lab0x13.scrow.configurator.node;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public interface SubFielded {
Map<String, ConfigNode<?>> fields();
@Nullable ConfigNode<?> getField(String name);
}

View file

@ -0,0 +1,102 @@
package site.lab0x13.scrow.configurator.noderegistry;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.node.ConfigListNode;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import site.lab0x13.scrow.configurator.node.ConfigRootNode;
import site.lab0x13.scrow.configurator.node.SubFielded;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public final class ConfigNodeWalker {
private final ConfigRootNode rootNode;
public ConfigNodeWalker(ConfigRootNode rootNode) {
this.rootNode = rootNode;
}
public Map<String, ConfigNode<?>> getAllNodes() {
var result = new HashMap<String, ConfigNode<?>>();
processNode("", result, rootNode);
return result;
}
private void processNode(String key, Map<String, ConfigNode<?>> result, ConfigNode<?> node) {
result.put(key, node);
if (node instanceof ConfigListNode<?> list) {
for (int i = 0; i < list.size(); i++) {
var element = list.get(i);
processNode(key + "." + i, result, element);
}
} else {
/* FIXME not clean at all */
SubFielded sf = null;
if (node instanceof SubFielded n)
sf = n;
else if (node.type() instanceof SubFielded n)
sf = n;
if (sf != null)
sf.fields().forEach((name, v) ->
processNode(key + "." + name, result, v));
}
}
public WalkResult walk(String key) {
return walk(Arrays.stream(key.split("\\.")).iterator());
}
public WalkResult walk(Iterator<String> iter) {
var cursor = new Cursor(iter);
ConfigNode<?> cur = rootNode;
String token;
while ((token = cursor.nextToken()) != null) {
if (token.isEmpty()) continue;
if (cur instanceof ConfigListNode<?> list) {
int index;
try {
index = Integer.parseInt(token);
} catch (NumberFormatException _) {
return new WalkResult(WalkResult.Type.INDEX_EXPECTED, cursor.key(), cur);
}
if (index < 0 || index >= list.size())
return new WalkResult(WalkResult.Type.INDEX_OUT_OF_RANGE, cursor.key(), cur);
cur = list.get(index);
} else if (cur.type() instanceof SubFielded subFielded) {
cur = subFielded.getField(token);
if (cur == null)
return new WalkResult(WalkResult.Type.NO_SUCH_FIELD, cursor.key(), cur);
} else {
return new WalkResult(WalkResult.Type.NO_FIELDS, cursor.key(), cur);
}
}
return new WalkResult(WalkResult.Type.SUCCESS, cursor.key(), cur);
}
private static class Cursor {
private String key = "";
private final Iterator<String> iter;
private Cursor(Iterator<String> iter) {
this.iter = iter;
}
public @Nullable String nextToken() {
if (!iter.hasNext()) return null;
var token = iter.next();
key += "." + token;
key = key.replaceFirst("\\.", "");
return token;
}
public String key() {
return key;
}
}
}

View file

@ -0,0 +1,35 @@
package site.lab0x13.scrow.configurator.noderegistry;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import java.util.function.Function;
public record WalkResult(
Type type,
String key,
@Nullable ConfigNode<?> node
) {
public String parentKey() {
var dotInd = key.lastIndexOf('.');
return dotInd < 0 ? "" : key.substring(0, dotInd - 1);
}
public @Nullable String error() {
return type().message.apply(this);
}
public enum Type {
INDEX_EXPECTED(r -> "Numeric index expected at " + r.key() + "."),
INDEX_OUT_OF_RANGE(_ -> "Index out of range."),
NO_FIELDS(r -> r.key() + " has no fields."),
NO_SUCH_FIELD(r -> "Invalid key " + r.key()),
SUCCESS(_ -> null);
private final Function<WalkResult, String> message;
Type(Function<WalkResult, String> message) {
this.message = message;
}
}
}

View file

@ -0,0 +1,52 @@
package site.lab0x13.scrow.configurator.type;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.node.SubFielded;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import java.util.HashMap;
import java.util.Map;
public abstract class ConfigComplexType<T> implements ConfigType<T>, SubFielded {
private final Map<String, ConfigNode<?>> entries = new HashMap<>();
protected abstract T deserialize(JsonObject obj);
protected void addEntry(String name, ConfigNode<?> node) {
entries.put(name, node);
}
@SuppressWarnings("unchecked")
protected <S> S readNode(JsonObject object, String name) {
var node = (ConfigNode<Object>) entries.get(name);
return (S) node.type().deserialize(object.get(name));
}
@Override
public T deserialize(JsonElement json) {
return deserialize(json.getAsJsonObject());
}
@Override
public JsonElement serialize(T value) {
var obj = new JsonObject();
entries.forEach((name, node) -> writeNode(obj, name, node));
return obj;
}
private static <S> void writeNode(JsonObject object, String name, ConfigNode<S> node) {
var value = node.type().serialize(node.value());
object.add(name, value);
}
public Map<String, ConfigNode<?>> fields() {
return new HashMap<>(entries);
}
public @Nullable ConfigNode<?> getField(String name) {
return entries.get(name);
}
}

View file

@ -0,0 +1,54 @@
package site.lab0x13.scrow.configurator.type;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import site.lab0x13.scrow.configurator.node.SubFielded;
import java.util.HashMap;
import java.util.Map;
public class ConfigMapType<T> implements ConfigType<Map<String, T>>, SubFielded {
private final Map<String, ConfigType<?>> types;
public ConfigMapType(Map<String, ConfigType<?>> types) {
this.types = types;
}
@SuppressWarnings("unchecked")
@Override
public Map<String, T> deserialize(JsonElement json) {
var result = new HashMap<String, T>();
var obj = json.getAsJsonObject();
types.forEach((k, v) -> {
var type = (ConfigType<Object>) v;
var value = (T) type.deserialize(obj.get(k));
result.put(k, value);
});
return result;
}
@SuppressWarnings("unchecked")
@Override
public JsonElement serialize(Map<String, T> value) {
var res = new JsonObject();
types.forEach((k, v) -> {
var type = (ConfigType<T>) v;
var serialized = type.serialize(value.get(k));
res.add(k, serialized);
});
return res;
}
@Override
public Map<String, ConfigNode<?>> fields() {
return Map.of();
}
@Override
public @Nullable ConfigNode<?> getField(String name) {
return null;
}
}

View file

@ -0,0 +1,45 @@
package site.lab0x13.scrow.configurator.type;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import java.math.BigInteger;
import java.util.function.Function;
public class ConfigPrimitiveType<T> implements ConfigType<T> {
public static final ConfigPrimitiveType<Double> DOUBLE =
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsDouble);
public static final ConfigPrimitiveType<Integer> INT =
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsInt);
public static final ConfigPrimitiveType<Float> FLOAT =
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsFloat);
public static final ConfigPrimitiveType<Long> LONG =
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsLong);
public static final ConfigPrimitiveType<BigInteger> BIG_INT =
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsBigInteger);
public static final ConfigPrimitiveType<Boolean> BOOL =
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsBoolean);
public static final ConfigPrimitiveType<String> STRING =
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsString);
public static final ConfigPrimitiveType<Byte> BYTE =
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsByte);
private final Function<T, JsonPrimitive> serializer;
private final Function<JsonPrimitive, T> deserializer;
private ConfigPrimitiveType(Function<T, JsonPrimitive> serializer, Function<JsonPrimitive, T> deserializer) {
this.serializer = serializer;
this.deserializer = deserializer;
}
@Override
public JsonElement serialize(T value) {
return serializer.apply(value);
}
@Override
public T deserialize(JsonElement json) {
return deserializer.apply(json.getAsJsonPrimitive());
}
}

View file

@ -0,0 +1,28 @@
package site.lab0x13.scrow.configurator.type;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import java.util.Collections;
import java.util.List;
public interface ConfigType<T> {
default List<ConfigOptionAction<?>> actions() {
return Collections.emptyList();
}
JsonElement serialize(T value);
T deserialize(JsonElement json);
default String toJson(T value) {
return serialize(value).toString();
}
default T fromJson(String json) {
return deserialize(JsonParser.parseString(json));
}
}

View file

@ -0,0 +1,46 @@
package site.lab0x13.scrow.configurator.type.location;
import com.google.gson.JsonObject;
import org.bukkit.Location;
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import site.lab0x13.scrow.configurator.node.ConfigValueNode;
import site.lab0x13.scrow.configurator.type.ConfigComplexType;
import site.lab0x13.scrow.configurator.type.ConfigPrimitiveType;
import site.lab0x13.scrow.configurator.type.world.WorldConfigType;
import java.util.List;
public class LocationConfigType extends ConfigComplexType<Location> {
public static final LocationConfigType INSTANCE = new LocationConfigType();
private LocationConfigType() {
addEntry("world", new ConfigValueNode<>(WorldConfigType.INSTANCE));
addEntry("x", new ConfigValueNode<>(ConfigPrimitiveType.DOUBLE));
addEntry("y", new ConfigValueNode<>(ConfigPrimitiveType.DOUBLE));
addEntry("z", new ConfigValueNode<>(ConfigPrimitiveType.DOUBLE));
addEntry("yaw", new ConfigValueNode<>(ConfigPrimitiveType.FLOAT));
addEntry("pitch", new ConfigValueNode<>(ConfigPrimitiveType.FLOAT));
}
@Override
public List<ConfigOptionAction<?>> actions() {
return List.of(
new LocationTeleportAction(),
new LocationPickCurrentAction()
);
}
@Override
public Location deserialize(JsonObject obj) {
return new Location(
readNode(obj, "world"),
readNode(obj, "x"),
readNode(obj, "y"),
readNode(obj, "z"),
readNode(obj, "yaw"),
readNode(obj, "pitch")
);
}
}

View file

@ -0,0 +1,24 @@
package site.lab0x13.scrow.configurator.type.location;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import java.util.concurrent.CompletableFuture;
class LocationPickCurrentAction extends PickAction<Location, ConfigNode<Location>> {
@Override
public String name() {
return "pick-current";
}
@Override
public boolean requiresValue() {
return false;
}
@Override
protected CompletableFuture<Location> pick(Player player) {
return CompletableFuture.completedFuture(player.getLocation());
}
}

View file

@ -0,0 +1,36 @@
package site.lab0x13.scrow.configurator.type.location;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
import site.lab0x13.scrow.configurator.node.ConfigValueNode;
import java.util.Collections;
import java.util.List;
class LocationTeleportAction implements ConfigOptionAction<ConfigValueNode<Location>> {
@Override
public String name() {
return "teleport";
}
@Override
public List<CommandArgumentSpec<Player, ?>> arguments() {
return Collections.emptyList();
}
@Override
public boolean requiresValue() {
return true;
}
@Override
public void execute(ScrowMessageStyle style, ConfigValueNode<Location> node, CommandContext<Player> ctx) {
ctx.sender().teleport(node.value());
ctx.sender().sendMessage(style.ok("You've been teleported."));
}
}

View file

@ -0,0 +1,44 @@
package site.lab0x13.scrow.configurator.type.location;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
import site.lab0x13.scrow.configurator.node.ConfigNode;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public abstract class PickAction<S, T extends ConfigNode<S>> implements ConfigOptionAction<T> {
protected abstract CompletableFuture<S> pick(Player player);
@Override
public String name() {
return "pick";
}
@Override
public List<CommandArgumentSpec<Player, ?>> arguments() {
return Collections.emptyList();
}
@Override
public void execute(ScrowMessageStyle style, T node, CommandContext<Player> ctx) {
pick(ctx.sender())
.orTimeout(10, TimeUnit.MINUTES)
.whenComplete((picked, ex) -> {
if (ex instanceof TimeoutException) {
ctx.sender().sendMessage(style.err("Timed out picking value."));
}
node.value(picked);
ctx.sender().playSound(ctx.sender().getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.5f, 0f);
});
}
}

View file

@ -0,0 +1,68 @@
package site.lab0x13.scrow.configurator.type.world;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.type.WorldArgumentType;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
import site.lab0x13.scrow.configurator.node.ConfigValueNode;
import site.lab0x13.scrow.configurator.type.ConfigType;
import java.util.List;
import java.util.UUID;
public class WorldConfigType implements ConfigType<World> {
public static ConfigType<World> INSTANCE = new WorldConfigType();
private WorldConfigType() {
}
@Override
public JsonElement serialize(World value) {
return new JsonPrimitive(value.getUID().toString());
}
@Override
public World deserialize(JsonElement json) {
return Bukkit.getWorld(UUID.fromString(json.getAsString()));
}
@Override
public List<ConfigOptionAction<?>> actions() {
return List.of(new SetAction());
}
private static class SetAction implements ConfigOptionAction<ConfigValueNode<World>> {
@Override
public String name() {
return "set";
}
@Override
public List<CommandArgumentSpec<Player, ?>> arguments() {
return List.of(
CommandArgumentSpec.builder("world", new WorldArgumentType<Player>()).build()
);
}
@Override
public boolean requiresValue() {
return false;
}
@Override
public void execute(ScrowMessageStyle style, ConfigValueNode<World> node, CommandContext<Player> ctx) {
World world = ctx.getArg("world");
node.value(world);
ctx.sender().sendMessage(style.ok("Set world to " + world.getName() + "("+ world.getUID() + ")"));
}
}
}

View file

@ -0,0 +1,6 @@
name: "Configurator"
version: "0"
depend: [ "CoreBukkit" ]
main: "site.lab0x13.scrow.configurator.ConfiguratorPlugin"
api-version: "26.2"
author: "Kento2"

View file

@ -16,21 +16,24 @@ java_library(
artifact("de.kentoj.scrow:kencommandapi-bukkit"),
artifact("org.mongodb:bson"),
],
neverlink = True,
)
java_binary(
name = "_plugin",
srcs = glob(["plugin/main/java/**/*.java"]),
srcs = glob(["plugin/main/java/**/*.java", "api/main/java/**/*.java"]),
create_executable = False,
resources = glob(["plugin/main/resources/**"]),
resource_strip_prefix = "core/bukkit/plugin/main/resources",
deps = [
":api",
"//core/common",
artifact("de.kentoj.scrow:kencommandapi-bukkit"),
artifact("net.luckperms:api"),
artifact("io.papermc.paper:paper-api"),
artifact("io.nats:jnats"),
artifact("net.kyori:adventure-key:5.2.0"),
artifact("org.postgresql:postgresql"),
artifact("org.mongodb:bson"),
],
)

View file

@ -1,6 +1,5 @@
package de.kentoj.scrow.bukkit;
import de.kentoj.scrow.bukkit.style.ChatStyleListener;
import de.kentoj.scrow.bukkit.command.economy.CoinsCommand;
import de.kentoj.scrow.bukkit.command.friends.FriendCommand;
import de.kentoj.scrow.bukkit.command.instance.InstanceCache;
@ -9,22 +8,22 @@ import de.kentoj.scrow.bukkit.command.instance.LobbyCommand;
import de.kentoj.scrow.bukkit.command.instance.PlayCommand;
import de.kentoj.scrow.bukkit.internal.player.prefix.CachedPrefixProvider;
import de.kentoj.scrow.bukkit.internal.player.prefix.LuckPermsPrefixSetter;
import de.kentoj.scrow.bukkit.style.ChatStyleListener;
import de.kentoj.scrow.bukkit.style.TabStyleListener;
import net.luckperms.api.LuckPermsProvider;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class CoreImplPlugin extends JavaPlugin {
private final Logger log = LoggerFactory.getLogger(CoreImplPlugin.class);
@Override
public void onEnable() {
var cachedPrefixProvider = new CachedPrefixProvider();
ScrowAPISurface.initScrowAPI(this, cachedPrefixProvider);
log.info("Initialized ScrowAPI");
ScrowAPI.playerCommands().register(new CoinsCommand().rootLiteral());
ScrowAPI.playerCommands().register(new FriendCommand().rootLiteral());

View file

@ -24,7 +24,7 @@ public final class InstanceDestroyLiteral {
this.style = style;
handleArg = CommandArgumentSpec.builder("handle", new StringArgumentType<CommandSender>())
.withSuggestionProvider(_ ->
.withSuggestionProvider((_, _) ->
cache.getInstances().stream()
.map(ServerInstance::handle)
.toList())

View file

@ -16,16 +16,19 @@ java_library(
artifact("de.kentoj.scrow:kencommandapi-velocity"),
artifact("org.mongodb:bson"),
],
neverlink = True,
)
java_binary(
name = "plugin",
srcs = glob(["plugin/main/java/**/*.java"]),
srcs = glob(["plugin/main/java/**/*.java", "api/main/java/**/*.java"]),
create_executable = False,
deps = [
":api",
"//core/common",
artifact("com.velocitypowered:velocity-api"),
artifact("com.google.inject:guice"),
artifact("io.nats:jnats"),
artifact("de.kentoj.scrow:kencommandapi-velocity"),
artifact("org.mongodb:bson"),
],
)

View file

@ -1,35 +0,0 @@
load(":minecraft.bzl", "dev_server", "fetch_file")
dev_server(
name = "dev",
server_jar = ":papermc",
plugins = [
":luckperms",
":viaversion",
":viabackwards",
],
local_plugins = [
"//core/bukkit:plugin",
],
)
fetch_file(
name = "papermc",
filename = "paper.jar",
url = "https://fill-data.papermc.io/v1/objects/5953a1f1deaa3572be420c3a5b18406c4b752412a78be4acab7c8b467633caed/paper-26.2-100.jar",
)
fetch_file(
name = "luckperms",
filename = "LuckPerms.jar",
url = "https://download.luckperms.net/1652/bukkit/loader/LuckPerms-Bukkit-5.5.65.jar",
)
fetch_file(
name = "viaversion",
filename = "ViaVersion.jar",
url = "https://hangarcdn.papermc.io/plugins/ViaVersion/ViaVersion/versions/5.11.0/PAPER/ViaVersion-5.11.0.jar"
)
fetch_file(
name = "viabackwards",
filename = "ViaBackwards.jar",
url = "https://hangarcdn.papermc.io/plugins/ViaVersion/ViaBackwards/versions/5.11.0/PAPER/ViaBackwards-5.11.0.jar"
)

View file

@ -1,72 +0,0 @@
def _dev_server_impl(ctx):
script = ctx.actions.declare_file(ctx.label.name + "_runner")
content = """
#!/bin/sh
set -eu
cp -f '%s' server.jar
printf eula=true > eula.txt
mkdir -p plugins
rm -f plugins/*.jar
for plugin in %s
do
cp -v \"$plugin\" plugins/
done
mkdir -p postgres-data
podman run \
--rm \
--name scrow-postgres-dev \
-p 5432:5432 \
-v ./postgres-data/:/data \
-e POSTGRES_USER=postgres \
-e POSTGRES_HOST_AUTH_METHOD=trust \
-d \
docker.io/postgres:alpine || true
echo "Starting paper server in $PWD"
exec env HOST_POSTGRES='jdbc:postgresql://127.0.0.1:5432/postgres' java -jar server.jar --nogui %s
""" % (
ctx.file.server_jar.short_path,
" ".join([p.short_path for p in ctx.files.plugins]),
" ".join(["--add-plugin %s" % p.short_path for p in ctx.files.local_plugins]),
)
ctx.actions.write(
output = script,
content = content,
is_executable = True,
)
return DefaultInfo(
executable = script,
runfiles = ctx.runfiles(files = [ctx.file.server_jar] + ctx.files.plugins + ctx.files.local_plugins)
)
dev_server = rule(
implementation = _dev_server_impl,
executable = True,
attrs = {
"server_jar": attr.label(allow_single_file = True, mandatory = True),
"plugins": attr.label_list(),
"local_plugins": attr.label_list(),
},
)
def _fetch_plugin_impl(ctx):
out_file = ctx.actions.declare_file(ctx.attr.filename)
ctx.actions.run_shell(
outputs = [out_file],
command = "curl -Lso \"$1\" \"$2\"",
arguments = [out_file.path, ctx.attr.url],
execution_requirements = {
"requires-network": "1",
},
progress_message = "Downloading plugin %s" % ctx.attr.filename,
)
return [DefaultInfo(files = depset([out_file]))]
fetch_file = rule(
implementation = _fetch_plugin_impl,
attrs = {
"url": attr.string(mandatory = True),
"filename": attr.string(mandatory = True),
},
)