This commit is contained in:
kento2 2026-08-30 17:02:37 +02:00
parent 855050c2d1
commit 77decae10c
74 changed files with 1190 additions and 861 deletions

View file

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

View file

@ -30,9 +30,9 @@ maven.install(
"org.postgresql:postgresql:42.7.12", "org.postgresql:postgresql:42.7.12",
"org.mongodb:bson:5.8.0", "org.mongodb:bson:5.8.0",
"io.nats:jnats:2.25.2", "io.nats:jnats:2.25.2",
"site.lab0x13.scrow:commands-common:1.3.3", "site.lab0x13.scrow:commands-common:1.4.0",
"site.lab0x13.scrow:commands-bukkit:1.3.3", "site.lab0x13.scrow:commands-bukkit:1.4.0",
"site.lab0x13.scrow:commands-velocity:1.3.3", "site.lab0x13.scrow:commands-velocity:1.4.0",
"io.papermc.paper:paper-api:[26.2.build,)", "io.papermc.paper:paper-api:[26.2.build,)",
"net.luckperms:api:5.5", "net.luckperms:api:5.5",
"com.google.code.gson:gson:2.14.0", "com.google.code.gson:gson:2.14.0",

View file

View file

@ -1,15 +0,0 @@
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() + ")";
}
}

View file

@ -1,42 +0,0 @@
package site.lab0x13.scrow.configurator;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.type.ConfigType;
/**
* @param <V> type of the value
*/
public final class ConfigNode<V> {
private final ConfigType<V> type;
private final @Nullable V defaultValue;
private @Nullable V value;
public ConfigNode(ConfigType<V> type, @Nullable V defaultValue) {
this.type = type;
this.defaultValue = defaultValue;
reset();
}
// TODO requirements
public boolean required() {
return defaultValue == null;
}
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;
}
}

View file

@ -1,46 +0,0 @@
package site.lab0x13.scrow.configurator;
import de.kentoj.scrow.bukkit.ScrowAPI;
import org.bukkit.plugin.java.JavaPlugin;
import site.lab0x13.scrow.configurator.command.ConfigCommand;
import site.lab0x13.scrow.configurator.config.Config;
import site.lab0x13.scrow.configurator.config.ConfigImpl;
import site.lab0x13.scrow.configurator.config.ConfigRegistry;
import site.lab0x13.scrow.configurator.type.list.ConfigListType;
import site.lab0x13.scrow.configurator.type.location.LocationConfigType;
import java.io.IOException;
import java.util.ArrayList;
public class ConfiguratorPlugin extends JavaPlugin {
private final ConfigRegistry configRegistry = new ConfigRegistry();
@Override
public void onEnable() {
Config hotPotatoConfig = new ConfigImpl(this.getDataPath().resolve("config.json"));
configRegistry.register(hotPotatoConfig);
var centerLocation = new ConfigKey<>("centerLocation", new LocationConfigType());
var spawnLocations = new ConfigKey<>("spawnLocations", new ConfigListType<>(new LocationConfigType()));
hotPotatoConfig.register(centerLocation);
hotPotatoConfig.register(spawnLocations, new ArrayList<>());
try {
hotPotatoConfig.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
ScrowAPI.commands().register(new ConfigCommand(configRegistry).rootLiteral());
}
@Override
public void onDisable() {
try {
for (var cfg : configRegistry.allConfigs().values())
cfg.save();
} catch (IOException e) {
throw new RuntimeException("Failed saving config", e);
}
}
}

View file

@ -1,21 +0,0 @@
package site.lab0x13.scrow.configurator.action;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.configurator.ConfigNode;
import java.util.List;
/**
* @param <V> type of node value
*/
public interface ConfigAction<V> {
String name();
List<Argument<ScrowBukkitCC, ?>> arguments(ConfigNode<V> node);
boolean requiresValue();
void execute(ConfigNode<V> node, ScrowBukkitCC ctx);
}

View file

@ -1,43 +0,0 @@
package site.lab0x13.scrow.configurator.command;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import net.kyori.adventure.text.format.TextColor;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.literal.Literal;
import site.lab0x13.scrow.commands.model.literal.LiteralMeta;
import site.lab0x13.scrow.commands.model.literal.RootLiteral;
import site.lab0x13.scrow.configurator.config.ConfigRegistry;
import java.util.List;
public final class ConfigCommand {
private final RootLiteral<ScrowBukkitCC> rootLiteral;
private final ConfigRegistry configRegistry;
public ConfigCommand(ConfigRegistry configRegistry) {
this.configRegistry = configRegistry;
// TODO undo literal that undoes the last set command
this.rootLiteral = Literal.<ScrowBukkitCC>dynamic("cfg")
.withSubLiteralMetas(this::subLiteralMetas)
.withSubLiteralProvider(this::configLiteral)
.asRootLiteral(new ScrowMessageStyle("Configurator", TextColor.color(0x9070C0)));
}
private List<LiteralMeta> subLiteralMetas() {
return configRegistry.allConfigs().keySet().stream()
.map(LiteralMeta::of)
.toList();
}
private @Nullable Literal<ScrowBukkitCC> configLiteral(String name) {
var cfg = configRegistry.getConfig(name);
if (cfg == null) return null;
return ConfigLiteral.literal(name, cfg);
}
public RootLiteral<ScrowBukkitCC> rootLiteral() {
return rootLiteral;
}
}

View file

@ -1,36 +0,0 @@
package site.lab0x13.scrow.configurator.command;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import site.lab0x13.scrow.commands.model.literal.Literal;
import site.lab0x13.scrow.configurator.config.Config;
final class ShowLiteral {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
private final Literal<ScrowBukkitCC> literal;
private final Config cfg;
ShowLiteral(Config cfg) {
this.cfg = cfg;
this.literal = Literal.<ScrowBukkitCC>builder("show")
.withSyncExecutor(this::execute)
.build();
}
private void execute(ScrowBukkitCC ctx) {
var prettyJson = GSON.toJson(cfg.toJson(), JsonElement.class);
var component = Component.text(prettyJson + " [click to copy]")
.clickEvent(ClickEvent.copyToClipboard(prettyJson));
ctx.sender().sendMessage(ctx.style().ok(component));
}
public Literal<ScrowBukkitCC> literal() {
return literal;
}
}

View file

@ -1,43 +0,0 @@
package site.lab0x13.scrow.configurator.config;
import com.google.gson.JsonElement;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.ConfigKey;
import site.lab0x13.scrow.configurator.ConfigNode;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
public interface Config {
void save() throws IOException;
Path path();
<V> void register(ConfigKey<V> key, @Nullable V defaultValue);
default <V> void register(ConfigKey<V> key) {
this.register(key, null);
}
@Nullable ConfigNode<?> getNode(String id);
default <V> @Nullable ConfigNode<?> getNode(ConfigKey<V> key) {
return this.getNode(key.id());
}
default <V> @Nullable V get(ConfigKey<V> key) {
var n = getNode(key);
if (n == null) return null;
return (V) n.value();
}
Map<String, ConfigNode<?>> allNodes();
JsonElement toJson();
void loadJson(JsonElement json);
void load() throws IOException;
}

View file

@ -1,125 +0,0 @@
package site.lab0x13.scrow.configurator.config;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import site.lab0x13.scrow.configurator.ConfigKey;
import site.lab0x13.scrow.configurator.ConfigNode;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
public final class ConfigImpl implements Config {
private static final Logger log = LoggerFactory.getLogger(ConfigImpl.class);
private final Map<String, ConfigNode<?>> nodes = new HashMap<>();
private final Path path;
public ConfigImpl(Path path) {
this.path = path;
}
@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 ConfigNode<?> getNode(String id) {
ConfigNode<Object> node;
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 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);
}
}
@Override
public void loadJson(JsonElement json) {
var obj = json.getAsJsonObject();
nodes.forEach((key, _node) -> {
var node = (ConfigNode<Object>) _node;
var nodeValue = obj.get(key);
node.value(node.type().deserialize(nodeValue));
});
}
@Override
public JsonElement toJson() {
var obj = new JsonObject();
nodes.forEach((key, _node) -> {
var node = (ConfigNode<Object>) _node;
var nodeValue = node.type().serialize(node.value());
obj.add(key, nodeValue);
});
return obj;
}
@Override
public void load() throws IOException {
if (!Files.exists(this.path)) return;
var json = Files.readString(this.path());
this.loadJson(JsonParser.parseString(json));
}
@Override
public void save() throws IOException {
Files.createDirectories(this.path().getParent());
Files.writeString(this.path(), this.toJson().toString());
}
@Override
public Path path() {
return path;
}
}

View file

@ -1,28 +0,0 @@
package site.lab0x13.scrow.configurator.config;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class ConfigRegistry {
private static final Logger log = LoggerFactory.getLogger(ConfigRegistry.class);
private final Map<String, Config> configs = new HashMap<>();
public void register(Config config) {
var oldValue = configs.put(config.path().toString(), config);
if (oldValue != null)
log.warn("Overwriting old config with same path '{}'", config.path());
}
public Map<String, Config> allConfigs() {
return new HashMap<>(configs);
}
public @Nullable Config getConfig(String path) {
return configs.get(path);
}
}

View file

@ -1,51 +0,0 @@
package site.lab0x13.scrow.configurator.type;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public abstract class ConfigComplexType<V> implements ConfigType<V> {
private final Map<String, Field<V, ?>> fields = new HashMap<>();
protected abstract V deserialize(JsonObject obj);
protected <S> void addField(String name, ConfigType<S> type, Function<V, S> getter) {
fields.put(name, new Field<>(type, getter));
}
protected <S> S readField(JsonObject object, String name) {
var field = fields.get(name);
return (S) field.type().deserialize(object.get(name));
}
@Override
public V deserialize(JsonElement json) {
if (json.getAsJsonObject().isEmpty())
return null;
return deserialize(json.getAsJsonObject());
}
@Override
public JsonElement serialize(V value) {
if (value == null) return new JsonObject();
var obj = new JsonObject();
fields.forEach((name, field) -> writeNode(obj, name, field, value));
return obj;
}
private <S> void writeNode(JsonObject object, String name, Field<V, S> field, V value) {
var fieldValue = field.getter.apply(value);
var serializedFieldValue = field.type().serialize(fieldValue);
object.add(name, serializedFieldValue);
}
public record Field<T, S>(
ConfigType<S> type,
Function<T, S> getter
) {
}
}

View file

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

View file

@ -1,33 +0,0 @@
package site.lab0x13.scrow.configurator.type;
import de.kentoj.scrowlib.utils.Serializer;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.ConfigNode;
import site.lab0x13.scrow.configurator.action.ConfigAction;
import site.lab0x13.scrow.configurator.action.GlobalSetJsonAction;
import site.lab0x13.scrow.configurator.action.GlobalShowAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @param <V> type of the stored value
*/
public interface ConfigType<V> extends Serializer<V> {
default List<ConfigAction<?>> actions() {
return new ArrayList<>(List.of(
new GlobalShowAction(),
new GlobalSetJsonAction()
));
}
default List<String> getSubNodeNames(V value) {
return Collections.emptyList();
}
default @Nullable ConfigNode<?> getSubNode(V value, String name) {
return null;
}
}

View file

@ -1,66 +0,0 @@
package site.lab0x13.scrow.configurator.type.list;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.ConfigNode;
import site.lab0x13.scrow.configurator.action.ConfigAction;
import site.lab0x13.scrow.configurator.type.ConfigType;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public final class ConfigListType<V> implements ConfigType<List<ConfigNode<V>>> {
private final ConfigType<V> elementType;
private final List<ConfigAction<?>> actions;
public ConfigListType(ConfigType<V> elementType) {
this.elementType = elementType;
actions = ConfigType.super.actions();
actions.add(new ListDeleteAction<>());
actions.add(new ListAddAction<>(() -> new ConfigNode<>(elementType, null)));
}
@Override
public List<ConfigAction<?>> actions() {
return this.actions;
}
@Override
public List<ConfigNode<V>> deserialize(JsonElement json) {
var result = new ArrayList<ConfigNode<V>>();
var array = json.getAsJsonArray();
for (int i = 0; i < array.asList().size(); i++) {
var subNode = new ConfigNode<>(elementType, null);
subNode.value(elementType.deserialize(array.get(i)));
result.add(i, subNode);
}
return result;
}
@Override
public JsonElement serialize(List<ConfigNode<V>> value) {
var res = new JsonArray();
for (var subNode : value) {
var subNodeValue = subNode.value();
if (subNodeValue == null) continue;
var element = elementType.serialize(subNodeValue);
res.add(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));
}
}

View file

@ -1,42 +0,0 @@
package site.lab0x13.scrow.configurator.type.list;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.configurator.action.ConfigAction;
import site.lab0x13.scrow.configurator.ConfigNode;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
class ListAddAction<V> implements ConfigAction<List<ConfigNode<V>>> {
private final Supplier<ConfigNode<V>> newElement;
ListAddAction(Supplier<ConfigNode<V>> newElement) {
this.newElement = newElement;
}
@Override
public String name() {
return "add";
}
@Override
public List<Argument<ScrowBukkitCC, ?>> arguments(ConfigNode<List<ConfigNode<V>>> __) {
return Collections.emptyList();
}
@Override
public boolean requiresValue() {
return true;
}
@Override
public void execute(ConfigNode<List<ConfigNode<V>>> node, ScrowBukkitCC ctx) {
assert node.value() != null;
var element = newElement.get();
node.value().add(element);
ctx.sender().sendMessage(ctx.style().ok("Extended list-node by one with null value."));
}
}

View file

@ -1,49 +0,0 @@
package site.lab0x13.scrow.configurator.type.list;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.commands.model.argument.types.IntegerArgumentType;
import site.lab0x13.scrow.configurator.action.ConfigAction;
import site.lab0x13.scrow.configurator.ConfigNode;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
class ListDeleteAction<V> implements ConfigAction<List<ConfigNode<V>>> {
@Override
public String name() {
return "delete";
}
@Override
public List<Argument<ScrowBukkitCC, ?>> arguments(ConfigNode<List<ConfigNode<V>>> node) {
return List.of(Argument.builder("index", new IntegerArgumentType<ScrowBukkitCC>())
.withRequirement((_, index) -> {
assert node.value() != null;
return index >= 0 && index < node.value().size();
}, "index out of range")
.withSuggestionProvider((_, _) -> {
if (node.value() == null)
return Collections.emptyList();
return IntStream.range(0, node.value().size())
.mapToObj(Integer::toString).toList();
})
.build());
}
@Override
public boolean requiresValue() {
return true;
}
@Override
public void execute(ConfigNode<List<ConfigNode<V>>> node, ScrowBukkitCC ctx) {
assert node.value() != null;
int index = ctx.getArg("index");
var oldValue = node.value().remove(index);
var msg = oldValue == null ? "Deleted empty element." : "Deleted element.";
ctx.sender().sendMessage(ctx.style().ok(msg));
}
}

View file

@ -1,49 +0,0 @@
package site.lab0x13.scrow.configurator.type.location;
import com.google.gson.JsonObject;
import org.bukkit.Location;
import site.lab0x13.scrow.configurator.action.ConfigAction;
import site.lab0x13.scrow.configurator.type.ConfigComplexType;
import site.lab0x13.scrow.configurator.type.ConfigPrimitiveType;
import site.lab0x13.scrow.configurator.type.ConfigType;
import site.lab0x13.scrow.configurator.type.world.WorldConfigType;
import java.util.ArrayList;
import java.util.List;
public class LocationConfigType extends ConfigComplexType<Location> {
public static final LocationConfigType INSTANCE = new LocationConfigType();
private final List<ConfigAction<?>> actions;
public LocationConfigType() {
addField("world", WorldConfigType.INSTANCE, Location::getWorld);
addField("x", ConfigPrimitiveType.DOUBLE, Location::getX);
addField("y", ConfigPrimitiveType.DOUBLE, Location::getY);
addField("z", ConfigPrimitiveType.DOUBLE, Location::getZ);
addField("yaw", ConfigPrimitiveType.FLOAT, Location::getYaw);
addField("pitch", ConfigPrimitiveType.FLOAT, Location::getPitch);
actions = new ArrayList<>(super.actions());
actions.add(new LocationPickCurrentAction());
actions.add(new LocationTeleportAction());
}
@Override
public List<ConfigAction<?>> actions() {
return actions;
}
@Override
public Location deserialize(JsonObject obj) {
return new Location(
readField(obj, "world"),
readField(obj, "x"),
readField(obj, "y"),
readField(obj, "z"),
readField(obj, "yaw"),
readField(obj, "pitch")
);
}
}

View file

@ -1,34 +0,0 @@
package site.lab0x13.scrow.configurator.type.world;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import org.bukkit.Bukkit;
import org.bukkit.World;
import site.lab0x13.scrow.configurator.action.ConfigAction;
import site.lab0x13.scrow.configurator.type.ConfigType;
import java.util.List;
import java.util.UUID;
public class WorldConfigType implements ConfigType<World> {
public static ConfigType<World> INSTANCE = new WorldConfigType();
private WorldConfigType() {
}
@Override
public JsonElement serialize(World value) {
return new JsonPrimitive(value.getUID().toString());
}
@Override
public World deserialize(JsonElement json) {
return Bukkit.getWorld(UUID.fromString(json.getAsString()));
}
@Override
public List<ConfigAction<?>> actions() {
return List.of(new SetWorldAction());
}
}

View file

@ -6,7 +6,7 @@ import java.util.concurrent.CompletableFuture;
public interface Minigame { public interface Minigame {
ScrowMessageStyle messageStyle(); ScrowMessageStyle style();
/** /**
* Required participant count * Required participant count

View file

@ -30,7 +30,7 @@ public class PhaseContext<T extends Minigame> {
} }
public ScrowMessageStyle style() { public ScrowMessageStyle style() {
return game.messageStyle(); return game.style();
} }
public PhaseListeners listeners() { public PhaseListeners listeners() {

29
core/configurator/BUILD Normal file
View file

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

View file

@ -5,7 +5,7 @@ the fields' values in-game using commands such as `/cfg key chat.messageDelay se
<small> <small>
the key literal in `/cfg key` is required, because there are other modes like `/cfg check` that checks the key literal in `/cfg key` is required, because there are other modes like `/cfg check` that checks
the config for uninitialized values. the configFile for uninitialized values.
</small> </small>
# Design # Design
@ -14,7 +14,7 @@ Plugins register configuration nodes by key. A key consists of a unique identifi
The type handles (de)serialization and defines actions. Actions can be used on nodes. The type handles (de)serialization and defines actions. Actions can be used on nodes.
Nodes hold values. Nodes hold values.
The `/config` or `/cfg` command is used by admins to configure the plugin: The `/configFile` or `/cfg` command is used by admins to configure the plugin:
A lobby plugin might register a node with the identifier `lobby.spawnLocation` and type `ConfigLocationType`, A lobby plugin might register a node with the identifier `lobby.spawnLocation` and type `ConfigLocationType`,
which gives us the command `/cfg key lobby.spawnLocation`. 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") Let's say the ConfigLocationType defines an action to use the player's location as the value("pick-current")

View file

@ -0,0 +1,48 @@
package site.lab0x13.scrow.configurator;
import de.kentoj.scrow.bukkit.ScrowAPI;
import org.bukkit.plugin.java.JavaPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import site.lab0x13.scrow.configurator.command.ConfiguratorCommand;
import site.lab0x13.scrow.configurator.configfile.ConfigFile;
import site.lab0x13.scrow.configurator.configfile.ConfigFileRegistry;
import site.lab0x13.scrow.configurator.storable.impl.VectorStorable;
import site.lab0x13.scrow.configurator.storable.impl.location.LocationStorable;
import java.io.IOException;
public class ConfiguratorPlugin extends JavaPlugin {
private static final Logger log = LoggerFactory.getLogger(ConfiguratorPlugin.class);
@Override
public void onEnable() {
ConfigFile hotPotatoConfig = ConfigFile.of(this.getDataPath().resolve("config.json"));
ConfigFileRegistry.get().register(hotPotatoConfig);
var someVector = new VectorStorable();
var someLocation = new LocationStorable();
hotPotatoConfig.registerStorable("someVector", someVector);
hotPotatoConfig.registerStorable("someLocation", someLocation);
try {
hotPotatoConfig.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
ScrowAPI.commands().register(ConfiguratorCommand.rootLiteral(ConfigFileRegistry.get()));
}
@Override
public void onDisable() {
try {
for (var cfg : ConfigFileRegistry.get().allConfigs().values()) {
log.info("saving {}...", cfg.path());
cfg.save();
}
} catch (IOException e) {
throw new RuntimeException("Failed saving config", e);
}
}
}

View file

@ -4,11 +4,11 @@ import com.google.gson.JsonElement;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC; import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import de.kentoj.scrowlib.command.type.JsonArgumentType; import de.kentoj.scrowlib.command.type.JsonArgumentType;
import site.lab0x13.scrow.commands.model.argument.Argument; import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.configurator.ConfigNode; import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.List; import java.util.List;
public final class GlobalSetJsonAction implements ConfigAction<Object> { public final class GlobalSetJsonAction implements StorableAction<Object, Storable<Object>> {
private final Argument<ScrowBukkitCC, JsonElement> valueArg = private final Argument<ScrowBukkitCC, JsonElement> valueArg =
Argument.builder("jsonValue", new JsonArgumentType<ScrowBukkitCC>()).build(); Argument.builder("jsonValue", new JsonArgumentType<ScrowBukkitCC>()).build();
@ -19,7 +19,7 @@ public final class GlobalSetJsonAction implements ConfigAction<Object> {
} }
@Override @Override
public List<Argument<ScrowBukkitCC, ?>> arguments(ConfigNode<Object> __) { public List<Argument<ScrowBukkitCC, ?>> arguments(Storable<Object> __) {
return List.of(valueArg); return List.of(valueArg);
} }
@ -29,15 +29,13 @@ public final class GlobalSetJsonAction implements ConfigAction<Object> {
} }
@Override @Override
public void execute(ConfigNode<Object> node, ScrowBukkitCC ctx) { public void execute(Storable<Object> storable, ScrowBukkitCC ctx) {
Object value;
try { try {
value = node.type().deserialize(ctx.getArg(valueArg)); storable.loadJson(ctx.getArg(valueArg));
} catch (Exception ex) { } catch (Exception ex) {
ctx.sender().sendMessage(ctx.style().err("Failed serializing: " + ex.getMessage())); ctx.sender().sendMessage(ctx.style().err("Failed serializing: " + ex.getMessage()));
return; return;
} }
node.value(value);
ctx.sender().sendMessage(ctx.style().ok("Value set.")); ctx.sender().sendMessage(ctx.style().ok("Value set."));
} }
} }

View file

@ -7,11 +7,12 @@ import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
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 site.lab0x13.scrow.commands.model.argument.Argument; import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.configurator.ConfigNode; import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.Collections;
import java.util.List; import java.util.List;
public final class GlobalShowAction implements ConfigAction<Object> { public final class GlobalShowAction implements StorableAction<Object, Storable<Object>> {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create(); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
@ -21,8 +22,8 @@ public final class GlobalShowAction implements ConfigAction<Object> {
} }
@Override @Override
public List<Argument<ScrowBukkitCC, ?>> arguments(ConfigNode<Object> __) { public List<Argument<ScrowBukkitCC, ?>> arguments(Storable<Object> __) {
return List.of(); return Collections.emptyList();
} }
@Override @Override
@ -31,9 +32,8 @@ public final class GlobalShowAction implements ConfigAction<Object> {
} }
@Override @Override
public void execute(ConfigNode<Object> node, ScrowBukkitCC ctx) { public void execute(Storable<Object> storable, ScrowBukkitCC ctx) {
var jsonElement = node.type().serialize(node.value()); var prettyJson = GSON.toJson(storable.toJson(), 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(ctx.style().ok(component)); ctx.sender().sendMessage(ctx.style().ok(component));

View file

@ -1,10 +1,10 @@
package site.lab0x13.scrow.configurator.action; package site.lab0x13.scrow.configurator.action;
import site.lab0x13.scrow.commands.model.argument.Argument;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC; import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import site.lab0x13.scrow.configurator.ConfigNode; import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -12,12 +12,12 @@ 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<V> implements ConfigAction<V> { public abstract class PickAction<V> implements StorableAction<Object, Storable<Object>> {
protected abstract CompletableFuture<V> pick(Player player); protected abstract CompletableFuture<V> pick(Player player);
@Override @Override
public List<Argument<ScrowBukkitCC, ?>> arguments(ConfigNode<V> __) { public List<Argument<ScrowBukkitCC, ?>> arguments(Storable<Object> __) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -27,7 +27,7 @@ public abstract class PickAction<V> implements ConfigAction<V> {
} }
@Override @Override
public void execute(ConfigNode<V> node, ScrowBukkitCC ctx) { public void execute(Storable<Object> node, ScrowBukkitCC ctx) {
pick(ctx.player()) pick(ctx.player())
.orTimeout(10, TimeUnit.MINUTES) .orTimeout(10, TimeUnit.MINUTES)
.whenComplete((picked, ex) -> { .whenComplete((picked, ex) -> {

View file

@ -0,0 +1,18 @@
package site.lab0x13.scrow.configurator.action;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.List;
public interface StorableAction<V, T extends Storable<V>> {
String name();
List<Argument<ScrowBukkitCC, ?>> arguments(T node);
boolean requiresValue();
void execute(T storable, ScrowBukkitCC ctx);
}

View file

@ -2,17 +2,19 @@ package site.lab0x13.scrow.configurator.command;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC; import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import site.lab0x13.scrow.commands.model.literal.Literal; import site.lab0x13.scrow.commands.model.literal.Literal;
import site.lab0x13.scrow.configurator.config.Config; import site.lab0x13.scrow.configurator.configfile.ConfigFile;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Objects.*;
final class CheckLiteral { final class CheckLiteral {
private final Literal<ScrowBukkitCC> literal; private final Literal<ScrowBukkitCC> literal;
private final Config cfg; private final ConfigFile configFile;
public CheckLiteral(Config cfg) { public CheckLiteral(ConfigFile configFile) {
this.cfg = cfg; this.configFile = configFile;
this.literal = Literal.<ScrowBukkitCC>builder("check") this.literal = Literal.<ScrowBukkitCC>builder("check")
.withSyncExecutor(this::execute) .withSyncExecutor(this::execute)
.build(); .build();
@ -20,16 +22,16 @@ final class CheckLiteral {
private void execute(ScrowBukkitCC ctx) { private void execute(ScrowBukkitCC ctx) {
var badNodes = new AtomicInteger(); var badNodes = new AtomicInteger();
cfg.allNodes().forEach((key, node) -> { configFile.allKeys().forEach(key -> {
if (!node.required() || node.value() != null) return; var error = requireNonNull(configFile.getStorable(key)).checkValue();
ctx.sender().sendMessage(ctx.style().ok("Missing value for " + key + ".")); if (error == null) return;
badNodes.getAndIncrement(); ctx.sender().sendMessage(ctx.style().ok(key + ": " + error));
}); });
if (badNodes.get() > 0) if (badNodes.get() > 0)
ctx.sender().sendMessage(ctx.style().err("Missing " + badNodes + " values.")); ctx.sender().sendMessage(ctx.style().err("Found " + badNodes + " errors."));
else else
ctx.sender().sendMessage(ctx.style().ok("No missing values.")); ctx.sender().sendMessage(ctx.style().ok("No errors."));
} }
public Literal<ScrowBukkitCC> literal() { public Literal<ScrowBukkitCC> literal() {

View file

@ -2,16 +2,15 @@ package site.lab0x13.scrow.configurator.command;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC; import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import site.lab0x13.scrow.commands.model.literal.Literal; import site.lab0x13.scrow.commands.model.literal.Literal;
import site.lab0x13.scrow.configurator.config.Config; import site.lab0x13.scrow.configurator.configfile.ConfigFile;
final class ConfigLiteral { public final class ConfigLiteral {
private ConfigLiteral() { private ConfigLiteral() {
} }
public static Literal<ScrowBukkitCC> literal(String configName, Config cfg) { public static Literal<ScrowBukkitCC> literal(ConfigFile cfg) {
return Literal.<ScrowBukkitCC>builder(configName) return Literal.<ScrowBukkitCC>builder(cfg.path().normalize().toString())
.withSubLiteral(new KeyLiteral(cfg).literal()) .withSubLiteral(new KeyLiteral(cfg).literal())
.withSubLiteral(new ShowLiteral(cfg).literal())
.withSubLiteral(new CheckLiteral(cfg).literal()) .withSubLiteral(new CheckLiteral(cfg).literal())
.build(); .build();
} }

View file

@ -0,0 +1,27 @@
package site.lab0x13.scrow.configurator.command;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import net.kyori.adventure.text.format.TextColor;
import site.lab0x13.scrow.commands.model.literal.Literal;
import site.lab0x13.scrow.commands.model.literal.LiteralMeta;
import site.lab0x13.scrow.commands.model.literal.RootLiteral;
import site.lab0x13.scrow.configurator.configfile.ConfigFileRegistry;
public final class ConfiguratorCommand {
private ConfiguratorCommand() {
}
public static RootLiteral<ScrowBukkitCC> rootLiteral(ConfigFileRegistry registry) {
return Literal.<ScrowBukkitCC>dynamic("configurator", "cfg")
.withPermission("command.config")
.withSubLiteralMetas(() -> registry.allConfigs().keySet().stream().map(LiteralMeta::of).toList())
.withSubLiteralProvider(name -> {
var configFile = registry.getConfigFile(name);
if (configFile == null) return null;
return ConfigLiteral.literal(configFile);
})
.asRootLiteral(new ScrowMessageStyle("Configurator", TextColor.color(0x9070C0)));
}
}

View file

@ -4,43 +4,38 @@ import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.literal.Literal; import site.lab0x13.scrow.commands.model.literal.Literal;
import site.lab0x13.scrow.commands.model.literal.LiteralMeta; import site.lab0x13.scrow.commands.model.literal.LiteralMeta;
import site.lab0x13.scrow.configurator.config.Config; import site.lab0x13.scrow.configurator.configfile.ConfigFile;
import site.lab0x13.scrow.configurator.ConfigNode; import site.lab0x13.scrow.configurator.storable.Storable;
import site.lab0x13.scrow.configurator.action.ConfigAction; import site.lab0x13.scrow.configurator.action.StorableAction;
final class KeyLiteral { final class KeyLiteral {
private final Literal<ScrowBukkitCC> literal; private final Literal<ScrowBukkitCC> literal;
private final Config cfg; private final ConfigFile configFile;
KeyLiteral(Config cfg) { KeyLiteral(ConfigFile configFile) {
this.cfg = cfg; this.configFile = configFile;
literal = Literal.<ScrowBukkitCC>dynamic("key") literal = Literal.<ScrowBukkitCC>dynamic("key")
.withSubLiteralMetas(() -> cfg.allNodes().keySet().stream().map(LiteralMeta::of).toList()) .withSubLiteralMetas(() -> configFile.allKeys().stream().map(LiteralMeta::of).toList())
.withSubLiteralProvider(this::keyLiteral) .withSubLiteralProvider(this::keyLiteral)
.build(); .build();
} }
private @Nullable Literal<ScrowBukkitCC> keyLiteral(String name) { private @Nullable Literal<ScrowBukkitCC> keyLiteral(String name) {
ConfigNode<Object> node; var storable = (Storable<Object>) configFile.getStorable(name);
try { if (storable == null) return null;
node = (ConfigNode<Object>) cfg.getNode(name);
} catch (Exception ex) {
return null;
}
if (node == null) return null;
var literalBuilder = Literal.<ScrowBukkitCC>builder(name); var literalBuilder = Literal.<ScrowBukkitCC>builder(name);
for (var _action : node.type().actions()) { for (var _action : storable.actions()) {
var action = (ConfigAction<Object>) _action; var action = (StorableAction<Object, Storable<Object>>) _action;
var actionLiteralBuilder = Literal.<ScrowBukkitCC>builder(action.name()); var actionLiteralBuilder = Literal.<ScrowBukkitCC>builder(action.name());
action.arguments(node).forEach(actionLiteralBuilder::withArgument); action.arguments(storable).forEach(actionLiteralBuilder::withArgument);
actionLiteralBuilder.withSyncExecutor(ctx -> { actionLiteralBuilder.withSyncExecutor(ctx -> {
if (action.requiresValue() && node.value() == null) { if (action.requiresValue() && storable.value() == null) {
ctx.sender().sendMessage(ctx.style().err("No value set.")); ctx.sender().sendMessage(ctx.style().err("No value set."));
return; return;
} }
action.execute(node, ctx); action.execute(storable, ctx);
}); });
literalBuilder.withSubLiteral(actionLiteralBuilder.build()); literalBuilder.withSubLiteral(actionLiteralBuilder.build());
} }

View file

@ -0,0 +1,28 @@
package site.lab0x13.scrow.configurator.configfile;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
public interface ConfigFile extends Storable<Map<String, Storable<?>>> {
static ConfigFile of(Path path) {
return new ConfigFileImpl(path.normalize());
}
@Nullable Storable<?> getStorable(String key);
void registerStorable(String key, Storable<?> storable);
List<String> allKeys();
void load() throws IOException;
void save() throws IOException;
Path path();
}

View file

@ -0,0 +1,83 @@
package site.lab0x13.scrow.configurator.configfile;
import com.google.gson.JsonParser;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import site.lab0x13.scrow.configurator.storable.MapStorable;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.requireNonNull;
class ConfigFileImpl extends MapStorable<Storable<?>> implements ConfigFile {
private static final Logger log = LoggerFactory.getLogger(ConfigFileImpl.class);
private final Path path;
ConfigFileImpl(Path path) {
this.path = path;
}
@Override
public @Nullable Storable<?> getStorable(String key) {
var index = key.indexOf(':');
if (index == -1)
return getSubField(key);
if (key.endsWith(":"))
return null;
var fieldName = key.substring(0, index);
Storable<?> storable = getSubField(fieldName);
if (storable == null)
return null;
for (String subFieldName : key.substring(index + 1).split(":")) {
if (subFieldName.isEmpty()) continue;
storable = storable.getSubField(subFieldName);
if (storable == null)
return null;
}
return storable;
}
@Override
public void registerStorable(String key, Storable<?> storable) {
registerEntry(key, (Storable<Object>) storable);
}
@Override
public List<String> allKeys() {
var res = new ArrayList<String>();
subFieldNames().forEach(key -> addKeysRecursively(res, key, requireNonNull(getSubField(key))));
return res;
}
private void addKeysRecursively(List<String> result, String key, Storable<?> storable) {
result.add(key);
storable.subFieldNames().forEach(subKey ->
addKeysRecursively(result, key + ":" + subKey, requireNonNull(storable.getSubField(subKey))));
}
@Override
public void load() throws IOException {
if (!Files.exists(this.path)) return;
var json = Files.readString(this.path());
this.loadJson(JsonParser.parseString(json));
}
@Override
public void save() throws IOException {
Files.createDirectories(this.path().getParent());
Files.writeString(this.path(), this.toJson().toString());
}
@Override
public Path path() {
return path;
}
}

View file

@ -0,0 +1,34 @@
package site.lab0x13.scrow.configurator.configfile;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class ConfigFileRegistry {
private static final Logger log = LoggerFactory.getLogger(ConfigFileRegistry.class);
private static final ConfigFileRegistry instance = new ConfigFileRegistry();
public static ConfigFileRegistry get() {
return instance;
}
private final Map<String, ConfigFile> configFiles = new HashMap<>();
public void register(ConfigFile configFile) {
var oldValue = configFiles.put(configFile.path().toString(), configFile);
if (oldValue != null)
log.warn("Overwriting old config with same path '{}'", configFile.path());
}
public Map<String, ConfigFile> allConfigs() {
return new HashMap<>(configFiles);
}
public @Nullable ConfigFile getConfigFile(String path) {
return configFiles.get(path);
}
}

View file

@ -0,0 +1,44 @@
package site.lab0x13.scrow.configurator.storable;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public abstract class AbstractStorable<T> implements Storable<T> {
private final List<ValueChangeSubscriber> subscribers = new ArrayList<>();
@Nullable
protected T value = null;
private final List<ValueRequirement<T>> requirements = new ArrayList<>();
protected void requireValue(Predicate<T> predicate, String error) {
requirements.add(new ValueRequirement<>(predicate, error));
}
@Override
public void subscribe(ValueChangeSubscriber subscriber) {
subscribers.add(subscriber);
}
@Override
public @Nullable T value() {
return value;
}
@Override
public void value(@Nullable T value) {
this.value = value;
subscribers.forEach(ValueChangeSubscriber::notifyValueChange);
}
@Override
public @Nullable String checkValue() {
for (var requirement : requirements) {
if (!requirement.predicate().test(value()))
return requirement.error();
}
return null;
}
}

View file

@ -0,0 +1,6 @@
package site.lab0x13.scrow.configurator.storable;
@FunctionalInterface
public interface Action {
void apply();
}

View file

@ -0,0 +1,26 @@
package site.lab0x13.scrow.configurator.storable;
import site.lab0x13.scrow.configurator.storable.complex.ComplexStorable;
import site.lab0x13.scrow.configurator.storable.complex.FieldReader;
import site.lab0x13.scrow.configurator.storable.complex.FieldRegistry;
import java.util.HashMap;
import java.util.Map;
public class MapStorable<V> extends ComplexStorable<Map<String, V>> {
public <S> void registerEntry(String name, Storable<S> storable) {
fieldRegistry().registerField(name, storable, map -> (S) map.get(name));
}
@Override
protected void registerFields(FieldRegistry<Map<String, V>> registry) {
}
@Override
protected Map<String, V> construct(FieldReader<Map<String, V>> reader) {
var res = new HashMap<String, V>();
reader.subFieldNames().forEach(key -> res.put(key, reader.read(key)));
return res;
}
}

View file

@ -0,0 +1,39 @@
package site.lab0x13.scrow.configurator.storable;
import com.google.gson.JsonElement;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.action.StorableAction;
import site.lab0x13.scrow.configurator.action.GlobalSetJsonAction;
import site.lab0x13.scrow.configurator.action.GlobalShowAction;
import java.util.ArrayList;
import java.util.List;
public interface Storable<T> {
@Nullable T value();
void value(@Nullable T value);
JsonElement toJson();
void loadJson(JsonElement json);
List<String> subFieldNames();
@Nullable Storable<?> getSubField(String name);
/**
* @return error message or null if value is fine
*/
@Nullable String checkValue();
default List<StorableAction<?, ?>> actions() {
return new ArrayList<>(List.of(
new GlobalShowAction(),
new GlobalSetJsonAction()
));
}
void subscribe(ValueChangeSubscriber subscriber);
}

View file

@ -0,0 +1,7 @@
package site.lab0x13.scrow.configurator.storable;
@FunctionalInterface
public interface ValueChangeSubscriber {
void notifyValueChange();
}

View file

@ -0,0 +1,9 @@
package site.lab0x13.scrow.configurator.storable;
import java.util.function.Predicate;
public record ValueRequirement<T>(
Predicate<T> predicate,
String error
) {
}

View file

@ -0,0 +1,66 @@
package site.lab0x13.scrow.configurator.storable;
import com.google.gson.JsonElement;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public abstract class WrappedStorable<R, T> extends AbstractStorable<T> {
private final Storable<R> realStorable;
private boolean skipUpdates = false;
public WrappedStorable(Storable<R> realStorable) {
this.realStorable = realStorable;
realStorable.subscribe(() -> {
if (realStorable.value() == null)
value(null);
else
value(wrap(realStorable.value()));
});
}
abstract protected T wrap(R real);
abstract protected R unwrap(T value);
@Override
public void value(@Nullable T value) {
if (skipUpdates)
return;
super.value(value);
skipUpdates(() -> realStorable.value(value == null ? null : unwrap(value)));
}
@Override
public JsonElement toJson() {
return realStorable.toJson();
}
@Override
public void loadJson(JsonElement json) {
realStorable.loadJson(json);
}
@Override
public List<String> subFieldNames() {
return realStorable.subFieldNames();
}
@Override
public @Nullable Storable<?> getSubField(String name) {
return realStorable.getSubField(name);
}
/**
* Run action without updating the value
*/
private void skipUpdates(Action action) {
try {
skipUpdates = true;
action.apply();
} finally {
skipUpdates = false;
}
}
}

View file

@ -0,0 +1,32 @@
package site.lab0x13.scrow.configurator.storable.complex;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.storable.Storable;
public abstract class ComplexStorable<T> extends SubFieldedStorable<T> {
@Override
protected @Nullable T defaultValue() {
return null;
}
@Override
public JsonElement toJson() {
var obj = new JsonObject();
fieldRegistry().subFields().forEach((subFieldName, subField) -> {
var v = (Storable<Object>) subField.storable();
obj.add(subFieldName, v.toJson());
});
return obj;
}
@Override
public void loadJson(JsonElement json) {
var obj = json.getAsJsonObject();
skipUpdates(() -> fieldRegistry().subFields().forEach((subFieldName, subField) ->
subField.storable().loadJson(obj.get(subFieldName))));
updateValue();
}
}

View file

@ -0,0 +1,23 @@
package site.lab0x13.scrow.configurator.storable.complex;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.function.Function;
public class Field<T, S> {
private final Storable<S> storable;
private final Function<T, S> getter;
public Field(Storable<S> storable, Function<T, S> getter) {
this.storable = storable;
this.getter = getter;
}
public Storable<S> storable() {
return storable;
}
public Function<T, S> getter() {
return getter;
}
}

View file

@ -0,0 +1,23 @@
package site.lab0x13.scrow.configurator.storable.complex;
import java.util.ArrayList;
import java.util.List;
public class FieldReader<T> {
private final FieldRegistry<T> fieldRegistry;
public FieldReader(FieldRegistry<T> fieldRegistry) {
this.fieldRegistry = fieldRegistry;
}
public <S> S read(String name) {
var sf = fieldRegistry.get(name);
if (sf == null) return null;
return (S) sf.storable().value();
}
public List<String> subFieldNames() {
return new ArrayList<>(fieldRegistry.subFields().keySet());
}
}

View file

@ -0,0 +1,26 @@
package site.lab0x13.scrow.configurator.storable.complex;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class FieldRegistry<T> {
private final Map<String, Field<T, ?>> subFields = new HashMap<>();
// TODO move field stuff into own class where register field is one block and after that ready is called
public <S> void registerField(String name, Storable<S> storable, Function<T, S> getter) {
subFields.put(name, new Field<>(storable, getter));
}
public Map<String, Field<T, ?>> subFields() {
return subFields;
}
public @Nullable Field<T, ?> get(String name) {
return subFields.get(name);
}
}

View file

@ -0,0 +1,75 @@
package site.lab0x13.scrow.configurator.storable.complex;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.storable.AbstractStorable;
import site.lab0x13.scrow.configurator.storable.Action;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.ArrayList;
import java.util.List;
public abstract class SubFieldedStorable<T> extends AbstractStorable<T> {
private final FieldRegistry<T> fieldRegistry = new FieldRegistry<>();
private boolean skipUpdates = false;
public SubFieldedStorable() {
this.registerFields(fieldRegistry);
fieldRegistry.subFields().values().forEach(f -> f.storable().subscribe(this::updateValue));
}
protected abstract void registerFields(FieldRegistry<T> registry);
protected abstract T construct(FieldReader<T> reader);
protected abstract @Nullable T defaultValue();
/**
* Run action without updating the value
*/
protected void skipUpdates(Action action) {
try {
skipUpdates = true;
action.apply();
} finally {
skipUpdates = false;
}
}
protected void updateValue() {
if (skipUpdates)
return;
super.value(construct(new FieldReader<>(fieldRegistry)));
}
@Override
public void value(@Nullable T value) {
if (value == null) {
super.value(defaultValue());
return;
}
super.value(value);
// reflect value change no sub fields
skipUpdates(() -> fieldRegistry.subFields().values().forEach(_field -> {
var field = (Field<T, Object>) _field;
field.storable().value(field.getter().apply(value));
}));
}
@Override
public List<String> subFieldNames() {
return new ArrayList<>(fieldRegistry.subFields().keySet());
}
@Override
public Storable<?> getSubField(String name) {
var sf = fieldRegistry.subFields().get(name);
if (sf == null) return null;
return sf.storable();
}
protected FieldRegistry<T> fieldRegistry() {
return fieldRegistry;
}
}

View file

@ -0,0 +1,26 @@
package site.lab0x13.scrow.configurator.storable.impl;
import org.bukkit.util.Vector;
import site.lab0x13.scrow.configurator.storable.complex.ComplexStorable;
import site.lab0x13.scrow.configurator.storable.complex.FieldReader;
import site.lab0x13.scrow.configurator.storable.complex.FieldRegistry;
import site.lab0x13.scrow.configurator.storable.primitive.DoubleStorable;
public class VectorStorable extends ComplexStorable<Vector> {
@Override
protected void registerFields(FieldRegistry<Vector> registry) {
registry.registerField("x", new DoubleStorable(), Vector::getX);
registry.registerField("y", new DoubleStorable(), Vector::getY);
registry.registerField("z", new DoubleStorable(), Vector::getZ);
}
@Override
protected Vector construct(FieldReader<Vector> reader) {
return new Vector(
reader.<Double>read("x"),
reader.<Double>read("y"),
reader.<Double>read("z")
);
}
}

View file

@ -0,0 +1,31 @@
package site.lab0x13.scrow.configurator.storable.impl.list;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.configurator.action.StorableAction;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.List;
public class ListExpandAction<V extends Storable<?>> implements StorableAction<List<V>, ListStorable<V>> {
@Override
public String name() {
return "expand";
}
@Override
public List<Argument<ScrowBukkitCC, ?>> arguments(ListStorable<V> node) {
return List.of();
}
@Override
public boolean requiresValue() {
return true;
}
@Override
public void execute(ListStorable<V> storable, ScrowBukkitCC ctx) {
storable.expandList();
ctx.sender().sendMessage(ctx.style().ok("Expanded list-storable."));
}
}

View file

@ -0,0 +1,82 @@
package site.lab0x13.scrow.configurator.storable.impl.list;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.action.StorableAction;
import site.lab0x13.scrow.configurator.storable.AbstractStorable;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import static java.util.Objects.requireNonNull;
public class ListStorable<S extends Storable<?>> extends AbstractStorable<List<S>> {
private final Supplier<S> newElementSupplier;
private final List<StorableAction<?, ?>> actions;
public ListStorable(Supplier<S> newElementSupplier) {
this.newElementSupplier = newElementSupplier;
actions = super.actions();
actions.add(new ListExpandAction<>());
}
@Override
public List<StorableAction<?, ?>> actions() {
return actions;
}
@Override
public @NotNull List<S> value() {
var v = super.value();
if (v == null) {
v = new ArrayList<>();
value(v);
}
return v;
}
/**
* Expands the list with a default value storable
*/
public S expandList() {
var newElement = newElementSupplier.get();
requireNonNull(value()).add(newElement);
return newElement;
}
@Override
public JsonElement toJson() {
var out = new JsonArray();
requireNonNull(value()).forEach(storable -> out.add(storable.toJson()));
return out;
}
@Override
public void loadJson(JsonElement json) {
var in = json.getAsJsonArray();
in.forEach(jsonElement -> expandList().loadJson(jsonElement));
}
@Override
public List<String> subFieldNames() {
var v = value();
if (v == null) return Collections.emptyList();
return IntStream.range(0, v.size())
.mapToObj(Integer::toString)
.toList();
}
@Override
public @Nullable Storable<?> getSubField(String name) {
var v = value();
if (v == null) return null;
return v.get(Integer.parseInt(name));
}
}

View file

@ -1,4 +1,4 @@
package site.lab0x13.scrow.configurator.type.location; package site.lab0x13.scrow.configurator.storable.impl.location;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;

View file

@ -0,0 +1,52 @@
package site.lab0x13.scrow.configurator.storable.impl.location;
import org.bukkit.Location;
import site.lab0x13.scrow.configurator.action.StorableAction;
import site.lab0x13.scrow.configurator.storable.complex.ComplexStorable;
import site.lab0x13.scrow.configurator.storable.primitive.DoubleStorable;
import site.lab0x13.scrow.configurator.storable.primitive.FloatStorable;
import site.lab0x13.scrow.configurator.storable.primitive.PrimitiveStorable;
import site.lab0x13.scrow.configurator.storable.complex.FieldReader;
import site.lab0x13.scrow.configurator.storable.complex.FieldRegistry;
import site.lab0x13.scrow.configurator.storable.impl.world.WorldStorable;
import java.util.ArrayList;
import java.util.List;
public class LocationStorable extends ComplexStorable<Location> {
private final List<StorableAction<?, ?>> actions;
public LocationStorable() {
actions = new ArrayList<>(super.actions());
actions.add(new LocationPickCurrentAction());
actions.add(new LocationTeleportAction());
}
@Override
protected void registerFields(FieldRegistry<Location> registry) {
registry.registerField("world", new WorldStorable(), Location::getWorld);
registry.registerField("x", new DoubleStorable(), Location::getX);
registry.registerField("y", new DoubleStorable(), Location::getY);
registry.registerField("z", new DoubleStorable(), Location::getZ);
registry.registerField("yaw", new FloatStorable(), Location::getYaw);
registry.registerField("pitch", new FloatStorable(), Location::getPitch);
}
@Override
public List<StorableAction<?, ?>> actions() {
return actions;
}
@Override
protected Location construct(FieldReader<Location> reader) {
return new Location(
reader.read("world"),
reader.read("x"),
reader.read("y"),
reader.read("z"),
reader.read("yaw"),
reader.read("pitch")
);
}
}

View file

@ -1,15 +1,15 @@
package site.lab0x13.scrow.configurator.type.location; package site.lab0x13.scrow.configurator.storable.impl.location;
import site.lab0x13.scrow.commands.model.argument.Argument;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC; import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import org.bukkit.Location; import org.bukkit.Location;
import site.lab0x13.scrow.configurator.action.ConfigAction; import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.configurator.ConfigNode; import site.lab0x13.scrow.configurator.action.StorableAction;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
class LocationTeleportAction implements ConfigAction<Location> { class LocationTeleportAction implements StorableAction<Location, Storable<Location>> {
@Override @Override
public String name() { public String name() {
@ -17,7 +17,7 @@ class LocationTeleportAction implements ConfigAction<Location> {
} }
@Override @Override
public List<Argument<ScrowBukkitCC, ?>> arguments(ConfigNode<Location> __) { public List<Argument<ScrowBukkitCC, ?>> arguments(Storable<Location> __) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -27,7 +27,7 @@ class LocationTeleportAction implements ConfigAction<Location> {
} }
@Override @Override
public void execute(ConfigNode<Location> node, ScrowBukkitCC ctx) { public void execute(Storable<Location> node, ScrowBukkitCC ctx) {
assert node.value() != null; assert node.value() != null;
ctx.player().teleport(node.value()); ctx.player().teleport(node.value());
ctx.player().sendMessage(ctx.style().ok("You've been teleported.")); ctx.player().sendMessage(ctx.style().ok("You've been teleported."));

View file

@ -1,15 +1,15 @@
package site.lab0x13.scrow.configurator.type.world; package site.lab0x13.scrow.configurator.storable.impl.world;
import site.lab0x13.scrow.commands.bukkit.type.WorldArgumentType;
import site.lab0x13.scrow.commands.model.argument.Argument;
import de.kentoj.scrow.bukkit.command.ScrowBukkitCC; import de.kentoj.scrow.bukkit.command.ScrowBukkitCC;
import org.bukkit.World; import org.bukkit.World;
import site.lab0x13.scrow.configurator.action.ConfigAction; import site.lab0x13.scrow.commands.bukkit.type.WorldArgumentType;
import site.lab0x13.scrow.configurator.ConfigNode; import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.configurator.action.StorableAction;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.List; import java.util.List;
class SetWorldAction implements ConfigAction<World> { class SetWorldAction implements StorableAction<World, Storable<World>> {
private final Argument<ScrowBukkitCC, World> arg = private final Argument<ScrowBukkitCC, World> arg =
Argument.builder("world", new WorldArgumentType<ScrowBukkitCC>()).build(); Argument.builder("world", new WorldArgumentType<ScrowBukkitCC>()).build();
@ -20,7 +20,7 @@ class SetWorldAction implements ConfigAction<World> {
} }
@Override @Override
public List<Argument<ScrowBukkitCC, ?>> arguments(ConfigNode<World> __) { public List<Argument<ScrowBukkitCC, ?>> arguments(Storable<World> __) {
return List.of(arg); return List.of(arg);
} }
@ -30,7 +30,7 @@ class SetWorldAction implements ConfigAction<World> {
} }
@Override @Override
public void execute(ConfigNode<World> node, ScrowBukkitCC ctx) { public void execute(Storable<World> node, ScrowBukkitCC ctx) {
World world = ctx.getArg(arg); World world = ctx.getArg(arg);
node.value(world); node.value(world);
ctx.sender().sendMessage(ctx.style().ok("Set world to " + world.getName() + "(" + world.getUID() + ")")); ctx.sender().sendMessage(ctx.style().ok("Set world to " + world.getName() + "(" + world.getUID() + ")"));

View file

@ -0,0 +1,36 @@
package site.lab0x13.scrow.configurator.storable.impl.world;
import org.bukkit.Bukkit;
import org.bukkit.World;
import site.lab0x13.scrow.configurator.action.StorableAction;
import site.lab0x13.scrow.configurator.storable.WrappedStorable;
import site.lab0x13.scrow.configurator.storable.primitive.StringStorable;
import java.util.ArrayList;
import java.util.List;
public class WorldStorable extends WrappedStorable<String, World> {
private final List<StorableAction<?, ?>> actions;
public WorldStorable() {
super(new StringStorable());
actions = new ArrayList<>(super.actions());
actions.add(new SetWorldAction());
}
@Override
public List<StorableAction<?, ?>> actions() {
return actions;
}
@Override
protected World wrap(String real) {
return Bukkit.getWorld(real);
}
@Override
protected String unwrap(World value) {
return value.getName();
}
}

View file

@ -0,0 +1,10 @@
package site.lab0x13.scrow.configurator.storable.primitive;
import com.google.gson.JsonPrimitive;
public class BooleanStorable extends PrimitiveStorable<Boolean>{
public BooleanStorable() {
super(false, JsonPrimitive::new, JsonPrimitive::getAsBoolean);
}
}

View file

@ -0,0 +1,10 @@
package site.lab0x13.scrow.configurator.storable.primitive;
import com.google.gson.JsonPrimitive;
public class ByteStorable extends PrimitiveStorable<Byte>{
public ByteStorable() {
super((byte)0, JsonPrimitive::new, JsonPrimitive::getAsByte);
}
}

View file

@ -0,0 +1,10 @@
package site.lab0x13.scrow.configurator.storable.primitive;
import com.google.gson.JsonPrimitive;
public class DoubleStorable extends PrimitiveStorable<Double>{
public DoubleStorable() {
super(0D, JsonPrimitive::new, JsonPrimitive::getAsDouble);
}
}

View file

@ -0,0 +1,10 @@
package site.lab0x13.scrow.configurator.storable.primitive;
import com.google.gson.JsonPrimitive;
public class FloatStorable extends PrimitiveStorable<Float>{
public FloatStorable() {
super(0F, JsonPrimitive::new, JsonPrimitive::getAsFloat);
}
}

View file

@ -0,0 +1,10 @@
package site.lab0x13.scrow.configurator.storable.primitive;
import com.google.gson.JsonPrimitive;
public class IntStorable extends PrimitiveStorable<Integer>{
public IntStorable() {
super(0, JsonPrimitive::new, JsonPrimitive::getAsInt);
}
}

View file

@ -0,0 +1,55 @@
package site.lab0x13.scrow.configurator.storable.primitive;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonPrimitive;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.configurator.storable.AbstractStorable;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
public class PrimitiveStorable<V> extends AbstractStorable<V> {
private final V defaultValue;
private final Function<V, JsonPrimitive> serializer;
private final Function<JsonPrimitive, V> deserializer;
public PrimitiveStorable(V defaultValue, Function<V, JsonPrimitive> serializer, Function<JsonPrimitive, V> deserializer) {
this.defaultValue = defaultValue;
this.serializer = serializer;
this.deserializer = deserializer;
}
@Override
public JsonElement toJson() {
if (value() == null)
return JsonNull.INSTANCE;
return serializer.apply(value());
}
@Override
public void loadJson(JsonElement json) {
if (json.isJsonNull())
value(defaultValue);
else
value(deserializer.apply(json.getAsJsonPrimitive()));
}
@Override
public void value(@Nullable V value) {
super.value(value == null ? defaultValue : value);
}
@Override
public List<String> subFieldNames() {
return Collections.emptyList();
}
@Override
public @Nullable Storable<?> getSubField(String name) {
return null;
}
}

View file

@ -0,0 +1,10 @@
package site.lab0x13.scrow.configurator.storable.primitive;
import com.google.gson.JsonPrimitive;
public class StringStorable extends PrimitiveStorable<String>{
public StringStorable() {
super("", JsonPrimitive::new, JsonPrimitive::getAsString);
}
}

View file

@ -22,9 +22,9 @@
"org.slf4j:slf4j-api": -2005163028, "org.slf4j:slf4j-api": -2005163028,
"org.slf4j:slf4j-simple": 1999565066, "org.slf4j:slf4j-simple": 1999565066,
"repositories": 1947988996, "repositories": 1947988996,
"site.lab0x13.scrow:commands-bukkit": 1307785370, "site.lab0x13.scrow:commands-bukkit": 638230104,
"site.lab0x13.scrow:commands-common": -721574803, "site.lab0x13.scrow:commands-common": -1391130069,
"site.lab0x13.scrow:commands-velocity": 1947497915 "site.lab0x13.scrow:commands-velocity": 1277942649
}, },
"__RESOLVED_ARTIFACTS_HASH": { "__RESOLVED_ARTIFACTS_HASH": {
"aopalliance:aopalliance": 1434507571, "aopalliance:aopalliance": 1434507571,
@ -102,9 +102,9 @@
"org.spongepowered:configurate-hocon": -1372729557, "org.spongepowered:configurate-hocon": -1372729557,
"org.spongepowered:configurate-yaml": -1304688999, "org.spongepowered:configurate-yaml": -1304688999,
"org.yaml:snakeyaml": -1320689696, "org.yaml:snakeyaml": -1320689696,
"site.lab0x13.scrow:commands-bukkit": -1697064734, "site.lab0x13.scrow:commands-bukkit": -359227548,
"site.lab0x13.scrow:commands-common": -1958371554, "site.lab0x13.scrow:commands-common": 9371898,
"site.lab0x13.scrow:commands-velocity": -1504816576 "site.lab0x13.scrow:commands-velocity": -1580488284
}, },
"artifacts": { "artifacts": {
"aopalliance:aopalliance": { "aopalliance:aopalliance": {
@ -561,19 +561,19 @@
"shasums": { "shasums": {
"jar": "19fdf6daa971172fcd010bcb2d11f29d5ef17225144bf780205011d5ef80419e" "jar": "19fdf6daa971172fcd010bcb2d11f29d5ef17225144bf780205011d5ef80419e"
}, },
"version": "1.3.3" "version": "1.4.0"
}, },
"site.lab0x13.scrow:commands-common": { "site.lab0x13.scrow:commands-common": {
"shasums": { "shasums": {
"jar": "21a9bdb29cb60bb424f1e3f8f69c11782c38a240dd18d41ba32a24534a0e50aa" "jar": "ce45c326316d8afeb0d486f06f3eb7680654868529e9c7cd9bc74c98568324dc"
}, },
"version": "1.3.3" "version": "1.4.0"
}, },
"site.lab0x13.scrow:commands-velocity": { "site.lab0x13.scrow:commands-velocity": {
"shasums": { "shasums": {
"jar": "362fd98a011c78aad2ffecb24135aada3a72a03d77db090a914b6aa4b0997715" "jar": "408764728340c5a5a0be5cc0523a3a5ea27d8caa7555fadb0de9e958b130b082"
}, },
"version": "1.3.3" "version": "1.4.0"
} }
}, },
"conflict_resolution": { "conflict_resolution": {

View file

@ -3,12 +3,13 @@ load("@rules_java//java:java_binary.bzl", "java_binary")
java_binary( java_binary(
name = "_plugin", name = "_plugin",
srcs = glob(["main/java/**/*.java"]), srcs = glob(["site/lab0x13/scrow/**/*.java"]),
create_executable = False, create_executable = False,
resources = glob(["main/resources/**"]), resources = glob(["resources/**"]),
resource_strip_prefix = "buildserver/configurator/main/resources", resource_strip_prefix = "minigame/extraction/resources",
deps = [ deps = [
"//core/bukkit:api", "//core/bukkit:api",
"//core/configurator:lib",
artifact("io.papermc.paper:paper-api"), artifact("io.papermc.paper:paper-api"),
], ],
) )

View file

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

View file

@ -0,0 +1,40 @@
package site.lab0x13.scrow.extraction;
import de.kentoj.scrow.bukkit.minigame.Minigame;
import de.kentoj.scrow.bukkit.minigame.PlayerManager;
import de.kentoj.scrow.bukkit.minigame.phaseflow.LinearPhaseFlow;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import net.kyori.adventure.text.format.TextColor;
import java.util.concurrent.CompletableFuture;
public class ExtractionGame implements Minigame {
private final ScrowMessageStyle style = new ScrowMessageStyle("PotatoRun", TextColor.color(0x8D7726));
private final PlayerManager playerManager = new PlayerManager(this);
private final LinearPhaseFlow phaseFlow = new LinearPhaseFlow("potatoRun.root");
@Override
public int minParticipants() {
return 0;
}
@Override
public int maxParticipants() {
return 0;
}
@Override
public CompletableFuture<Void> start() {
return phaseFlow.start();
}
@Override
public ScrowMessageStyle style() {
return style;
}
public PlayerManager playerManager() {
return playerManager;
}
}

View file

@ -0,0 +1,29 @@
package site.lab0x13.scrow.extraction;
import org.bukkit.plugin.java.JavaPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import site.lab0x13.scrow.configurator.configfile.ConfigFile;
import site.lab0x13.scrow.configurator.configfile.ConfigFileRegistry;
import site.lab0x13.scrow.extraction.config.ExtractionConfig;
import java.io.IOException;
public class ExtractionPlugin extends JavaPlugin {
private final Logger log = LoggerFactory.getLogger(ExtractionPlugin.class);
@Override
public void onEnable() {
log.info("loading");
var cfg = ConfigFile.of(this.getDataPath().resolve("config.json"));
ConfigFileRegistry.get().register(cfg);
cfg.registerStorable("arenas", ExtractionConfig.arenas);
try {
cfg.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
log.info("loaded");
}
}

View file

@ -0,0 +1,11 @@
package site.lab0x13.scrow.extraction.config;
import org.bukkit.Location;
import site.lab0x13.scrow.configurator.storable.Storable;
import java.util.List;
public record ArenaConfig(
List<Storable<Location>> spawnLocations
) {
}

View file

@ -0,0 +1,20 @@
package site.lab0x13.scrow.extraction.config;
import site.lab0x13.scrow.configurator.storable.complex.ComplexStorable;
import site.lab0x13.scrow.configurator.storable.complex.FieldReader;
import site.lab0x13.scrow.configurator.storable.complex.FieldRegistry;
import site.lab0x13.scrow.configurator.storable.impl.list.ListStorable;
import site.lab0x13.scrow.configurator.storable.impl.location.LocationStorable;
public class ArenaConfigStorable extends ComplexStorable<ArenaConfig> {
@Override
protected void registerFields(FieldRegistry<ArenaConfig> registry) {
registry.registerField("spawnLocations", new ListStorable<>(LocationStorable::new), ArenaConfig::spawnLocations);
}
@Override
protected ArenaConfig construct(FieldReader<ArenaConfig> reader) {
return new ArenaConfig(reader.read("spawnLocations"));
}
}

View file

@ -0,0 +1,10 @@
package site.lab0x13.scrow.extraction.config;
import site.lab0x13.scrow.configurator.storable.impl.list.ListStorable;
public final class ExtractionConfig {
private ExtractionConfig() {
}
public static final ListStorable<ArenaConfigStorable> arenas = new ListStorable<>(ArenaConfigStorable::new);
}

View file

@ -2,7 +2,7 @@ load("//rules:dev_server.bzl", "dev_server")
load("//rules:fetch_file.bzl", "fetch_file") load("//rules:fetch_file.bzl", "fetch_file")
dev_server( dev_server(
name = "dev", name = "server",
server_jar = ":papermc", server_jar = ":papermc",
plugins = [ plugins = [
":luckperms", ":luckperms",
@ -11,7 +11,8 @@ dev_server(
], ],
local_plugins = [ local_plugins = [
"//core/bukkit:plugin", "//core/bukkit:plugin",
"//buildserver/configurator:plugin", "//core/configurator:plugin",
"//minigame/extraction:plugin",
], ],
) )