Merge remote-tracking branch 'origin/develop-chatReply' into develop-chatReply

This commit is contained in:
Lars Neuhaus 2024-09-29 22:44:10 +02:00
commit d8259b79ae
83 changed files with 1797 additions and 219 deletions

View File

@ -26,6 +26,7 @@ dependencies {
compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT' compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT'
implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'com.sparkjava:spark-core:2.9.4' implementation 'com.sparkjava:spark-core:2.9.4'
implementation 'org.reflections:reflections:0.10.2'
} }
def targetJavaVersion = 21 def targetJavaVersion = 21

View File

@ -2,39 +2,16 @@ package eu.mhsl.craftattack.spawn;
import eu.mhsl.craftattack.spawn.api.HttpServer; import eu.mhsl.craftattack.spawn.api.HttpServer;
import eu.mhsl.craftattack.spawn.appliance.Appliance; 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 eu.mhsl.craftattack.spawn.config.Configuration;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.reflections.Reflections;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public final class Main extends JavaPlugin { public final class Main extends JavaPlugin {
@ -49,49 +26,46 @@ public final class Main extends JavaPlugin {
instance = this; instance = this;
logger = instance().getLogger(); logger = instance().getLogger();
saveDefaultConfig(); 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( private void wrappedEnable() {
new AdminMarker(), Configuration.readConfig();
new WorldMuseum(), List<String> disabledAppliances = Configuration.pluginConfig.getStringList("disabledAppliances");
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()
);
Main.logger.info("Loading appliances..."); Main.logger.info("Loading appliances...");
appliances.forEach(appliance -> { Reflections reflections = new Reflections(this.getClass().getPackageName());
Main.logger().info("Enabling " + appliance.getClass().getSimpleName()); Set<Class<? extends Appliance>> applianceClasses = reflections.getSubTypesOf(Appliance.class);
this.appliances = applianceClasses.stream()
.filter(applianceClass -> !disabledAppliances.contains(applianceClass.getSimpleName()))
.map(applianceClass -> {
try {
return (Appliance) applianceClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(String.format("Failed to create instance of '%s'", applianceClass.getName()), e);
}
})
.toList();
Main.logger().info(String.format("Loaded %d appliances!", appliances.size()));
Main.logger().info("Initializing appliances...");
this.appliances.forEach(appliance -> {
appliance.onEnable(); appliance.onEnable();
appliance.initialize(this); 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(); this.httpApi = new HttpServer();
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
Main.logger().info("Startup complete!");
} }
@Override @Override

View File

@ -42,7 +42,7 @@ public abstract class Appliance {
* @return List of listeners * @return List of listeners
*/ */
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return new ArrayList<>(); return new ArrayList<>();
} }
@ -72,7 +72,8 @@ public abstract class Appliance {
*/ */
@NotNull @NotNull
public ConfigurationSection localConfig() { 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() { public void onEnable() {
@ -82,7 +83,7 @@ public abstract class Appliance {
} }
public void initialize(@NotNull JavaPlugin plugin) { public void initialize(@NotNull JavaPlugin plugin) {
this.listeners = eventHandlers(); this.listeners = listeners();
this.commands = commands(); this.commands = commands();
listeners.forEach(listener -> Bukkit.getPluginManager().registerEvents(listener, plugin)); listeners.forEach(listener -> Bukkit.getPluginManager().registerEvents(listener, plugin));
@ -93,7 +94,7 @@ public abstract class Appliance {
listeners.forEach(HandlerList::unregisterAll); listeners.forEach(HandlerList::unregisterAll);
} }
public <T extends Appliance> T queryAppliance(Class<T> clazz) { protected static <T extends Appliance> T queryAppliance(Class<T> clazz) {
return Main.instance().getAppliance(clazz); return Main.instance().getAppliance(clazz);
} }

View File

@ -17,7 +17,7 @@ import java.util.Optional;
/** /**
* Utility class which enables command name definition over a constructor. * Utility class which enables command name definition over a constructor.
*/ */
public abstract class ApplianceCommand<T extends Appliance> extends ApplianceSupplier<T> implements TabCompleter, CommandExecutor { public abstract class ApplianceCommand<T extends Appliance> extends CachedApplianceSupplier<T> implements TabCompleter, CommandExecutor {
public String commandName; public String commandName;
protected Component errorMessage = Component.text("Fehler: ").color(NamedTextColor.RED); protected Component errorMessage = Component.text("Fehler: ").color(NamedTextColor.RED);

View File

@ -8,6 +8,6 @@ import org.bukkit.event.Listener;
* *
* @param <T> the type of your appliance * @param <T> the type of your appliance
*/ */
public abstract class ApplianceListener<T extends Appliance> extends ApplianceSupplier<T> implements Listener { public abstract class ApplianceListener<T extends Appliance> extends CachedApplianceSupplier<T> implements Listener {
} }

View File

@ -2,10 +2,10 @@ package eu.mhsl.craftattack.spawn.appliance;
import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.Main;
public class ApplianceSupplier<T extends Appliance> implements IApplianceSupplier<T> { public class CachedApplianceSupplier<T extends Appliance> implements IApplianceSupplier<T> {
private final T appliance; private final T appliance;
public ApplianceSupplier() { public CachedApplianceSupplier() {
this.appliance = Main.instance().getAppliance(Main.getApplianceType(getClass())); this.appliance = Main.instance().getAppliance(Main.getApplianceType(getClass()));
} }

View File

@ -18,7 +18,7 @@ public class AdminMarker extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of(new AdminMarkerListener()); return List.of(new AdminMarkerListener());
} }
} }

View File

@ -3,7 +3,6 @@ package eu.mhsl.craftattack.spawn.appliances.antiSignEdit;
import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.SelectSetting; 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.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
@ -15,6 +14,11 @@ import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
public class AntiSignEdit extends Appliance { public class AntiSignEdit extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(SignEditSetting.class);
}
public boolean preventSignEdit(Player p, SignSide sign) { public boolean preventSignEdit(Player p, SignSide sign) {
SelectSetting.Options.Option setting = Settings.instance().getSetting(p, Settings.Key.SignEdit, SelectSetting.Options.Option.class); SelectSetting.Options.Option setting = Settings.instance().getSetting(p, Settings.Key.SignEdit, SelectSetting.Options.Option.class);
if(setting.is(SignEditSetting.editable)) return false; if(setting.is(SignEditSetting.editable)) return false;
@ -37,7 +41,7 @@ public class AntiSignEdit extends Appliance {
} }
@Override @Override
protected @NotNull List<Listener> eventHandlers() { protected @NotNull List<Listener> listeners() {
return List.of(new OnSignEditListener()); return List.of(new OnSignEditListener());
} }
} }

View File

@ -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.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory;

View File

@ -1,6 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.autoShulker; package eu.mhsl.craftattack.spawn.appliances.autoShulker;
import eu.mhsl.craftattack.spawn.appliance.Appliance; 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.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Material; import org.bukkit.Material;
@ -16,6 +17,11 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
public class AutoShulker extends Appliance { public class AutoShulker extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(AutoShulkerSetting.class);
}
public boolean tryAutoShulker(Player p, Item item) { public boolean tryAutoShulker(Player p, Item item) {
ItemStack itemStack = item.getItemStack(); ItemStack itemStack = item.getItemStack();
ItemStack offhandStack = p.getInventory().getItemInOffHand(); ItemStack offhandStack = p.getInventory().getItemInOffHand();
@ -40,7 +46,7 @@ public class AutoShulker extends Appliance {
} }
@Override @Override
protected @NotNull List<Listener> eventHandlers() { protected @NotNull List<Listener> listeners() {
return List.of(new ItemPickupListener()); return List.of(new ItemPickupListener());
} }
} }

View File

@ -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.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory;

View File

@ -3,7 +3,6 @@ package eu.mhsl.craftattack.spawn.appliances.autoShulker;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.SelectSetting; 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.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.EntityPickupItemEvent;

View File

@ -3,7 +3,6 @@ package eu.mhsl.craftattack.spawn.appliances.chatMention;
import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.settings.settings.ChatMentionSetting;
import net.kyori.adventure.sound.Sound; import net.kyori.adventure.sound.Sound;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
@ -56,11 +55,12 @@ public class ChatMention extends Appliance {
@Override @Override
public void onEnable() { public void onEnable() {
Settings.instance().declareSetting(ChatMentionSetting.class);
refreshPlayers(); refreshPlayers();
} }
@Override @Override
protected @NotNull List<Listener> eventHandlers() { protected @NotNull List<Listener> listeners() {
return List.of(new ChatMentionListener()); return List.of(new ChatMentionListener());
} }
} }

View File

@ -4,7 +4,7 @@ import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages; import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings; 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 io.papermc.paper.event.player.AsyncChatDecorateEvent;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@ -42,7 +42,7 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
return Component.text(word); return Component.text(word);
} }
}) })
.reduce((a, b) -> a.append(Component.text(" ")).append(b)) .reduce(ComponentUtil::appendWithSpace)
.orElseThrow(); .orElseThrow();
getAppliance().notifyPlayers(mentioned); getAppliance().notifyPlayers(mentioned);

View File

@ -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.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory;
@ -9,7 +9,7 @@ import org.bukkit.Material;
public class ChatMentionSetting extends MultiBoolSetting<ChatMentionSetting.ChatMentionConfig> implements CategorizedSetting { public class ChatMentionSetting extends MultiBoolSetting<ChatMentionSetting.ChatMentionConfig> implements CategorizedSetting {
@Override @Override
public SettingCategory category() { public SettingCategory category() {
return SettingCategory.Chat; return SettingCategory.Visuals;
} }
public record ChatMentionConfig( public record ChatMentionConfig(

View File

@ -1,6 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.chatMessages; package eu.mhsl.craftattack.spawn.appliances.chatMessages;
import eu.mhsl.craftattack.spawn.appliance.Appliance; 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.Component;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent;
@ -12,6 +13,11 @@ import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
public class ChatMessages extends Appliance { public class ChatMessages extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(ShowJoinAndLeaveMessagesSetting.class);
}
public Component getReportablePlayerName(Player player) { public Component getReportablePlayerName(Player player) {
return addReportActions(player.displayName(), player.getName()); return addReportActions(player.displayName(), player.getName());
} }
@ -24,7 +30,7 @@ public class ChatMessages extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of(new ChatMessagesListener()); return List.of(new ChatMessagesListener());
} }
} }

View File

@ -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.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory;
@ -33,6 +33,6 @@ public class ShowJoinAndLeaveMessagesSetting extends BoolSetting implements Cate
@Override @Override
public SettingCategory category() { public SettingCategory category() {
return SettingCategory.Chat; return SettingCategory.Visuals;
} }
} }

View File

@ -17,7 +17,7 @@ public class CustomAdvancements extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of(new CustomAdvancementsDamageEntityListener()); return List.of(new CustomAdvancementsDamageEntityListener());
} }
} }

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.customAdvancements; package eu.mhsl.craftattack.spawn.appliances.customAdvancements;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.entity.Entity; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent;
@ -9,12 +9,11 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent;
public class CustomAdvancementsDamageEntityListener extends ApplianceListener<CustomAdvancements> { public class CustomAdvancementsDamageEntityListener extends ApplianceListener<CustomAdvancements> {
@EventHandler @EventHandler
public void onEntityDamageEntity(EntityDamageByEntityEvent event) { public void onEntityDamageEntity(EntityDamageByEntityEvent event) {
Entity damaged = event.getEntity(); if(!(event.getEntity() instanceof Player damaged)) return;
if(!(damaged instanceof Player)) return; if(!(event.getDamager() instanceof Player damager)) return;
Entity damager = event.getDamager(); if(!damager.getInventory().getItemInMainHand().getType().equals(Material.AIR)) return;
if(!(damager instanceof Player)) return; if(!damaged.hasPermission("admin")) return;
if(damaged.hasPermission("admin")) {
getAppliance().grantAdvancement("search_trouble", (Player) damager); getAppliance().grantAdvancement("search_trouble", damager);
}
} }
} }

View File

@ -1,9 +1,11 @@
package eu.mhsl.craftattack.spawn.appliances.displayName; 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.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker; import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker;
import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarkerListener; import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarkerListener;
import eu.mhsl.craftattack.spawn.appliances.outlawed.Outlawed; 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.Component;
import net.kyori.adventure.text.ComponentBuilder; import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
@ -14,19 +16,22 @@ import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.logging.Level;
public class DisplayName extends Appliance { public class DisplayName extends Appliance {
public void update(Player player) { public void update(Player player) {
TextColor playerColor = queryAppliance(AdminMarker.class).getPlayerColor(player); TextColor playerColor = queryAppliance(AdminMarker.class).getPlayerColor(player);
List<Supplier<Component>> prefixes = List.of( List<Supplier<Component>> prefixes = List.of(
() -> queryAppliance(Outlawed.class).getNamePrefix(player) () -> queryAppliance(Outlawed.class).getNamePrefix(player),
() -> queryAppliance(YearRank.class).getNamePrefix(player)
); );
ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text(); ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text();
prefixes.forEach(supplier -> { prefixes.forEach(supplier -> {
Component prefix = supplier.get(); Component prefix = supplier.get();
if(prefix == null) return; 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)); playerName.append(Component.text(player.getName(), playerColor));
@ -41,14 +46,14 @@ public class DisplayName extends Appliance {
player.playerListName(component); player.playerListName(component);
} catch(Exception e) { } catch(Exception e) {
//TODO this throws often exceptions, but still works, don't know why //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 @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of(new AdminMarkerListener()); return List.of(new AdminMarkerListener());
} }
} }

View File

@ -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<Listener> listeners() {
return List.of(new OnDoorInteractListener());
}
}

View File

@ -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;
}
}

View File

@ -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<DoubleDoor> {
@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);
}
}

View File

@ -201,17 +201,15 @@ public class Event extends Appliance {
public void advertise() { public void advertise() {
advertiseCountdown.cancelIfRunning(); advertiseCountdown.cancelIfRunning();
this.advertiseStatus = AdvertisementStatus.ADVERTISED; this.advertiseStatus = AdvertisementStatus.ADVERTISED;
IteratorUtil.onlinePlayers(player -> { IteratorUtil.onlinePlayers(player -> player.sendMessage(
player.sendMessage( Component.text()
Component.text() .append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline()
.append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline() .append(Component.text("Ein Event wurde gestartet!", NamedTextColor.GOLD)).appendNewline()
.append(Component.text("Ein Event wurde gestartet!", NamedTextColor.GOLD)).appendNewline() .append(Component.text("Nutze "))
.append(Component.text("Nutze ")) .append(Component.text("/event", NamedTextColor.AQUA))
.append(Component.text("/event", NamedTextColor.AQUA)) .append(Component.text(", um dem Event beizutreten!")).appendNewline()
.append(Component.text(", um dem Event beizutreten!")).appendNewline() .append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline()
.append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline() ));
);
});
advertiseCountdown.start(); advertiseCountdown.start();
} }
@ -237,7 +235,7 @@ public class Event extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of( return List.of(
new ApplyPendingRewardsListener(), new ApplyPendingRewardsListener(),
new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> joinEvent(playerInteractAtEntityEvent.getPlayer())), new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> joinEvent(playerInteractAtEntityEvent.getPlayer())),

View File

@ -19,7 +19,7 @@ public class Fleischerchest extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of(new FleischerchestCraftItemListener()); return List.of(new FleischerchestCraftItemListener());
} }
} }

View File

@ -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<Listener> listeners() {
return List.of(new OnBerryEaten());
}
}

View File

@ -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<GlowingBerries> {
@EventHandler
public void onEat(PlayerItemConsumeEvent event) {
if(event.getItem().getType().equals(Material.GLOW_BERRIES)) getAppliance().letPlayerGlow(event.getPlayer());
}
}

View File

@ -2,6 +2,7 @@ package eu.mhsl.craftattack.spawn.appliances.hotbarRefill;
import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance; 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.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -16,6 +17,11 @@ import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
public class HotbarRefill extends Appliance { public class HotbarRefill extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(HotbarRefillSetting.class);
}
public void handleHotbarChange(Player player, ItemStack item) { public void handleHotbarChange(Player player, ItemStack item) {
if(player.getGameMode().equals(GameMode.CREATIVE)) return; if(player.getGameMode().equals(GameMode.CREATIVE)) return;
if(item.getAmount() != 1) return; if(item.getAmount() != 1) return;
@ -46,7 +52,7 @@ public class HotbarRefill extends Appliance {
} }
@Override @Override
protected @NotNull List<Listener> eventHandlers() { protected @NotNull List<Listener> listeners() {
return List.of(new ItemRefillListener()); return List.of(new HotbarRefillListener());
} }
} }

View File

@ -2,7 +2,6 @@ package eu.mhsl.craftattack.spawn.appliances.hotbarRefill;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings; 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.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockPlaceEvent; 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.event.player.PlayerItemConsumeEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
public class ItemRefillListener extends ApplianceListener<HotbarRefill> { public class HotbarRefillListener extends ApplianceListener<HotbarRefill> {
private HotbarReplaceSetting.HotbarReplaceConfig getPlayerSetting(Player player) { private HotbarRefillSetting.HotbarReplaceConfig getPlayerSetting(Player player) {
return Settings.instance().getSetting( return Settings.instance().getSetting(
player, player,
Settings.Key.HotbarReplacer, Settings.Key.HotbarReplacer,
HotbarReplaceSetting.HotbarReplaceConfig.class HotbarRefillSetting.HotbarReplaceConfig.class
); );
} }

View File

@ -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.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; 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 eu.mhsl.craftattack.spawn.appliances.settings.datatypes.MultiBoolSetting;
import org.bukkit.Material; import org.bukkit.Material;
public class HotbarReplaceSetting extends MultiBoolSetting<HotbarReplaceSetting.HotbarReplaceConfig> implements CategorizedSetting { public class HotbarRefillSetting extends MultiBoolSetting<HotbarRefillSetting.HotbarReplaceConfig> implements CategorizedSetting {
@Override @Override
public SettingCategory category() { public SettingCategory category() {
return SettingCategory.Gameplay; return SettingCategory.Gameplay;
@ -18,7 +18,7 @@ public class HotbarReplaceSetting extends MultiBoolSetting<HotbarReplaceSetting.
@DisplayName("Essen") boolean onConsumable @DisplayName("Essen") boolean onConsumable
) {} ) {}
public HotbarReplaceSetting() { public HotbarRefillSetting() {
super(Settings.Key.HotbarReplacer); super(Settings.Key.HotbarReplacer);
} }

View File

@ -0,0 +1,65 @@
package eu.mhsl.craftattack.spawn.appliances.knockDoor;
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.datatypes.SelectSetting;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class KnockDoor extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(KnockDoorSetting.class);
}
public void knockAtDoor(Player knockingPlayer, Block knockedBlock) {
SelectSetting.Options.Option setting = Settings.instance().getSetting(knockingPlayer, Settings.Key.KnockDoors, SelectSetting.Options.Option.class);
if(setting.is(KnockDoorSetting.disabled)) return;
if(setting.is(KnockDoorSetting.knockSingleTime)) {
playSound(knockedBlock);
}
if(setting.is(KnockDoorSetting.knockThreeTimes)) {
String metadataKey = new NamespacedKey(Main.instance(), KnockDoor.class.getName()).getNamespace();
if(knockingPlayer.hasMetadata(metadataKey)) return;
knockingPlayer.setMetadata(metadataKey, new FixedMetadataValue(Main.instance(), 0));
new BukkitRunnable() {
@Override
public void run() {
int timesKnocked = knockingPlayer.getMetadata(metadataKey).getFirst().asInt();
if(timesKnocked >= 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<Listener> listeners() {
return List.of(new KnockDoorListener());
}
}

View File

@ -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<KnockDoor> {
@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);
}
}

View File

@ -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;
}
}

View File

@ -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<ApplianceCommand<?>> commands() {
return List.of(new MaintenanceCommand());
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new PreventMaintenanceJoinListener());
}
}

View File

@ -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<Maintenance> {
Map<String, Boolean> 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<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return arguments.keySet().stream().toList();
}
}

View File

@ -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<Maintenance> {
@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());
}
}

View File

@ -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<ComponentSupplier, Component> component, Function<UriSupplier, UriConsumer> uri) {}
List<SuppliedLink> 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<Listener> listeners() {
return List.of(new UpdateLinksListener());
}
}

View File

@ -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<OptionLinks> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
getAppliance().setServerLinks(event.getPlayer());
}
}

View File

@ -38,7 +38,7 @@ public class Outlawed extends Appliance {
if(!player.isOnline()) return; if(!player.isOnline()) return;
if(status != Status.FORCED) return; if(status != Status.FORCED) return;
try { try {
Main.instance().getAppliance(Whitelist.class).integrityCheck(player); queryAppliance(Whitelist.class).integrityCheck(player);
} catch(DisconnectInfo.Throwable e) { } catch(DisconnectInfo.Throwable e) {
Bukkit.getScheduler().runTask(Main.instance(), () -> e.getDisconnectScreen().applyKick(player)); 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) { private void setLawStatus(Player player, Status status) {
playerStatusMap.put(player, status); playerStatusMap.put(player, status);
Main.instance().getAppliance(DisplayName.class).update(player); queryAppliance(DisplayName.class).update(player);
List<String> newList = localConfig().getStringList(voluntarilyEntry); List<String> newList = localConfig().getStringList(voluntarilyEntry);
if(status.equals(Status.VOLUNTARILY)) { if(status.equals(Status.VOLUNTARILY)) {
@ -93,7 +93,7 @@ public class Outlawed extends Appliance {
} }
private boolean isTimeout(Player player) { 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) { private void setTimeout(Player player) {
@ -127,7 +127,7 @@ public class Outlawed extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of(new OutlawedReminderListener()); return List.of(new OutlawedReminderListener());
} }
} }

View File

@ -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<PackSelect> {
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());
}
}

View File

@ -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<PackSelect> {
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<Component> 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<Pack> packs) {
public Optional<Pack> findFromItem(ItemStack itemStack) {
return packs.stream()
.filter(pack -> pack.equalsItem(itemStack))
.findFirst();
}
}
public record SerializedPackList(List<UUID> 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<Pack> getPackList() {
return packList.packs();
}
}

View File

@ -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<PackSelect> {
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<PackConfiguration.Pack> 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
);
}
}

View File

@ -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<Player, PackConfigurationInventory> openInventories = new WeakHashMap<>();
public PackSelect() {
super("packselect");
}
@Override
public void onEnable() {
Settings.instance().declareSetting(PackSelectSetting.class);
List<Map<?, ?>> packs = localConfig().getMapList("packs");
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> packs.stream()
.flatMap(pack -> pack.entrySet().stream())
.forEach(pack -> {
@SuppressWarnings("unchecked") Map<String, String> packData = (Map<String, String>) 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<ApplianceCommand<?>> commands() {
return List.of(new ChangePackCommand());
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new ClosePackInventoryListener(),
new ClickPackInventoryListener(),
new SetPacksOnJoinListener()
);
}
}

View File

@ -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;
}
}

View File

@ -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<ResourcePackInfo> 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();
}
}

View File

@ -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<PackSelect> {
@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()
);
}
}

View File

@ -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<PackSelect> {
@EventHandler
public void onClose(InventoryCloseEvent event) {
if(!(event.getPlayer() instanceof Player player)) return;
if(getAppliance().isNotPackInventory(player, event.getInventory())) return;
getAppliance().openInventories.remove(player);
}
}

View File

@ -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<PackSelect> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Bukkit.getScheduler().runTask(
Main.instance(),
() -> getAppliance().setPack(event.getPlayer(), getAppliance().getPackConfigurationForPlayer(event.getPlayer()))
);
}
}

View File

@ -50,7 +50,7 @@ public class PanicBan extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of(new PanicBanJoinListener()); return List.of(new PanicBanJoinListener());
} }
} }

View File

@ -31,7 +31,7 @@ public class PlayerLimit extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of( return List.of(
new PlayerLimiterListener() new PlayerLimiterListener()
); );

View File

@ -3,11 +3,13 @@ package eu.mhsl.craftattack.spawn.appliances.portableCrafting;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
public class OnCraftingTableUseListener extends ApplianceListener<PortableCrafting> { public class OnCraftingTableUseListener extends ApplianceListener<PortableCrafting> {
@EventHandler @EventHandler
public void inInteract(PlayerInteractEvent event) { public void inInteract(PlayerInteractEvent event) {
if(!event.getAction().equals(Action.RIGHT_CLICK_AIR)) return;
if(!event.getMaterial().equals(Material.CRAFTING_TABLE)) return; if(!event.getMaterial().equals(Material.CRAFTING_TABLE)) return;
getAppliance().openFor(event.getPlayer()); getAppliance().openFor(event.getPlayer());
} }

View File

@ -9,13 +9,18 @@ import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
public class PortableCrafting extends Appliance { public class PortableCrafting extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(PortableCraftingSetting.class);
}
public void openFor(Player player) { public void openFor(Player player) {
if(!Settings.instance().getSetting(player, Settings.Key.EnablePortableCrafting, Boolean.class)) return; if(!Settings.instance().getSetting(player, Settings.Key.EnablePortableCrafting, Boolean.class)) return;
player.openWorkbench(null, true); player.openWorkbench(null, true);
} }
@Override @Override
protected @NotNull List<Listener> eventHandlers() { protected @NotNull List<Listener> listeners() {
return List.of(new OnCraftingTableUseListener()); return List.of(new OnCraftingTableUseListener());
} }
} }

View File

@ -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.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory;

View File

@ -175,7 +175,7 @@ public class ProjectStart extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of( return List.of(
new PlayerInvincibleListener(), new PlayerInvincibleListener(),
new NoAdvancementsListener() new NoAdvancementsListener()

View File

@ -2,6 +2,6 @@ package eu.mhsl.craftattack.spawn.appliances.settings;
public enum SettingCategory { public enum SettingCategory {
Gameplay, Gameplay,
Chat, Visuals,
Misc, Misc,
} }

View File

@ -1,12 +1,10 @@
package eu.mhsl.craftattack.spawn.appliances.settings; 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.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.Setting; 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.OpenSettingsShortcutListener;
import eu.mhsl.craftattack.spawn.appliances.settings.listeners.SettingsInventoryListener; import eu.mhsl.craftattack.spawn.appliances.settings.listeners.SettingsInventoryListener;
import eu.mhsl.craftattack.spawn.appliances.settings.settings.*;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -14,13 +12,14 @@ import org.bukkit.event.Listener;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Arrays; import java.lang.reflect.InvocationTargetException;
import java.util.List; import java.util.*;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class Settings extends Appliance { public class Settings extends Appliance {
private static Settings settingsInstance; private static Settings settingsInstance;
private final Set<Class<? extends Setting<?>>> declaredSettings = new HashSet<>();
public enum Key { public enum Key {
TechnicalTab, TechnicalTab,
@ -31,13 +30,24 @@ public class Settings extends Appliance {
SignEdit, SignEdit,
HotbarReplacer, HotbarReplacer,
ChatMentions, ChatMentions,
DoubleDoors,
KnockDoors,
} }
public static Settings instance() { public static Settings instance() {
if(settingsInstance != null) return settingsInstance; if(settingsInstance != null) return settingsInstance;
Settings instance = Main.instance().getAppliance(Settings.class); Settings.settingsInstance = queryAppliance(Settings.class);
Settings.settingsInstance = instance; return settingsInstance;
return instance; }
public void declareSetting(Class<? extends Setting<?>> setting) {
this.declaredSettings.add(setting);
this.settingsCache.clear();
}
@Override
public void onEnable() {
Settings.instance().declareSetting(SettingsShortcutSetting.class);
} }
public record OpenSettingsInventory(Inventory inventory, List<Setting<?>> settings) { public record OpenSettingsInventory(Inventory inventory, List<Setting<?>> settings) {
@ -49,25 +59,31 @@ public class Settings extends Appliance {
private List<Setting<?>> getSettings(Player player) { private List<Setting<?>> getSettings(Player player) {
if(settingsCache.containsKey(player)) return settingsCache.get(player); if(settingsCache.containsKey(player)) return settingsCache.get(player);
List<Setting<?>> settings = List.of( List<Setting<?>> settings = this.declaredSettings.stream()
new PortableCraftingSetting(), .map(clazz -> {
new AutoShulkerSetting(), try {
new SignEditSetting(), return clazz.getDeclaredConstructor();
new HotbarReplaceSetting(), } catch (NoSuchMethodException e) {
new ChatMentionSetting(), throw new RuntimeException(String.format("Setting '%s' does not have an accessible constructor", clazz.getName()), e);
new ShowJoinAndLeaveMessagesSetting(), }
new TechnicalTablistSetting(), })
new SettingsShortcutSetting() .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); this.settingsCache.put(player, settings);
return settings; return settings;
} }
public <T> T getSetting(Player player, Key key, Class<T> clazz) { public <T> T getSetting(Player player, Key key, Class<T> clazz) {
Setting<?> setting = getSettings(player).stream() Setting<?> setting = getSettings(player).stream()
.filter(s -> s.getKey().equals(key)) .filter(s -> Objects.equals(s.getKey(), key))
.findFirst() .findFirst()
.orElseThrow(); .orElseThrow();
@ -119,11 +135,15 @@ public class Settings extends Appliance {
} }
private int calculateInvSize(List<Setting<?>> settings) { private int calculateInvSize(List<Setting<?>> 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()) return Arrays.stream(SettingCategory.values())
.map(settingCategory -> settings.stream() .map(settingCategory -> settings.stream()
.filter(setting -> setting instanceof CategorizedSetting) .filter(setting -> setting instanceof CategorizedSetting)
.filter(setting -> ((CategorizedSetting) setting).category().equals(settingCategory)) .map(setting -> (CategorizedSetting) setting)
.filter(categorizedSetting -> categorizedSetting.category().equals(settingCategory))
.count()) .count())
.map(itemCount -> itemCount + countOfUncategorized) .map(itemCount -> itemCount + countOfUncategorized)
.map(itemCount -> (int) Math.ceil((double) itemCount / 9)) .map(itemCount -> (int) Math.ceil((double) itemCount / 9))
@ -148,7 +168,7 @@ public class Settings extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of( return List.of(
new SettingsInventoryListener(), new SettingsInventoryListener(),
new OpenSettingsShortcutListener() new OpenSettingsShortcutListener()

View File

@ -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 eu.mhsl.craftattack.spawn.appliances.settings.datatypes.BoolSetting;
import org.bukkit.Material; import org.bukkit.Material;

View File

@ -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<Void> {
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;
}
}

View File

@ -3,6 +3,7 @@ package eu.mhsl.craftattack.spawn.appliances.settings.datatypes;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
@ -47,7 +48,7 @@ public abstract class BoolSetting extends Setting<Boolean> {
} }
@Override @Override
protected void change(ClickType clickType) { protected void change(Player player, ClickType clickType) {
this.state = !this.state; this.state = !this.state;
} }

View File

@ -4,6 +4,7 @@ import com.google.gson.Gson;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
@ -87,7 +88,7 @@ public abstract class MultiBoolSetting<T> extends Setting<T> {
} }
@Override @Override
protected void change(ClickType clickType) { protected void change(Player player, ClickType clickType) {
var recordComponents = this.state.getClass().getRecordComponents(); var recordComponents = this.state.getClass().getRecordComponents();
int currentIndex = IntStream.range(0, recordComponents.length) int currentIndex = IntStream.range(0, recordComponents.length)

View File

@ -5,6 +5,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
@ -57,7 +58,7 @@ public abstract class SelectSetting extends Setting<SelectSetting.Options.Option
} }
@Override @Override
protected void change(ClickType clickType) { protected void change(Player player, ClickType clickType) {
int optionModifier = clickType.equals(ClickType.LEFT) ? 1 : -1; int optionModifier = clickType.equals(ClickType.LEFT) ? 1 : -1;
List<Options.Option> options = this.options.options; List<Options.Option> options = this.options.options;
this.state = IntStream.range(0, options.size()) this.state = IntStream.range(0, options.size())

View File

@ -37,7 +37,7 @@ public abstract class Setting<TDataType> {
} }
public void triggerChange(Player p, ClickType clickType) { public void triggerChange(Player p, ClickType clickType) {
this.change(clickType); this.change(p, clickType);
toStorage(p.getPersistentDataContainer(), this.state()); toStorage(p.getPersistentDataContainer(), this.state());
} }
@ -57,7 +57,7 @@ public abstract class Setting<TDataType> {
protected abstract String description(); protected abstract String description();
protected abstract Material icon(); protected abstract Material icon();
public abstract ItemMeta buildMeta(ItemMeta meta); 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 TDataType defaultValue();
protected abstract void fromStorage(PersistentDataContainer container); protected abstract void fromStorage(PersistentDataContainer container);
protected abstract void toStorage(PersistentDataContainer container, TDataType value); protected abstract void toStorage(PersistentDataContainer container, TDataType value);

View File

@ -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<Listener> listeners() {
return List.of(new SnowballKnockbackListener());
}
}

View File

@ -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<SnowballKnockback> {
@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);
}
}

View File

@ -16,6 +16,8 @@ import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
@ -23,6 +25,7 @@ import java.util.List;
public class Tablist extends Appliance { public class Tablist extends Appliance {
private final RainbowComponent serverName = new RainbowComponent(" CraftAttack 7 ", 7, 3); private final RainbowComponent serverName = new RainbowComponent(" CraftAttack 7 ", 7, 3);
private NetworkMonitor networkMonitor; private NetworkMonitor networkMonitor;
private OperatingSystemMXBean systemMonitor;
public Tablist() { public Tablist() {
super("tablist"); super("tablist");
@ -30,8 +33,12 @@ public class Tablist extends Appliance {
@Override @Override
public void onEnable() { public void onEnable() {
Settings.instance().declareSetting(TechnicalTablistSetting.class);
int tabRefreshRate = 3; int tabRefreshRate = 3;
this.networkMonitor = new NetworkMonitor(localConfig().getString("interface"), Duration.ofSeconds(1)); this.networkMonitor = new NetworkMonitor(localConfig().getString("interface"), Duration.ofSeconds(1));
this.systemMonitor = ManagementFactory.getOperatingSystemMXBean();
Bukkit.getScheduler().runTaskTimerAsynchronously( Bukkit.getScheduler().runTaskTimerAsynchronously(
Main.instance(), Main.instance(),
() -> IteratorUtil.onlinePlayers(this::updateHeader), () -> IteratorUtil.onlinePlayers(this::updateHeader),
@ -63,8 +70,9 @@ public class Tablist extends Appliance {
.append(ComponentUtil.getFormattedPing(player)).appendNewline() .append(ComponentUtil.getFormattedPing(player)).appendNewline()
.append(ComponentUtil.getFormattedNetworkStats( .append(ComponentUtil.getFormattedNetworkStats(
this.networkMonitor.getTraffic(), this.networkMonitor.getTraffic(),
this.networkMonitor.getPackets()) this.networkMonitor.getPackets()
).appendNewline(); )).appendNewline()
.append(ComponentUtil.getFormattedSystemStats(this.systemMonitor)).appendNewline();
} }
player.sendPlayerListHeader(header); player.sendPlayerListHeader(header);
@ -76,7 +84,7 @@ public class Tablist extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of(new TablistListener()); return List.of(new TablistListener());
} }
} }

View File

@ -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.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory; import eu.mhsl.craftattack.spawn.appliances.settings.SettingCategory;

View File

@ -14,7 +14,7 @@ public class TitleClear extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of( return List.of(
new TitleClearListener() new TitleClearListener()
); );

View File

@ -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) String purePlayerName = Floodgate.isBedrock(player)
? Floodgate.getBedrockPlayer(player).getUsername() ? Floodgate.getBedrockPlayer(player).getUsername()
@ -144,7 +144,7 @@ public class Whitelist extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of( return List.of(
new PlayerJoinListener() new PlayerJoinListener()
); );

View File

@ -44,15 +44,13 @@ public class WorldMuseum extends Appliance {
public void handleVillagerInteraction(Player player) { public void handleVillagerInteraction(Player player) {
if(Floodgate.isBedrock(player)) { if(Floodgate.isBedrock(player)) {
Floodgate.runBedrockOnly(player, floodgatePlayer -> { Floodgate.runBedrockOnly(player, floodgatePlayer -> floodgatePlayer.sendForm(
floodgatePlayer.sendForm( SimpleForm.builder()
SimpleForm.builder() .title("Nicht unterstützt")
.title("Nicht unterstützt") .content("Bedrock-Spieler werden derzeit für das Weltenmuseum aus Kompatiblitätsgründen nicht zugelassen! Tut uns Leid.")
.content("Bedrock-Spieler werden derzeit für das Weltenmuseum aus Kompatiblitätsgründen nicht zugelassen! Tut uns Leid.") .button("Ok")
.button("Ok") .build()
.build() ));
);
});
return; return;
} }
@ -68,7 +66,7 @@ public class WorldMuseum extends Appliance {
@Override @Override
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return List.of( return List.of(
new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> handleVillagerInteraction(playerInteractAtEntityEvent.getPlayer())), new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> handleVillagerInteraction(playerInteractAtEntityEvent.getPlayer())),
new DismissInventoryOpenFromHolder(this.villager.getUniqueId()) new DismissInventoryOpenFromHolder(this.villager.getUniqueId())

View File

@ -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<UUID, List<CraftAttackYear>> rankMap = new HashMap<>();
@Override
public void onEnable() {
File folder = new File(Main.instance().getDataFolder(), "yearRank");
//noinspection ResultOfMethodCallIgnored
folder.mkdirs();
Optional<File[]> dataFolders = Optional.ofNullable(folder.listFiles());
if(dataFolders.isEmpty()) return;
List.of(dataFolders.get()).forEach(playerDataFolder -> {
Optional<File[]> 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<ApplianceCommand<?>> commands() {
return List.of(new YearRankCommand());
}
}

View File

@ -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<YearRank> {
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());
}
}

View File

@ -1,6 +1,7 @@
package eu.mhsl.craftattack.spawn.config; package eu.mhsl.craftattack.spawn.config;
import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.Main;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
@ -10,9 +11,11 @@ public class Configuration {
private static final String configName = "config.yml"; private static final String configName = "config.yml";
private static final File configFile = new File(Main.instance().getDataFolder().getAbsolutePath() + "/" + configName); private static final File configFile = new File(Main.instance().getDataFolder().getAbsolutePath() + "/" + configName);
public static FileConfiguration cfg; public static FileConfiguration cfg;
public static ConfigurationSection pluginConfig;
public static void readConfig() { public static void readConfig() {
cfg = YamlConfiguration.loadConfiguration(configFile); cfg = YamlConfiguration.loadConfiguration(configFile);
pluginConfig = cfg.getConfigurationSection("plugin");
} }
public static void saveChanges() { public static void saveChanges() {

View File

@ -5,9 +5,14 @@ import org.bukkit.GameRule;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class IteratorUtil { public class IteratorUtil {
public static void worlds(Consumer<World> world) { public static void worlds(Consumer<World> world) {
@ -25,4 +30,25 @@ public class IteratorUtil {
public static void setGameRules(Map<GameRule<Boolean>, Boolean> rules, boolean inverse) { public static void setGameRules(Map<GameRule<Boolean>, Boolean> rules, boolean inverse) {
rules.forEach((gameRule, value) -> IteratorUtil.worlds(world -> world.setGameRule(gameRule, value ^ inverse))); rules.forEach((gameRule, value) -> IteratorUtil.worlds(world -> world.setGameRule(gameRule, value ^ inverse)));
} }
public static void times(int times, Runnable callback) {
IntStream.range(0, times).forEach(value -> callback.run());
}
public static <T> void iterateListInGlobal(int sectionStart, List<T> list, BiConsumer<Integer, T> callback) {
IntStream.range(sectionStart, sectionStart + list.size())
.forEach(value -> callback.accept(value, list.get(value - sectionStart)));
}
public static void iterateLocalInGlobal(int sectionStart, int localLength, BiConsumer<Integer, Integer> callback) {
IntStream.range(sectionStart, sectionStart + localLength)
.forEach(value -> callback.accept(value, value + sectionStart));
}
public static <T> List<T> expandList(List<T> list, int targetSize, T defaultValue) {
return Stream.concat(
list.stream(),
Stream.generate(() -> defaultValue).limit(Math.max(0, targetSize - list.size()))
).collect(Collectors.toList());
}
} }

View File

@ -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;
}
}

View File

@ -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<Component, Component> 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<Component> 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<ItemMeta> callback) {
ItemMeta meta = this.itemStack.getItemMeta();
callback.accept(meta);
this.itemStack.setItemMeta(meta);
return this;
}
public ItemStack build() {
return this.itemStack;
}
}

View File

@ -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();
}

View File

@ -1,6 +1,7 @@
package eu.mhsl.craftattack.spawn.util.server; package eu.mhsl.craftattack.spawn.util.server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.geysermc.cumulus.form.SimpleForm;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
@ -24,4 +25,22 @@ public class Floodgate {
public static void runJavaOnly(Player p, Consumer<Player> callback) { public static void runJavaOnly(Player p, Consumer<Player> callback) {
if(!isBedrock(p)) callback.accept(p); 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()));
}
}
} }

View File

@ -10,6 +10,7 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.awt.*; import java.awt.*;
import java.lang.management.OperatingSystemMXBean;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -21,6 +22,13 @@ public class ComponentUtil {
return Component.text().append(a.appendNewline().append(b)).build(); return Component.text().append(a.appendNewline().append(b)).build();
} }
public static TextComponent appendWithSpace(Component a, Component b) {
return Component.text().append(a).append(Component.text(" ")).append(b).build();
}
public static Stream<String> lineBreak(String text) {
return lineBreak(text, 50);
}
public static Stream<String> lineBreak(String text, int charactersPerLine) { public static Stream<String> lineBreak(String text, int charactersPerLine) {
List<String> lines = new ArrayList<>(); List<String> lines = new ArrayList<>();
String[] words = text.split(" "); String[] words = text.split(" ");
@ -46,34 +54,6 @@ public class ComponentUtil {
return lines.collect(Collectors.joining("\n")); 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) { public static Component getFormattedTickTimes(boolean detailed) {
long[] times = Bukkit.getServer().getTickTimes(); long[] times = Bukkit.getServer().getTickTimes();
float mspt = ((float) Arrays.stream(times).sum() / times.length) * 1.0E-6f; 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) { public static Component getFormattedNetworkStats(NetworkMonitor.Traffic traffic, NetworkMonitor.Packets packets) {
return Component.text() return Component.text()
.append(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 NamedTextColor.GREEN
)) ))
.append(Component.text(" | ", NamedTextColor.GRAY)) .append(Component.text(" | ", NamedTextColor.GRAY))
.append(Component.text( .append(Component.text(
DataSizeConverter.convertBytesToHumanReadable(traffic.txBytes()) + "" + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps", DataSizeConverter.convertBytesPerSecond(traffic.txBytes()) + "" + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps",
NamedTextColor.RED NamedTextColor.RED
)) ))
.build(); .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();
}
} }

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.util.text; package eu.mhsl.craftattack.spawn.util.text;
public class DataSizeConverter { public class DataSizeConverter {
public static String convertBytesToHumanReadable(long bytes) { public static String convertBytesPerSecond(long bytes) {
double kbits = bytes * 8.0 / 1000.0; double kbits = bytes * 8.0 / 1000.0;
double mbits = kbits / 1000.0; double mbits = kbits / 1000.0;
@ -11,4 +11,31 @@ public class DataSizeConverter {
return String.format("%.2f Kbit", kbits); 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);
}
} }

View File

@ -6,12 +6,12 @@ import net.kyori.adventure.text.format.TextColor;
import java.awt.*; import java.awt.*;
public class RainbowComponent { public class RainbowComponent {
private int hueOffset = 0; private float hueOffset = 0;
private final String text; private final String text;
private final int density; 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.text = text;
this.density = density; this.density = density;
this.speed = speed; this.speed = speed;
@ -19,15 +19,16 @@ public class RainbowComponent {
public Component getRainbowState() { public Component getRainbowState() {
Component builder = Component.empty(); Component builder = Component.empty();
int hue = this.hueOffset; float hue = this.hueOffset;
for(char c : text.toCharArray()) { for (char c : text.toCharArray()) {
TextColor color = TextColor.color(Color.getHSBColor((float) hue / 360, 1, 1).getRGB()); float normalizedHue = (hue % 360) / 360;
TextColor color = TextColor.color(Color.getHSBColor(normalizedHue, 1, 1).getRGB());
builder = builder.append(Component.text(c).color(color)); builder = builder.append(Component.text(c).color(color));
hue += density; hue += density;
} }
if(this.hueOffset > Byte.MAX_VALUE - speed) this.hueOffset = 0; this.hueOffset = (this.hueOffset + speed) % 360;
this.hueOffset += (byte) speed;
return builder; return builder;
} }
} }

View File

@ -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);
}
}

View File

@ -1,3 +1,7 @@
plugin:
disabledAppliances:
- NameOfApplianceClass
worldMuseum: worldMuseum:
uuid: uuid:
connect-server-name: worldmuseum connect-server-name: worldmuseum
@ -42,7 +46,7 @@ help:
spawn: "Der Weltspawn befindet sich bei x:0 y:0 z:0" spawn: "Der Weltspawn befindet sich bei x:0 y:0 z:0"
playerLimit: playerLimit:
maxPlayers: 100 maxPlayers: 10
whitelist: whitelist:
overrideIntegrityCheck: false overrideIntegrityCheck: false
@ -50,3 +54,16 @@ whitelist:
tablist: tablist:
interface: eth0 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

View File

@ -37,5 +37,8 @@ commands:
panicBan: panicBan:
vogelfrei: vogelfrei:
settings: settings:
texturepack:
maintanance:
yearRank:
msg: msg:
r: r: