.
This commit is contained in:
parent
46978ae799
commit
ce56c6e105
53 changed files with 776 additions and 891 deletions
1
.bazelrc
1
.bazelrc
|
|
@ -1,4 +1,5 @@
|
||||||
build --define=maven_repo=https://git.lab0x13.site/api/packages/scrow/maven
|
build --define=maven_repo=https://git.lab0x13.site/api/packages/scrow/maven
|
||||||
|
build --define=generic_repo=https://git.lab0x13.site/api/packages/scrow/generic
|
||||||
build --remote_cache=https://cache.lab0x13.site
|
build --remote_cache=https://cache.lab0x13.site
|
||||||
build --remote_upload_local_results=false
|
build --remote_upload_local_results=false
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ Building
|
||||||
|
|
||||||
compile the bukkit-impl plugin:
|
compile the bukkit-impl plugin:
|
||||||
```shell
|
```shell
|
||||||
bazel build //core-bukkit-impl:bukkit-impl_deploy.jar
|
bazel build //core/bukkit:plugin
|
||||||
```
|
```
|
||||||
This produces `bazel-bin/core-bukkit-impl/bukkit-impl.jar` with all dependencies built-in except for
|
This produces `bazel-bin/core/bukkit/plugin.jar` I think.
|
||||||
LuckPerms.
|
|
||||||
|
|
@ -1,40 +1,34 @@
|
||||||
# configurator
|
# configurator
|
||||||
|
|
||||||
helper for generating json configs interactively.
|
This plugin/API allows developers to define configuration fields by name(or rather key) and data type, and have admins easily set
|
||||||
Plugins that hook into this plugin can register configuration nodes like "lobby.spawnpoint" with type Location.
|
the fields' values in-game using commands such as `/cfg key chat.messageDelay set 50 ms`.
|
||||||
server operators can then go ingame and do `/cfg key lobby.spawnpoint` to set a spawn location and then
|
|
||||||
`/cfg export` ig
|
|
||||||
|
|
||||||
## TODO
|
<small>
|
||||||
|
the key literal in `/cfg key` is required, because there are other modes like `/cfg check` that checks
|
||||||
|
the config for uninitialized values.
|
||||||
|
</small>
|
||||||
|
|
||||||
- lists
|
# Design
|
||||||
- exporting
|
|
||||||
- proper namespaces
|
|
||||||
- split into api and impl
|
|
||||||
- more types(region pickers especially; also itemstacks)
|
|
||||||
- type variants like location without yaw/pitch
|
|
||||||
|
|
||||||
## design
|
Plugins register configuration nodes by key. A key consists of a unique identifier and a type.
|
||||||
|
The type handles (de)serialization and defines actions. Actions can be used on nodes.
|
||||||
|
Nodes hold values.
|
||||||
|
|
||||||
in json we have primitives(like "hi", 1, false"), structures(like {"foo":"bar}) and lists(like []).
|
The `/config` or `/cfg` command is used by admins to configure the plugin:
|
||||||
everything consists of primitives eventually so we can go from
|
A lobby plugin might register a node with the identifier `lobby.spawnLocation` and type `ConfigLocationType`,
|
||||||
|
which gives us the command `/cfg key lobby.spawnLocation`.
|
||||||
|
Let's say the ConfigLocationType defines an action to use the player's location as the value("pick-current")
|
||||||
|
and an action to teleport the user to the defined location("teleport").
|
||||||
|
Thus, we can do `/cfg key lobby.spawnLocation pick-current` or `/cfg key lobby.spawnLocation teleport`.
|
||||||
|
Tab completion makes this pretty obvious.
|
||||||
|
|
||||||
```json
|
## Lists
|
||||||
{
|
|
||||||
"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
|
Now maybe we want to make our lobby have some launch pads(i.e. some pressure plates that launch the player).
|
||||||
|
So, lists would be handy.
|
||||||
|
For lists, we simply use a Node of type List<ConfigType of Element>, so for our launch pads, we could make a node
|
||||||
|
with identifier "lobby.launchpads" and type `ConfigListType<ConfigLocationType>`.
|
||||||
|
Now to add a launch pad, we can just `/cfg key lobby.launchpads append`, which creates a new uninitialized element
|
||||||
|
in the list, followed by `/cfg key lobby.launchpads.0 pick-current` which actually sets the node's value.
|
||||||
|
|
||||||
|
The node `lobby.launchpads.0` is not a real registered node. It is a Sub-Node of the `lobby.launchpads` node.
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package site.lab0x13.scrow.configurator;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface ConfigApi {
|
||||||
|
|
||||||
|
<V> void register(ConfigKey<V> key, @Nullable V defaultValue);
|
||||||
|
|
||||||
|
default <V> void register(ConfigKey<V> key) {
|
||||||
|
this.register(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
<V> @Nullable ConfigNode<V> getNode(ConfigKey<V> key);
|
||||||
|
|
||||||
|
default <V> @Nullable V get(ConfigKey<V> key) {
|
||||||
|
var n = getNode(key);
|
||||||
|
if (n == null) return null;
|
||||||
|
return n.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ConfigNode<?>> allNodes();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package site.lab0x13.scrow.configurator;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ConfigApiImpl implements ConfigApi {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(ConfigApiImpl.class);
|
||||||
|
|
||||||
|
private final Map<String, ConfigNode<?>> nodes = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> void register(ConfigKey<V> key, @Nullable V defaultValue) {
|
||||||
|
var oldValue = nodes.put(key.id(), new ConfigNode<>(key.type(), defaultValue));
|
||||||
|
if (oldValue != null)
|
||||||
|
log.warn("Overwriting config key {}", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sub-Nodes make this a bit difficult. We need to get the last real registered node in the
|
||||||
|
identifier first, and then descend into the Sub-Nodes.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public @Nullable <V> ConfigNode<V> getNode(ConfigKey<V> key) {
|
||||||
|
ConfigNode<Object> node;
|
||||||
|
var id = key.id();
|
||||||
|
var fields = new ArrayDeque<String>();
|
||||||
|
while (true) {
|
||||||
|
node = (ConfigNode<Object>) nodes.get(id);
|
||||||
|
if (node != null)
|
||||||
|
break;
|
||||||
|
var dot = id.lastIndexOf('.');
|
||||||
|
if (dot < 0)
|
||||||
|
return null;
|
||||||
|
fields.add(id.substring(dot + 1));
|
||||||
|
id = id.substring(0, dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!fields.isEmpty()) {
|
||||||
|
var field = fields.pop();
|
||||||
|
if (node.value() == null)
|
||||||
|
return null;
|
||||||
|
if (node.type().getSubNodeNames(node.value()).isEmpty())
|
||||||
|
return null;
|
||||||
|
node = (ConfigNode<Object>) node.type().getSubNode(node.value(), field);
|
||||||
|
if (node == null)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (ConfigNode<V>) node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, ConfigNode<?>> allNodes() {
|
||||||
|
var result = new HashMap<String, ConfigNode<?>>();
|
||||||
|
nodes.forEach((id, node) -> addNodeRecursively(result, id, (ConfigNode<Object>) node));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNodeRecursively(Map<String, ConfigNode<?>> result, String nodeId, ConfigNode<Object> node) {
|
||||||
|
result.put(nodeId, node);
|
||||||
|
if (node.value() != null)
|
||||||
|
for (String fieldName : node.type().getSubNodeNames(node.value())) {
|
||||||
|
var subNode = (ConfigNode<Object>) node.type().getSubNode(node.value(), fieldName);
|
||||||
|
if (subNode == null)
|
||||||
|
continue;
|
||||||
|
addNodeRecursively(result, nodeId + "." + fieldName, subNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package site.lab0x13.scrow.configurator;
|
||||||
|
|
||||||
|
import site.lab0x13.scrow.configurator.type.ConfigType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id unique identifier with, e.g `foo.bar`
|
||||||
|
* @param <V> type of the value
|
||||||
|
*/
|
||||||
|
public record ConfigKey<V>(String id, ConfigType<V> type) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(id=" + id + ",type=" + type.getClass().getCanonicalName() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,8 +3,6 @@ package site.lab0x13.scrow.configurator;
|
||||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
import de.kentoj.scrow.bukkit.ScrowAPI;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import site.lab0x13.scrow.configurator.command.ConfigCommand;
|
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;
|
import site.lab0x13.scrow.configurator.type.location.LocationConfigType;
|
||||||
|
|
||||||
public class ConfiguratorPlugin extends JavaPlugin {
|
public class ConfiguratorPlugin extends JavaPlugin {
|
||||||
|
|
@ -12,7 +10,7 @@ public class ConfiguratorPlugin extends JavaPlugin {
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
ConfigRootNode nodeRegistry = new ConfigRootNode();
|
ConfigRootNode nodeRegistry = new ConfigRootNode();
|
||||||
nodeRegistry.register("spawnLocation", new ConfigValueNode<>(LocationConfigType.INSTANCE));
|
nodeRegistry.register("spawnLocation", new Config<>(LocationConfigType.INSTANCE));
|
||||||
ScrowAPI.playerCommands().register(new ConfigCommand(nodeRegistry).rootLiteral());
|
ScrowAPI.playerCommands().register(new ConfigCommand(nodeRegistry).rootLiteral());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,25 +2,20 @@ package site.lab0x13.scrow.configurator.action;
|
||||||
|
|
||||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
||||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
||||||
import de.kentoj.kencommandapi.api.platform.MessageStyle;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface ConfigOptionAction<T> {
|
/**
|
||||||
|
* @param <N> type of ConfigNode
|
||||||
/* FIXME not too clean */
|
*/
|
||||||
List<ConfigOptionAction<?>> GLOBAL_ACTIONS = List.of(new GlobalShowAction<>());
|
public interface ConfigAction<N extends ConfigNode<?>> {
|
||||||
|
|
||||||
String name();
|
String name();
|
||||||
|
|
||||||
List<CommandArgumentSpec<Player, ?>> arguments();
|
List<CommandArgumentSpec<Player, ?>> arguments();
|
||||||
|
|
||||||
boolean requiresValue();
|
boolean requiresValue();
|
||||||
|
|
||||||
void execute(
|
void execute(N node, CommandContext<Player> ctx);
|
||||||
MessageStyle style,
|
|
||||||
T node,
|
|
||||||
CommandContext<Player> ctx
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
@ -5,16 +5,15 @@ import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
||||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
||||||
import de.kentoj.kencommandapi.api.platform.MessageStyle;
|
|
||||||
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.event.ClickEvent;
|
import net.kyori.adventure.text.event.ClickEvent;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
||||||
|
import site.lab0x13.scrow.configurator.type.ConfigType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class GlobalShowAction<S extends ConfigNode<S>> implements ConfigOptionAction<S> {
|
public final class GlobalShowAction implements ConfigAction<ConfigNode<Object>> {
|
||||||
|
|
||||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
|
||||||
|
|
@ -34,11 +33,11 @@ public final class GlobalShowAction<S extends ConfigNode<S>> implements ConfigOp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(MessageStyle style, S node, CommandContext<Player> ctx) {
|
public void execute(ConfigNode<Object> node, CommandContext<Player> ctx) {
|
||||||
var jsonElement = node.type().serialize(node.value());
|
var jsonElement = node.type().serialize(node.value());
|
||||||
var prettyJson = GSON.toJson(jsonElement, JsonElement.class);
|
var prettyJson = GSON.toJson(jsonElement, JsonElement.class);
|
||||||
var component = Component.text(prettyJson + " [click to copy]")
|
var component = Component.text(prettyJson + " [click to copy]")
|
||||||
.clickEvent(ClickEvent.copyToClipboard(prettyJson));
|
.clickEvent(ClickEvent.copyToClipboard(prettyJson));
|
||||||
ctx.sender().sendMessage(style.ok(component));
|
ctx.sender().sendMessage(ctx.style().ok(component));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package site.lab0x13.scrow.configurator.action;
|
||||||
|
|
||||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
||||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
||||||
import de.kentoj.kencommandapi.api.platform.MessageStyle;
|
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
||||||
|
|
@ -13,9 +12,9 @@ import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
public abstract class PickAction<S, T extends ConfigNode<S>> implements ConfigOptionAction<T> {
|
public abstract class PickAction<V, N extends ConfigNode<V>> implements ConfigAction<N> {
|
||||||
|
|
||||||
protected abstract CompletableFuture<S> pick(Player player);
|
protected abstract CompletableFuture<V> pick(Player player);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String name() {
|
public String name() {
|
||||||
|
|
@ -28,16 +27,21 @@ public abstract class PickAction<S, T extends ConfigNode<S>> implements ConfigOp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(MessageStyle style, T node, CommandContext<Player> ctx) {
|
public boolean requiresValue() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(N node, CommandContext<Player> ctx) {
|
||||||
pick(ctx.sender())
|
pick(ctx.sender())
|
||||||
.orTimeout(10, TimeUnit.MINUTES)
|
.orTimeout(10, TimeUnit.MINUTES)
|
||||||
.whenComplete((picked, ex) -> {
|
.whenComplete((picked, ex) -> {
|
||||||
if (ex instanceof TimeoutException) {
|
if (ex instanceof TimeoutException) {
|
||||||
ctx.sender().sendMessage(style.err("Timed out picking value."));
|
ctx.sender().sendMessage(ctx.style().err("Timed out picking value."));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.value(picked);
|
|
||||||
ctx.sender().playSound(ctx.sender().getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.5f, 0f);
|
ctx.sender().playSound(ctx.sender().getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.5f, 0f);
|
||||||
|
node.value(picked);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,17 @@ import de.kentoj.kencommandapi.api.literal.RootLiteral;
|
||||||
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
||||||
import net.kyori.adventure.text.format.TextColor;
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import site.lab0x13.scrow.configurator.node.ConfigRootNode;
|
import site.lab0x13.scrow.configurator.ConfigApi;
|
||||||
import site.lab0x13.scrow.configurator.noderegistry.ConfigNodeWalker;
|
import site.lab0x13.scrow.configurator.noderegistry.ConfigNodeWalker;
|
||||||
|
|
||||||
public final class ConfigCommand {
|
public final class ConfigCommand {
|
||||||
private final RootLiteral<Player> rootLiteral;
|
private final RootLiteral<Player> rootLiteral;
|
||||||
|
|
||||||
public ConfigCommand(ConfigRootNode nodeRegistry) {
|
public ConfigCommand(ConfigApi configApi) {
|
||||||
var style = new ScrowMessageStyle("config", TextColor.color(0x28B088));
|
var style = new ScrowMessageStyle("config", TextColor.color(0x28B088));
|
||||||
rootLiteral = Literal.<Player>builder("config", "cfg")
|
rootLiteral = Literal.<Player>builder("config", "cfg")
|
||||||
.withPermission("command.config")
|
.withPermission("command.config")
|
||||||
.withLiteral(new KeyLiteral(style, new ConfigNodeWalker(nodeRegistry)).literal())
|
.withLiteral(new KeyLiteral(style, configApi).literal())
|
||||||
.asRootLiteral(style);
|
.asRootLiteral(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,10 @@ import com.leakyabstractions.result.core.Results;
|
||||||
import de.kentoj.kencommandapi.api.argument.ArgumentType;
|
import de.kentoj.kencommandapi.api.argument.ArgumentType;
|
||||||
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
|
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
|
||||||
import org.bukkit.entity.Player;
|
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.ConfigNode;
|
||||||
import site.lab0x13.scrow.configurator.node.ConfigRootNode;
|
|
||||||
import site.lab0x13.scrow.configurator.node.SubFielded;
|
|
||||||
import site.lab0x13.scrow.configurator.noderegistry.ConfigNodeWalker;
|
import site.lab0x13.scrow.configurator.noderegistry.ConfigNodeWalker;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
public class ConfigNodeArgumentType implements ArgumentType<Player, ConfigNode<?>> {
|
public class ConfigNodeArgumentType implements ArgumentType<Player, ConfigNode<?>> {
|
||||||
|
|
||||||
|
|
@ -37,17 +33,16 @@ public class ConfigNodeArgumentType implements ArgumentType<Player, ConfigNode<?
|
||||||
return (_, input) -> {
|
return (_, input) -> {
|
||||||
var res = nodeWalker.walk(input);
|
var res = nodeWalker.walk(input);
|
||||||
switch (res.type()) {
|
switch (res.type()) {
|
||||||
case NO_FIELDS, SUCCESS -> Collections.emptyList();
|
|
||||||
case NO_SUCH_FIELD, INDEX_OUT_OF_RANGE, INDEX_EXPECTED -> {
|
case NO_SUCH_FIELD, INDEX_OUT_OF_RANGE, INDEX_EXPECTED -> {
|
||||||
if (res.node() instanceof ConfigListNode<?> list)
|
assert res.node() != null;
|
||||||
return IntStream.range(0, list.size()).mapToObj(Integer::toString).toList();
|
return res.node().getFieldNames().stream()
|
||||||
if (res.node() instanceof SubFielded subFielded)
|
.map(it -> res.parentKey() + "." + it)
|
||||||
return subFielded.fields().keySet().stream()
|
.toList();
|
||||||
.map(it -> res.parentKey() + "." + it)
|
}
|
||||||
.toList();
|
default -> {
|
||||||
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Collections.emptyList();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import de.kentoj.kencommandapi.api.literal.Literal;
|
||||||
import de.kentoj.kencommandapi.api.literal.LiteralBuilder;
|
import de.kentoj.kencommandapi.api.literal.LiteralBuilder;
|
||||||
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
|
import site.lab0x13.scrow.configurator.ConfigApi;
|
||||||
|
import site.lab0x13.scrow.configurator.action.ConfigAction;
|
||||||
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
||||||
import site.lab0x13.scrow.configurator.noderegistry.ConfigNodeWalker;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -16,23 +16,41 @@ class KeyLiteral {
|
||||||
private final ScrowMessageStyle style;
|
private final ScrowMessageStyle style;
|
||||||
private final Literal<Player> literal;
|
private final Literal<Player> literal;
|
||||||
|
|
||||||
KeyLiteral(ScrowMessageStyle style, ConfigNodeWalker nodeWalker) {
|
KeyLiteral(ScrowMessageStyle style, ConfigApi api) {
|
||||||
|
/*
|
||||||
|
All registered nodes should be added as literals. SubNoded nodes should be added
|
||||||
|
recursively.
|
||||||
|
When foo is a list node(implements SubNoded):
|
||||||
|
/cfg key foo <- list node itself is registered, has actions like append, delete, etc.
|
||||||
|
/cfg key foo.0 <- operate on first element, has actions of foo.0's type
|
||||||
|
*/
|
||||||
this.style = style;
|
this.style = style;
|
||||||
|
|
||||||
var builder = Literal.<Player>builder("key");
|
var builder = Literal.<Player>builder("key");
|
||||||
nodeWalker.getAllNodes().forEach((key, node) ->
|
api.allNodes().forEach((key, node) ->
|
||||||
addNodeLiteral(builder, key, node));
|
addNodeLiteral(builder, key, node));
|
||||||
literal = builder.build();
|
this.literal = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addNodeLiteral(LiteralBuilder<Player> builder, String key, ConfigNode<?> node) {
|
private void addNodeLiteral(LiteralBuilder<Player> builder, String key, ConfigNode<?> node) {
|
||||||
var nodeLiteralBuilder = Literal.<Player>builder(key);
|
var nodeLiteralBuilder = Literal.<Player>builder(key);
|
||||||
addNodeActionLiteral(nodeLiteralBuilder, (ConfigNode<Object>) node, new ArrayList<>(node.type().actions()));
|
addNodeActionLiteral(nodeLiteralBuilder, (ConfigNode<Object>) node, new ArrayList<>(node.type().actions()));
|
||||||
addNodeActionLiteral(nodeLiteralBuilder, (ConfigNode<Object>) node, ConfigOptionAction.GLOBAL_ACTIONS);
|
/*
|
||||||
|
FUCK ME WE NEED TO ADD LITERALS AT RUNTIME.
|
||||||
|
I just need /cfg key <node> <actions dependent on node>
|
||||||
|
except nodes are registered at runtime so we need to register literals at runtime.
|
||||||
|
or we make it an argument, but then we can't make action's work nicely(only hackishly at best);
|
||||||
|
or a dy
|
||||||
|
*/
|
||||||
|
for (String fieldName : node.type().getSubNodeNames(node.value())) {
|
||||||
|
var subNode = node.type().getSubNode(node.value(), fieldName);
|
||||||
|
if (subNode == null) continue;
|
||||||
|
addNodeActionLiteral();
|
||||||
|
}
|
||||||
builder.withLiteral(nodeLiteralBuilder.build());
|
builder.withLiteral(nodeLiteralBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addNodeActionLiteral(LiteralBuilder<Player> builder, ConfigNode<Object> node, List<ConfigOptionAction<?>> actions) {
|
private void addNodeActionLiteral(LiteralBuilder<Player> builder, ConfigNode<Object> node, List<ConfigAction<?>> actions) {
|
||||||
for (var _action : actions) {
|
for (var _action : actions) {
|
||||||
var action = (ConfigOptionAction<ConfigNode<Object>>) _action;
|
var action = (ConfigOptionAction<ConfigNode<Object>>) _action;
|
||||||
var actionLiteralBuilder = Literal.<Player>builder(action.name());
|
var actionLiteralBuilder = Literal.<Player>builder(action.name());
|
||||||
|
|
@ -43,7 +61,7 @@ class KeyLiteral {
|
||||||
ctx.sender().sendMessage(style.err("No value set."));
|
ctx.sender().sendMessage(style.err("No value set."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
action.execute(style, node, ctx);
|
action.execute(node, ctx);
|
||||||
});
|
});
|
||||||
builder.withLiteral(actionLiteralBuilder.build());
|
builder.withLiteral(actionLiteralBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package site.lab0x13.scrow.configurator.command;
|
||||||
|
|
||||||
|
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
||||||
|
import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
|
||||||
|
import de.kentoj.kencommandapi.api.literal.Literal;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class KeyLiteralImpl implements Literal<Player> {
|
||||||
|
private final ConfigNode<?> node;
|
||||||
|
|
||||||
|
public KeyLiteralImpl(String key, ConfigNode<?> node) {
|
||||||
|
this.key = key;
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Literal<Player> getLiteral(@NotNull String name) {
|
||||||
|
node.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull List<Literal<Player>> literals() {
|
||||||
|
gh
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String[] names() {
|
||||||
|
return new String[]{key};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String description() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CommandArgumentSpec<Player, ?>> arguments() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable CommandExecutor<Player> executor() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String permission() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,36 @@
|
||||||
package site.lab0x13.scrow.configurator.node;
|
package site.lab0x13.scrow.configurator.node;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import site.lab0x13.scrow.configurator.type.ConfigType;
|
import site.lab0x13.scrow.configurator.type.ConfigType;
|
||||||
|
|
||||||
public sealed interface ConfigNode<T> permits ConfigValueNode, ConfigListNode, ConfigRootNode {
|
/**
|
||||||
ConfigType<T> type();
|
* @param <V> type of the value
|
||||||
|
*/
|
||||||
|
public final class ConfigNode<V> {
|
||||||
|
|
||||||
T value();
|
private final ConfigType<V> type;
|
||||||
|
private final @Nullable V defaultValue;
|
||||||
|
private @Nullable V value;
|
||||||
|
|
||||||
void value(T value);
|
public ConfigNode(ConfigType<V> type, @Nullable V defaultValue) {
|
||||||
|
this.type = type;
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.value(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void value(V value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigType<V> type() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,27 +2,24 @@ package site.lab0x13.scrow.configurator.type;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
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.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public abstract class ConfigComplexType<T> implements ConfigType<T>, SubFielded {
|
public abstract class ConfigComplexType<T> implements ConfigType<T> {
|
||||||
|
|
||||||
private final Map<String, ConfigNode<?>> entries = new HashMap<>();
|
private final Map<String, Field<T, ?>> fields = new HashMap<>();
|
||||||
|
|
||||||
protected abstract T deserialize(JsonObject obj);
|
protected abstract T deserialize(JsonObject obj);
|
||||||
|
|
||||||
protected void addEntry(String name, ConfigNode<?> node) {
|
protected <S> void addField(String name, ConfigType<S> type, Function<T, S> getter) {
|
||||||
entries.put(name, node);
|
fields.put(name, new Field<>(type, getter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
protected <S> S readField(JsonObject object, String name) {
|
||||||
protected <S> S readNode(JsonObject object, String name) {
|
var field = fields.get(name);
|
||||||
var node = (ConfigNode<Object>) entries.get(name);
|
return (S) field.type().deserialize(object.get(name));
|
||||||
return (S) node.type().deserialize(object.get(name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -33,20 +30,19 @@ public abstract class ConfigComplexType<T> implements ConfigType<T>, SubFielded
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(T value) {
|
public JsonElement serialize(T value) {
|
||||||
var obj = new JsonObject();
|
var obj = new JsonObject();
|
||||||
entries.forEach((name, node) -> writeNode(obj, name, node));
|
fields.forEach((name, field) -> writeNode(obj, name, field, value));
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <S> void writeNode(JsonObject object, String name, ConfigNode<S> node) {
|
private <S> void writeNode(JsonObject object, String name, Field<T, S> field, T value) {
|
||||||
var value = node.type().serialize(node.value());
|
var fieldValue = field.getter.apply(value);
|
||||||
object.add(name, value);
|
var serializedFieldValue = field.type().serialize(fieldValue);
|
||||||
|
object.add(name, serializedFieldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, ConfigNode<?>> fields() {
|
public record Field<T, S>(
|
||||||
return new HashMap<>(entries);
|
ConfigType<S> type,
|
||||||
}
|
Function<T, S> getter
|
||||||
|
) {
|
||||||
public @Nullable ConfigNode<?> getField(String name) {
|
|
||||||
return entries.get(name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
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 java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
public record ConfigListType<V>(
|
||||||
|
ConfigType<V> elementType
|
||||||
|
) implements ConfigType<List<ConfigNode<V>>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ConfigNode<V>> deserialize(JsonElement json) {
|
||||||
|
var result = new ArrayList<ConfigNode<V>>();
|
||||||
|
var obj = json.getAsJsonObject();
|
||||||
|
obj.asMap().forEach((k, v) -> {
|
||||||
|
var subNode = new ConfigNode<>(elementType, null);
|
||||||
|
subNode.value(elementType.deserialize(v));
|
||||||
|
result.set(Integer.parseInt(k), subNode);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(List<ConfigNode<V>> value) {
|
||||||
|
var res = new JsonObject();
|
||||||
|
for (int i = 0; i < value.size(); i++) {
|
||||||
|
var subNodeValue = value.get(i).value();
|
||||||
|
if (subNodeValue == null) continue;
|
||||||
|
var element = elementType.serialize(subNodeValue);
|
||||||
|
res.add(Integer.toString(i), element);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getSubNodeNames(List<ConfigNode<V>> value) {
|
||||||
|
return IntStream.range(0, value.size())
|
||||||
|
.mapToObj(Integer::toString)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ConfigNode<V> getSubNode(List<ConfigNode<V>> value, String name) {
|
||||||
|
return value.get(Integer.parseInt(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,7 @@ import com.google.gson.JsonPrimitive;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class ConfigPrimitiveType<T> implements ConfigType<T> {
|
public final class ConfigPrimitiveType<V> implements ConfigType<V> {
|
||||||
|
|
||||||
public static final ConfigPrimitiveType<Double> DOUBLE =
|
public static final ConfigPrimitiveType<Double> DOUBLE =
|
||||||
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsDouble);
|
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsDouble);
|
||||||
|
|
@ -25,21 +25,21 @@ public class ConfigPrimitiveType<T> implements ConfigType<T> {
|
||||||
public static final ConfigPrimitiveType<Byte> BYTE =
|
public static final ConfigPrimitiveType<Byte> BYTE =
|
||||||
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsByte);
|
new ConfigPrimitiveType<>(JsonPrimitive::new, JsonPrimitive::getAsByte);
|
||||||
|
|
||||||
private final Function<T, JsonPrimitive> serializer;
|
private final Function<V, JsonPrimitive> serializer;
|
||||||
private final Function<JsonPrimitive, T> deserializer;
|
private final Function<JsonPrimitive, V> deserializer;
|
||||||
|
|
||||||
private ConfigPrimitiveType(Function<T, JsonPrimitive> serializer, Function<JsonPrimitive, T> deserializer) {
|
private ConfigPrimitiveType(Function<V, JsonPrimitive> serializer, Function<JsonPrimitive, V> deserializer) {
|
||||||
this.serializer = serializer;
|
this.serializer = serializer;
|
||||||
this.deserializer = deserializer;
|
this.deserializer = deserializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(T value) {
|
public JsonElement serialize(V value) {
|
||||||
return serializer.apply(value);
|
return serializer.apply(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T deserialize(JsonElement json) {
|
public V deserialize(JsonElement json) {
|
||||||
return deserializer.apply(json.getAsJsonPrimitive());
|
return deserializer.apply(json.getAsJsonPrimitive());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,26 @@
|
||||||
package site.lab0x13.scrow.configurator.type;
|
package site.lab0x13.scrow.configurator.type;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import com.google.gson.JsonParser;
|
import site.lab0x13.scrow.configurator.action.ConfigAction;
|
||||||
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
|
|
||||||
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface ConfigType<T> {
|
/**
|
||||||
|
* @param <V> type of the stored value
|
||||||
|
*/
|
||||||
|
public interface ConfigType<V> extends Serializable<V> {
|
||||||
|
|
||||||
default List<ConfigOptionAction<?>> actions() {
|
default List<ConfigAction<?>> actions() {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonElement serialize(T value);
|
default List<String> getSubNodeNames(V value) {
|
||||||
|
return Collections.emptyList();
|
||||||
T deserialize(JsonElement json);
|
|
||||||
|
|
||||||
default String toJson(T value) {
|
|
||||||
return serialize(value).toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default T fromJson(String json) {
|
default @Nullable ConfigNode<?> getSubNode(V value, String name) {
|
||||||
return deserialize(JsonParser.parseString(json));
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package site.lab0x13.scrow.configurator.type;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
public interface Serializable<V> {
|
||||||
|
|
||||||
|
JsonElement serialize(V value);
|
||||||
|
|
||||||
|
V deserialize(JsonElement json);
|
||||||
|
|
||||||
|
default String toJson(V value) {
|
||||||
|
return serialize(value).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
default V fromJson(String json) {
|
||||||
|
return deserialize(JsonParser.parseString(json));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,9 +2,7 @@ package site.lab0x13.scrow.configurator.type.location;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
|
import site.lab0x13.scrow.configurator.action.ConfigAction;
|
||||||
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.ConfigComplexType;
|
||||||
import site.lab0x13.scrow.configurator.type.ConfigPrimitiveType;
|
import site.lab0x13.scrow.configurator.type.ConfigPrimitiveType;
|
||||||
import site.lab0x13.scrow.configurator.type.world.WorldConfigType;
|
import site.lab0x13.scrow.configurator.type.world.WorldConfigType;
|
||||||
|
|
@ -16,31 +14,28 @@ public class LocationConfigType extends ConfigComplexType<Location> {
|
||||||
public static final LocationConfigType INSTANCE = new LocationConfigType();
|
public static final LocationConfigType INSTANCE = new LocationConfigType();
|
||||||
|
|
||||||
private LocationConfigType() {
|
private LocationConfigType() {
|
||||||
addEntry("world", new ConfigValueNode<>(WorldConfigType.INSTANCE));
|
addField("world", WorldConfigType.INSTANCE, Location::getWorld);
|
||||||
addEntry("x", new ConfigValueNode<>(ConfigPrimitiveType.DOUBLE));
|
addField("x", ConfigPrimitiveType.DOUBLE, Location::getX);
|
||||||
addEntry("y", new ConfigValueNode<>(ConfigPrimitiveType.DOUBLE));
|
addField("y", ConfigPrimitiveType.DOUBLE, Location::getY);
|
||||||
addEntry("z", new ConfigValueNode<>(ConfigPrimitiveType.DOUBLE));
|
addField("z", ConfigPrimitiveType.DOUBLE, Location::getZ);
|
||||||
addEntry("yaw", new ConfigValueNode<>(ConfigPrimitiveType.FLOAT));
|
addField("yaw", ConfigPrimitiveType.FLOAT, Location::getYaw);
|
||||||
addEntry("pitch", new ConfigValueNode<>(ConfigPrimitiveType.FLOAT));
|
addField("pitch", ConfigPrimitiveType.FLOAT, Location::getPitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ConfigOptionAction<?>> actions() {
|
public List<ConfigAction<?>> actions() {
|
||||||
return List.of(
|
return List.of(new LocationTeleportAction(), new LocationPickCurrentAction());
|
||||||
new LocationTeleportAction(),
|
|
||||||
new LocationPickCurrentAction()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Location deserialize(JsonObject obj) {
|
public Location deserialize(JsonObject obj) {
|
||||||
return new Location(
|
return new Location(
|
||||||
readNode(obj, "world"),
|
readField(obj, "world"),
|
||||||
readNode(obj, "x"),
|
readField(obj, "x"),
|
||||||
readNode(obj, "y"),
|
readField(obj, "y"),
|
||||||
readNode(obj, "z"),
|
readField(obj, "z"),
|
||||||
readNode(obj, "yaw"),
|
readField(obj, "yaw"),
|
||||||
readNode(obj, "pitch")
|
readField(obj, "pitch")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,6 @@ class LocationPickCurrentAction extends PickAction<Location, ConfigNode<Location
|
||||||
return "pick-current";
|
return "pick-current";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresValue() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CompletableFuture<Location> pick(Player player) {
|
protected CompletableFuture<Location> pick(Player player) {
|
||||||
return CompletableFuture.completedFuture(player.getLocation());
|
return CompletableFuture.completedFuture(player.getLocation());
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,15 @@ package site.lab0x13.scrow.configurator.type.location;
|
||||||
|
|
||||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
||||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
||||||
import de.kentoj.kencommandapi.api.platform.MessageStyle;
|
|
||||||
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
|
import site.lab0x13.scrow.configurator.action.ConfigAction;
|
||||||
import site.lab0x13.scrow.configurator.node.ConfigValueNode;
|
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
class LocationTeleportAction implements ConfigOptionAction<ConfigValueNode<Location>> {
|
class LocationTeleportAction implements ConfigAction<ConfigNode<Location>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String name() {
|
public String name() {
|
||||||
|
|
@ -30,8 +28,8 @@ class LocationTeleportAction implements ConfigOptionAction<ConfigValueNode<Locat
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(MessageStyle style, ConfigValueNode<Location> node, CommandContext<Player> ctx) {
|
public void execute(ConfigNode<Location> node, CommandContext<Player> ctx) {
|
||||||
ctx.sender().teleport(node.value());
|
ctx.sender().teleport(node.value());
|
||||||
ctx.sender().sendMessage(style.ok("You've been teleported."));
|
ctx.sender().sendMessage(ctx.style().ok("You've been teleported."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package site.lab0x13.scrow.configurator.type.world;
|
||||||
|
|
||||||
|
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
||||||
|
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
||||||
|
import de.kentoj.kencommandapi.type.WorldArgumentType;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import site.lab0x13.scrow.configurator.action.ConfigAction;
|
||||||
|
import site.lab0x13.scrow.configurator.node.ConfigNode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class SetWorldAction implements ConfigAction<ConfigNode<World>> {
|
||||||
|
|
||||||
|
private final CommandArgumentSpec<Player, World> arg =
|
||||||
|
CommandArgumentSpec.builder("world", new WorldArgumentType<Player>()).build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "set";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CommandArgumentSpec<Player, ?>> arguments() {
|
||||||
|
return List.of(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresValue() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(ConfigNode<World> node, CommandContext<Player> ctx) {
|
||||||
|
World world = ctx.getArg(arg);
|
||||||
|
node.value(world);
|
||||||
|
ctx.sender().sendMessage(ctx.style().ok("Set world to " + world.getName() + "(" + world.getUID() + ")"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,18 +2,9 @@ package site.lab0x13.scrow.configurator.type.world;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonPrimitive;
|
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.api.platform.MessageStyle;
|
|
||||||
import de.kentoj.kencommandapi.type.WorldArgumentType;
|
|
||||||
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.entity.Player;
|
import site.lab0x13.scrow.configurator.action.ConfigAction;
|
||||||
import site.lab0x13.scrow.configurator.action.ConfigOptionAction;
|
|
||||||
import site.lab0x13.scrow.configurator.node.ConfigValueNode;
|
|
||||||
import site.lab0x13.scrow.configurator.type.ConfigType;
|
import site.lab0x13.scrow.configurator.type.ConfigType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -37,33 +28,7 @@ public class WorldConfigType implements ConfigType<World> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ConfigOptionAction<?>> actions() {
|
public List<ConfigAction<?>> actions() {
|
||||||
return List.of(new SetAction());
|
return List.of(new SetWorldAction());
|
||||||
}
|
|
||||||
|
|
||||||
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(MessageStyle 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() + ")"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,4 +43,12 @@ genrule(
|
||||||
outs = ["plugin.jar"],
|
outs = ["plugin.jar"],
|
||||||
cmd = "cp $< $@",
|
cmd = "cp $< $@",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
load("//rules:publish_plugin.bzl", "publish_plugin")
|
||||||
|
publish_plugin(
|
||||||
|
name = "plugin-publish",
|
||||||
|
package = "core-bukkit",
|
||||||
|
filename = "CoreBukkit.jar",
|
||||||
|
src = ":plugin",
|
||||||
)
|
)
|
||||||
|
|
@ -2,10 +2,6 @@ package de.kentoj.scrow.bukkit;
|
||||||
|
|
||||||
import de.kentoj.scrow.bukkit.command.economy.CoinsCommand;
|
import de.kentoj.scrow.bukkit.command.economy.CoinsCommand;
|
||||||
import de.kentoj.scrow.bukkit.command.friends.FriendCommand;
|
import de.kentoj.scrow.bukkit.command.friends.FriendCommand;
|
||||||
import de.kentoj.scrow.bukkit.command.instance.InstanceCache;
|
|
||||||
import de.kentoj.scrow.bukkit.command.instance.InstanceCommand;
|
|
||||||
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.CachedPrefixProvider;
|
||||||
import de.kentoj.scrow.bukkit.internal.player.prefix.LuckPermsPrefixSetter;
|
import de.kentoj.scrow.bukkit.internal.player.prefix.LuckPermsPrefixSetter;
|
||||||
import de.kentoj.scrow.bukkit.style.ChatStyleListener;
|
import de.kentoj.scrow.bukkit.style.ChatStyleListener;
|
||||||
|
|
@ -27,10 +23,6 @@ public final class CoreImplPlugin extends JavaPlugin {
|
||||||
|
|
||||||
ScrowAPI.playerCommands().register(new CoinsCommand().rootLiteral());
|
ScrowAPI.playerCommands().register(new CoinsCommand().rootLiteral());
|
||||||
ScrowAPI.playerCommands().register(new FriendCommand().rootLiteral());
|
ScrowAPI.playerCommands().register(new FriendCommand().rootLiteral());
|
||||||
var instanceCache = new InstanceCache();
|
|
||||||
ScrowAPI.commands().register(new InstanceCommand(instanceCache).rootLiteral());
|
|
||||||
ScrowAPI.playerCommands().register(new PlayCommand().rootLiteral());
|
|
||||||
ScrowAPI.playerCommands().register(new LobbyCommand().rootLiteral());
|
|
||||||
|
|
||||||
var luckperms = LuckPermsProvider.get();
|
var luckperms = LuckPermsProvider.get();
|
||||||
new LuckPermsPrefixSetter(luckperms, cachedPrefixProvider).init(this);
|
new LuckPermsPrefixSetter(luckperms, cachedPrefixProvider).init(this);
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
package de.kentoj.scrow.bukkit.command.instance;
|
|
||||||
|
|
||||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
|
||||||
import de.kentoj.scrowlib.instancemanager.ServerInstance;
|
|
||||||
import org.bson.Document;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
|
|
||||||
public class InstanceCache {
|
|
||||||
|
|
||||||
private final ConcurrentMap<String, ServerInstance> cache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public InstanceCache() {
|
|
||||||
ScrowAPI.messageBroker().subscribe("event.instance.up", doc -> {
|
|
||||||
var inst = toInstance(doc);
|
|
||||||
cache.put(inst.handle(), inst);
|
|
||||||
});
|
|
||||||
ScrowAPI.messageBroker().subscribe("event.instance.down", doc ->
|
|
||||||
cache.remove(doc.getString("handle")));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<ServerInstance> getInstances() {
|
|
||||||
return cache.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable ServerInstance getInstance(String handle) {
|
|
||||||
return cache.get(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ServerInstance toInstance(Document doc) {
|
|
||||||
return new ServerInstance(
|
|
||||||
doc.getString("handle"),
|
|
||||||
doc.getString("template"),
|
|
||||||
doc.getInteger("port")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package de.kentoj.scrow.bukkit.command.instance;
|
|
||||||
|
|
||||||
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.NamedTextColor;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
|
|
||||||
public final class InstanceCommand {
|
|
||||||
|
|
||||||
private final RootLiteral<CommandSender> rootLiteral;
|
|
||||||
|
|
||||||
public InstanceCommand(InstanceCache instanceCache) {
|
|
||||||
rootLiteral = Literal.<CommandSender>builder("instance", "server-manager")
|
|
||||||
.withLiteral(new InstanceListLiteral().literal())
|
|
||||||
.withLiteral(new InstanceDeployLiteral().literal())
|
|
||||||
.withLiteral(new InstanceDestroyLiteral(instanceCache).literal())
|
|
||||||
.asRootLiteral(new ScrowMessageStyle("server-manager", NamedTextColor.RED));
|
|
||||||
}
|
|
||||||
|
|
||||||
public RootLiteral<CommandSender> rootLiteral() {
|
|
||||||
return rootLiteral;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package de.kentoj.scrow.bukkit.command.instance;
|
|
||||||
|
|
||||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
|
||||||
import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
|
|
||||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
|
||||||
import de.kentoj.kencommandapi.api.literal.Literal;
|
|
||||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
|
||||||
import de.kentoj.scrow.bukkit.scheduler.Tasks;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
public final class InstanceDeployLiteral {
|
|
||||||
|
|
||||||
private final Literal<CommandSender> literal;
|
|
||||||
private final CommandArgumentSpec<CommandSender, String> templateArg;
|
|
||||||
|
|
||||||
InstanceDeployLiteral() {
|
|
||||||
templateArg = CommandArgumentSpec.builder("template", new StringArgumentType<CommandSender>())
|
|
||||||
.build();
|
|
||||||
literal = Literal.<CommandSender>builder("deploy")
|
|
||||||
.withArgument(templateArg)
|
|
||||||
.withExecutor(this::deployInstance)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<?> deployInstance(CommandContext<CommandSender> ctx) {
|
|
||||||
var template = ctx.getArg(templateArg);
|
|
||||||
ctx.sender().sendMessage(ctx.style().ok("Deploying instance... this may take a while."));
|
|
||||||
return Tasks.supplyAsync(() ->
|
|
||||||
ScrowAPI.instanceManager().deployInstance(template))
|
|
||||||
.thenSync(instance -> {
|
|
||||||
ctx.sender().sendMessage(ctx.style().ok("Deployed instance of " + template + " with handle " + instance.handle() + "."));
|
|
||||||
// FIXME handle unknown server
|
|
||||||
}).toFuture();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Literal<CommandSender> literal() {
|
|
||||||
return literal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package de.kentoj.scrow.bukkit.command.instance;
|
|
||||||
|
|
||||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
|
||||||
import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
|
|
||||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
|
||||||
import de.kentoj.kencommandapi.api.literal.Literal;
|
|
||||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
|
||||||
import de.kentoj.scrow.bukkit.scheduler.Tasks;
|
|
||||||
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
|
||||||
import de.kentoj.scrowlib.instancemanager.ServerInstance;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
public final class InstanceDestroyLiteral {
|
|
||||||
|
|
||||||
private final Literal<CommandSender> literal;
|
|
||||||
private final CommandArgumentSpec<CommandSender, String> handleArg;
|
|
||||||
|
|
||||||
InstanceDestroyLiteral(InstanceCache cache) {
|
|
||||||
handleArg = CommandArgumentSpec.builder("handle", new StringArgumentType<CommandSender>())
|
|
||||||
.withSuggestionProvider((_, _) ->
|
|
||||||
cache.getInstances().stream()
|
|
||||||
.map(ServerInstance::handle)
|
|
||||||
.toList())
|
|
||||||
.build();
|
|
||||||
literal = Literal.<CommandSender>builder("destroy")
|
|
||||||
.withArgument(handleArg)
|
|
||||||
.withExecutor(this::destroyInstance)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<?> destroyInstance(CommandContext<CommandSender> ctx) {
|
|
||||||
var handle = ctx.getArg(handleArg);
|
|
||||||
return Tasks.supplyAsync(() -> ScrowAPI.instanceManager().destroyInstance(handle))
|
|
||||||
.thenSync(found -> {
|
|
||||||
if (!found) {
|
|
||||||
ctx.sender().sendMessage(ctx.style().err("No instance with that handle found."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.sender().sendMessage(ctx.style().ok("Destroyed server with handle " + handle + "."));
|
|
||||||
}).toFuture();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Literal<CommandSender> literal() {
|
|
||||||
return literal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package de.kentoj.scrow.bukkit.command.instance;
|
|
||||||
|
|
||||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
|
||||||
import de.kentoj.kencommandapi.api.literal.Literal;
|
|
||||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
|
||||||
import de.kentoj.scrow.bukkit.scheduler.Tasks;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
public final class InstanceListLiteral {
|
|
||||||
|
|
||||||
private final Literal<CommandSender> literal;
|
|
||||||
|
|
||||||
InstanceListLiteral() {
|
|
||||||
this.literal = Literal.<CommandSender>builder("list")
|
|
||||||
.withExecutor(this::displayInstanceList)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<?> displayInstanceList(CommandContext<CommandSender> ctx) {
|
|
||||||
return Tasks.supplyAsync(() ->
|
|
||||||
ScrowAPI.instanceManager().instances())
|
|
||||||
.thenSync(instances -> {
|
|
||||||
var msg = Component.text("Running instances:");
|
|
||||||
for (var inst : instances) {
|
|
||||||
msg = msg.appendNewline()
|
|
||||||
.append(Component.text(" - " + inst.handle() + " on port " + inst.port()));
|
|
||||||
}
|
|
||||||
ctx.sender().sendMessage(ctx.style().ok(msg));
|
|
||||||
}).toFuture();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Literal<CommandSender> literal() {
|
|
||||||
return literal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package de.kentoj.scrow.bukkit.command.instance;
|
|
||||||
|
|
||||||
import com.leakyabstractions.result.api.Result;
|
|
||||||
import com.leakyabstractions.result.core.Results;
|
|
||||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
|
||||||
import de.kentoj.kencommandapi.api.literal.Literal;
|
|
||||||
import de.kentoj.kencommandapi.api.literal.RootLiteral;
|
|
||||||
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
public final class LobbyCommand {
|
|
||||||
|
|
||||||
private final RootLiteral<Player> rootLiteral;
|
|
||||||
|
|
||||||
public LobbyCommand() {
|
|
||||||
rootLiteral = Literal.<Player>builder("lobby", "l", "hub")
|
|
||||||
.withSyncExecutor(this::sendToLobby)
|
|
||||||
.asRootLiteral(ScrowMessageStyle.GENERIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendToLobby(CommandContext<Player> ctx) {
|
|
||||||
Bukkit.dispatchCommand(ctx.sender(), "play lobby");
|
|
||||||
}
|
|
||||||
|
|
||||||
public RootLiteral<Player> rootLiteral() {
|
|
||||||
return rootLiteral;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
package de.kentoj.scrow.bukkit.command.instance;
|
|
||||||
|
|
||||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
|
||||||
import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
|
|
||||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
|
||||||
import de.kentoj.kencommandapi.api.literal.Literal;
|
|
||||||
import de.kentoj.kencommandapi.api.literal.RootLiteral;
|
|
||||||
import de.kentoj.scrow.bukkit.ScrowAPI;
|
|
||||||
import de.kentoj.scrow.bukkit.scheduler.Tasks;
|
|
||||||
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
public final class PlayCommand {
|
|
||||||
|
|
||||||
private final RootLiteral<Player> rootLiteral;
|
|
||||||
private final CommandArgumentSpec<Player, String> gamemodeArg;
|
|
||||||
|
|
||||||
public PlayCommand() {
|
|
||||||
var style = ScrowMessageStyle.GENERIC;
|
|
||||||
gamemodeArg = CommandArgumentSpec.builder("gamemode", new StringArgumentType<Player>()).build();
|
|
||||||
rootLiteral = Literal.<Player>builder("play", "queue")
|
|
||||||
.withArgument(gamemodeArg)
|
|
||||||
.withExecutor(this::execute)
|
|
||||||
.asRootLiteral(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<?> execute(CommandContext<Player> ctx) {
|
|
||||||
var gamemode = ctx.getArg(gamemodeArg);
|
|
||||||
return Tasks.supplyAsync(() -> ScrowAPI.instanceManager().instances())
|
|
||||||
.mapSync(instances -> {
|
|
||||||
var instance = instances.stream()
|
|
||||||
.filter(inst -> inst.template().equals(gamemode))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
if (instance == null) {
|
|
||||||
ctx.sender().sendMessage(ctx.style().err("No instance for " + gamemode + " found."));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance.handle();
|
|
||||||
})
|
|
||||||
.yieldIf(Objects::isNull)
|
|
||||||
.thenAsync(handle ->
|
|
||||||
ScrowAPI.getPlayer(ctx.sender()).sendToInstance(handle)
|
|
||||||
).toFuture();
|
|
||||||
}
|
|
||||||
|
|
||||||
public RootLiteral<Player> rootLiteral() {
|
|
||||||
return rootLiteral;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
name: "CoreBukkit"
|
name: "CoreBukkit"
|
||||||
version: "${version}"
|
version: "UNVERSIONED"
|
||||||
depend: [ "LuckPerms" ]
|
depend: [ "LuckPerms" ]
|
||||||
main: "de.kentoj.scrow.bukkit.CoreImplPlugin"
|
main: "de.kentoj.scrow.bukkit.CoreImplPlugin"
|
||||||
api-version: "1.21"
|
api-version: "1.21"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
load("@rules_jvm_external//:defs.bzl", "artifact")
|
load("@rules_jvm_external//:defs.bzl", "artifact")
|
||||||
load("@rules_java//java:java_library.bzl", "java_library")
|
load("@rules_java//java:java_library.bzl", "java_library")
|
||||||
load("@rules_java//java:java_binary.bzl", "java_binary")
|
load("@rules_java//java:java_binary.bzl", "java_binary")
|
||||||
|
load("@rules_jvm_external//:defs.bzl", "artifact", "java_plugin_artifact")
|
||||||
|
|
||||||
java_library(
|
java_library(
|
||||||
name = "api",
|
name = "api",
|
||||||
|
|
@ -20,7 +21,7 @@ java_library(
|
||||||
)
|
)
|
||||||
|
|
||||||
java_binary(
|
java_binary(
|
||||||
name = "plugin",
|
name = "_plugin",
|
||||||
srcs = glob(["plugin/main/java/**/*.java", "api/main/java/**/*.java"]),
|
srcs = glob(["plugin/main/java/**/*.java", "api/main/java/**/*.java"]),
|
||||||
create_executable = False,
|
create_executable = False,
|
||||||
deps = [
|
deps = [
|
||||||
|
|
@ -30,5 +31,29 @@ java_binary(
|
||||||
artifact("io.nats:jnats"),
|
artifact("io.nats:jnats"),
|
||||||
artifact("de.kentoj.scrow:kencommandapi-velocity"),
|
artifact("de.kentoj.scrow:kencommandapi-velocity"),
|
||||||
artifact("org.mongodb:bson"),
|
artifact("org.mongodb:bson"),
|
||||||
|
artifact("org.postgresql:postgresql"),
|
||||||
|
artifact("com.google.code.gson:gson"),
|
||||||
|
],
|
||||||
|
plugins = [
|
||||||
|
java_plugin_artifact(
|
||||||
|
"com.velocitypowered:velocity-api",
|
||||||
|
"com.velocitypowered.api.plugin.ap.PluginAnnotationProcessor",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
genrule(
|
||||||
|
name = "plugin",
|
||||||
|
srcs = [":_plugin_deploy.jar"],
|
||||||
|
outs = ["plugin.jar"],
|
||||||
|
cmd = "cp $< $@",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
load("//rules:publish_plugin.bzl", "publish_plugin")
|
||||||
|
publish_plugin(
|
||||||
|
name = "plugin-publish",
|
||||||
|
package = "core-velocity",
|
||||||
|
filename = "CoreVelocity.jar",
|
||||||
|
src = ":plugin",
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,21 @@ import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||||
import com.velocitypowered.api.plugin.Plugin;
|
import com.velocitypowered.api.plugin.Plugin;
|
||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
import de.kentoj.scrow.corevelocity.misc.OnlineCommand;
|
import de.kentoj.scrow.corevelocity.misc.OnlineCommand;
|
||||||
import de.kentoj.scrow.corevelocity.network.FriendNotifications;
|
|
||||||
import de.kentoj.scrow.corevelocity.network.SendPlayerWatchdog;
|
import de.kentoj.scrow.corevelocity.network.SendPlayerWatchdog;
|
||||||
import de.kentoj.scrow.corevelocity.network.ServerRegisterWatchdog;
|
|
||||||
import de.kentoj.scrow.corevelocity.privmsg.LastTargetCache;
|
import de.kentoj.scrow.corevelocity.privmsg.LastTargetCache;
|
||||||
import de.kentoj.scrow.corevelocity.privmsg.MsgCommand;
|
import de.kentoj.scrow.corevelocity.privmsg.MsgCommand;
|
||||||
import de.kentoj.scrow.corevelocity.privmsg.PrivMsgHandler;
|
import de.kentoj.scrow.corevelocity.privmsg.PrivMsgHandler;
|
||||||
import de.kentoj.scrow.corevelocity.privmsg.ReplyCommand;
|
import de.kentoj.scrow.corevelocity.privmsg.ReplyCommand;
|
||||||
|
import de.kentoj.scrow.corevelocity.serverregistration.ConsulV1ServerInstanceScanner;
|
||||||
|
import de.kentoj.scrow.corevelocity.serverregistration.RegistrationTask;
|
||||||
|
import de.kentoj.scrow.corevelocity.serverregistration.ServerInstanceScanner;
|
||||||
import de.kentoj.scrow.velocity.ScrowAPI;
|
import de.kentoj.scrow.velocity.ScrowAPI;
|
||||||
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
|
||||||
|
import de.kentoj.scrowlib.utils.EnvUtils;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
@Plugin(
|
@Plugin(
|
||||||
id = "corevelocity",
|
id = "corevelocity",
|
||||||
name = "CoreVelocity",
|
name = "CoreVelocity",
|
||||||
|
|
@ -39,11 +43,11 @@ public final class CoreVelocityPlugin {
|
||||||
ScrowAPI.playerCommands().register(new MsgCommand(server, lastTargetCache, privmsgHelper, style).rootLiteral());
|
ScrowAPI.playerCommands().register(new MsgCommand(server, lastTargetCache, privmsgHelper, style).rootLiteral());
|
||||||
ScrowAPI.commands().register(new OnlineCommand(server).rootLiteral());
|
ScrowAPI.commands().register(new OnlineCommand(server).rootLiteral());
|
||||||
|
|
||||||
var registerWatchdog = new ServerRegisterWatchdog(server);
|
|
||||||
var sendPlayerWatchdog = new SendPlayerWatchdog(server);
|
var sendPlayerWatchdog = new SendPlayerWatchdog(server);
|
||||||
registerWatchdog.listen();
|
|
||||||
sendPlayerWatchdog.listen();
|
sendPlayerWatchdog.listen();
|
||||||
|
ServerInstanceScanner instanceScanner = new ConsulV1ServerInstanceScanner(EnvUtils.envOrThrow("HOST_CONSUL"));
|
||||||
server.getEventManager().register(this, new FriendNotifications());
|
server.getScheduler().buildTask(this, new RegistrationTask(server, instanceScanner))
|
||||||
|
.repeat(Duration.ofSeconds(3))
|
||||||
|
.schedule();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package de.kentoj.scrow.corevelocity.network;
|
|
||||||
|
|
||||||
import com.velocitypowered.api.event.Subscribe;
|
|
||||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
|
||||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
|
||||||
import de.kentoj.scrow.velocity.ScrowAPI;
|
|
||||||
import de.kentoj.scrowlib.messaging.MessageBroker;
|
|
||||||
import org.bson.Document;
|
|
||||||
|
|
||||||
public class FriendNotifications {
|
|
||||||
|
|
||||||
private final MessageBroker messageBroker = ScrowAPI.messageBroker();
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
private void onConnect(PostLoginEvent ev) {
|
|
||||||
messageBroker.publish("event.player.connect", new Document("playerId", ev.getPlayer().getUniqueId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
private void onDisconnect(DisconnectEvent ev) {
|
|
||||||
messageBroker.publish("event.player.disconnect", new Document("playerId", ev.getPlayer().getUniqueId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package de.kentoj.scrow.corevelocity.network;
|
|
||||||
|
|
||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
|
||||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
|
||||||
import de.kentoj.scrow.velocity.ScrowAPI;
|
|
||||||
import de.kentoj.scrowlib.messaging.MessageBroker;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
|
|
||||||
public class ServerRegisterWatchdog {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ServerRegisterWatchdog.class);
|
|
||||||
|
|
||||||
private final MessageBroker messageBroker = ScrowAPI.messageBroker();
|
|
||||||
private final ProxyServer server;
|
|
||||||
|
|
||||||
public ServerRegisterWatchdog(ProxyServer server) {
|
|
||||||
this.server = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void listen() {
|
|
||||||
messageBroker.subscribe("event.instance.up", doc -> {
|
|
||||||
var handle = doc.getString("handle");
|
|
||||||
var port = doc.getInteger("port");
|
|
||||||
server.unregisterServer(new ServerInfo(handle, new InetSocketAddress(port)));
|
|
||||||
log.info("registered server {}", handle);
|
|
||||||
});
|
|
||||||
|
|
||||||
messageBroker.subscribe("event.instance.down", doc -> {
|
|
||||||
var handle = doc.getString("handle");
|
|
||||||
var port = doc.getInteger("port");
|
|
||||||
server.unregisterServer(new ServerInfo(handle, new InetSocketAddress(port)));
|
|
||||||
log.info("unregistered server {}", handle);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package de.kentoj.scrow.corevelocity.serverregistration;
|
||||||
|
|
||||||
|
import com.google.common.hash.Hashing;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class ConsulV1ServerInstanceScanner implements ServerInstanceScanner, Closeable {
|
||||||
|
|
||||||
|
private final String consulUrl;
|
||||||
|
private final HttpClient client;
|
||||||
|
|
||||||
|
public ConsulV1ServerInstanceScanner(String consulUrl) {
|
||||||
|
this.consulUrl = consulUrl;
|
||||||
|
this.client = HttpClient.newHttpClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
// FIXME query cluster-wide
|
||||||
|
public Stream<ServerInfo> queryMinecraftServers() throws IOException, InterruptedException {
|
||||||
|
var url = consulUrl + "/v1/agent/services/?filter=" + URLEncoder.encode("\"minecraft\" in Tags", StandardCharsets.UTF_8);
|
||||||
|
var request = HttpRequest.newBuilder(URI.create(url))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
var jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||||
|
return jsonResponse.entrySet().stream()
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.map(JsonElement::getAsJsonObject)
|
||||||
|
.map(service -> {
|
||||||
|
var name = toServerName(service.get("Service").getAsString(), service.get("ID").getAsString());
|
||||||
|
var host = InetSocketAddress.createUnresolved(service.get("Address").getAsString(),
|
||||||
|
service.get("Port").getAsInt());
|
||||||
|
return new ServerInfo(name, host);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toServerName(String serviceName, String id) {
|
||||||
|
return serviceName + "-" + Integer.toUnsignedString(Hashing.murmur3_32_fixed()
|
||||||
|
.hashString(id, StandardCharsets.UTF_8)
|
||||||
|
.asInt(), 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package de.kentoj.scrow.corevelocity.serverregistration;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class RegistrationTask implements Runnable {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(RegistrationTask.class);
|
||||||
|
|
||||||
|
private final ProxyServer server;
|
||||||
|
private final ServerInstanceScanner watcher;
|
||||||
|
|
||||||
|
public RegistrationTask(ProxyServer server, ServerInstanceScanner watcher) {
|
||||||
|
this.server = server;
|
||||||
|
this.watcher = watcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
watcher.queryMinecraftServers().forEach(server::registerServer);
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new RuntimeException("Failed querying available servers", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package de.kentoj.scrow.corevelocity.serverregistration;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public interface ServerInstanceScanner {
|
||||||
|
|
||||||
|
Stream<ServerInfo> queryMinecraftServers() throws IOException, InterruptedException;
|
||||||
|
}
|
||||||
37
rules/BUILD
Normal file
37
rules/BUILD
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
load("//rules:dev_server.bzl", "dev_server")
|
||||||
|
load("//rules:fetch_file.bzl", "fetch_file")
|
||||||
|
|
||||||
|
dev_server(
|
||||||
|
name = "dev",
|
||||||
|
server_jar = ":papermc",
|
||||||
|
plugins = [
|
||||||
|
":luckperms",
|
||||||
|
":viaversion",
|
||||||
|
":viabackwards",
|
||||||
|
],
|
||||||
|
local_plugins = [
|
||||||
|
"//core/bukkit:plugin",
|
||||||
|
"//buildserver/configurator: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"
|
||||||
|
)
|
||||||
51
rules/dev_server.bzl
Normal file
51
rules/dev_server.bzl
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
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(),
|
||||||
|
},
|
||||||
|
)
|
||||||
22
rules/fetch_file.bzl
Normal file
22
rules/fetch_file.bzl
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# TODO implement sha256sum attribute + checksum verification
|
||||||
|
|
||||||
|
def _fetch_file_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_file_impl,
|
||||||
|
attrs = {
|
||||||
|
"url": attr.string(mandatory = True),
|
||||||
|
"filename": attr.string(mandatory = True),
|
||||||
|
},
|
||||||
|
)
|
||||||
68
rules/publish_plugin.bzl
Normal file
68
rules/publish_plugin.bzl
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
# see https://forgejo.org/docs/latest/user/packages/generic/
|
||||||
|
# We need to delete the previous version to
|
||||||
|
# FIXME we're leaking credentials in the process listing(args can be seen in ps(1) output)
|
||||||
|
# solvable by using a temporary .netrc file(create with umask 0600 or smth)
|
||||||
|
|
||||||
|
_TEMPLATE = """#!/bin/sh
|
||||||
|
set -eux
|
||||||
|
: ${GENERIC_USERNAME:=__USERNAME__}
|
||||||
|
: ${GENERIC_PASSWORD:=__PASSWORD__}
|
||||||
|
: ${GENERIC_REPO:=__REPOSITORY__}
|
||||||
|
|
||||||
|
URL="$GENERIC_REPO"'/__PACKAGE__/UNVERSIONED/__FILENAME__'
|
||||||
|
CREDS="$GENERIC_USERNAME:$GENERIC_PASSWORD"
|
||||||
|
|
||||||
|
delete_previous_version() {
|
||||||
|
curl \
|
||||||
|
--fail \
|
||||||
|
--user "$CREDS" \
|
||||||
|
-X DELETE \
|
||||||
|
"$URL" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
publish_new_version() {
|
||||||
|
curl \
|
||||||
|
--fail \
|
||||||
|
--user "$CREDS" \
|
||||||
|
--upload-file '__SOURCE__' \
|
||||||
|
"$URL"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Deleting previous version at $URL"
|
||||||
|
delete_previous_version || echo "Note: 404 is expected if this is the first published version"
|
||||||
|
echo "Pushing new version to $URL"
|
||||||
|
publish_new_version
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _publish_plugin_impl(ctx):
|
||||||
|
script = ctx.actions.declare_file(ctx.label.name + "_publisher")
|
||||||
|
src = ctx.attr.src[DefaultInfo].files.to_list()[0]
|
||||||
|
repository = ctx.var.get("generic_repo", "")
|
||||||
|
username = ctx.var.get("generic_user", "")
|
||||||
|
password = ctx.var.get("generic_password", "")
|
||||||
|
ctx.actions.write(
|
||||||
|
output = script,
|
||||||
|
is_executable = True,
|
||||||
|
content = _TEMPLATE
|
||||||
|
.replace("__USERNAME__", username)
|
||||||
|
.replace("__PASSWORD__", password)
|
||||||
|
.replace("__REPOSITORY__", repository)
|
||||||
|
.replace("__PACKAGE__", ctx.attr.package)
|
||||||
|
.replace("__SOURCE__", src.short_path)
|
||||||
|
.replace("__FILENAME__", ctx.attr.filename)
|
||||||
|
)
|
||||||
|
|
||||||
|
return DefaultInfo(
|
||||||
|
executable = script,
|
||||||
|
runfiles = ctx.runfiles(files = [src]),
|
||||||
|
)
|
||||||
|
|
||||||
|
publish_plugin = rule(
|
||||||
|
implementation = _publish_plugin_impl,
|
||||||
|
executable = True,
|
||||||
|
attrs = {
|
||||||
|
"src": attr.label(allow_single_file = True, mandatory = True),
|
||||||
|
"package": attr.string(mandatory = True),
|
||||||
|
"filename": attr.string(mandatory = True),
|
||||||
|
},
|
||||||
|
)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue