diff --git a/build.gradle b/build.gradle index 2e6f0b6..f324caf 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ dependencies { compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT' implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'com.sparkjava:spark-core:2.9.4' + implementation 'org.reflections:reflections:0.10.2' } def targetJavaVersion = 21 diff --git a/src/main/java/eu/mhsl/craftattack/spawn/Main.java b/src/main/java/eu/mhsl/craftattack/spawn/Main.java index 4d948cf..56d8198 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/Main.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/Main.java @@ -2,39 +2,16 @@ package eu.mhsl.craftattack.spawn; import eu.mhsl.craftattack.spawn.api.HttpServer; import eu.mhsl.craftattack.spawn.appliance.Appliance; -import eu.mhsl.craftattack.spawn.appliances.antiSignEdit.AntiSignEdit; -import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker; -import eu.mhsl.craftattack.spawn.appliances.autoShulker.AutoShulker; -import eu.mhsl.craftattack.spawn.appliances.chatMention.ChatMention; -import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages; -import eu.mhsl.craftattack.spawn.appliances.customAdvancements.CustomAdvancements; -import eu.mhsl.craftattack.spawn.appliances.debug.Debug; -import eu.mhsl.craftattack.spawn.appliances.displayName.DisplayName; -import eu.mhsl.craftattack.spawn.appliances.event.Event; -import eu.mhsl.craftattack.spawn.appliances.fleischerchest.Fleischerchest; -import eu.mhsl.craftattack.spawn.appliances.help.Help; -import eu.mhsl.craftattack.spawn.appliances.hotbarRefill.HotbarRefill; -import eu.mhsl.craftattack.spawn.appliances.kick.Kick; -import eu.mhsl.craftattack.spawn.appliances.outlawed.Outlawed; -import eu.mhsl.craftattack.spawn.appliances.panicBan.PanicBan; -import eu.mhsl.craftattack.spawn.appliances.playerlimit.PlayerLimit; -import eu.mhsl.craftattack.spawn.appliances.portableCrafting.PortableCrafting; -import eu.mhsl.craftattack.spawn.appliances.privateMessage.PrivateMessage; -import eu.mhsl.craftattack.spawn.appliances.projectStart.ProjectStart; -import eu.mhsl.craftattack.spawn.appliances.report.Report; -import eu.mhsl.craftattack.spawn.appliances.restart.Restart; -import eu.mhsl.craftattack.spawn.appliances.settings.Settings; -import eu.mhsl.craftattack.spawn.appliances.tablist.Tablist; -import eu.mhsl.craftattack.spawn.appliances.titleClear.TitleClear; -import eu.mhsl.craftattack.spawn.appliances.whitelist.Whitelist; -import eu.mhsl.craftattack.spawn.appliances.worldmuseum.WorldMuseum; import eu.mhsl.craftattack.spawn.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 { @@ -49,49 +26,46 @@ public final class Main extends JavaPlugin { instance = this; logger = instance().getLogger(); saveDefaultConfig(); - Configuration.readConfig(); + try { + this.wrappedEnable(); + } catch (Exception e) { + Main.logger().log(Level.SEVERE, "Error while initializing Spawn plugin, shutting down!", e); + Bukkit.shutdown(); + } + } - appliances = List.of( - new AdminMarker(), - new WorldMuseum(), - new TitleClear(), - new ProjectStart(), - new Tablist(), - new ChatMessages(), - new Report(), - new Event(), - new Help(), - new PlayerLimit(), - new Whitelist(), - new Restart(), - new Kick(), - new PanicBan(), - new Outlawed(), - new DisplayName(), - new Debug(), - new Fleischerchest(), - new CustomAdvancements(), - new Settings(), - new PortableCrafting(), - new AutoShulker(), - new AntiSignEdit(), - new HotbarRefill(), - new ChatMention(), - new PrivateMessage() - ); + private void wrappedEnable() { + Configuration.readConfig(); + List disabledAppliances = Configuration.pluginConfig.getStringList("disabledAppliances"); Main.logger.info("Loading appliances..."); - appliances.forEach(appliance -> { - Main.logger().info("Enabling " + appliance.getClass().getSimpleName()); + Reflections reflections = new Reflections(this.getClass().getPackageName()); + Set> 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!", appliances.size())); + + Main.logger().info("Initializing appliances..."); + this.appliances.forEach(appliance -> { appliance.onEnable(); appliance.initialize(this); }); - Main.logger().info("Loaded " + appliances.size() + " appliances!"); + Main.logger().info(String.format("Initialized %d appliances!", appliances.size())); - Main.logger().info("Starting HTTP API"); + Main.logger().info("Starting HTTP API..."); this.httpApi = new HttpServer(); getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); + Main.logger().info("Startup complete!"); } @Override diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliance/Appliance.java b/src/main/java/eu/mhsl/craftattack/spawn/appliance/Appliance.java index dad2e54..f6c74f8 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliance/Appliance.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliance/Appliance.java @@ -42,7 +42,7 @@ public abstract class Appliance { * @return List of listeners */ @NotNull - protected List eventHandlers() { + protected List listeners() { return new ArrayList<>(); } @@ -72,7 +72,8 @@ public abstract class Appliance { */ @NotNull public ConfigurationSection localConfig() { - return Optional.ofNullable(Configuration.cfg.getConfigurationSection(localConfigPath)).orElse(Configuration.cfg); + return Optional.ofNullable(Configuration.cfg.getConfigurationSection(localConfigPath)) + .orElseGet(() -> Configuration.cfg.createSection(localConfigPath)); } public void onEnable() { @@ -82,7 +83,7 @@ public abstract class Appliance { } public void initialize(@NotNull JavaPlugin plugin) { - this.listeners = eventHandlers(); + this.listeners = listeners(); this.commands = commands(); listeners.forEach(listener -> Bukkit.getPluginManager().registerEvents(listener, plugin)); @@ -93,7 +94,7 @@ public abstract class Appliance { listeners.forEach(HandlerList::unregisterAll); } - public T queryAppliance(Class clazz) { + protected static T queryAppliance(Class clazz) { return Main.instance().getAppliance(clazz); } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceCommand.java index 56f7b84..f031610 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceCommand.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceCommand.java @@ -17,7 +17,7 @@ import java.util.Optional; /** * Utility class which enables command name definition over a constructor. */ -public abstract class ApplianceCommand extends ApplianceSupplier implements TabCompleter, CommandExecutor { +public abstract class ApplianceCommand extends CachedApplianceSupplier implements TabCompleter, CommandExecutor { public String commandName; protected Component errorMessage = Component.text("Fehler: ").color(NamedTextColor.RED); diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceListener.java index aba9f29..29eb370 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceListener.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceListener.java @@ -8,6 +8,6 @@ import org.bukkit.event.Listener; * * @param the type of your appliance */ -public abstract class ApplianceListener extends ApplianceSupplier implements Listener { +public abstract class ApplianceListener extends CachedApplianceSupplier implements Listener { } \ No newline at end of file diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceSupplier.java b/src/main/java/eu/mhsl/craftattack/spawn/appliance/CachedApplianceSupplier.java similarity index 69% rename from src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceSupplier.java rename to src/main/java/eu/mhsl/craftattack/spawn/appliance/CachedApplianceSupplier.java index 28773d3..b4aa4a9 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliance/ApplianceSupplier.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliance/CachedApplianceSupplier.java @@ -2,10 +2,10 @@ package eu.mhsl.craftattack.spawn.appliance; import eu.mhsl.craftattack.spawn.Main; -public class ApplianceSupplier implements IApplianceSupplier { +public class CachedApplianceSupplier implements IApplianceSupplier { private final T appliance; - public ApplianceSupplier() { + public CachedApplianceSupplier() { this.appliance = Main.instance().getAppliance(Main.getApplianceType(getClass())); } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminMarker/AdminMarker.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminMarker/AdminMarker.java index ecbe4ff..6a36841 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminMarker/AdminMarker.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/adminMarker/AdminMarker.java @@ -18,7 +18,7 @@ public class AdminMarker extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of(new AdminMarkerListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/antiSignEdit/AntiSignEdit.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/antiSignEdit/AntiSignEdit.java index 2e8e622..67b69e0 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/antiSignEdit/AntiSignEdit.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/antiSignEdit/AntiSignEdit.java @@ -3,7 +3,6 @@ package eu.mhsl.craftattack.spawn.appliances.antiSignEdit; import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.SelectSetting; -import eu.mhsl.craftattack.spawn.appliances.settings.settings.SignEditSetting; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; @@ -15,6 +14,11 @@ import org.jetbrains.annotations.NotNull; import java.util.List; public class AntiSignEdit extends Appliance { + @Override + public void onEnable() { + Settings.instance().declareSetting(SignEditSetting.class); + } + public boolean preventSignEdit(Player p, SignSide sign) { SelectSetting.Options.Option setting = Settings.instance().getSetting(p, Settings.Key.SignEdit, SelectSetting.Options.Option.class); if(setting.is(SignEditSetting.editable)) return false; @@ -37,7 +41,7 @@ public class AntiSignEdit extends Appliance { } @Override - protected @NotNull List eventHandlers() { + protected @NotNull List listeners() { return List.of(new OnSignEditListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/SignEditSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/antiSignEdit/SignEditSetting.java similarity index 96% rename from src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/SignEditSetting.java rename to src/main/java/eu/mhsl/craftattack/spawn/appliances/antiSignEdit/SignEditSetting.java index d1164ed..699ba0e 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/SignEditSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/antiSignEdit/SignEditSetting.java @@ -1,4 +1,4 @@ -package eu.mhsl.craftattack.spawn.appliances.settings.settings; +package eu.mhsl.craftattack.spawn.appliances.antiSignEdit; import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/AutoShulker.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/AutoShulker.java index 0d85e3b..b8bc57a 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/AutoShulker.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/AutoShulker.java @@ -1,6 +1,7 @@ package eu.mhsl.craftattack.spawn.appliances.autoShulker; import eu.mhsl.craftattack.spawn.appliance.Appliance; +import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Material; @@ -16,6 +17,11 @@ import java.util.HashMap; import java.util.List; public class AutoShulker extends Appliance { + @Override + public void onEnable() { + Settings.instance().declareSetting(AutoShulkerSetting.class); + } + public boolean tryAutoShulker(Player p, Item item) { ItemStack itemStack = item.getItemStack(); ItemStack offhandStack = p.getInventory().getItemInOffHand(); @@ -40,7 +46,7 @@ public class AutoShulker extends Appliance { } @Override - protected @NotNull List eventHandlers() { + protected @NotNull List listeners() { return List.of(new ItemPickupListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/AutoShulkerSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/AutoShulkerSetting.java similarity index 96% rename from src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/AutoShulkerSetting.java rename to src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/AutoShulkerSetting.java index 941adb8..c462516 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/AutoShulkerSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/AutoShulkerSetting.java @@ -1,4 +1,4 @@ -package eu.mhsl.craftattack.spawn.appliances.settings.settings; +package eu.mhsl.craftattack.spawn.appliances.autoShulker; import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/ItemPickupListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/ItemPickupListener.java index db74c7c..bd6b8c6 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/ItemPickupListener.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/autoShulker/ItemPickupListener.java @@ -3,7 +3,6 @@ package eu.mhsl.craftattack.spawn.appliances.autoShulker; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.SelectSetting; -import eu.mhsl.craftattack.spawn.appliances.settings.settings.AutoShulkerSetting; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.entity.EntityPickupItemEvent; diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMention.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMention.java index e9904b8..e2cb490 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMention.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMention.java @@ -3,7 +3,6 @@ package eu.mhsl.craftattack.spawn.appliances.chatMention; import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliances.settings.Settings; -import eu.mhsl.craftattack.spawn.appliances.settings.settings.ChatMentionSetting; import net.kyori.adventure.sound.Sound; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; @@ -56,11 +55,12 @@ public class ChatMention extends Appliance { @Override public void onEnable() { + Settings.instance().declareSetting(ChatMentionSetting.class); refreshPlayers(); } @Override - protected @NotNull List eventHandlers() { + protected @NotNull List listeners() { return List.of(new ChatMentionListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java index 182cf69..13a32c9 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java @@ -4,7 +4,7 @@ import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages; import eu.mhsl.craftattack.spawn.appliances.settings.Settings; -import eu.mhsl.craftattack.spawn.appliances.settings.settings.ChatMentionSetting; +import eu.mhsl.craftattack.spawn.util.text.ComponentUtil; import io.papermc.paper.event.player.AsyncChatDecorateEvent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -42,7 +42,7 @@ public class ChatMentionListener extends ApplianceListener { return Component.text(word); } }) - .reduce((a, b) -> a.append(Component.text(" ")).append(b)) + .reduce(ComponentUtil::appendWithSpace) .orElseThrow(); getAppliance().notifyPlayers(mentioned); diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/ChatMentionSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionSetting.java similarity index 92% rename from src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/ChatMentionSetting.java rename to src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionSetting.java index 62eed06..a54f6a8 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/ChatMentionSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionSetting.java @@ -1,4 +1,4 @@ -package eu.mhsl.craftattack.spawn.appliances.settings.settings; +package eu.mhsl.craftattack.spawn.appliances.chatMention; import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; @@ -9,7 +9,7 @@ import org.bukkit.Material; public class ChatMentionSetting extends MultiBoolSetting implements CategorizedSetting { @Override public SettingCategory category() { - return SettingCategory.Chat; + return SettingCategory.Visuals; } public record ChatMentionConfig( diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java index eb5652f..3f48ad3 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java @@ -1,6 +1,7 @@ package eu.mhsl.craftattack.spawn.appliances.chatMessages; import eu.mhsl.craftattack.spawn.appliance.Appliance; +import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; @@ -12,6 +13,11 @@ import org.jetbrains.annotations.NotNull; import java.util.List; public class ChatMessages extends Appliance { + @Override + public void onEnable() { + Settings.instance().declareSetting(ShowJoinAndLeaveMessagesSetting.class); + } + public Component getReportablePlayerName(Player player) { return addReportActions(player.displayName(), player.getName()); } @@ -24,7 +30,7 @@ public class ChatMessages extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of(new ChatMessagesListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/ShowJoinAndLeaveMessagesSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ShowJoinAndLeaveMessagesSetting.java similarity index 90% rename from src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/ShowJoinAndLeaveMessagesSetting.java rename to src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ShowJoinAndLeaveMessagesSetting.java index 00d9698..f729a97 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/ShowJoinAndLeaveMessagesSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ShowJoinAndLeaveMessagesSetting.java @@ -1,4 +1,4 @@ -package eu.mhsl.craftattack.spawn.appliances.settings.settings; +package eu.mhsl.craftattack.spawn.appliances.chatMessages; import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; @@ -33,6 +33,6 @@ public class ShowJoinAndLeaveMessagesSetting extends BoolSetting implements Cate @Override public SettingCategory category() { - return SettingCategory.Chat; + return SettingCategory.Visuals; } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/customAdvancements/CustomAdvancements.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/customAdvancements/CustomAdvancements.java index b0e13a1..9a92105 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/customAdvancements/CustomAdvancements.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/customAdvancements/CustomAdvancements.java @@ -17,7 +17,7 @@ public class CustomAdvancements extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of(new CustomAdvancementsDamageEntityListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/customAdvancements/CustomAdvancementsDamageEntityListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/customAdvancements/CustomAdvancementsDamageEntityListener.java index 0b890ef..047d34a 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/customAdvancements/CustomAdvancementsDamageEntityListener.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/customAdvancements/CustomAdvancementsDamageEntityListener.java @@ -1,7 +1,7 @@ package eu.mhsl.craftattack.spawn.appliances.customAdvancements; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; -import org.bukkit.entity.Entity; +import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.entity.EntityDamageByEntityEvent; @@ -9,12 +9,11 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; public class CustomAdvancementsDamageEntityListener extends ApplianceListener { @EventHandler public void onEntityDamageEntity(EntityDamageByEntityEvent event) { - Entity damaged = event.getEntity(); - if(!(damaged instanceof Player)) return; - Entity damager = event.getDamager(); - if(!(damager instanceof Player)) return; - if(damaged.hasPermission("admin")) { - getAppliance().grantAdvancement("search_trouble", (Player) damager); - } + if(!(event.getEntity() instanceof Player damaged)) return; + if(!(event.getDamager() instanceof Player damager)) return; + if(!damager.getInventory().getItemInMainHand().getType().equals(Material.AIR)) return; + if(!damaged.hasPermission("admin")) return; + + getAppliance().grantAdvancement("search_trouble", damager); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java index b4ffd2e..429cfdd 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/displayName/DisplayName.java @@ -1,9 +1,11 @@ package eu.mhsl.craftattack.spawn.appliances.displayName; +import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker; import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarkerListener; import eu.mhsl.craftattack.spawn.appliances.outlawed.Outlawed; +import eu.mhsl.craftattack.spawn.appliances.yearRank.YearRank; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentBuilder; import net.kyori.adventure.text.TextComponent; @@ -14,19 +16,22 @@ import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.function.Supplier; +import java.util.logging.Level; public class DisplayName extends Appliance { public void update(Player player) { TextColor playerColor = queryAppliance(AdminMarker.class).getPlayerColor(player); List> prefixes = List.of( - () -> queryAppliance(Outlawed.class).getNamePrefix(player) + () -> queryAppliance(Outlawed.class).getNamePrefix(player), + () -> queryAppliance(YearRank.class).getNamePrefix(player) ); ComponentBuilder playerName = Component.text(); prefixes.forEach(supplier -> { Component prefix = supplier.get(); if(prefix == null) return; - playerName.append(prefix).append(Component.text(" ")); + playerName.append(prefix).append( + Component.text(" ").hoverEvent(Component.empty().asHoverEvent())); }); playerName.append(Component.text(player.getName(), playerColor)); @@ -41,14 +46,14 @@ public class DisplayName extends Appliance { player.playerListName(component); } catch(Exception e) { //TODO this throws often exceptions, but still works, don't know why - //Main.instance().getLogger().log(Level.SEVERE, e, e::getMessage); + Main.instance().getLogger().log(Level.SEVERE, e, e::getMessage); } } @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of(new AdminMarkerListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/doubeDoor/DoubleDoor.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/doubeDoor/DoubleDoor.java new file mode 100644 index 0000000..66fd59c --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/doubeDoor/DoubleDoor.java @@ -0,0 +1,50 @@ +package eu.mhsl.craftattack.spawn.appliances.doubeDoor; + +import eu.mhsl.craftattack.spawn.appliance.Appliance; +import eu.mhsl.craftattack.spawn.appliances.settings.Settings; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Door; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class DoubleDoor extends Appliance { + @Override + public void onEnable() { + Settings.instance().declareSetting(DoubleDoorSetting.class); + } + + public void openNextDoor(Block block) { + BlockData clickedData = block.getBlockData(); + if (!(clickedData instanceof Door clickedDoor)) return; + + BlockFace neighborFace = getNeighborFace(clickedDoor.getFacing(), clickedDoor.getHinge()); + Block neighbourBlock = block.getRelative(neighborFace); + BlockData neighbourData = neighbourBlock.getBlockData(); + + if(!(neighbourData instanceof Door neighbourDoor)) return; + if(!(neighbourDoor.getFacing() == clickedDoor.getFacing())) return; + if(neighbourDoor.getHinge() == clickedDoor.getHinge()) return; + + neighbourDoor.setOpen(!clickedDoor.isOpen()); + neighbourBlock.setBlockData(neighbourDoor); + } + + private @NotNull BlockFace getNeighborFace(BlockFace face, Door.Hinge hinge) { + return switch(face) { + case EAST -> (hinge == Door.Hinge.RIGHT) ? BlockFace.NORTH : BlockFace.SOUTH; + case WEST -> (hinge == Door.Hinge.RIGHT) ? BlockFace.SOUTH : BlockFace.NORTH; + case SOUTH -> (hinge == Door.Hinge.RIGHT) ? BlockFace.EAST : BlockFace.WEST; + case NORTH -> (hinge == Door.Hinge.RIGHT) ? BlockFace.WEST : BlockFace.EAST; + default -> throw new IllegalStateException(String.format("BlockFace '%s' of clicked door is not valid!", face)); + }; + } + + @Override + protected @NotNull List listeners() { + return List.of(new OnDoorInteractListener()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/doubeDoor/DoubleDoorSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/doubeDoor/DoubleDoorSetting.java new file mode 100644 index 0000000..7c96b7c --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/doubeDoor/DoubleDoorSetting.java @@ -0,0 +1,38 @@ +package eu.mhsl.craftattack.spawn.appliances.doubeDoor; + +import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; +import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; +import eu.mhsl.craftattack.spawn.appliances.settings.Settings; +import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.BoolSetting; +import org.bukkit.Material; + +public class DoubleDoorSetting extends BoolSetting implements CategorizedSetting { + public DoubleDoorSetting() { + super(Settings.Key.DoubleDoors); + } + + @Override + protected String title() { + return "Automatische Doppeltüren"; + } + + @Override + protected String description() { + return "Öffnet und schließt die zweite Hälfte einer Doppeltür automatisch"; + } + + @Override + protected Material icon() { + return Material.OAK_DOOR; + } + + @Override + protected Boolean defaultValue() { + return false; + } + + @Override + public SettingCategory category() { + return SettingCategory.Gameplay; + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/doubeDoor/OnDoorInteractListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/doubeDoor/OnDoorInteractListener.java new file mode 100644 index 0000000..0a5e676 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/doubeDoor/OnDoorInteractListener.java @@ -0,0 +1,27 @@ +package eu.mhsl.craftattack.spawn.appliances.doubeDoor; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; +import eu.mhsl.craftattack.spawn.appliances.settings.Settings; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; + +import java.util.Objects; + +public class OnDoorInteractListener extends ApplianceListener { + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if(!event.hasBlock()) return; + if(!Objects.equals(event.getHand(), EquipmentSlot.HAND)) return; + if(!event.getAction().equals(Action.RIGHT_CLICK_BLOCK)) return; + if(event.getPlayer().isSneaking()) return; + Block clickedBlock = event.getClickedBlock(); + if(clickedBlock == null) return; + if(clickedBlock.getType().equals(Material.IRON_DOOR)) return; + if(!Settings.instance().getSetting(event.getPlayer(), Settings.Key.DoubleDoors, Boolean.class)) return; + getAppliance().openNextDoor(clickedBlock); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/Event.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/Event.java index 548db72..4609f06 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/Event.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/Event.java @@ -201,17 +201,15 @@ public class Event extends Appliance { public void advertise() { advertiseCountdown.cancelIfRunning(); this.advertiseStatus = AdvertisementStatus.ADVERTISED; - IteratorUtil.onlinePlayers(player -> { - player.sendMessage( - Component.text() - .append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline() - .append(Component.text("Ein Event wurde gestartet!", NamedTextColor.GOLD)).appendNewline() - .append(Component.text("Nutze ")) - .append(Component.text("/event", NamedTextColor.AQUA)) - .append(Component.text(", um dem Event beizutreten!")).appendNewline() - .append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline() - ); - }); + IteratorUtil.onlinePlayers(player -> player.sendMessage( + Component.text() + .append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline() + .append(Component.text("Ein Event wurde gestartet!", NamedTextColor.GOLD)).appendNewline() + .append(Component.text("Nutze ")) + .append(Component.text("/event", NamedTextColor.AQUA)) + .append(Component.text(", um dem Event beizutreten!")).appendNewline() + .append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline() + )); advertiseCountdown.start(); } @@ -237,7 +235,7 @@ public class Event extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of( new ApplyPendingRewardsListener(), new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> joinEvent(playerInteractAtEntityEvent.getPlayer())), diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/fleischerchest/Fleischerchest.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/fleischerchest/Fleischerchest.java index add3457..0fec6d1 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/fleischerchest/Fleischerchest.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/fleischerchest/Fleischerchest.java @@ -19,7 +19,7 @@ public class Fleischerchest extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of(new FleischerchestCraftItemListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/glowingBerries/GlowingBerries.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/glowingBerries/GlowingBerries.java new file mode 100644 index 0000000..0ff1e5f --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/glowingBerries/GlowingBerries.java @@ -0,0 +1,35 @@ +package eu.mhsl.craftattack.spawn.appliances.glowingBerries; + +import eu.mhsl.craftattack.spawn.appliance.Appliance; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.util.Ticks; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class GlowingBerries extends Appliance { + private static final PotionEffect glowEffect = new PotionEffect( + PotionEffectType.GLOWING, + Ticks.TICKS_PER_SECOND * 15, + 1, + false, + true, + true + ); + + public void letPlayerGlow(Player player) { + player.addPotionEffect(glowEffect); + Sound sound = Sound.sound(org.bukkit.Sound.BLOCK_AMETHYST_BLOCK_CHIME.key(), Sound.Source.PLAYER, 1f, 1f); + player.stopSound(sound); + player.playSound(sound, Sound.Emitter.self()); + } + + @Override + protected @NotNull List listeners() { + return List.of(new OnBerryEaten()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/glowingBerries/OnBerryEaten.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/glowingBerries/OnBerryEaten.java new file mode 100644 index 0000000..c61c9b2 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/glowingBerries/OnBerryEaten.java @@ -0,0 +1,13 @@ +package eu.mhsl.craftattack.spawn.appliances.glowingBerries; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerItemConsumeEvent; + +public class OnBerryEaten extends ApplianceListener { + @EventHandler + public void onEat(PlayerItemConsumeEvent event) { + if(event.getItem().getType().equals(Material.GLOW_BERRIES)) getAppliance().letPlayerGlow(event.getPlayer()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/HotbarRefill.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/HotbarRefill.java index 5a7a425..0603576 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/HotbarRefill.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/HotbarRefill.java @@ -2,6 +2,7 @@ package eu.mhsl.craftattack.spawn.appliances.hotbarRefill; import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.appliance.Appliance; +import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; @@ -16,6 +17,11 @@ import java.util.List; import java.util.NoSuchElementException; public class HotbarRefill extends Appliance { + @Override + public void onEnable() { + Settings.instance().declareSetting(HotbarRefillSetting.class); + } + public void handleHotbarChange(Player player, ItemStack item) { if(player.getGameMode().equals(GameMode.CREATIVE)) return; if(item.getAmount() != 1) return; @@ -46,7 +52,7 @@ public class HotbarRefill extends Appliance { } @Override - protected @NotNull List eventHandlers() { - return List.of(new ItemRefillListener()); + protected @NotNull List listeners() { + return List.of(new HotbarRefillListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/ItemRefillListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/HotbarRefillListener.java similarity index 82% rename from src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/ItemRefillListener.java rename to src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/HotbarRefillListener.java index d001598..2ff2635 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/ItemRefillListener.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/HotbarRefillListener.java @@ -2,7 +2,6 @@ package eu.mhsl.craftattack.spawn.appliances.hotbarRefill; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliances.settings.Settings; -import eu.mhsl.craftattack.spawn.appliances.settings.settings.HotbarReplaceSetting; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.block.BlockPlaceEvent; @@ -10,12 +9,12 @@ import org.bukkit.event.player.PlayerItemBreakEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.inventory.ItemStack; -public class ItemRefillListener extends ApplianceListener { - private HotbarReplaceSetting.HotbarReplaceConfig getPlayerSetting(Player player) { +public class HotbarRefillListener extends ApplianceListener { + private HotbarRefillSetting.HotbarReplaceConfig getPlayerSetting(Player player) { return Settings.instance().getSetting( player, Settings.Key.HotbarReplacer, - HotbarReplaceSetting.HotbarReplaceConfig.class + HotbarRefillSetting.HotbarReplaceConfig.class ); } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/HotbarReplaceSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/HotbarRefillSetting.java similarity index 84% rename from src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/HotbarReplaceSetting.java rename to src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/HotbarRefillSetting.java index e00a5d5..25397f0 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/HotbarReplaceSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/HotbarRefillSetting.java @@ -1,4 +1,4 @@ -package eu.mhsl.craftattack.spawn.appliances.settings.settings; +package eu.mhsl.craftattack.spawn.appliances.hotbarRefill; import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; @@ -6,7 +6,7 @@ import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.MultiBoolSetting; import org.bukkit.Material; -public class HotbarReplaceSetting extends MultiBoolSetting implements CategorizedSetting { +public class HotbarRefillSetting extends MultiBoolSetting implements CategorizedSetting { @Override public SettingCategory category() { return SettingCategory.Gameplay; @@ -18,7 +18,7 @@ public class HotbarReplaceSetting extends MultiBoolSetting= 3) { + knockingPlayer.removeMetadata(metadataKey, Main.instance()); + cancel(); + return; + } + playSound(knockedBlock); + knockingPlayer.setMetadata(metadataKey, new FixedMetadataValue(Main.instance(), timesKnocked + 1)); + } + }.runTaskTimer(Main.instance(), 0, 8); + } + } + + private void playSound(Block knockedBlock) { + Location knockLocation = knockedBlock.getLocation(); + Sound sound = knockedBlock.getType() == Material.IRON_DOOR + ? Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR + : Sound.ITEM_SHIELD_BLOCK; + + knockLocation.getWorld().playSound(knockLocation, sound, SoundCategory.PLAYERS, 1f, 1f); + } + + @Override + protected @NotNull List listeners() { + return List.of(new KnockDoorListener()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/knockDoor/KnockDoorListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/knockDoor/KnockDoorListener.java new file mode 100644 index 0000000..2c37cbe --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/knockDoor/KnockDoorListener.java @@ -0,0 +1,18 @@ +package eu.mhsl.craftattack.spawn.appliances.knockDoor; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; +import org.bukkit.GameMode; +import org.bukkit.block.Block; +import org.bukkit.block.data.type.Door; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.BlockDamageAbortEvent; + +public class KnockDoorListener extends ApplianceListener { + @EventHandler + public void onKnock(BlockDamageAbortEvent event) { + if(event.getPlayer().getGameMode() != GameMode.SURVIVAL) return; + Block block = event.getBlock(); + if(!(block.getBlockData() instanceof Door)) return; + getAppliance().knockAtDoor(event.getPlayer(), block); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/knockDoor/KnockDoorSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/knockDoor/KnockDoorSetting.java new file mode 100644 index 0000000..09ae99b --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/knockDoor/KnockDoorSetting.java @@ -0,0 +1,50 @@ +package eu.mhsl.craftattack.spawn.appliances.knockDoor; + +import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; +import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; +import eu.mhsl.craftattack.spawn.appliances.settings.Settings; +import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.SelectSetting; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; + +import java.util.List; +import java.util.Locale; + +public class KnockDoorSetting extends SelectSetting implements CategorizedSetting { + private static final String namespace = KnockDoorSetting.class.getSimpleName().toLowerCase(Locale.ROOT); + public static Options.Option disabled = new Options.Option("Deaktiviert", new NamespacedKey(namespace, "disabled")); + public static Options.Option knockSingleTime = new Options.Option("Einmal an der Tür anklopfen", new NamespacedKey(namespace, "single")); + public static Options.Option knockThreeTimes = new Options.Option("Dreimal an der Tür anklopfen", new NamespacedKey(namespace, "three")); + + public KnockDoorSetting() { + super( + Settings.Key.KnockDoors, + new Options(List.of(disabled, knockSingleTime, knockThreeTimes)) + ); + } + + @Override + public SettingCategory category() { + return SettingCategory.Gameplay; + } + + @Override + protected String title() { + return "Klopfen an Türen"; + } + + @Override + protected String description() { + return "Klopft durch das schlagen an eine Tür an."; + } + + @Override + protected Material icon() { + return Material.BELL; + } + + @Override + protected Options.Option defaultValue() { + return disabled; + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/Maintenance.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/Maintenance.java new file mode 100644 index 0000000..26ad7af --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/Maintenance.java @@ -0,0 +1,43 @@ +package eu.mhsl.craftattack.spawn.appliances.maintenance; + +import eu.mhsl.craftattack.spawn.appliance.Appliance; +import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; +import eu.mhsl.craftattack.spawn.config.Configuration; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class Maintenance extends Appliance { + private boolean isInMaintenance; + private final String configKey = "enabled"; + + public Maintenance() { + super("maintenance"); + } + + @Override + public void onEnable() { + this.isInMaintenance = localConfig().getBoolean(configKey, false); + } + + public void setState(boolean enabled) { + this.isInMaintenance = enabled; + localConfig().set(configKey, enabled); + Configuration.saveChanges(); + } + + public boolean isInMaintenance() { + return isInMaintenance; + } + + @Override + protected @NotNull List> commands() { + return List.of(new MaintenanceCommand()); + } + + @Override + protected @NotNull List listeners() { + return List.of(new PreventMaintenanceJoinListener()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java new file mode 100644 index 0000000..9372b17 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/MaintenanceCommand.java @@ -0,0 +1,30 @@ +package eu.mhsl.craftattack.spawn.appliances.maintenance; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +public class MaintenanceCommand extends ApplianceCommand { + Map arguments = Map.of("enable", true, "disable", false); + + public MaintenanceCommand() { + super("maintanance"); + } + + @Override + protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { + if(args.length != 1 || !arguments.containsKey(args[0])) throw new Error("Argument 'enable' oder 'disable' gefordert!"); + getAppliance().setState(arguments.get(args[0])); + sender.sendMessage(String.format("Maintanance: %b", getAppliance().isInMaintenance())); + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + return arguments.keySet().stream().toList(); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/PreventMaintenanceJoinListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/PreventMaintenanceJoinListener.java new file mode 100644 index 0000000..9990d23 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/maintenance/PreventMaintenanceJoinListener.java @@ -0,0 +1,23 @@ +package eu.mhsl.craftattack.spawn.appliances.maintenance; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; +import eu.mhsl.craftattack.spawn.util.text.DisconnectInfo; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerLoginEvent; + +public class PreventMaintenanceJoinListener extends ApplianceListener { + @EventHandler + public void onJoin(PlayerLoginEvent event) { + if(!getAppliance().isInMaintenance()) return; + if(event.getPlayer().hasPermission("bypassMaintainance")) return; + + DisconnectInfo disconnectInfo = new DisconnectInfo( + "Wartunsarbeiten", + "Zurzeit können nur Admins dem Server beitreten!", + "Bitte warte bis die Warungsarbeiten wieder deaktiviert werden.", + event.getPlayer().getUniqueId() + ); + + event.disallow(PlayerLoginEvent.Result.KICK_OTHER, disconnectInfo.getComponent()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/optionLinks/OptionLinks.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/optionLinks/OptionLinks.java new file mode 100644 index 0000000..d92c198 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/optionLinks/OptionLinks.java @@ -0,0 +1,63 @@ +package eu.mhsl.craftattack.spawn.appliances.optionLinks; + +import eu.mhsl.craftattack.spawn.Main; +import eu.mhsl.craftattack.spawn.appliance.Appliance; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.ServerLinks; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; + +import java.net.URI; +import java.util.List; +import java.util.function.Function; +import java.util.logging.Level; + +@SuppressWarnings("UnstableApiUsage") +public class OptionLinks extends Appliance { + record ComponentSupplier() {} + record UriSupplier(Player player) {} + record UriConsumer(String uri) {} + record SuppliedLink(Function component, Function uri) {} + + List links = List.of( + new SuppliedLink( + componentSupplier -> Component.text("CraftAttack Homepage", NamedTextColor.GOLD), + uriSupplier -> new UriConsumer("https://mhsl.eu/craftattack") + ), + new SuppliedLink( + componentSupplier -> Component.text("Regeln", NamedTextColor.GOLD), + uriSupplier -> new UriConsumer("https://mhsl.eu/craftattack/rules") + ) + ); + + @Override + public void onEnable() { + Bukkit.getServer().getServerLinks().getLinks() + .forEach(serverLink -> Bukkit.getServer().getServerLinks().removeLink(serverLink)); + } + + public void setServerLinks(Player player) { + ServerLinks playerLinks = Bukkit.getServerLinks().copy(); + Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> { + this.links.forEach(suppliedLink -> { + Component component = suppliedLink.component.apply(new ComponentSupplier()); + String uri = suppliedLink.uri.apply(new UriSupplier(player)).uri; + + try { + playerLinks.addLink(component, URI.create(uri)); + } catch(IllegalArgumentException e) { + Main.logger().log(Level.INFO, String.format("Failed to create OptionLink '%s' for player '%s'", uri, player.getName()), e); + } + }); + player.sendLinks(playerLinks); + }); + } + + @Override + protected @NotNull List listeners() { + return List.of(new UpdateLinksListener()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/optionLinks/UpdateLinksListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/optionLinks/UpdateLinksListener.java new file mode 100644 index 0000000..388de99 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/optionLinks/UpdateLinksListener.java @@ -0,0 +1,12 @@ +package eu.mhsl.craftattack.spawn.appliances.optionLinks; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerJoinEvent; + +public class UpdateLinksListener extends ApplianceListener { + @EventHandler + public void onJoin(PlayerJoinEvent event) { + getAppliance().setServerLinks(event.getPlayer()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/outlawed/Outlawed.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/outlawed/Outlawed.java index 52cce8b..e71ffc0 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/outlawed/Outlawed.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/outlawed/Outlawed.java @@ -38,7 +38,7 @@ public class Outlawed extends Appliance { if(!player.isOnline()) return; if(status != Status.FORCED) return; try { - Main.instance().getAppliance(Whitelist.class).integrityCheck(player); + queryAppliance(Whitelist.class).integrityCheck(player); } catch(DisconnectInfo.Throwable e) { Bukkit.getScheduler().runTask(Main.instance(), () -> e.getDisconnectScreen().applyKick(player)); } @@ -75,7 +75,7 @@ public class Outlawed extends Appliance { private void setLawStatus(Player player, Status status) { playerStatusMap.put(player, status); - Main.instance().getAppliance(DisplayName.class).update(player); + queryAppliance(DisplayName.class).update(player); List newList = localConfig().getStringList(voluntarilyEntry); if(status.equals(Status.VOLUNTARILY)) { @@ -93,7 +93,7 @@ public class Outlawed extends Appliance { } private boolean isTimeout(Player player) { - return timeouts.get(player.getUniqueId()) < System.currentTimeMillis() - timeoutInMs; + return timeouts.getOrDefault(player.getUniqueId(), 0L) > System.currentTimeMillis() - timeoutInMs; } private void setTimeout(Player player) { @@ -127,7 +127,7 @@ public class Outlawed extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of(new OutlawedReminderListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/ChangePackCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/ChangePackCommand.java new file mode 100644 index 0000000..12172d0 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/ChangePackCommand.java @@ -0,0 +1,18 @@ +package eu.mhsl.craftattack.spawn.appliances.packSelect; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class ChangePackCommand extends ApplianceCommand.PlayerChecked { + public static final String commandName = "texturepack"; + public ChangePackCommand() { + super(commandName); + } + + @Override + protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { + getAppliance().openPackInventory(getPlayer()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackConfiguration.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackConfiguration.java new file mode 100644 index 0000000..43bbfb2 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackConfiguration.java @@ -0,0 +1,94 @@ +package eu.mhsl.craftattack.spawn.appliances.packSelect; + +import com.google.gson.*; +import eu.mhsl.craftattack.spawn.appliance.CachedApplianceSupplier; +import eu.mhsl.craftattack.spawn.util.inventory.HeadBuilder; +import eu.mhsl.craftattack.spawn.util.text.ComponentUtil; +import net.kyori.adventure.resource.ResourcePackInfo; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.*; +import java.util.stream.Collectors; + +public class PackConfiguration extends CachedApplianceSupplier { + public record Pack(UUID id, String name, String description, String author, ResourcePackInfo info, String icon) { + public ItemStack buildItem() { + ItemStack stack = HeadBuilder.getCustomTextureHead(icon); + ItemMeta meta = stack.getItemMeta(); + meta.itemName(Component.text(id.toString())); + meta.displayName(Component.text(name(), NamedTextColor.GOLD)); + List lore = new ArrayList<>(); + lore.add(Component.text("Autor: ", NamedTextColor.DARK_GRAY).append(Component.text(author))); + lore.add(Component.text(" ")); + lore.addAll( + ComponentUtil.lineBreak(description()) + .map(s -> Component.text(s, NamedTextColor.GRAY)) + .toList() + ); + lore.add(Component.text(" ")); + meta.lore(lore); + stack.setItemMeta(meta); + return stack; + } + + public boolean equalsItem(ItemStack other) { + String itemName = PlainTextComponentSerializer.plainText().serialize(other.getItemMeta().itemName()); + try { + return UUID.fromString(itemName).equals(id); + } catch(IllegalArgumentException ignored) { + return false; + } + } + } + public record PackList(List packs) { + public Optional findFromItem(ItemStack itemStack) { + return packs.stream() + .filter(pack -> pack.equalsItem(itemStack)) + .findFirst(); + } + } + public record SerializedPackList(List packs) {} + + private final PackList packList; + private final Gson gson = new GsonBuilder().create(); + + private PackConfiguration() { + this.packList = new PackList(new ArrayList<>()); + } + + private PackConfiguration(String data) { + SerializedPackList serializedData = gson.fromJson(data, SerializedPackList.class); + + var availablePackMap = getAppliance().availablePacks.packs().stream() + .collect(Collectors.toMap(PackConfiguration.Pack::id, pack -> pack)); + + this.packList = new PackList( + serializedData.packs().stream() + .map(availablePackMap::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()) + ); + + if (this.packList.packs().isEmpty()) throw new IllegalArgumentException("Serialized data does not contain any valid data!"); + } + + public static PackConfiguration deserialize(String data) { + return new PackConfiguration(data); + } + + public static PackConfiguration empty() { + return new PackConfiguration(); + } + + public String serialize() { + return gson.toJson(new SerializedPackList(this.packList.packs().stream().map(Pack::id).toList())); + } + + public List getPackList() { + return packList.packs(); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackConfigurationInventory.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackConfigurationInventory.java new file mode 100644 index 0000000..c1647a6 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackConfigurationInventory.java @@ -0,0 +1,171 @@ +package eu.mhsl.craftattack.spawn.appliances.packSelect; + +import eu.mhsl.craftattack.spawn.Main; +import eu.mhsl.craftattack.spawn.appliance.CachedApplianceSupplier; +import eu.mhsl.craftattack.spawn.util.IteratorUtil; +import eu.mhsl.craftattack.spawn.util.inventory.ItemBuilder; +import eu.mhsl.craftattack.spawn.util.inventory.PlaceholderItems; +import eu.mhsl.craftattack.spawn.util.world.InteractSounds; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; + +public class PackConfigurationInventory extends CachedApplianceSupplier { + private final PackConfiguration packConfiguration; + private final Player inventoryOwner; + private final Inventory inventory; + + private final ItemStack reset = ItemBuilder.of(Material.BARRIER) + .displayName(Component.text("Zurücksetzen", NamedTextColor.RED)) + .lore("Alle gewählten Texturepacks werden entfernt") + .build(); + + private final ItemStack info = ItemBuilder.of(Material.OAK_HANGING_SIGN) + .displayName(Component.text("Texturepacks", NamedTextColor.GOLD)) + .lore( + "Wähle aus der oberen Liste eine oder mehrere Texturepacks aus. " + + "Ändere die anzuwendende Reihenfolge durch Links/Rechtsklick in der unteren Liste. " + + "Klicke auf Speichern um die Änderungen anzuwenden!" + ) + .build(); + + private final ItemStack save = ItemBuilder.of(Material.GREEN_WOOL) + .displayName(Component.text("Anwenden", NamedTextColor.GREEN)) + .lore("Die Ausgewählten Texturepacks werden in der entsprechenden Reihenfolge angewandt. Das Anwenden kann durch den Download je nach Internetgeschwindigkeit eine Weile dauern.") + .build(); + + private final ItemStack unusedSlot = ItemBuilder.of(Material.LIME_STAINED_GLASS_PANE) + .displayName(Component.text("Freier Slot", NamedTextColor.GREEN)) + .lore("Klicke auf ein Texturepack um es hinzuzufügen.") + .build(); + + + public PackConfigurationInventory(PackConfiguration packConfiguration, Player inventoryOwner) { + this.packConfiguration = packConfiguration; + this.inventoryOwner = inventoryOwner; + this.inventory = Bukkit.createInventory(null, 9 * 6, Component.text("Texturepacks")); + this.draw(); + } + + public Inventory getInventory() { + return inventory; + } + + public void handleClick(@Nullable ItemStack clickedItem, int slot, ClickType clickType) { + if(clickedItem == null) return; + if(clickedItem.equals(reset)) reset(); + if(clickedItem.equals(save)) apply(); + + if(slot >= 9 && slot < 9 * 4) { + getAppliance().availablePacks.findFromItem(clickedItem) + .ifPresent(this::toggle); + } + + if(slot >= 9 * 5) { + getAppliance().availablePacks.findFromItem(clickedItem) + .ifPresent(pack -> { + switch(clickType) { + case RIGHT -> move(pack, true); + case LEFT -> move(pack, false); + case MIDDLE -> toggle(pack); + } + }); + } + } + + private void move(PackConfiguration.Pack pack, boolean moveToRight) { + List packs = packConfiguration.getPackList(); + int index = packs.indexOf(pack); + + if (index != -1) { + int newIndex = moveToRight ? index + 1 : index - 1; + if (newIndex >= 0 && newIndex < packs.size()) Collections.swap(packs, index, newIndex); + } + + InteractSounds.of(inventoryOwner).click(); + draw(); + } + + private void toggle(PackConfiguration.Pack pack) { + if(packConfiguration.getPackList().contains(pack)) { + packConfiguration.getPackList().remove(pack); + } else { + packConfiguration.getPackList().add(pack); + } + + InteractSounds.of(inventoryOwner).click(); + draw(); + } + + private void reset() { + packConfiguration.getPackList().clear(); + InteractSounds.of(inventoryOwner).delete(); + draw(); + } + + private void apply() { + inventoryOwner.closeInventory(); + InteractSounds.of(inventoryOwner).success(); + Bukkit.getScheduler().runTask(Main.instance(), () -> getAppliance().setPack(inventoryOwner, packConfiguration)); + } + + private void draw() { + inventory.clear(); + + inventory.setItem(0, packConfiguration.getPackList().isEmpty() ? PlaceholderItems.grayStainedGlassPane : reset); + IteratorUtil.times(3, () -> inventory.addItem(PlaceholderItems.grayStainedGlassPane)); + inventory.setItem(4, info); + IteratorUtil.times(3, () -> inventory.addItem(PlaceholderItems.grayStainedGlassPane)); + inventory.setItem(8, save); + + IteratorUtil.iterateListInGlobal( + 9, + getAppliance().availablePacks.packs().stream() + .filter(pack -> !packConfiguration.getPackList().contains(pack)) + .limit(9 * 3) + .map(pack -> { + ItemBuilder stack = ItemBuilder.of(pack.buildItem()); + if(packConfiguration.getPackList().contains(pack)) stack.glint(); + return stack.build(); + }) + .toList(), + inventory::setItem + ); + + IntStream.range(9 * 4, 9 * 5) + .forEach(slot -> inventory.setItem(slot, PlaceholderItems.grayStainedGlassPane)); + + IteratorUtil.iterateListInGlobal( + 9 * 5, + IteratorUtil.expandList( + IntStream.range(0, Math.min(packConfiguration.getPackList().size(), 9)) + .mapToObj(i -> { + PackConfiguration.Pack pack = packConfiguration.getPackList().get(i); + ItemBuilder builder = ItemBuilder.of(pack.buildItem()); + + builder + .displayName(existing -> Component.text(String.format("#%s ", i + 1), NamedTextColor.LIGHT_PURPLE).append(existing)) + .appendLore(Component.text("➡ Rechtsklick um nach Rechts zu verschieben", NamedTextColor.AQUA)) + .appendLore(Component.text("⬅ Linksklick um nach Links zu verschieben", NamedTextColor.AQUA)) + .appendLore(Component.text("\uD83D\uDDD1 Mausradklick um zu entfernen", NamedTextColor.AQUA)); + return builder.build(); + }) + .toList(), + 9, + unusedSlot + ), + inventory::setItem + ); + + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackSelect.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackSelect.java new file mode 100644 index 0000000..d494f37 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackSelect.java @@ -0,0 +1,139 @@ +package eu.mhsl.craftattack.spawn.appliances.packSelect; + +import eu.mhsl.craftattack.spawn.Main; +import eu.mhsl.craftattack.spawn.appliance.Appliance; +import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; +import eu.mhsl.craftattack.spawn.appliances.packSelect.listeners.ClosePackInventoryListener; +import eu.mhsl.craftattack.spawn.appliances.packSelect.listeners.ClickPackInventoryListener; +import eu.mhsl.craftattack.spawn.appliances.packSelect.listeners.SetPacksOnJoinListener; +import eu.mhsl.craftattack.spawn.appliances.settings.Settings; +import net.kyori.adventure.resource.ResourcePackInfo; +import net.kyori.adventure.resource.ResourcePackRequest; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.inventory.Inventory; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import java.net.URI; +import java.util.*; + +public class PackSelect extends Appliance { + private static final NamespacedKey packKey = new NamespacedKey(Main.instance(), PackSelect.class.getName().toLowerCase(Locale.ROOT)); + + public final PackConfiguration.PackList availablePacks = new PackConfiguration.PackList(new ArrayList<>()); + public final Map openInventories = new WeakHashMap<>(); + + public PackSelect() { + super("packselect"); + } + + @Override + public void onEnable() { + Settings.instance().declareSetting(PackSelectSetting.class); + + List> packs = localConfig().getMapList("packs"); + Bukkit.getScheduler().runTaskAsynchronously( + Main.instance(), + () -> packs.stream() + .flatMap(pack -> pack.entrySet().stream()) + .forEach(pack -> { + @SuppressWarnings("unchecked") Map packData = (Map) pack.getValue(); + + try { + ResourcePackInfo resourcePackInfo = ResourcePackInfoFactory + .createResourcePackInfo(URI.create(packData.get("url")), packData.get("hash")) + .join(); + + PackConfiguration.Pack packToAdd = new PackConfiguration.Pack( + UUID.nameUUIDFromBytes(pack.getKey().toString().getBytes()), + packData.get("name"), + packData.get("description"), + packData.get("author"), + resourcePackInfo, + packData.get("icon") + ); + availablePacks.packs().add(packToAdd); + } catch (Exception e) { + Main.logger().warning(String.format("Failed to add pack %s: %s", packData.get("name"), e.getMessage())); + } + }) + ); + } + + public PackConfiguration getPackConfigurationForPlayer(Player player) { + PersistentDataContainer persistentDataContainer = player.getPersistentDataContainer(); + try { + String serialized = persistentDataContainer.get(packKey, PersistentDataType.STRING); + Objects.requireNonNull(serialized); + return PackConfiguration.deserialize(serialized); + } catch(IllegalArgumentException | NullPointerException exception) { + return PackConfiguration.empty(); + } + } + + public void openPackInventory(Player player) { + PackConfigurationInventory packInventory = new PackConfigurationInventory(getPackConfigurationForPlayer(player), player); + player.openInventory(packInventory.getInventory()); + openInventories.put(player, packInventory); + } + + public void setPack(Player player, PackConfiguration packConfiguration) { + player.getPersistentDataContainer().set(packKey, PersistentDataType.STRING, packConfiguration.serialize()); + + int packCount = packConfiguration.getPackList().size(); + if(packCount > 0) { + player.sendMessage( + Component.text( + String.format("%s heruntergeladen und hinzugefügt...", packCount > 1 ? "Texturenpakete werden" : "Texturenpaket wird"), + NamedTextColor.DARK_GREEN + ) + ); + } + + player.sendResourcePacks( + ResourcePackRequest.resourcePackRequest() + .packs( + packConfiguration.getPackList().stream() + .map(PackConfiguration.Pack::info) + .toList() + .reversed() + ) + .replace(true) + .required(true) + .prompt( + Component.text() + .append(Component.text("Bestätige um fortzufahren! Du kannst deine Entscheidung jederzeit mit ", NamedTextColor.GRAY)) + .append(Component.text(String.format("/%s ", ChangePackCommand.commandName), NamedTextColor.GOLD)) + .append(Component.text("ändern.", NamedTextColor.GRAY)) + .build() + ) + .build() + ); + } + + public boolean isNotPackInventory(Player player, Inventory inventory) { + PackConfigurationInventory packConfigurationInventory = this.openInventories.get(player); + if(packConfigurationInventory == null) return true; + return packConfigurationInventory.getInventory() != inventory; + } + + @Override + protected @NotNull List> commands() { + return List.of(new ChangePackCommand()); + } + + @Override + protected @NotNull List listeners() { + return List.of( + new ClosePackInventoryListener(), + new ClickPackInventoryListener(), + new SetPacksOnJoinListener() + ); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackSelectSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackSelectSetting.java new file mode 100644 index 0000000..0872c17 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/PackSelectSetting.java @@ -0,0 +1,36 @@ +package eu.mhsl.craftattack.spawn.appliances.packSelect; + +import eu.mhsl.craftattack.spawn.Main; +import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; +import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; +import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.ActionSetting; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +public class PackSelectSetting extends ActionSetting implements CategorizedSetting { + @Override + protected String title() { + return "Texturepacks"; + } + + @Override + protected String description() { + return "Stelle dein persönliches Texturepack aus einer kuratierten Auswahl zusammen und gestalte deine Spielerfahrung individuell."; + } + + @Override + protected Material icon() { + return Material.PAINTING; + } + + @Override + protected void onAction(Player player, ClickType clickType) { + Main.instance().getAppliance(PackSelect.class).openPackInventory(player); + } + + @Override + public SettingCategory category() { + return SettingCategory.Visuals; + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/ResourcePackInfoFactory.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/ResourcePackInfoFactory.java new file mode 100644 index 0000000..8409155 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/ResourcePackInfoFactory.java @@ -0,0 +1,62 @@ +package eu.mhsl.craftattack.spawn.appliances.packSelect; + +import eu.mhsl.craftattack.spawn.Main; +import net.kyori.adventure.resource.ResourcePackInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.security.MessageDigest; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class ResourcePackInfoFactory { + + private static boolean isValidHash(@Nullable String hash) { + return hash != null && hash.length() == 40; + } + + public static @NotNull CompletableFuture createResourcePackInfo(@NotNull URI resourcePackUrl, @Nullable String hash) { + if (isValidHash(hash)) { + return CompletableFuture.completedFuture( + ResourcePackInfo.resourcePackInfo(UUID.nameUUIDFromBytes(hash.getBytes()), resourcePackUrl, hash) + ); + } + + return CompletableFuture.supplyAsync(() -> { + try { + Main.logger().info(String.format("Start calculating SHA1 Hash of %s", resourcePackUrl)); + HttpURLConnection connection = (HttpURLConnection) resourcePackUrl.toURL().openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + + try (InputStream inputStream = connection.getInputStream()) { + MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1"); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + sha1Digest.update(buffer, 0, bytesRead); + } + String sha1Hex = bytesToHex(sha1Digest.digest()); + + Main.logger().info(String.format("Calculating SHA1 Hash of %s completed: %s", resourcePackUrl, sha1Hex)); + return ResourcePackInfo.resourcePackInfo(UUID.nameUUIDFromBytes(sha1Hex.getBytes()), resourcePackUrl, sha1Hex); + } + } catch (Exception e) { + String error = String.format("Error whilst SHA1 calculation of %s: %s", resourcePackUrl, e.getMessage()); + Main.logger().warning(error); + throw new RuntimeException(error); + } + }); + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + hexString.append(String.format("%02x", b)); + } + return hexString.toString(); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/listeners/ClickPackInventoryListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/listeners/ClickPackInventoryListener.java new file mode 100644 index 0000000..3156961 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/listeners/ClickPackInventoryListener.java @@ -0,0 +1,22 @@ +package eu.mhsl.craftattack.spawn.appliances.packSelect.listeners; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; +import eu.mhsl.craftattack.spawn.appliances.packSelect.PackSelect; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.InventoryClickEvent; + +public class ClickPackInventoryListener extends ApplianceListener { + @EventHandler + public void interact(InventoryClickEvent event) { + if(!(event.getWhoClicked() instanceof Player player)) return; + if(getAppliance().isNotPackInventory(player, event.getInventory())) return; + event.setCancelled(true); + + getAppliance().openInventories.get(player).handleClick( + event.getCurrentItem(), + event.getSlot(), + event.getClick() + ); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/listeners/ClosePackInventoryListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/listeners/ClosePackInventoryListener.java new file mode 100644 index 0000000..b64cb88 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/listeners/ClosePackInventoryListener.java @@ -0,0 +1,16 @@ +package eu.mhsl.craftattack.spawn.appliances.packSelect.listeners; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; +import eu.mhsl.craftattack.spawn.appliances.packSelect.PackSelect; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.InventoryCloseEvent; + +public class ClosePackInventoryListener extends ApplianceListener { + @EventHandler + public void onClose(InventoryCloseEvent event) { + if(!(event.getPlayer() instanceof Player player)) return; + if(getAppliance().isNotPackInventory(player, event.getInventory())) return; + getAppliance().openInventories.remove(player); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/listeners/SetPacksOnJoinListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/listeners/SetPacksOnJoinListener.java new file mode 100644 index 0000000..85e5d54 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/packSelect/listeners/SetPacksOnJoinListener.java @@ -0,0 +1,18 @@ +package eu.mhsl.craftattack.spawn.appliances.packSelect.listeners; + +import eu.mhsl.craftattack.spawn.Main; +import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; +import eu.mhsl.craftattack.spawn.appliances.packSelect.PackSelect; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerJoinEvent; + +public class SetPacksOnJoinListener extends ApplianceListener { + @EventHandler + public void onJoin(PlayerJoinEvent event) { + Bukkit.getScheduler().runTask( + Main.instance(), + () -> getAppliance().setPack(event.getPlayer(), getAppliance().getPackConfigurationForPlayer(event.getPlayer())) + ); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/panicBan/PanicBan.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/panicBan/PanicBan.java index 5ecd1fc..fef73a8 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/panicBan/PanicBan.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/panicBan/PanicBan.java @@ -50,7 +50,7 @@ public class PanicBan extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of(new PanicBanJoinListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/playerlimit/PlayerLimit.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playerlimit/PlayerLimit.java index 90ee541..4f620b3 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/playerlimit/PlayerLimit.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/playerlimit/PlayerLimit.java @@ -31,7 +31,7 @@ public class PlayerLimit extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of( new PlayerLimiterListener() ); diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/OnCraftingTableUseListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/OnCraftingTableUseListener.java index 0363c78..d100a9b 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/OnCraftingTableUseListener.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/OnCraftingTableUseListener.java @@ -3,11 +3,13 @@ package eu.mhsl.craftattack.spawn.appliances.portableCrafting; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import org.bukkit.Material; import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; public class OnCraftingTableUseListener extends ApplianceListener { @EventHandler public void inInteract(PlayerInteractEvent event) { + if(!event.getAction().equals(Action.RIGHT_CLICK_AIR)) return; if(!event.getMaterial().equals(Material.CRAFTING_TABLE)) return; getAppliance().openFor(event.getPlayer()); } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/PortableCrafting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/PortableCrafting.java index 5b00283..0cc0b1b 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/PortableCrafting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/PortableCrafting.java @@ -9,13 +9,18 @@ import org.jetbrains.annotations.NotNull; import java.util.List; public class PortableCrafting extends Appliance { + @Override + public void onEnable() { + Settings.instance().declareSetting(PortableCraftingSetting.class); + } + public void openFor(Player player) { if(!Settings.instance().getSetting(player, Settings.Key.EnablePortableCrafting, Boolean.class)) return; player.openWorkbench(null, true); } @Override - protected @NotNull List eventHandlers() { + protected @NotNull List listeners() { return List.of(new OnCraftingTableUseListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/PortableCraftingSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/PortableCraftingSetting.java similarity index 94% rename from src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/PortableCraftingSetting.java rename to src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/PortableCraftingSetting.java index 04a24b3..026f7b5 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/PortableCraftingSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/portableCrafting/PortableCraftingSetting.java @@ -1,4 +1,4 @@ -package eu.mhsl.craftattack.spawn.appliances.settings.settings; +package eu.mhsl.craftattack.spawn.appliances.portableCrafting; import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/projectStart/ProjectStart.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/projectStart/ProjectStart.java index 2f7560c..e12abce 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/projectStart/ProjectStart.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/projectStart/ProjectStart.java @@ -175,7 +175,7 @@ public class ProjectStart extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of( new PlayerInvincibleListener(), new NoAdvancementsListener() diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/SettingCategory.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/SettingCategory.java index 5a7bf41..6cf4503 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/SettingCategory.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/SettingCategory.java @@ -2,6 +2,6 @@ package eu.mhsl.craftattack.spawn.appliances.settings; public enum SettingCategory { Gameplay, - Chat, + Visuals, Misc, } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/Settings.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/Settings.java index 3dfceed..4703619 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/Settings.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/Settings.java @@ -1,12 +1,10 @@ package eu.mhsl.craftattack.spawn.appliances.settings; -import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.Setting; import eu.mhsl.craftattack.spawn.appliances.settings.listeners.OpenSettingsShortcutListener; import eu.mhsl.craftattack.spawn.appliances.settings.listeners.SettingsInventoryListener; -import eu.mhsl.craftattack.spawn.appliances.settings.settings.*; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -14,13 +12,14 @@ import org.bukkit.event.Listener; import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.NotNull; -import java.util.Arrays; -import java.util.List; -import java.util.WeakHashMap; +import java.lang.reflect.InvocationTargetException; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; public class Settings extends Appliance { private static Settings settingsInstance; + private final Set>> declaredSettings = new HashSet<>(); public enum Key { TechnicalTab, @@ -31,13 +30,24 @@ public class Settings extends Appliance { SignEdit, HotbarReplacer, ChatMentions, + DoubleDoors, + KnockDoors, } public static Settings instance() { if(settingsInstance != null) return settingsInstance; - Settings instance = Main.instance().getAppliance(Settings.class); - Settings.settingsInstance = instance; - return instance; + Settings.settingsInstance = queryAppliance(Settings.class); + return settingsInstance; + } + + public void declareSetting(Class> setting) { + this.declaredSettings.add(setting); + this.settingsCache.clear(); + } + + @Override + public void onEnable() { + Settings.instance().declareSetting(SettingsShortcutSetting.class); } public record OpenSettingsInventory(Inventory inventory, List> settings) { @@ -49,25 +59,31 @@ public class Settings extends Appliance { private List> getSettings(Player player) { if(settingsCache.containsKey(player)) return settingsCache.get(player); - List> settings = List.of( - new PortableCraftingSetting(), - new AutoShulkerSetting(), - new SignEditSetting(), - new HotbarReplaceSetting(), - new ChatMentionSetting(), - new ShowJoinAndLeaveMessagesSetting(), - new TechnicalTablistSetting(), - new SettingsShortcutSetting() - ); + List> settings = this.declaredSettings.stream() + .map(clazz -> { + try { + return clazz.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException(String.format("Setting '%s' does not have an accessible constructor", clazz.getName()), e); + } + }) + .map(constructor -> { + try { + return constructor.newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(String.format("Failed to create instance of setting '%s'", constructor.getDeclaringClass().getName()), e); + } + }) + .peek(setting -> setting.initializeFromPlayer(player)) + .collect(Collectors.toList()); - settings.forEach(setting -> setting.initializeFromPlayer(player)); this.settingsCache.put(player, settings); return settings; } public T getSetting(Player player, Key key, Class clazz) { Setting setting = getSettings(player).stream() - .filter(s -> s.getKey().equals(key)) + .filter(s -> Objects.equals(s.getKey(), key)) .findFirst() .orElseThrow(); @@ -119,11 +135,15 @@ public class Settings extends Appliance { } private int calculateInvSize(List> settings) { - int countOfUncategorized = (int) settings.stream().filter(setting -> !(setting instanceof CategorizedSetting)).count(); + int countOfUncategorized = (int) settings.stream() + .filter(setting -> !(setting instanceof CategorizedSetting)) + .count(); + return Arrays.stream(SettingCategory.values()) .map(settingCategory -> settings.stream() .filter(setting -> setting instanceof CategorizedSetting) - .filter(setting -> ((CategorizedSetting) setting).category().equals(settingCategory)) + .map(setting -> (CategorizedSetting) setting) + .filter(categorizedSetting -> categorizedSetting.category().equals(settingCategory)) .count()) .map(itemCount -> itemCount + countOfUncategorized) .map(itemCount -> (int) Math.ceil((double) itemCount / 9)) @@ -148,7 +168,7 @@ public class Settings extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of( new SettingsInventoryListener(), new OpenSettingsShortcutListener() diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/SettingsShortcutSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/SettingsShortcutSetting.java similarity index 76% rename from src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/SettingsShortcutSetting.java rename to src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/SettingsShortcutSetting.java index bd1cf72..b43bbcb 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/SettingsShortcutSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/SettingsShortcutSetting.java @@ -1,8 +1,5 @@ -package eu.mhsl.craftattack.spawn.appliances.settings.settings; +package eu.mhsl.craftattack.spawn.appliances.settings; -import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; -import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; -import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.BoolSetting; import org.bukkit.Material; diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/ActionSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/ActionSetting.java new file mode 100644 index 0000000..59e2071 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/ActionSetting.java @@ -0,0 +1,50 @@ +package eu.mhsl.craftattack.spawn.appliances.settings.datatypes; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; + + +public abstract class ActionSetting extends Setting { + public ActionSetting() { + super(null); + } + + protected abstract void onAction(Player player, ClickType clickType); + + @Override + public ItemMeta buildMeta(ItemMeta meta) { + meta.displayName(Component.text(title(), NamedTextColor.WHITE)); + meta.lore(buildDescription(description())); + return meta; + } + + @Override + protected void change(Player player, ClickType clickType) { + onAction(player, clickType); + } + + @Override + protected Void defaultValue() { + return null; + } + + @Override + protected void fromStorage(PersistentDataContainer container) {} + + @Override + protected void toStorage(PersistentDataContainer container, Void value) {} + + @Override + public Class dataType() { + return null; + } + + @Override + public Void state() { + return null; + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/BoolSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/BoolSetting.java index 75189ac..12ccbb5 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/BoolSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/BoolSetting.java @@ -3,6 +3,7 @@ package eu.mhsl.craftattack.spawn.appliances.settings.datatypes; import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataContainer; @@ -47,7 +48,7 @@ public abstract class BoolSetting extends Setting { } @Override - protected void change(ClickType clickType) { + protected void change(Player player, ClickType clickType) { this.state = !this.state; } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/MultiBoolSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/MultiBoolSetting.java index fb61063..7055030 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/MultiBoolSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/MultiBoolSetting.java @@ -4,6 +4,7 @@ import com.google.gson.Gson; import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataContainer; @@ -87,7 +88,7 @@ public abstract class MultiBoolSetting extends Setting { } @Override - protected void change(ClickType clickType) { + protected void change(Player player, ClickType clickType) { var recordComponents = this.state.getClass().getRecordComponents(); int currentIndex = IntStream.range(0, recordComponents.length) diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/SelectSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/SelectSetting.java index 7f56cf6..c00c5b6 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/SelectSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/SelectSetting.java @@ -5,6 +5,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataContainer; @@ -57,7 +58,7 @@ public abstract class SelectSetting extends Setting options = this.options.options; this.state = IntStream.range(0, options.size()) diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/Setting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/Setting.java index 8fe768e..0fc5580 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/Setting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/Setting.java @@ -37,7 +37,7 @@ public abstract class Setting { } public void triggerChange(Player p, ClickType clickType) { - this.change(clickType); + this.change(p, clickType); toStorage(p.getPersistentDataContainer(), this.state()); } @@ -57,7 +57,7 @@ public abstract class Setting { protected abstract String description(); protected abstract Material icon(); public abstract ItemMeta buildMeta(ItemMeta meta); - protected abstract void change(ClickType clickType); + protected abstract void change(Player player, ClickType clickType); protected abstract TDataType defaultValue(); protected abstract void fromStorage(PersistentDataContainer container); protected abstract void toStorage(PersistentDataContainer container, TDataType value); diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/snowballKnockback/SnowballKnockback.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/snowballKnockback/SnowballKnockback.java new file mode 100644 index 0000000..760bc50 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/snowballKnockback/SnowballKnockback.java @@ -0,0 +1,22 @@ +package eu.mhsl.craftattack.spawn.appliances.snowballKnockback; + +import eu.mhsl.craftattack.spawn.appliance.Appliance; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class SnowballKnockback extends Appliance { + public void dealSnowballKnockback(LivingEntity entity, Entity snowball) { + entity.damage(0.1); + entity.knockback(0.4, -snowball.getVelocity().getX(), -snowball.getVelocity().getZ()); + } + + @Override + @NotNull + protected List listeners() { + return List.of(new SnowballKnockbackListener()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/snowballKnockback/SnowballKnockbackListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/snowballKnockback/SnowballKnockbackListener.java new file mode 100644 index 0000000..30a4849 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/snowballKnockback/SnowballKnockbackListener.java @@ -0,0 +1,18 @@ +package eu.mhsl.craftattack.spawn.appliances.snowballKnockback; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.ProjectileHitEvent; + +public class SnowballKnockbackListener extends ApplianceListener { + @EventHandler + public void onSnowballHit(ProjectileHitEvent event) { + if(event.getHitEntity() == null) return; + if(!event.getEntityType().equals(EntityType.SNOWBALL)) return; + if(!(event.getHitEntity() instanceof LivingEntity hitEntity)) return; + + Entity snowball = event.getEntity(); + getAppliance().dealSnowballKnockback(hitEntity, snowball); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/tablist/Tablist.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/tablist/Tablist.java index 90a02da..c6fee66 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/tablist/Tablist.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/tablist/Tablist.java @@ -16,6 +16,8 @@ import org.bukkit.entity.Player; import org.bukkit.event.Listener; import org.jetbrains.annotations.NotNull; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; import java.time.Duration; import java.util.List; @@ -23,6 +25,7 @@ import java.util.List; public class Tablist extends Appliance { private final RainbowComponent serverName = new RainbowComponent(" CraftAttack 7 ", 7, 3); private NetworkMonitor networkMonitor; + private OperatingSystemMXBean systemMonitor; public Tablist() { super("tablist"); @@ -30,8 +33,12 @@ public class Tablist extends Appliance { @Override public void onEnable() { + Settings.instance().declareSetting(TechnicalTablistSetting.class); + int tabRefreshRate = 3; this.networkMonitor = new NetworkMonitor(localConfig().getString("interface"), Duration.ofSeconds(1)); + this.systemMonitor = ManagementFactory.getOperatingSystemMXBean(); + Bukkit.getScheduler().runTaskTimerAsynchronously( Main.instance(), () -> IteratorUtil.onlinePlayers(this::updateHeader), @@ -63,8 +70,9 @@ public class Tablist extends Appliance { .append(ComponentUtil.getFormattedPing(player)).appendNewline() .append(ComponentUtil.getFormattedNetworkStats( this.networkMonitor.getTraffic(), - this.networkMonitor.getPackets()) - ).appendNewline(); + this.networkMonitor.getPackets() + )).appendNewline() + .append(ComponentUtil.getFormattedSystemStats(this.systemMonitor)).appendNewline(); } player.sendPlayerListHeader(header); @@ -76,7 +84,7 @@ public class Tablist extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of(new TablistListener()); } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/TechnicalTablistSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/tablist/TechnicalTablistSetting.java similarity index 94% rename from src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/TechnicalTablistSetting.java rename to src/main/java/eu/mhsl/craftattack/spawn/appliances/tablist/TechnicalTablistSetting.java index 482913a..854f27d 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/TechnicalTablistSetting.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/tablist/TechnicalTablistSetting.java @@ -1,4 +1,4 @@ -package eu.mhsl.craftattack.spawn.appliances.settings.settings; +package eu.mhsl.craftattack.spawn.appliances.tablist; import eu.mhsl.craftattack.spawn.appliances.settings.CategorizedSetting; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/titleClear/TitleClear.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/titleClear/TitleClear.java index 678fac7..0f79052 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/titleClear/TitleClear.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/titleClear/TitleClear.java @@ -14,7 +14,7 @@ public class TitleClear extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of( new TitleClearListener() ); diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java index 920c2c6..dd707fb 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java @@ -64,7 +64,7 @@ public class Whitelist extends Appliance { ); } - Main.instance().getAppliance(Outlawed.class).updateForcedStatus(player, timestampRelevant(user.outlawed_until)); + queryAppliance(Outlawed.class).updateForcedStatus(player, timestampRelevant(user.outlawed_until)); String purePlayerName = Floodgate.isBedrock(player) ? Floodgate.getBedrockPlayer(player).getUsername() @@ -144,7 +144,7 @@ public class Whitelist extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of( new PlayerJoinListener() ); diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/worldmuseum/WorldMuseum.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/worldmuseum/WorldMuseum.java index 5e55ac2..0520a48 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/worldmuseum/WorldMuseum.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/worldmuseum/WorldMuseum.java @@ -44,15 +44,13 @@ public class WorldMuseum extends Appliance { public void handleVillagerInteraction(Player player) { if(Floodgate.isBedrock(player)) { - Floodgate.runBedrockOnly(player, floodgatePlayer -> { - floodgatePlayer.sendForm( - SimpleForm.builder() - .title("Nicht unterstützt") - .content("Bedrock-Spieler werden derzeit für das Weltenmuseum aus Kompatiblitätsgründen nicht zugelassen! Tut uns Leid.") - .button("Ok") - .build() - ); - }); + Floodgate.runBedrockOnly(player, floodgatePlayer -> floodgatePlayer.sendForm( + SimpleForm.builder() + .title("Nicht unterstützt") + .content("Bedrock-Spieler werden derzeit für das Weltenmuseum aus Kompatiblitätsgründen nicht zugelassen! Tut uns Leid.") + .button("Ok") + .build() + )); return; } @@ -68,7 +66,7 @@ public class WorldMuseum extends Appliance { @Override @NotNull - protected List eventHandlers() { + protected List listeners() { return List.of( new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> handleVillagerInteraction(playerInteractAtEntityEvent.getPlayer())), new DismissInventoryOpenFromHolder(this.villager.getUniqueId()) diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/yearRank/YearRank.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/yearRank/YearRank.java new file mode 100644 index 0000000..75e3acb --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/yearRank/YearRank.java @@ -0,0 +1,99 @@ +package eu.mhsl.craftattack.spawn.appliances.yearRank; + +import eu.mhsl.craftattack.spawn.Main; +import eu.mhsl.craftattack.spawn.appliance.Appliance; +import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.*; + +public class YearRank extends Appliance { + record CraftAttackYear(String name) {} + private final Map> rankMap = new HashMap<>(); + + @Override + public void onEnable() { + File folder = new File(Main.instance().getDataFolder(), "yearRank"); + + //noinspection ResultOfMethodCallIgnored + folder.mkdirs(); + + Optional dataFolders = Optional.ofNullable(folder.listFiles()); + if(dataFolders.isEmpty()) return; + + List.of(dataFolders.get()).forEach(playerDataFolder -> { + Optional datFiles = Optional.ofNullable(playerDataFolder.listFiles()); + if(datFiles.isEmpty()) return; + + CraftAttackYear craftAttackYear = new CraftAttackYear(playerDataFolder.getName()); + + Arrays.stream(datFiles.get()) + .map(file -> file.getName().split("\\.")[0]) + .distinct() + .map(UUID::fromString) + .peek(uuid -> rankMap.computeIfAbsent(uuid, p -> new ArrayList<>())) + .forEach(uuid -> rankMap.get(uuid).add(craftAttackYear)); + }); + } + + public @Nullable Component getNamePrefix(Player player) { + if(!rankMap.containsKey(player.getUniqueId())) return null; + int yearCount = rankMap.get(player.getUniqueId()).size(); + if(yearCount <= 3) return null; + + return Component.text() + .append(Component.text("[\uD83C\uDF1F]", NamedTextColor.GOLD)) + .hoverEvent(HoverEvent.showText( + Component.text(String.format("Langzeitspieler: %s ist bereits seit %d Jahren dabei!", player.getName(), yearCount)) + )) + .build(); + } + + public Component listYearRanks() { + TextComponent.Builder builder = Component.text(); + builder.append(Component.text("Top 30 Spieler: ", NamedTextColor.GOLD)); + rankMap.keySet().stream() + .map(uuid -> Map.entry(uuid, rankMap.get(uuid).size())) + .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) + .limit(30) + .forEach(entry -> builder + .appendNewline() + .append(Component.text(entry.getKey().toString(), NamedTextColor.GRAY) + .hoverEvent(HoverEvent.showText(Component.text(entry.getKey().toString()))) + .clickEvent(ClickEvent.copyToClipboard(entry.getKey().toString()))) + .append(Component.text(": ")) + .append(Component.text(entry.getValue(), NamedTextColor.GOLD))); + + builder + .appendNewline() + .appendNewline() + .append(Component.text("Übersischt:", NamedTextColor.GOLD)); + + rankMap.values().stream() + .mapMulti(Iterable::forEach) + .filter(o -> o instanceof CraftAttackYear) + .map(o -> (CraftAttackYear) o) + .distinct() + .forEach(craftAttackYear -> builder + .appendNewline() + .append(Component.text(craftAttackYear.name, NamedTextColor.GRAY)) + .append(Component.text(": ")) + .append(Component.text(rankMap.keySet().stream() + .filter(uuid -> rankMap.get(uuid).contains(craftAttackYear)).count()))); + + return builder.build(); + } + + @Override + protected @NotNull List> commands() { + return List.of(new YearRankCommand()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/yearRank/YearRankCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/yearRank/YearRankCommand.java new file mode 100644 index 0000000..b879b04 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/yearRank/YearRankCommand.java @@ -0,0 +1,17 @@ +package eu.mhsl.craftattack.spawn.appliances.yearRank; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class YearRankCommand extends ApplianceCommand { + public YearRankCommand() { + super("yearRank"); + } + + @Override + protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { + sender.sendMessage(getAppliance().listYearRanks()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/config/Configuration.java b/src/main/java/eu/mhsl/craftattack/spawn/config/Configuration.java index 68e033a..d7ee8e2 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/config/Configuration.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/config/Configuration.java @@ -1,6 +1,7 @@ package eu.mhsl.craftattack.spawn.config; import eu.mhsl.craftattack.spawn.Main; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -10,9 +11,11 @@ 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() { diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/IteratorUtil.java b/src/main/java/eu/mhsl/craftattack/spawn/util/IteratorUtil.java index 1606072..57b07ad 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/util/IteratorUtil.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/util/IteratorUtil.java @@ -5,9 +5,14 @@ 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) { @@ -25,4 +30,25 @@ public class IteratorUtil { public static void setGameRules(Map, 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 void iterateListInGlobal(int sectionStart, List list, BiConsumer 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 callback) { + IntStream.range(sectionStart, sectionStart + localLength) + .forEach(value -> callback.accept(value, value + sectionStart)); + } + + public static List expandList(List list, int targetSize, T defaultValue) { + return Stream.concat( + list.stream(), + Stream.generate(() -> defaultValue).limit(Math.max(0, targetSize - list.size())) + ).collect(Collectors.toList()); + } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/inventory/HeadBuilder.java b/src/main/java/eu/mhsl/craftattack/spawn/util/inventory/HeadBuilder.java new file mode 100644 index 0000000..98def19 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/util/inventory/HeadBuilder.java @@ -0,0 +1,22 @@ +package eu.mhsl.craftattack.spawn.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; + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/inventory/ItemBuilder.java b/src/main/java/eu/mhsl/craftattack/spawn/util/inventory/ItemBuilder.java new file mode 100644 index 0000000..a516c21 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/util/inventory/ItemBuilder.java @@ -0,0 +1,92 @@ +package eu.mhsl.craftattack.spawn.util.inventory; + +import eu.mhsl.craftattack.spawn.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 process) { + return this.displayName(process.apply(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 lore = 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 callback) { + ItemMeta meta = this.itemStack.getItemMeta(); + callback.accept(meta); + this.itemStack.setItemMeta(meta); + return this; + } + + public ItemStack build() { + return this.itemStack; + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/inventory/PlaceholderItems.java b/src/main/java/eu/mhsl/craftattack/spawn/util/inventory/PlaceholderItems.java new file mode 100644 index 0000000..65080f5 --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/util/inventory/PlaceholderItems.java @@ -0,0 +1,13 @@ +package eu.mhsl.craftattack.spawn.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(); +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/server/Floodgate.java b/src/main/java/eu/mhsl/craftattack/spawn/util/server/Floodgate.java index 428bf44..815137c 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/util/server/Floodgate.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/util/server/Floodgate.java @@ -1,6 +1,7 @@ package eu.mhsl.craftattack.spawn.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; @@ -24,4 +25,22 @@ public class Floodgate { public static void runJavaOnly(Player p, Consumer 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())); + } + } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/text/ComponentUtil.java b/src/main/java/eu/mhsl/craftattack/spawn/util/text/ComponentUtil.java index f4a5ee0..978ddd5 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/util/text/ComponentUtil.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/util/text/ComponentUtil.java @@ -10,6 +10,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import java.awt.*; +import java.lang.management.OperatingSystemMXBean; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -21,6 +22,13 @@ public class ComponentUtil { 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 lineBreak(String text) { + return lineBreak(text, 50); + } public static Stream lineBreak(String text, int charactersPerLine) { List lines = new ArrayList<>(); String[] words = text.split(" "); @@ -46,34 +54,6 @@ public class ComponentUtil { return lines.collect(Collectors.joining("\n")); } - public static Component getFormattedTPS() { - double[] tpsValues = Bukkit.getTPS(); - - double min1 = Math.min(1.0, Math.max(0.0, tpsValues[0] / 20.0)); - double min2 = Math.min(1.0, Math.max(0.0, tpsValues[1] / 20.0)); - double min3 = Math.min(1.0, Math.max(0.0, tpsValues[2] / 20.0)); - - int red1 = (int) (255 * (1.0 - min1)); - int green1 = (int) (255 * min1); - int red2 = (int) (255 * (1.0 - min2)); - int green2 = (int) (255 * min2); - int red3 = (int) (255 * (1.0 - min3)); - int green3 = (int) (255 * min3); - - TextColor tpsColor1 = TextColor.color(red1, green1, 0); - TextColor tpsColor2 = TextColor.color(red2, green2, 0); - TextColor tpsColor3 = TextColor.color(red3, green3, 0); - - return Component.text() - .append(Component.text("TPS 1, 5, 15m: ", NamedTextColor.GRAY)) - .append(Component.text(String.format("%.2f", tpsValues[0]), tpsColor1)) - .append(Component.text(", ")) - .append(Component.text(String.format("%.2f", tpsValues[1]), tpsColor2)) - .append(Component.text(", ")) - .append(Component.text(String.format("%.2f", tpsValues[2]), tpsColor3)) - .build(); - } - public static Component getFormattedTickTimes(boolean detailed) { long[] times = Bukkit.getServer().getTickTimes(); float mspt = ((float) Arrays.stream(times).sum() / times.length) * 1.0E-6f; @@ -132,14 +112,72 @@ public class ComponentUtil { public static Component getFormattedNetworkStats(NetworkMonitor.Traffic traffic, NetworkMonitor.Packets packets) { return Component.text() .append(Component.text( - DataSizeConverter.convertBytesToHumanReadable(traffic.rxBytes()) + " ↓ " + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps", + DataSizeConverter.convertBytesPerSecond(traffic.rxBytes()) + " ↓ " + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps", NamedTextColor.GREEN )) .append(Component.text(" | ", NamedTextColor.GRAY)) .append(Component.text( - DataSizeConverter.convertBytesToHumanReadable(traffic.txBytes()) + " ↑ " + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps", + 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(); + } } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/text/DataSizeConverter.java b/src/main/java/eu/mhsl/craftattack/spawn/util/text/DataSizeConverter.java index c736b96..2154c3c 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/util/text/DataSizeConverter.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/util/text/DataSizeConverter.java @@ -1,7 +1,7 @@ package eu.mhsl.craftattack.spawn.util.text; public class DataSizeConverter { - public static String convertBytesToHumanReadable(long bytes) { + public static String convertBytesPerSecond(long bytes) { double kbits = bytes * 8.0 / 1000.0; double mbits = kbits / 1000.0; @@ -11,4 +11,31 @@ public class DataSizeConverter { 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 %= 60; + return String.format("%dd%dh%dm%ds", days, hours, minutes, seconds); + } } \ No newline at end of file diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/text/RainbowComponent.java b/src/main/java/eu/mhsl/craftattack/spawn/util/text/RainbowComponent.java index da974b5..eb74d36 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/util/text/RainbowComponent.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/util/text/RainbowComponent.java @@ -6,12 +6,12 @@ import net.kyori.adventure.text.format.TextColor; import java.awt.*; public class RainbowComponent { - private int hueOffset = 0; + private float hueOffset = 0; private final String text; private final int density; - private final int speed; + private final float speed; - public RainbowComponent(String text, int density, int speed) { + public RainbowComponent(String text, int density, float speed) { this.text = text; this.density = density; this.speed = speed; @@ -19,15 +19,16 @@ public class RainbowComponent { public Component getRainbowState() { Component builder = Component.empty(); - int hue = this.hueOffset; - for(char c : text.toCharArray()) { - TextColor color = TextColor.color(Color.getHSBColor((float) hue / 360, 1, 1).getRGB()); + float hue = this.hueOffset; + for (char c : 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 += density; } - if(this.hueOffset > Byte.MAX_VALUE - speed) this.hueOffset = 0; - this.hueOffset += (byte) speed; + this.hueOffset = (this.hueOffset + speed) % 360; + return builder; } -} +} \ No newline at end of file diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/world/InteractSounds.java b/src/main/java/eu/mhsl/craftattack/spawn/util/world/InteractSounds.java new file mode 100644 index 0000000..f03854b --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/util/world/InteractSounds.java @@ -0,0 +1,37 @@ +package eu.mhsl.craftattack.spawn.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) { + player.playSound(getSound(sound.key()), Sound.Emitter.self()); + } + + private Sound getSound(Key soundKey) { + return Sound.sound(soundKey, Sound.Source.PLAYER, 1f, 1f); + } + + public void click() { + playSound(org.bukkit.Sound.UI_BUTTON_CLICK); + } + + public void success() { + playSound(org.bukkit.Sound.ENTITY_PLAYER_LEVELUP); + } + + public void delete() { + playSound(org.bukkit.Sound.ENTITY_SILVERFISH_DEATH); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1e2a49f..a6240d1 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,3 +1,7 @@ +plugin: + disabledAppliances: + - NameOfApplianceClass + worldMuseum: uuid: connect-server-name: worldmuseum @@ -42,11 +46,24 @@ help: spawn: "Der Weltspawn befindet sich bei x:0 y:0 z:0" playerLimit: - maxPlayers: 100 + maxPlayers: 10 whitelist: overrideIntegrityCheck: false api: https://mhsl.eu/craftattack/api/user tablist: - interface: eth0 \ No newline at end of file + 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 \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 9e86bca..0a3437b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -37,5 +37,8 @@ commands: panicBan: vogelfrei: settings: + texturepack: + maintanance: + yearRank: msg: r: \ No newline at end of file