splittet project to core and common functionalities
This commit is contained in:
118
core/src/main/java/eu/mhsl/craftattack/core/Main.java
Normal file
118
core/src/main/java/eu/mhsl/craftattack/core/Main.java
Normal file
@ -0,0 +1,118 @@
|
||||
package eu.mhsl.craftattack.core;
|
||||
|
||||
import eu.mhsl.craftattack.core.api.client.RepositoryLoader;
|
||||
import eu.mhsl.craftattack.core.api.server.HttpServer;
|
||||
import eu.mhsl.craftattack.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.core.config.Configuration;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public final class Main extends JavaPlugin {
|
||||
private static Main instance;
|
||||
private static Logger logger;
|
||||
|
||||
private List<Appliance> appliances;
|
||||
private RepositoryLoader repositoryLoader;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
logger = instance().getLogger();
|
||||
this.saveDefaultConfig();
|
||||
try {
|
||||
this.wrappedEnable();
|
||||
} catch(Exception e) {
|
||||
Main.logger().log(Level.SEVERE, "Error while initializing Spawn plugin, shutting down!", e);
|
||||
Bukkit.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private void wrappedEnable() {
|
||||
Configuration.readConfig();
|
||||
List<String> disabledAppliances = Configuration.pluginConfig.getStringList("disabledAppliances");
|
||||
|
||||
Main.logger().info("Loading Repositories...");
|
||||
this.repositoryLoader = new RepositoryLoader();
|
||||
Main.logger().info(String.format("Loaded %d repositories!", this.repositoryLoader.getRepositories().size()));
|
||||
|
||||
Main.logger().info("Loading appliances...");
|
||||
Reflections reflections = new Reflections("eu.mhsl.craftattack.spawn");
|
||||
Set<Class<? extends Appliance>> applianceClasses = reflections.getSubTypesOf(Appliance.class);
|
||||
|
||||
this.appliances = applianceClasses.stream()
|
||||
.filter(applianceClass -> !disabledAppliances.contains(applianceClass.getSimpleName()))
|
||||
.map(applianceClass -> {
|
||||
try {
|
||||
return (Appliance) applianceClass.getDeclaredConstructor().newInstance();
|
||||
} catch(Exception e) {
|
||||
throw new RuntimeException(String.format("Failed to create instance of '%s'", applianceClass.getName()), e);
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
Main.logger().info(String.format("Loaded %d appliances!", this.appliances.size()));
|
||||
|
||||
Main.logger().info("Initializing appliances...");
|
||||
this.appliances.forEach(appliance -> {
|
||||
appliance.onEnable();
|
||||
appliance.initialize(this);
|
||||
});
|
||||
Main.logger().info(String.format("Initialized %d appliances!", this.appliances.size()));
|
||||
|
||||
Main.logger().info("Starting HTTP API...");
|
||||
new HttpServer();
|
||||
|
||||
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
|
||||
Main.logger().info("Startup complete!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
Main.logger().info("Disabling appliances...");
|
||||
this.appliances.forEach(appliance -> {
|
||||
Main.logger().info("Disabling " + appliance.getClass().getSimpleName());
|
||||
appliance.onDisable();
|
||||
appliance.destruct(this);
|
||||
});
|
||||
|
||||
HandlerList.unregisterAll(this);
|
||||
Bukkit.getScheduler().cancelTasks(this);
|
||||
Main.logger().info("Disabled " + this.appliances.size() + " appliances!");
|
||||
}
|
||||
|
||||
public <T extends Appliance> T getAppliance(Class<T> clazz) {
|
||||
return this.appliances.stream()
|
||||
.filter(clazz::isInstance)
|
||||
.map(clazz::cast)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException(String.format("Appliance %s not loaded or instantiated!", clazz)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Class<T> getApplianceType(Class<?> clazz) {
|
||||
return (Class<T>) ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
}
|
||||
|
||||
public List<Appliance> getAppliances() {
|
||||
return this.appliances;
|
||||
}
|
||||
|
||||
public RepositoryLoader getRepositoryLoader() {
|
||||
return this.repositoryLoader;
|
||||
}
|
||||
|
||||
public static Main instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static Logger logger() {
|
||||
return logger;
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package eu.mhsl.craftattack.core.api.client;
|
||||
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RepositoryLoader.IgnoreRepository
|
||||
public abstract class HttpRepository extends Repository {
|
||||
private final Consumer<URIBuilder> baseUriBuilder;
|
||||
|
||||
public HttpRepository(URI basePath) {
|
||||
this(basePath, null);
|
||||
}
|
||||
|
||||
public HttpRepository(URI basePath, @Nullable Consumer<URIBuilder> baseUriBuilder) {
|
||||
super(basePath);
|
||||
|
||||
this.baseUriBuilder = baseUriBuilder == null
|
||||
? uriBuilder -> {
|
||||
}
|
||||
: baseUriBuilder;
|
||||
}
|
||||
|
||||
protected <TInput, TOutput> ReqResp<TOutput> post(String command, TInput data, Class<TOutput> outputType) {
|
||||
return this.post(command, parameters -> {
|
||||
}, data, outputType);
|
||||
}
|
||||
|
||||
protected <TInput, TOutput> ReqResp<TOutput> post(String command, Consumer<URIBuilder> parameters, TInput data, Class<TOutput> outputType) {
|
||||
HttpRequest request = this.getRequestBuilder(this.getUri(command, parameters))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(this.gson.toJson(data)))
|
||||
.build();
|
||||
|
||||
return this.execute(request, outputType);
|
||||
}
|
||||
|
||||
protected <TOutput> ReqResp<TOutput> get(String command, Class<TOutput> outputType) {
|
||||
return this.get(command, parameters -> {
|
||||
}, outputType);
|
||||
}
|
||||
|
||||
protected <TOutput> ReqResp<TOutput> get(String command, Consumer<URIBuilder> parameters, Class<TOutput> outputType) {
|
||||
HttpRequest request = this.getRequestBuilder(this.getUri(command, parameters))
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
return this.execute(request, outputType);
|
||||
}
|
||||
|
||||
private URI getUri(String command, Consumer<URIBuilder> parameters) {
|
||||
try {
|
||||
URIBuilder builder = new URIBuilder(this.basePath + "/" + command);
|
||||
this.baseUriBuilder.accept(builder);
|
||||
parameters.accept(builder);
|
||||
return builder.build();
|
||||
} catch(URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpRequest.Builder getRequestBuilder(URI endpoint) {
|
||||
return HttpRequest.newBuilder()
|
||||
.uri(endpoint)
|
||||
.header("User-Agent", Main.instance().getServer().getBukkitVersion())
|
||||
.header("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
private <TResponse> ReqResp<TResponse> execute(HttpRequest request, Class<TResponse> clazz) {
|
||||
ReqResp<String> rawResponse = this.sendHttp(request);
|
||||
return new ReqResp<>(rawResponse.status(), this.gson.fromJson(rawResponse.data(), clazz));
|
||||
}
|
||||
|
||||
private ReqResp<String> sendHttp(HttpRequest request) {
|
||||
try(HttpClient client = HttpClient.newHttpClient()) {
|
||||
this.validateThread(request.uri().getPath());
|
||||
HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
return new ReqResp<>(httpResponse.statusCode(), httpResponse.body());
|
||||
} catch(IOException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package eu.mhsl.craftattack.core.api.client;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public abstract class Repository {
|
||||
protected URI basePath;
|
||||
protected Gson gson;
|
||||
|
||||
public Repository(URI basePath) {
|
||||
this.basePath = basePath;
|
||||
this.gson = new Gson();
|
||||
}
|
||||
|
||||
protected void validateThread(String commandName) {
|
||||
if(!Bukkit.isPrimaryThread()) return;
|
||||
|
||||
Main.logger().warning(String.format(
|
||||
"Repository '%s' was called synchronously with command '%s'!",
|
||||
this.getClass().getSimpleName(),
|
||||
commandName
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package eu.mhsl.craftattack.core.api.client;
|
||||
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class RepositoryLoader {
|
||||
private final List<Repository> repositories;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface IgnoreRepository {
|
||||
}
|
||||
|
||||
public RepositoryLoader() {
|
||||
Reflections reflections = new Reflections(this.getClass().getPackageName());
|
||||
Set<Class<? extends Repository>> repositories = reflections.getSubTypesOf(Repository.class);
|
||||
|
||||
this.repositories = repositories.stream()
|
||||
.filter(repository -> !repository.isAnnotationPresent(IgnoreRepository.class))
|
||||
.map(repository -> {
|
||||
try {
|
||||
return (Repository) repository.getDeclaredConstructor().newInstance();
|
||||
} catch(InstantiationException | IllegalAccessException | InvocationTargetException |
|
||||
NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
public <T> T getRepository(Class<T> clazz) {
|
||||
//noinspection unchecked
|
||||
return this.repositories.stream()
|
||||
.filter(clazz::isInstance)
|
||||
.map(repository -> (T) repository)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new NotImplementedException(String.format("Repository '%s' not found!", clazz.getSimpleName())));
|
||||
}
|
||||
|
||||
public List<Repository> getRepositories() {
|
||||
return this.repositories;
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package eu.mhsl.craftattack.core.api.client;
|
||||
|
||||
public record ReqResp<TData>(int status, TData data) {
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package eu.mhsl.craftattack.core.api.client.repositories;
|
||||
|
||||
import eu.mhsl.craftattack.core.api.client.HttpRepository;
|
||||
import eu.mhsl.craftattack.core.api.client.ReqResp;
|
||||
import eu.mhsl.craftattack.core.util.api.EventApiUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EventRepository extends HttpRepository {
|
||||
public EventRepository() {
|
||||
super(EventApiUtil.getBaseUri());
|
||||
}
|
||||
|
||||
public record CreatedRoom(UUID uuid) {
|
||||
}
|
||||
|
||||
public record QueueRoom(UUID player, UUID room) {
|
||||
public record Response(String error) {
|
||||
}
|
||||
}
|
||||
|
||||
public ReqResp<CreatedRoom> createSession() {
|
||||
return this.post("room", null, CreatedRoom.class);
|
||||
}
|
||||
|
||||
public ReqResp<QueueRoom.Response> queueRoom(QueueRoom request) {
|
||||
return this.post("queueRoom", request, QueueRoom.Response.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package eu.mhsl.craftattack.core.api.client.repositories;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import eu.mhsl.craftattack.core.api.client.HttpRepository;
|
||||
import eu.mhsl.craftattack.core.api.client.ReqResp;
|
||||
import eu.mhsl.craftattack.core.util.api.WebsiteApiUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FeedbackRepository extends HttpRepository {
|
||||
public FeedbackRepository() {
|
||||
super(WebsiteApiUtil.getBaseUri(), WebsiteApiUtil::withAuthorizationSecret);
|
||||
}
|
||||
|
||||
public record Request(String event, List<UUID> users) {
|
||||
}
|
||||
|
||||
public ReqResp<Map<UUID, String>> createFeedbackUrls(Request data) {
|
||||
final Type responseType = new TypeToken<Map<UUID, String>>() {
|
||||
}.getType();
|
||||
ReqResp<Object> rawData = this.post("feedback", data, Object.class);
|
||||
return new ReqResp<>(rawData.status(), this.gson.fromJson(this.gson.toJson(rawData.data()), responseType));
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package eu.mhsl.craftattack.core.api.client.repositories;
|
||||
|
||||
import eu.mhsl.craftattack.core.api.client.HttpRepository;
|
||||
import eu.mhsl.craftattack.core.api.client.ReqResp;
|
||||
import eu.mhsl.craftattack.core.util.api.WebsiteApiUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ReportRepository extends HttpRepository {
|
||||
public ReportRepository() {
|
||||
super(WebsiteApiUtil.getBaseUri(), WebsiteApiUtil::withAuthorizationSecret);
|
||||
}
|
||||
|
||||
public record ReportCreationInfo(@NotNull UUID reporter, @Nullable UUID reported, String reason) {
|
||||
}
|
||||
|
||||
public record ReportUrl(@NotNull String url) {
|
||||
}
|
||||
|
||||
public record PlayerReports(
|
||||
List<Report> from_self,
|
||||
Object to_self
|
||||
) {
|
||||
public record Report(
|
||||
@Nullable Reporter reported,
|
||||
@NotNull String subject,
|
||||
boolean draft,
|
||||
@NotNull String status,
|
||||
@NotNull String url
|
||||
) {
|
||||
public record Reporter(
|
||||
@NotNull String username,
|
||||
@NotNull String uuid
|
||||
) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ReqResp<PlayerReports> queryReports(UUID player) {
|
||||
return this.get(
|
||||
"report",
|
||||
(parameters) -> parameters.addParameter("uuid", player.toString()),
|
||||
PlayerReports.class
|
||||
);
|
||||
}
|
||||
|
||||
public ReqResp<ReportUrl> createReport(ReportCreationInfo data) {
|
||||
return this.post(
|
||||
"report",
|
||||
data,
|
||||
ReportUrl.class
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package eu.mhsl.craftattack.core.api.client.repositories;
|
||||
|
||||
import eu.mhsl.craftattack.core.api.client.HttpRepository;
|
||||
import eu.mhsl.craftattack.core.api.client.ReqResp;
|
||||
import eu.mhsl.craftattack.core.util.api.WebsiteApiUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class WhitelistRepository extends HttpRepository {
|
||||
public WhitelistRepository() {
|
||||
super(WebsiteApiUtil.getBaseUri(), WebsiteApiUtil::withAuthorizationSecret);
|
||||
}
|
||||
|
||||
public record UserData(
|
||||
UUID uuid,
|
||||
String username,
|
||||
String firstname,
|
||||
String lastname,
|
||||
Long banned_until,
|
||||
Long outlawed_until
|
||||
) {
|
||||
}
|
||||
|
||||
public ReqResp<UserData> getUserData(UUID userId) {
|
||||
return this.get(
|
||||
"user",
|
||||
parameters -> parameters.addParameter("uuid", userId.toString()),
|
||||
UserData.class
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package eu.mhsl.craftattack.core.api.server;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
import eu.mhsl.craftattack.core.appliance.Appliance;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import spark.Request;
|
||||
import spark.Spark;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class HttpServer {
|
||||
private final ConfigurationSection apiConf = Main.instance().getConfig().getConfigurationSection("api");
|
||||
protected final Gson gson = new Gson();
|
||||
|
||||
public static Object nothing = null;
|
||||
|
||||
public HttpServer() {
|
||||
Spark.port(8080);
|
||||
|
||||
Spark.get("/ping", (request, response) -> System.currentTimeMillis());
|
||||
|
||||
Main.instance().getAppliances().forEach(appliance -> appliance.httpApi(new ApiBuilder(appliance)));
|
||||
}
|
||||
|
||||
public record Response(Status status, Object error, Object response) {
|
||||
public enum Status {
|
||||
FAILURE,
|
||||
SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
public class ApiBuilder {
|
||||
@FunctionalInterface
|
||||
public interface RequestProvider<TParsed, TOriginal, TResponse> {
|
||||
TResponse apply(TParsed parsed, TOriginal original);
|
||||
}
|
||||
|
||||
private final String applianceName;
|
||||
|
||||
private ApiBuilder(Appliance appliance) {
|
||||
this.applianceName = appliance.getClass().getSimpleName().toLowerCase();
|
||||
}
|
||||
|
||||
public void get(String path, Function<Request, Object> onCall) {
|
||||
Spark.get(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req)));
|
||||
}
|
||||
|
||||
public void rawPost(String path, Function<Request, Object> onCall) {
|
||||
Spark.post(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req)));
|
||||
}
|
||||
|
||||
public <TRequest> void post(String path, Class<TRequest> clazz, RequestProvider<TRequest, Request, Object> onCall) {
|
||||
Spark.post(this.buildRoute(path), (req, resp) -> {
|
||||
Main.instance().getLogger().info(req.body());
|
||||
TRequest parsed = HttpServer.this.gson.fromJson(req.body(), clazz);
|
||||
return this.process(() -> onCall.apply(parsed, req));
|
||||
});
|
||||
}
|
||||
|
||||
public String buildRoute(String path) {
|
||||
return String.format("/api/%s/%s", this.applianceName, path);
|
||||
}
|
||||
|
||||
private String process(Supplier<Object> exec) {
|
||||
HttpServer.Response response;
|
||||
try {
|
||||
response = new Response(Response.Status.SUCCESS, null, exec.get());
|
||||
} catch(Exception e) {
|
||||
response = new Response(Response.Status.FAILURE, e, null);
|
||||
}
|
||||
return HttpServer.this.gson.toJson(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package eu.mhsl.craftattack.core.appliance;
|
||||
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
import eu.mhsl.craftattack.core.api.client.Repository;
|
||||
import eu.mhsl.craftattack.core.api.server.HttpServer;
|
||||
import eu.mhsl.craftattack.core.config.Configuration;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Any implementation of this class can be seen as a "sub-plugin" with its own event handlers and commands.
|
||||
* Appliances can be enabled or disabled independent of other appliances
|
||||
*/
|
||||
public abstract class Appliance {
|
||||
private String localConfigPath;
|
||||
private List<Listener> listeners;
|
||||
private List<ApplianceCommand<?>> commands;
|
||||
|
||||
public Appliance() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this constructor to specify a config sub-path for use with the localConfig() method.
|
||||
*
|
||||
* @param localConfigPath sub path, if not found, the whole config will be used
|
||||
*/
|
||||
public Appliance(String localConfigPath) {
|
||||
this.localConfigPath = localConfigPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of listeners for the appliance. All listeners will be automatically registered.
|
||||
*
|
||||
* @return List of listeners
|
||||
*/
|
||||
@NotNull
|
||||
protected List<Listener> listeners() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of commands for the appliance. All commands will be automatically registered.
|
||||
*
|
||||
* @return List of commands
|
||||
*/
|
||||
@NotNull
|
||||
protected List<ApplianceCommand<?>> commands() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on initialization to add all needed API Routes.
|
||||
* The routeBuilder can be used to get the correct Path prefixes
|
||||
*
|
||||
* @param apiBuilder holds data for needed route prefixes.
|
||||
*/
|
||||
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a localized config section. Path can be set in appliance constructor.
|
||||
*
|
||||
* @return Section of configuration for your appliance
|
||||
*/
|
||||
@NotNull
|
||||
public ConfigurationSection localConfig() {
|
||||
return Optional.ofNullable(Configuration.cfg.getConfigurationSection(this.localConfigPath))
|
||||
.orElseGet(() -> Configuration.cfg.createSection(this.localConfigPath));
|
||||
}
|
||||
|
||||
public void onEnable() {
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
}
|
||||
|
||||
public void initialize(@NotNull JavaPlugin plugin) {
|
||||
this.listeners = this.listeners();
|
||||
this.commands = this.commands();
|
||||
|
||||
this.listeners.forEach(listener -> Bukkit.getPluginManager().registerEvents(listener, plugin));
|
||||
this.commands.forEach(command -> this.setCommandExecutor(plugin, command.commandName, command));
|
||||
}
|
||||
|
||||
public void destruct(@NotNull JavaPlugin plugin) {
|
||||
this.listeners.forEach(HandlerList::unregisterAll);
|
||||
}
|
||||
|
||||
protected <T extends Appliance> T queryAppliance(Class<T> clazz) {
|
||||
return Main.instance().getAppliance(clazz);
|
||||
}
|
||||
|
||||
protected <T extends Repository> T queryRepository(Class<T> clazz) {
|
||||
return Main.instance().getRepositoryLoader().getRepository(clazz);
|
||||
}
|
||||
|
||||
private void setCommandExecutor(JavaPlugin plugin, String name, ApplianceCommand<?> executor) {
|
||||
PluginCommand command = plugin.getCommand(name);
|
||||
if(command != null && executor != null) {
|
||||
command.setExecutor(executor);
|
||||
command.setTabCompleter(executor);
|
||||
} else {
|
||||
Main.logger().warning("Command " + name + " is not specified in plugin.yml!");
|
||||
throw new RuntimeException("All commands must be registered in plugin.yml. Missing command: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Listener> getListeners() {
|
||||
return this.listeners;
|
||||
}
|
||||
|
||||
public List<ApplianceCommand<?>> getCommands() {
|
||||
return this.commands;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package eu.mhsl.craftattack.core.appliance;
|
||||
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Utility class which enables command name definition over a constructor.
|
||||
*/
|
||||
public abstract class ApplianceCommand<T extends Appliance> extends CachedApplianceSupplier<T> implements TabCompleter, CommandExecutor {
|
||||
public String commandName;
|
||||
protected Component errorMessage = Component.text("Fehler: ").color(NamedTextColor.RED);
|
||||
|
||||
public ApplianceCommand(String command) {
|
||||
this.commandName = command;
|
||||
}
|
||||
|
||||
public ApplianceCommand(String command, Component errorMessage) {
|
||||
this(command);
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
try {
|
||||
this.execute(sender, command, label, args);
|
||||
} catch(Error e) {
|
||||
sender.sendMessage(this.errorMessage.append(Component.text(e.getMessage())));
|
||||
} catch(Exception e) {
|
||||
sender.sendMessage(this.errorMessage.append(Component.text("Interner Fehler")));
|
||||
Main.logger().warning("Error executing appliance command " + this.commandName + ": " + e.getMessage());
|
||||
e.printStackTrace(System.err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected List<String> tabCompleteReducer(List<String> response, String[] args) {
|
||||
return response.stream().filter(s -> s.startsWith(args[args.length - 1])).toList();
|
||||
}
|
||||
|
||||
protected abstract void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception;
|
||||
|
||||
/**
|
||||
* Utility class for command which can only be used as a Player. You can access the executing player with the getPlayer() method.
|
||||
*/
|
||||
public static abstract class PlayerChecked<T extends Appliance> extends ApplianceCommand<T> {
|
||||
private Player player;
|
||||
private Component notPlayerMessage = Component.text("Dieser Command kann nur von Spielern ausgeführt werden!").color(NamedTextColor.RED);
|
||||
|
||||
public PlayerChecked(String command) {
|
||||
super(command);
|
||||
}
|
||||
|
||||
public PlayerChecked(String command, Component errorMessage) {
|
||||
super(command, errorMessage);
|
||||
}
|
||||
|
||||
public PlayerChecked(String command, @Nullable Component errorMessage, Component notPlayerMessage) {
|
||||
super(command);
|
||||
this.errorMessage = Optional.ofNullable(errorMessage).orElse(this.errorMessage);
|
||||
this.notPlayerMessage = notPlayerMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if(!(sender instanceof Player)) {
|
||||
sender.sendMessage(this.notPlayerMessage);
|
||||
return false;
|
||||
}
|
||||
this.player = (Player) sender;
|
||||
return super.onCommand(sender, command, label, args);
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Error extends RuntimeException {
|
||||
public Error(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package eu.mhsl.craftattack.core.appliance;
|
||||
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
/**
|
||||
* Utility class which provides a specific, type save appliance.
|
||||
* You can access the appliance with the protected 'appliance' field.
|
||||
*
|
||||
* @param <T> the type of your appliance
|
||||
*/
|
||||
public abstract class ApplianceListener<T extends Appliance> extends CachedApplianceSupplier<T> implements Listener {
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package eu.mhsl.craftattack.core.appliance;
|
||||
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
|
||||
public class CachedApplianceSupplier<T extends Appliance> implements IApplianceSupplier<T> {
|
||||
private final T appliance;
|
||||
|
||||
public CachedApplianceSupplier() {
|
||||
this.appliance = Main.instance().getAppliance(Main.getApplianceType(this.getClass()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getAppliance() {
|
||||
return this.appliance;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package eu.mhsl.craftattack.core.appliance;
|
||||
|
||||
public interface IApplianceSupplier<T extends Appliance> {
|
||||
T getAppliance();
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package eu.mhsl.craftattack.core.config;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ConfigUtil {
|
||||
public static class Position {
|
||||
public static Location paseLocation(ConfigurationSection section) {
|
||||
return new Location(
|
||||
Bukkit.getWorld(Optional.ofNullable(section.getString("world")).orElse("world")),
|
||||
section.getDouble("x"),
|
||||
section.getDouble("y"),
|
||||
section.getDouble("z"),
|
||||
(float) section.getDouble("yaw"),
|
||||
(float) section.getDouble("pitch")
|
||||
);
|
||||
}
|
||||
|
||||
public static void writeLocation(ConfigurationSection section, Location location) {
|
||||
section.set("world", location.getWorld().getName());
|
||||
section.set("x", location.x());
|
||||
section.set("y", location.y());
|
||||
section.set("z", location.z());
|
||||
section.set("yaw", location.getYaw());
|
||||
section.set("pitch", location.getPitch());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package eu.mhsl.craftattack.core.config;
|
||||
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class Configuration {
|
||||
private static final String configName = "config.yml";
|
||||
private static final File configFile = new File(Main.instance().getDataFolder().getAbsolutePath() + "/" + configName);
|
||||
public static FileConfiguration cfg;
|
||||
public static ConfigurationSection pluginConfig;
|
||||
|
||||
public static void readConfig() {
|
||||
cfg = YamlConfiguration.loadConfiguration(configFile);
|
||||
pluginConfig = cfg.getConfigurationSection("plugin");
|
||||
}
|
||||
|
||||
public static void saveChanges() {
|
||||
try {
|
||||
cfg.save(configFile);
|
||||
} catch(Exception e) {
|
||||
Main.logger().warning("Could not save configuration: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package eu.mhsl.craftattack.core.util;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class IteratorUtil {
|
||||
public static void worlds(Consumer<World> world) {
|
||||
worlds(w -> w, world);
|
||||
}
|
||||
|
||||
public static <T> void worlds(Function<World, T> selector, Consumer<T> selected) {
|
||||
Bukkit.getWorlds().forEach(world -> selected.accept(selector.apply(world)));
|
||||
}
|
||||
|
||||
public static void onlinePlayers(Consumer<Player> player) {
|
||||
Bukkit.getOnlinePlayers().forEach(player);
|
||||
}
|
||||
|
||||
public static void setGameRules(Map<GameRule<Boolean>, Boolean> rules, boolean inverse) {
|
||||
rules.forEach((gameRule, value) -> IteratorUtil.worlds(world -> world.setGameRule(gameRule, value ^ inverse)));
|
||||
}
|
||||
|
||||
public static void times(int times, Runnable callback) {
|
||||
IntStream.range(0, times).forEach(value -> callback.run());
|
||||
}
|
||||
|
||||
public static <T> void iterateListInGlobal(int sectionStart, List<T> list, BiConsumer<Integer, T> callback) {
|
||||
IntStream.range(sectionStart, sectionStart + list.size())
|
||||
.forEach(value -> callback.accept(value, list.get(value - sectionStart)));
|
||||
}
|
||||
|
||||
public static void iterateLocalInGlobal(int sectionStart, int localLength, BiConsumer<Integer, Integer> callback) {
|
||||
IntStream.range(sectionStart, sectionStart + localLength)
|
||||
.forEach(value -> callback.accept(value, value + sectionStart));
|
||||
}
|
||||
|
||||
public static <T> List<T> expandList(List<T> list, int targetSize, T defaultValue) {
|
||||
return Stream.concat(
|
||||
list.stream(),
|
||||
Stream.generate(() -> defaultValue).limit(Math.max(0, targetSize - list.size()))
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package eu.mhsl.craftattack.core.util;
|
||||
|
||||
public class NumberUtil {
|
||||
public static double map(double oldValue, double oldMin, double oldMax, double newMin, double newMax) {
|
||||
double out = (((oldValue - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin;
|
||||
if(out > newMax) out = newMax;
|
||||
if(out < newMin) out = newMin;
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package eu.mhsl.craftattack.core.util.api;
|
||||
|
||||
import eu.mhsl.craftattack.core.config.Configuration;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class EventApiUtil {
|
||||
private final static ConfigurationSection apiConfig = Objects.requireNonNull(Configuration.cfg.getConfigurationSection("event"));
|
||||
public final static String basePath = apiConfig.getString("api");
|
||||
|
||||
public static URI getBaseUri() {
|
||||
Objects.requireNonNull(basePath);
|
||||
try {
|
||||
return new URI(basePath);
|
||||
} catch(URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package eu.mhsl.craftattack.core.util.api;
|
||||
|
||||
public class HttpStatus {
|
||||
public static final int OK = 200;
|
||||
public static final int CREATED = 201;
|
||||
public static final int ACCEPTED = 202;
|
||||
public static final int NO_CONTENT = 204;
|
||||
public static final int BAD_REQUEST = 400;
|
||||
public static final int UNAUTHORIZED = 401;
|
||||
public static final int FORBIDDEN = 403;
|
||||
public static final int NOT_FOUND = 404;
|
||||
public static final int INTERNAL_SERVER_ERROR = 500;
|
||||
public static final int SERVICE_UNAVAILABLE = 503;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package eu.mhsl.craftattack.core.util.api;
|
||||
|
||||
import eu.mhsl.craftattack.core.config.Configuration;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class WebsiteApiUtil {
|
||||
private final static ConfigurationSection apiConfig = Objects.requireNonNull(Configuration.cfg.getConfigurationSection("api"));
|
||||
public final static String basePath = apiConfig.getString("baseurl");
|
||||
public final static String apiSecret = apiConfig.getString("secret");
|
||||
|
||||
public static URI getBaseUri() {
|
||||
Objects.requireNonNull(basePath);
|
||||
try {
|
||||
return new URI(basePath);
|
||||
} catch(URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void withAuthorizationSecret(URIBuilder builder) {
|
||||
builder.addParameter("secret", apiSecret);
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package eu.mhsl.craftattack.core.util.entity;
|
||||
|
||||
import eu.mhsl.craftattack.core.config.ConfigUtil;
|
||||
import eu.mhsl.craftattack.core.config.Configuration;
|
||||
import eu.mhsl.craftattack.core.util.world.ChunkUtils;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Villager;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class DisplayVillager {
|
||||
private final Location location;
|
||||
private Villager villager;
|
||||
|
||||
public DisplayVillager(UUID uuid, Location location, Consumer<Villager> villagerCreator) {
|
||||
this.location = location;
|
||||
|
||||
try {
|
||||
ChunkUtils.loadChunkAtLocation(this.location);
|
||||
this.villager = (Villager) this.location.getWorld().getEntity(uuid);
|
||||
Objects.requireNonNull(this.villager);
|
||||
} catch(NullPointerException | IllegalArgumentException e) {
|
||||
this.villager = this.getBaseVillager();
|
||||
villagerCreator.accept(this.villager);
|
||||
}
|
||||
|
||||
this.villager.teleport(this.location);
|
||||
}
|
||||
|
||||
public Villager getVillager() {
|
||||
return this.villager;
|
||||
}
|
||||
|
||||
private Villager getBaseVillager() {
|
||||
Villager villager = (Villager) this.location.getWorld().spawnEntity(this.location, EntityType.VILLAGER);
|
||||
|
||||
villager.setRemoveWhenFarAway(false);
|
||||
villager.setInvulnerable(true);
|
||||
villager.setPersistent(true);
|
||||
villager.setGravity(false);
|
||||
villager.setAI(false);
|
||||
villager.setCollidable(false);
|
||||
villager.setCustomNameVisible(true);
|
||||
return villager;
|
||||
}
|
||||
|
||||
public static class ConfigBound {
|
||||
private final DisplayVillager villager;
|
||||
private final ConfigurationSection config;
|
||||
|
||||
public ConfigBound(ConfigurationSection configurationSection, Consumer<Villager> villagerCreator) {
|
||||
this.config = configurationSection;
|
||||
|
||||
Location location = ConfigUtil.Position.paseLocation(Objects.requireNonNull(this.config.getConfigurationSection("villagerLocation")));
|
||||
this.villager = new DisplayVillager(
|
||||
UUID.fromString(this.config.getString("uuid", UUID.randomUUID().toString())),
|
||||
location,
|
||||
villager -> {
|
||||
this.config.set("uuid", villager.getUniqueId().toString());
|
||||
Configuration.saveChanges();
|
||||
|
||||
villagerCreator.accept(villager);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void updateLocation(Location location) {
|
||||
ConfigUtil.Position.writeLocation(
|
||||
Objects.requireNonNull(this.config.getConfigurationSection("villagerLocation")),
|
||||
location
|
||||
);
|
||||
Configuration.saveChanges();
|
||||
|
||||
this.villager.getVillager().teleport(location);
|
||||
}
|
||||
|
||||
public Villager getVillager() {
|
||||
return this.villager.getVillager();
|
||||
}
|
||||
|
||||
public UUID getUniqueId() {
|
||||
return this.getVillager().getUniqueId();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package eu.mhsl.craftattack.core.util.entity;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PlayerUtils {
|
||||
public static void resetStatistics(Player player) {
|
||||
for(Statistic statistic : Statistic.values()) {
|
||||
for(Material material : Material.values()) {
|
||||
try {
|
||||
player.setStatistic(statistic, material, 0);
|
||||
} catch(IllegalArgumentException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for(EntityType entityType : EntityType.values()) {
|
||||
try {
|
||||
player.setStatistic(statistic, entityType, 0);
|
||||
} catch(IllegalArgumentException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
player.setStatistic(statistic, 0);
|
||||
} catch(IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package eu.mhsl.craftattack.core.util.inventory;
|
||||
|
||||
import com.destroystokyo.paper.profile.PlayerProfile;
|
||||
import com.destroystokyo.paper.profile.ProfileProperty;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class HeadBuilder {
|
||||
public static ItemStack getCustomTextureHead(String base64) {
|
||||
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
|
||||
SkullMeta meta = (SkullMeta) head.getItemMeta();
|
||||
PlayerProfile profile = Bukkit.createProfile(UUID.nameUUIDFromBytes(base64.getBytes()), null);
|
||||
profile.setProperty(new ProfileProperty("textures", base64));
|
||||
meta.setPlayerProfile(profile);
|
||||
head.setItemMeta(meta);
|
||||
return head;
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package eu.mhsl.craftattack.core.util.inventory;
|
||||
|
||||
import eu.mhsl.craftattack.core.util.text.ComponentUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ItemBuilder {
|
||||
private final ItemStack itemStack;
|
||||
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
public static @NotNull ItemBuilder of(Material material) {
|
||||
return new ItemBuilder(material);
|
||||
}
|
||||
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
public static @NotNull ItemBuilder of(ItemStack itemStack) {
|
||||
return new ItemBuilder(itemStack);
|
||||
}
|
||||
|
||||
private ItemBuilder(Material material) {
|
||||
this.itemStack = ItemStack.of(material);
|
||||
}
|
||||
|
||||
private ItemBuilder(ItemStack itemStack) {
|
||||
this.itemStack = itemStack;
|
||||
}
|
||||
|
||||
public ItemBuilder displayName(Component displayName) {
|
||||
return this.withMeta(itemMeta -> itemMeta.displayName(displayName));
|
||||
}
|
||||
|
||||
public ItemBuilder displayName(Function<Component, Component> process) {
|
||||
return this.displayName(process.apply(this.itemStack.displayName()));
|
||||
}
|
||||
|
||||
public ItemBuilder lore(String text) {
|
||||
return this.lore(text, 50, NamedTextColor.GRAY);
|
||||
}
|
||||
|
||||
public ItemBuilder lore(String text, NamedTextColor color) {
|
||||
return this.lore(text, 50, color);
|
||||
}
|
||||
|
||||
public ItemBuilder lore(String text, int linebreak, NamedTextColor color) {
|
||||
return this.withMeta(itemMeta -> itemMeta.lore(
|
||||
ComponentUtil.lineBreak(text, linebreak)
|
||||
.map(s -> Component.text(s, color))
|
||||
.toList()
|
||||
));
|
||||
}
|
||||
|
||||
public ItemBuilder appendLore(Component text) {
|
||||
List<Component> lore = this.itemStack.lore();
|
||||
Objects.requireNonNull(lore, "Cannot append lore to Item without lore");
|
||||
lore.add(text);
|
||||
return this.withMeta(itemMeta -> itemMeta.lore(lore));
|
||||
}
|
||||
|
||||
public ItemBuilder noStacking() {
|
||||
return this.withMeta(itemMeta -> itemMeta.setMaxStackSize(1));
|
||||
}
|
||||
|
||||
public ItemBuilder glint() {
|
||||
return this.withMeta(itemMeta -> itemMeta.setEnchantmentGlintOverride(true));
|
||||
}
|
||||
|
||||
public ItemBuilder amount(int amount) {
|
||||
this.itemStack.setAmount(amount);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ItemBuilder withMeta(Consumer<ItemMeta> callback) {
|
||||
ItemMeta meta = this.itemStack.getItemMeta();
|
||||
callback.accept(meta);
|
||||
this.itemStack.setItemMeta(meta);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ItemStack build() {
|
||||
return this.itemStack;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package eu.mhsl.craftattack.core.util.inventory;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class PlaceholderItems {
|
||||
private static final Component emptyName = Component.text(" ");
|
||||
public static final ItemStack grayStainedGlassPane = ItemBuilder.of(Material.GRAY_STAINED_GLASS_PANE)
|
||||
.displayName(emptyName)
|
||||
.noStacking()
|
||||
.build();
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package eu.mhsl.craftattack.core.util.listener;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class DismissInventoryOpenFromHolder implements Listener {
|
||||
private final UUID inventoryHolder;
|
||||
|
||||
public DismissInventoryOpenFromHolder(UUID inventoryHolder) {
|
||||
this.inventoryHolder = inventoryHolder;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryOpen(InventoryOpenEvent event) {
|
||||
if(event.getInventory().getHolder() instanceof Entity holder) {
|
||||
if(holder.getUniqueId().equals(this.inventoryHolder)) event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package eu.mhsl.craftattack.core.util.listener;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class PlayerInteractAtEntityEventListener implements Listener {
|
||||
private final UUID interactableEntityUUID;
|
||||
private final Consumer<PlayerInteractAtEntityEvent> callback;
|
||||
|
||||
public PlayerInteractAtEntityEventListener(UUID interactableEntityUUID, Consumer<PlayerInteractAtEntityEvent> callback) {
|
||||
this.interactableEntityUUID = interactableEntityUUID;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInteract(PlayerInteractAtEntityEvent event) {
|
||||
if(!event.getRightClicked().getUniqueId().equals(this.interactableEntityUUID)) return;
|
||||
this.callback.accept(event);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package eu.mhsl.craftattack.core.util.server;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.geysermc.cumulus.form.SimpleForm;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Floodgate {
|
||||
private static final FloodgateApi instance = FloodgateApi.getInstance();
|
||||
|
||||
public static boolean isBedrock(Player p) {
|
||||
return instance.isFloodgatePlayer(p.getUniqueId());
|
||||
}
|
||||
|
||||
public static FloodgatePlayer getBedrockPlayer(Player p) {
|
||||
return instance.getPlayer(p.getUniqueId());
|
||||
}
|
||||
|
||||
public static void runBedrockOnly(Player p, Consumer<FloodgatePlayer> callback) {
|
||||
if(isBedrock(p)) callback.accept(instance.getPlayer(p.getUniqueId()));
|
||||
}
|
||||
|
||||
public static void runJavaOnly(Player p, Consumer<Player> callback) {
|
||||
if(!isBedrock(p)) callback.accept(p);
|
||||
}
|
||||
|
||||
public static void throwWithMessageWhenBedrock(Player player) {
|
||||
if(isBedrock(player)) {
|
||||
SimpleForm.builder()
|
||||
.title("Nicht unterstützt")
|
||||
.content("Bedrock-Spieler werden derzeit für diese Aktion unterstützt! Tut uns Leid.")
|
||||
.button("Ok")
|
||||
.build();
|
||||
|
||||
throw new BedrockNotSupportedException(player);
|
||||
}
|
||||
}
|
||||
|
||||
public static class BedrockNotSupportedException extends RuntimeException {
|
||||
public BedrockNotSupportedException(Player player) {
|
||||
super(String.format("Bedrock player '%s' tried using an Operation which is unsupported.", player.getName()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package eu.mhsl.craftattack.core.util.server;
|
||||
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PluginMessage {
|
||||
public static void connect(Player player, String server) {
|
||||
ByteArrayDataOutput output = ByteStreams.newDataOutput();
|
||||
output.writeUTF("Connect");
|
||||
output.writeUTF(server);
|
||||
player.sendPluginMessage(Main.instance(), "BungeeCord", output.toByteArray());
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package eu.mhsl.craftattack.core.util.statistics;
|
||||
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
import net.kyori.adventure.util.Ticks;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class NetworkMonitor {
|
||||
private long previousRxBytes = 0;
|
||||
private long previousTxBytes = 0;
|
||||
|
||||
private long previousRxPackets = 0;
|
||||
private long previousTxPackets = 0;
|
||||
|
||||
private final String iFace;
|
||||
|
||||
private long rxBytesLastDuration = 0;
|
||||
private long txBytesLastDuration = 0;
|
||||
private long rxPacketsLastDuration = 0;
|
||||
private long txPacketsLastDuration = 0;
|
||||
|
||||
private final BukkitTask updateTask;
|
||||
|
||||
public NetworkMonitor(String iFace, Duration sampleDuration) {
|
||||
this.iFace = iFace;
|
||||
|
||||
this.updateTask = Bukkit.getScheduler().runTaskTimerAsynchronously(
|
||||
Main.instance(),
|
||||
this::update,
|
||||
0,
|
||||
sampleDuration.getSeconds() * Ticks.TICKS_PER_SECOND
|
||||
);
|
||||
}
|
||||
|
||||
public record Traffic(long rxBytes, long txBytes) {
|
||||
}
|
||||
|
||||
public record Packets(long rxCount, long txCount) {
|
||||
}
|
||||
|
||||
public Traffic getTraffic() {
|
||||
return new Traffic(this.rxBytesLastDuration, this.txBytesLastDuration);
|
||||
}
|
||||
|
||||
public Packets getPackets() {
|
||||
return new Packets(this.rxPacketsLastDuration, this.txPacketsLastDuration);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.updateTask.cancel();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
long rxBytes = this.getNetworkStatistic("rx_bytes");
|
||||
long txBytes = this.getNetworkStatistic("tx_bytes");
|
||||
long rxPackets = this.getNetworkStatistic("rx_packets");
|
||||
long txPackets = this.getNetworkStatistic("tx_packets");
|
||||
|
||||
this.rxBytesLastDuration = rxBytes - this.previousRxBytes;
|
||||
this.txBytesLastDuration = txBytes - this.previousTxBytes;
|
||||
this.rxPacketsLastDuration = rxPackets - this.previousRxPackets;
|
||||
this.txPacketsLastDuration = txPackets - this.previousTxPackets;
|
||||
|
||||
this.previousRxBytes = rxBytes;
|
||||
this.previousTxBytes = txBytes;
|
||||
this.previousRxPackets = rxPackets;
|
||||
this.previousTxPackets = txPackets;
|
||||
}
|
||||
|
||||
private long getNetworkStatistic(String statistic) {
|
||||
try {
|
||||
String path = String.format("/sys/class/net/%s/statistics/%s", this.iFace, statistic);
|
||||
String content = new String(Files.readAllBytes(Paths.get(path)));
|
||||
return Long.parseLong(content.trim());
|
||||
} catch(IOException e) {
|
||||
Main.logger().log(Level.SEVERE, "Statistics are only supported on Linux! Is tablist.interface config set correctly?");
|
||||
this.stop();
|
||||
throw new RuntimeException("Failed reading network statistic", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package eu.mhsl.craftattack.core.util.statistics;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ServerMonitor {
|
||||
public static float getServerMSPT() {
|
||||
long[] times = Bukkit.getServer().getTickTimes();
|
||||
return ((float) Arrays.stream(times).sum() / times.length) * 1.0E-6f;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package eu.mhsl.craftattack.core.util.text;
|
||||
|
||||
import eu.mhsl.craftattack.core.util.NumberUtil;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class ColorUtil {
|
||||
public static TextColor mapGreenToRed(double value, double minValue, double maxValue, boolean lowIsGreen) {
|
||||
float hue = (float) NumberUtil.map(value, minValue, maxValue, 0, 120);
|
||||
|
||||
if(lowIsGreen) {
|
||||
hue = Math.abs(hue - 120);
|
||||
}
|
||||
|
||||
return TextColor.color(Color.getHSBColor(hue / 360, 1f, 1f).getRGB());
|
||||
}
|
||||
|
||||
public static TextColor msptColor(float mspt) {
|
||||
if(mspt > 50) return TextColor.color(255, 0, 0);
|
||||
return ColorUtil.mapGreenToRed(mspt, 25, 60, true);
|
||||
}
|
||||
|
||||
public static TextColor tpsColor(float tps) {
|
||||
return ColorUtil.mapGreenToRed(tps, 15, 20, false);
|
||||
}
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
package eu.mhsl.craftattack.core.util.text;
|
||||
|
||||
import eu.mhsl.craftattack.core.util.statistics.NetworkMonitor;
|
||||
import eu.mhsl.craftattack.core.util.statistics.ServerMonitor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentBuilder;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.awt.*;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ComponentUtil {
|
||||
public static TextComponent pleaseWait() {
|
||||
return Component.text("Bitte warte einen Augenblick...", NamedTextColor.GRAY);
|
||||
}
|
||||
|
||||
public static Component clearedSpace() {
|
||||
return Component.text(" ").hoverEvent(Component.empty().asHoverEvent());
|
||||
}
|
||||
|
||||
public static TextComponent appendWithNewline(Component a, Component b) {
|
||||
return Component.text().append(a.appendNewline().append(b)).build();
|
||||
}
|
||||
|
||||
public static TextComponent appendWithSpace(Component a, Component b) {
|
||||
return Component.text().append(a).append(Component.text(" ")).append(b).build();
|
||||
}
|
||||
|
||||
public static Stream<String> lineBreak(String text) {
|
||||
return lineBreak(text, 50);
|
||||
}
|
||||
|
||||
public static Stream<String> lineBreak(String text, int charactersPerLine) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
String[] words = text.split(" ");
|
||||
StringBuilder line = new StringBuilder();
|
||||
|
||||
for(String word : words) {
|
||||
if(line.length() + word.length() + 1 > charactersPerLine) {
|
||||
lines.add(line.toString().trim());
|
||||
line = new StringBuilder();
|
||||
}
|
||||
line.append(word).append(" ");
|
||||
}
|
||||
|
||||
if(!line.isEmpty()) {
|
||||
lines.add(line.toString().trim());
|
||||
}
|
||||
|
||||
return lines.stream();
|
||||
}
|
||||
|
||||
public static String lineBreakNL(String text, int charactersPerLine) {
|
||||
Stream<String> lines = lineBreak(text, charactersPerLine);
|
||||
return lines.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
public static Component getFormattedTickTimes(boolean detailed) {
|
||||
float mspt = ServerMonitor.getServerMSPT();
|
||||
float roundedMspt = Math.round(mspt * 100f) / 100f;
|
||||
int loadPercentage = (int) (Math.min(100, (mspt / 50.0) * 100));
|
||||
float roundedTPS = Math.round(Bukkit.getTPS()[0] * 100f) / 100f;
|
||||
|
||||
TextColor percentageColor = ColorUtil.mapGreenToRed(loadPercentage, 80, 100, true);
|
||||
|
||||
ComponentBuilder<TextComponent, TextComponent.Builder> tickTimes = Component.text()
|
||||
.append(Component.text("Serverlast: ", NamedTextColor.GRAY))
|
||||
.append(Component.text(loadPercentage + "% ", percentageColor))
|
||||
.appendNewline();
|
||||
|
||||
if(detailed) {
|
||||
tickTimes
|
||||
.append(Component.text(roundedMspt + "mspt", ColorUtil.msptColor(mspt)))
|
||||
.append(Component.text(" | ", NamedTextColor.GRAY));
|
||||
}
|
||||
|
||||
return tickTimes
|
||||
.append(Component.text(roundedTPS + "tps", ColorUtil.tpsColor(roundedTPS)))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
public static Component getFormattedPing(Player player) {
|
||||
int playerPing = player.getPing();
|
||||
int averagePing = Bukkit.getOnlinePlayers().stream()
|
||||
.map(Player::getPing).reduce(Integer::sum)
|
||||
.orElse(0) / Bukkit.getOnlinePlayers().size();
|
||||
|
||||
return Component.text()
|
||||
.append(Component.text("Dein Ping: ", NamedTextColor.GRAY))
|
||||
.append(Component.text(playerPing + "ms", ColorUtil.mapGreenToRed(playerPing, 50, 200, true)))
|
||||
.append(Component.text(" | ", NamedTextColor.GRAY))
|
||||
.append(Component.text("Durschnitt: ", NamedTextColor.GRAY))
|
||||
.append(Component.text(averagePing + "ms", ColorUtil.mapGreenToRed(averagePing, 50, 200, true)))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Component createRainbowText(String text, int step) {
|
||||
Component builder = Component.empty();
|
||||
int hue = 0;
|
||||
for(char c : text.toCharArray()) {
|
||||
TextColor color = TextColor.color(Color.getHSBColor((float) hue / 360, 1, 1).getRGB());
|
||||
builder = builder.append(Component.text(c).color(color));
|
||||
hue += step;
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static Component getFormattedNetworkStats(NetworkMonitor.Traffic traffic, NetworkMonitor.Packets packets) {
|
||||
return Component.text()
|
||||
.append(Component.text(
|
||||
DataSizeConverter.convertBytesPerSecond(traffic.rxBytes()) + " ↓ " + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps",
|
||||
NamedTextColor.GREEN
|
||||
))
|
||||
.append(Component.text(" | ", NamedTextColor.GRAY))
|
||||
.append(Component.text(
|
||||
DataSizeConverter.convertBytesPerSecond(traffic.txBytes()) + " ↑ " + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps",
|
||||
NamedTextColor.RED
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Component getFormattedSystemStats(OperatingSystemMXBean systemMonitor) {
|
||||
if(!(systemMonitor instanceof com.sun.management.OperatingSystemMXBean monitor))
|
||||
return Component.text("Could not get System information", NamedTextColor.DARK_GRAY);
|
||||
|
||||
return Component.text()
|
||||
.append(Component.text("proc: ", NamedTextColor.GRAY))
|
||||
.append(Component.text(
|
||||
String.format("%.0f%%cpu", monitor.getProcessCpuLoad() * 100),
|
||||
NamedTextColor.GOLD
|
||||
))
|
||||
.append(Component.text(" | ", NamedTextColor.GRAY))
|
||||
.append(Component.text(
|
||||
String.format("%s time", DataSizeConverter.formatCpuTimeToHumanReadable(monitor.getProcessCpuTime())),
|
||||
NamedTextColor.LIGHT_PURPLE
|
||||
))
|
||||
.append(Component.text(" | ", NamedTextColor.GRAY))
|
||||
.append(Component.text(
|
||||
String.format(
|
||||
"%s free, %s committed RAM",
|
||||
DataSizeConverter.formatBytesToHumanReadable(monitor.getFreeMemorySize()),
|
||||
DataSizeConverter.formatBytesToHumanReadable(monitor.getCommittedVirtualMemorySize())
|
||||
),
|
||||
NamedTextColor.DARK_AQUA
|
||||
))
|
||||
.appendNewline()
|
||||
.append(Component.text("sys: ", NamedTextColor.GRAY))
|
||||
.append(Component.text(
|
||||
String.format("%.0f%%cpu", monitor.getCpuLoad() * 100),
|
||||
NamedTextColor.GOLD
|
||||
))
|
||||
.append(Component.text(" | ", NamedTextColor.GRAY))
|
||||
.append(Component.text(
|
||||
String.format(
|
||||
"1min %.2f load avg (%.0f%%)",
|
||||
monitor.getSystemLoadAverage(),
|
||||
(monitor.getSystemLoadAverage() / monitor.getAvailableProcessors()) * 100
|
||||
),
|
||||
NamedTextColor.LIGHT_PURPLE
|
||||
))
|
||||
.append(Component.text(" | ", NamedTextColor.GRAY))
|
||||
.append(Component.text(
|
||||
String.format("%s total RAM", DataSizeConverter.formatBytesToHumanReadable(monitor.getTotalMemorySize())),
|
||||
NamedTextColor.DARK_AQUA
|
||||
))
|
||||
.appendNewline()
|
||||
.append(Component.text(
|
||||
String.format(
|
||||
"%s(%s) \uD83D\uDE80 on %s with %s cpu(s)",
|
||||
monitor.getName(),
|
||||
monitor.getVersion(),
|
||||
monitor.getArch(),
|
||||
monitor.getAvailableProcessors()
|
||||
),
|
||||
NamedTextColor.GRAY
|
||||
))
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package eu.mhsl.craftattack.core.util.text;
|
||||
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Countdown {
|
||||
private final int countdownFrom;
|
||||
private boolean running;
|
||||
private int taskId;
|
||||
private int current;
|
||||
private final Consumer<Component> announcementConsumer;
|
||||
private final Function<AnnouncementData, Component> announcementBuilder;
|
||||
private Function<Integer, AnnouncementData> defaultAnnouncements;
|
||||
private final List<CustomAnnouncements> customAnnouncements = new ArrayList<>();
|
||||
private final Runnable onDone;
|
||||
|
||||
public record AnnouncementData(int count, String unit) {
|
||||
}
|
||||
|
||||
public record CustomAnnouncements(Function<Integer, Boolean> test, Consumer<Integer> task) {
|
||||
}
|
||||
|
||||
public Countdown(int countdownFrom, Function<AnnouncementData, Component> announcementBuilder, Consumer<Component> announcementConsumer, Runnable onDone) {
|
||||
this.countdownFrom = countdownFrom;
|
||||
this.current = countdownFrom;
|
||||
this.announcementBuilder = announcementBuilder;
|
||||
this.announcementConsumer = announcementConsumer;
|
||||
this.onDone = onDone;
|
||||
|
||||
this.defaultAnnouncements = count -> {
|
||||
if(this.current > 60 && this.current % 60 == 0) {
|
||||
return new AnnouncementData(this.current / 60, "Minuten");
|
||||
}
|
||||
|
||||
if(this.current <= 60 && (this.current <= 10 || this.current % 10 == 0)) {
|
||||
return new AnnouncementData(this.current, "Sekunden");
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
public void addCustomAnnouncement(CustomAnnouncements announcement) {
|
||||
this.customAnnouncements.add(announcement);
|
||||
}
|
||||
|
||||
public void setDefaultAnnouncements(Function<Integer, AnnouncementData> defaultAnnouncement) {
|
||||
this.defaultAnnouncements = defaultAnnouncement;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if(this.running) throw new IllegalStateException("Countdown already running!");
|
||||
this.running = true;
|
||||
this.taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Main.instance(), this::tick, 20, 20);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if(!this.running) throw new IllegalStateException("Countdown not running!");
|
||||
this.running = false;
|
||||
Bukkit.getScheduler().cancelTask(this.taskId);
|
||||
this.taskId = 0;
|
||||
this.current = this.countdownFrom;
|
||||
}
|
||||
|
||||
public void cancelIfRunning() {
|
||||
if(this.running) this.cancel();
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
AnnouncementData defaultAnnouncementData = this.defaultAnnouncements.apply(this.current);
|
||||
if(defaultAnnouncementData != null) {
|
||||
this.announcementConsumer.accept(this.announcementBuilder.apply(defaultAnnouncementData));
|
||||
}
|
||||
|
||||
this.customAnnouncements
|
||||
.stream()
|
||||
.filter(a -> a.test.apply(this.current))
|
||||
.forEach(a -> a.task.accept(this.current));
|
||||
|
||||
this.current--;
|
||||
|
||||
if(this.isDone()) {
|
||||
this.onDone.run();
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return this.current <= 0;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package eu.mhsl.craftattack.core.util.text;
|
||||
|
||||
public class DataSizeConverter {
|
||||
public static String convertBytesPerSecond(long bytes) {
|
||||
double kbits = bytes * 8.0 / 1000.0;
|
||||
double mbits = kbits / 1000.0;
|
||||
|
||||
if(mbits >= 1) {
|
||||
return String.format("%.2f Mbit", mbits);
|
||||
} else {
|
||||
return String.format("%.2f Kbit", kbits);
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatBytesToHumanReadable(long bytes) {
|
||||
String[] units = {"B", "KB", "MB", "GB", "TB", "PB", "EB"};
|
||||
int unitIndex = 0;
|
||||
double readableSize = bytes;
|
||||
|
||||
while(readableSize >= 1024 && unitIndex < units.length - 1) {
|
||||
readableSize /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return String.format("%.2f%s", readableSize, units[unitIndex]);
|
||||
}
|
||||
|
||||
public static String formatCpuTimeToHumanReadable(long nanoseconds) {
|
||||
if(nanoseconds < 0) return "unsupported";
|
||||
|
||||
long seconds = nanoseconds / 1_000_000_000;
|
||||
long minutes = seconds / 60;
|
||||
long hours = minutes / 60;
|
||||
long days = hours / 24;
|
||||
|
||||
seconds %= 60;
|
||||
minutes %= 60;
|
||||
hours %= 24;
|
||||
return String.format("%dd%dh%dm%ds", days, hours, minutes, seconds);
|
||||
}
|
||||
|
||||
public static String formatSecondsToHumanReadable(int seconds) {
|
||||
if(seconds < 0) return "unsupported";
|
||||
|
||||
int minutes = seconds / 60;
|
||||
int hours = minutes / 60;
|
||||
int days = hours / 24;
|
||||
|
||||
seconds %= 60;
|
||||
minutes %= 60;
|
||||
hours %= 24;
|
||||
|
||||
return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package eu.mhsl.craftattack.core.util.text;
|
||||
|
||||
import eu.mhsl.craftattack.core.Main;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record DisconnectInfo(String error, String description, String help, UUID user) {
|
||||
public void applyKick(Player player) {
|
||||
Bukkit.getScheduler().runTask(Main.instance(), () -> player.kick(this.getComponent()));
|
||||
}
|
||||
|
||||
public Component getComponent() {
|
||||
return Component.text()
|
||||
.appendNewline().appendNewline()
|
||||
.append(Component.text(this.error, NamedTextColor.DARK_RED)).appendNewline()
|
||||
.append(Component.text(this.description, NamedTextColor.RED)).appendNewline().appendNewline()
|
||||
.append(Component.text(this.help, NamedTextColor.GRAY)).appendNewline().appendNewline()
|
||||
.append(Component.text(this.user.toString(), NamedTextColor.DARK_GRAY)).appendNewline()
|
||||
.build();
|
||||
}
|
||||
|
||||
public static class Throwable extends Exception {
|
||||
public String error;
|
||||
public String description;
|
||||
public String help;
|
||||
public UUID user;
|
||||
|
||||
public Throwable(String error, String description, String help, UUID user) {
|
||||
super(description);
|
||||
this.error = error;
|
||||
this.description = description;
|
||||
this.help = help;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public DisconnectInfo getDisconnectScreen() {
|
||||
return new DisconnectInfo(this.error, this.description, this.help, this.user);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package eu.mhsl.craftattack.core.util.text;
|
||||
|
||||
public class NumberAbbreviation {
|
||||
public static <T extends Number & Comparable<T>> String abbreviateNumber(T number) {
|
||||
double value = number.doubleValue();
|
||||
if(value >= 1_000_000) {
|
||||
return String.format("%.1fM", value / 1_000_000.0);
|
||||
} else if(value >= 1_000) {
|
||||
return String.format("%.1fk", value / 1_000.0);
|
||||
} else {
|
||||
return number.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package eu.mhsl.craftattack.core.util.text;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class RainbowComponent {
|
||||
private float hueOffset = 0;
|
||||
private final String text;
|
||||
private final int density;
|
||||
private final float speed;
|
||||
|
||||
public RainbowComponent(String text, int density, float speed) {
|
||||
this.text = text;
|
||||
this.density = density;
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
public Component getRainbowState() {
|
||||
Component builder = Component.empty();
|
||||
float hue = this.hueOffset;
|
||||
for(char c : this.text.toCharArray()) {
|
||||
float normalizedHue = (hue % 360) / 360;
|
||||
TextColor color = TextColor.color(Color.getHSBColor(normalizedHue, 1, 1).getRGB());
|
||||
builder = builder.append(Component.text(c).color(color));
|
||||
hue += this.density;
|
||||
}
|
||||
|
||||
this.hueOffset = (this.hueOffset + this.speed) % 360;
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package eu.mhsl.craftattack.core.util.world;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BlockCycle {
|
||||
private final Location location;
|
||||
private final Material defaultBlock;
|
||||
private final List<Material> blockList;
|
||||
private int current = 0;
|
||||
|
||||
public BlockCycle(Location location, Material defaultBlock, List<Material> blockList) {
|
||||
this.location = location;
|
||||
this.defaultBlock = defaultBlock;
|
||||
this.blockList = blockList;
|
||||
}
|
||||
|
||||
public void next() {
|
||||
this.location.getBlock().setType(this.blockList.get(this.current));
|
||||
this.current++;
|
||||
if(this.current >= this.blockList.size()) this.current = 0;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.location.getBlock().setType(this.defaultBlock);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package eu.mhsl.craftattack.core.util.world;
|
||||
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
|
||||
public class ChunkUtils {
|
||||
public record ChunkPos(int x, int z) {
|
||||
}
|
||||
|
||||
public static Chunk loadChunkAtLocation(Location location) {
|
||||
ChunkPos chunkPos = locationToChunk(location);
|
||||
return location.getWorld().getChunkAt(chunkPos.x, chunkPos.z);
|
||||
}
|
||||
|
||||
public static ChunkPos locationToChunk(Location location) {
|
||||
return new ChunkPos(floor(location.x()) >> 4, floor(location.z()) >> 4);
|
||||
}
|
||||
|
||||
private static int floor(double num) {
|
||||
int floor = (int) num;
|
||||
return floor == num ? floor : floor - (int) (Double.doubleToRawLongBits(num) >>> 63);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package eu.mhsl.craftattack.core.util.world;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class InteractSounds {
|
||||
private final Player player;
|
||||
|
||||
public static InteractSounds of(Player player) {
|
||||
return new InteractSounds(player);
|
||||
}
|
||||
|
||||
private InteractSounds(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
private void playSound(org.bukkit.Sound sound) {
|
||||
this.player.playSound(this.getSound(sound.key()), Sound.Emitter.self());
|
||||
}
|
||||
|
||||
private Sound getSound(Key soundKey) {
|
||||
return Sound.sound(soundKey, Sound.Source.PLAYER, 1f, 1f);
|
||||
}
|
||||
|
||||
public void click() {
|
||||
this.playSound(org.bukkit.Sound.UI_BUTTON_CLICK);
|
||||
}
|
||||
|
||||
public void success() {
|
||||
this.playSound(org.bukkit.Sound.ENTITY_PLAYER_LEVELUP);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
this.playSound(org.bukkit.Sound.ENTITY_SILVERFISH_DEATH);
|
||||
}
|
||||
}
|
73
core/src/main/resources/config.yml
Normal file
73
core/src/main/resources/config.yml
Normal file
@ -0,0 +1,73 @@
|
||||
plugin:
|
||||
disabledAppliances:
|
||||
- NameOfApplianceClass
|
||||
|
||||
api:
|
||||
secret: secret
|
||||
baseurl: https://mhsl.eu/craftattack/api/
|
||||
|
||||
worldMuseum:
|
||||
uuid:
|
||||
connect-server-name: worldmuseum
|
||||
villagerLocation:
|
||||
world: world
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0
|
||||
yaw: 0
|
||||
pitch: 0
|
||||
|
||||
adminMarker:
|
||||
permission: admin
|
||||
color: AQUA
|
||||
|
||||
countdown:
|
||||
enabled: false
|
||||
start-permission: admin
|
||||
countdown: 60
|
||||
worldborder-before: 37
|
||||
|
||||
event:
|
||||
api: http://10.20.6.5:8080/
|
||||
connect-server-name: event
|
||||
enabled: false
|
||||
roomId:
|
||||
uuid:
|
||||
villagerLocation:
|
||||
world: world
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0
|
||||
yaw: 0
|
||||
pitch: 0
|
||||
|
||||
help:
|
||||
teamspeak: myserver.com
|
||||
spawn: "Der Weltspawn befindet sich bei x:0 y:0 z:0"
|
||||
|
||||
playerLimit:
|
||||
maxPlayers: 10
|
||||
|
||||
whitelist:
|
||||
overrideIntegrityCheck: false
|
||||
|
||||
tablist:
|
||||
interface: eth0
|
||||
|
||||
outlawed:
|
||||
voluntarily: []
|
||||
|
||||
packselect:
|
||||
packs:
|
||||
- somepack:
|
||||
name: "Texture pack name"
|
||||
description: "Texture pack description"
|
||||
author: "Pack Author(s)"
|
||||
url: "https://example.com/download/pack.zip"
|
||||
hash: "" # SHA1 hash of ZIP file (will be auto determined by the server on startup when not set)
|
||||
icon: "" # base64 player-head texture, can be obtained from sites like https://minecraft-heads.com/ under developers > Value
|
||||
|
||||
endPrevent:
|
||||
endDisabled: true
|
||||
|
||||
spawnpoint:
|
Reference in New Issue
Block a user