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'
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'com.sparkjava:spark-core:2.9.4'
implementation 'org.reflections:reflections:0.10.2'
}
def targetJavaVersion = 21

View File

@ -2,39 +2,16 @@ package eu.mhsl.craftattack.spawn;
import eu.mhsl.craftattack.spawn.api.HttpServer;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.antiSignEdit.AntiSignEdit;
import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker;
import eu.mhsl.craftattack.spawn.appliances.autoShulker.AutoShulker;
import eu.mhsl.craftattack.spawn.appliances.chatMention.ChatMention;
import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
import eu.mhsl.craftattack.spawn.appliances.customAdvancements.CustomAdvancements;
import eu.mhsl.craftattack.spawn.appliances.debug.Debug;
import eu.mhsl.craftattack.spawn.appliances.displayName.DisplayName;
import eu.mhsl.craftattack.spawn.appliances.event.Event;
import eu.mhsl.craftattack.spawn.appliances.fleischerchest.Fleischerchest;
import eu.mhsl.craftattack.spawn.appliances.help.Help;
import eu.mhsl.craftattack.spawn.appliances.hotbarRefill.HotbarRefill;
import eu.mhsl.craftattack.spawn.appliances.kick.Kick;
import eu.mhsl.craftattack.spawn.appliances.outlawed.Outlawed;
import eu.mhsl.craftattack.spawn.appliances.panicBan.PanicBan;
import eu.mhsl.craftattack.spawn.appliances.playerlimit.PlayerLimit;
import eu.mhsl.craftattack.spawn.appliances.portableCrafting.PortableCrafting;
import eu.mhsl.craftattack.spawn.appliances.privateMessage.PrivateMessage;
import eu.mhsl.craftattack.spawn.appliances.projectStart.ProjectStart;
import eu.mhsl.craftattack.spawn.appliances.report.Report;
import eu.mhsl.craftattack.spawn.appliances.restart.Restart;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.tablist.Tablist;
import eu.mhsl.craftattack.spawn.appliances.titleClear.TitleClear;
import eu.mhsl.craftattack.spawn.appliances.whitelist.Whitelist;
import eu.mhsl.craftattack.spawn.appliances.worldmuseum.WorldMuseum;
import eu.mhsl.craftattack.spawn.config.Configuration;
import org.bukkit.Bukkit;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin;
import org.reflections.Reflections;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class Main extends JavaPlugin {
@ -49,49 +26,46 @@ public final class Main extends JavaPlugin {
instance = this;
logger = instance().getLogger();
saveDefaultConfig();
Configuration.readConfig();
try {
this.wrappedEnable();
} catch (Exception e) {
Main.logger().log(Level.SEVERE, "Error while initializing Spawn plugin, shutting down!", e);
Bukkit.shutdown();
}
}
appliances = List.of(
new AdminMarker(),
new WorldMuseum(),
new TitleClear(),
new ProjectStart(),
new Tablist(),
new ChatMessages(),
new Report(),
new Event(),
new Help(),
new PlayerLimit(),
new Whitelist(),
new Restart(),
new Kick(),
new PanicBan(),
new Outlawed(),
new DisplayName(),
new Debug(),
new Fleischerchest(),
new CustomAdvancements(),
new Settings(),
new PortableCrafting(),
new AutoShulker(),
new AntiSignEdit(),
new HotbarRefill(),
new ChatMention(),
new PrivateMessage()
);
private void wrappedEnable() {
Configuration.readConfig();
List<String> disabledAppliances = Configuration.pluginConfig.getStringList("disabledAppliances");
Main.logger.info("Loading appliances...");
appliances.forEach(appliance -> {
Main.logger().info("Enabling " + appliance.getClass().getSimpleName());
Reflections reflections = new Reflections(this.getClass().getPackageName());
Set<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.initialize(this);
});
Main.logger().info("Loaded " + appliances.size() + " appliances!");
Main.logger().info(String.format("Initialized %d appliances!", appliances.size()));
Main.logger().info("Starting HTTP API");
Main.logger().info("Starting HTTP API...");
this.httpApi = new HttpServer();
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
Main.logger().info("Startup complete!");
}
@Override

View File

@ -42,7 +42,7 @@ public abstract class Appliance {
* @return List of listeners
*/
@NotNull
protected List<Listener> eventHandlers() {
protected List<Listener> listeners() {
return new ArrayList<>();
}
@ -72,7 +72,8 @@ public abstract class Appliance {
*/
@NotNull
public ConfigurationSection localConfig() {
return Optional.ofNullable(Configuration.cfg.getConfigurationSection(localConfigPath)).orElse(Configuration.cfg);
return Optional.ofNullable(Configuration.cfg.getConfigurationSection(localConfigPath))
.orElseGet(() -> Configuration.cfg.createSection(localConfigPath));
}
public void onEnable() {
@ -82,7 +83,7 @@ public abstract class Appliance {
}
public void initialize(@NotNull JavaPlugin plugin) {
this.listeners = eventHandlers();
this.listeners = listeners();
this.commands = commands();
listeners.forEach(listener -> Bukkit.getPluginManager().registerEvents(listener, plugin));
@ -93,7 +94,7 @@ public abstract class Appliance {
listeners.forEach(HandlerList::unregisterAll);
}
public <T extends Appliance> T queryAppliance(Class<T> clazz) {
protected static <T extends Appliance> T queryAppliance(Class<T> 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.
*/
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;
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
*/
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;
public class ApplianceSupplier<T extends Appliance> implements IApplianceSupplier<T> {
public class CachedApplianceSupplier<T extends Appliance> implements IApplianceSupplier<T> {
private final T appliance;
public ApplianceSupplier() {
public CachedApplianceSupplier() {
this.appliance = Main.instance().getAppliance(Main.getApplianceType(getClass()));
}

View File

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

View File

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

View File

@ -3,7 +3,6 @@ package eu.mhsl.craftattack.spawn.appliances.chatMention;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.settings.settings.ChatMentionSetting;
import net.kyori.adventure.sound.Sound;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -56,11 +55,12 @@ public class ChatMention extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(ChatMentionSetting.class);
refreshPlayers();
}
@Override
protected @NotNull List<Listener> eventHandlers() {
protected @NotNull List<Listener> listeners() {
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.appliances.chatMessages.ChatMessages;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.settings.settings.ChatMentionSetting;
import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
@ -42,7 +42,7 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
return Component.text(word);
}
})
.reduce((a, b) -> a.append(Component.text(" ")).append(b))
.reduce(ComponentUtil::appendWithSpace)
.orElseThrow();
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.SettingCategory;
@ -9,7 +9,7 @@ import org.bukkit.Material;
public class ChatMentionSetting extends MultiBoolSetting<ChatMentionSetting.ChatMentionConfig> implements CategorizedSetting {
@Override
public SettingCategory category() {
return SettingCategory.Chat;
return SettingCategory.Visuals;
}
public record ChatMentionConfig(

View File

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

View File

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

View File

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

View File

@ -1,9 +1,11 @@
package eu.mhsl.craftattack.spawn.appliances.displayName;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker;
import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarkerListener;
import eu.mhsl.craftattack.spawn.appliances.outlawed.Outlawed;
import eu.mhsl.craftattack.spawn.appliances.yearRank.YearRank;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent;
@ -14,19 +16,22 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.Supplier;
import java.util.logging.Level;
public class DisplayName extends Appliance {
public void update(Player player) {
TextColor playerColor = queryAppliance(AdminMarker.class).getPlayerColor(player);
List<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();
prefixes.forEach(supplier -> {
Component prefix = supplier.get();
if(prefix == null) return;
playerName.append(prefix).append(Component.text(" "));
playerName.append(prefix).append(
Component.text(" ").hoverEvent(Component.empty().asHoverEvent()));
});
playerName.append(Component.text(player.getName(), playerColor));
@ -41,14 +46,14 @@ public class DisplayName extends Appliance {
player.playerListName(component);
} catch(Exception e) {
//TODO this throws often exceptions, but still works, don't know why
//Main.instance().getLogger().log(Level.SEVERE, e, e::getMessage);
Main.instance().getLogger().log(Level.SEVERE, e, e::getMessage);
}
}
@Override
@NotNull
protected List<Listener> eventHandlers() {
protected List<Listener> listeners() {
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() {
advertiseCountdown.cancelIfRunning();
this.advertiseStatus = AdvertisementStatus.ADVERTISED;
IteratorUtil.onlinePlayers(player -> {
player.sendMessage(
Component.text()
.append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline()
.append(Component.text("Ein Event wurde gestartet!", NamedTextColor.GOLD)).appendNewline()
.append(Component.text("Nutze "))
.append(Component.text("/event", NamedTextColor.AQUA))
.append(Component.text(", um dem Event beizutreten!")).appendNewline()
.append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline()
);
});
IteratorUtil.onlinePlayers(player -> player.sendMessage(
Component.text()
.append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline()
.append(Component.text("Ein Event wurde gestartet!", NamedTextColor.GOLD)).appendNewline()
.append(Component.text("Nutze "))
.append(Component.text("/event", NamedTextColor.AQUA))
.append(Component.text(", um dem Event beizutreten!")).appendNewline()
.append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline()
));
advertiseCountdown.start();
}
@ -237,7 +235,7 @@ public class Event extends Appliance {
@Override
@NotNull
protected List<Listener> eventHandlers() {
protected List<Listener> listeners() {
return List.of(
new ApplyPendingRewardsListener(),
new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> joinEvent(playerInteractAtEntityEvent.getPlayer())),

View File

@ -19,7 +19,7 @@ public class Fleischerchest extends Appliance {
@Override
@NotNull
protected List<Listener> eventHandlers() {
protected List<Listener> listeners() {
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.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
@ -16,6 +17,11 @@ import java.util.List;
import java.util.NoSuchElementException;
public class HotbarRefill extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(HotbarRefillSetting.class);
}
public void handleHotbarChange(Player player, ItemStack item) {
if(player.getGameMode().equals(GameMode.CREATIVE)) return;
if(item.getAmount() != 1) return;
@ -46,7 +52,7 @@ public class HotbarRefill extends Appliance {
}
@Override
protected @NotNull List<Listener> eventHandlers() {
return List.of(new ItemRefillListener());
protected @NotNull List<Listener> listeners() {
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.appliances.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.settings.settings.HotbarReplaceSetting;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockPlaceEvent;
@ -10,12 +9,12 @@ import org.bukkit.event.player.PlayerItemBreakEvent;
import org.bukkit.event.player.PlayerItemConsumeEvent;
import org.bukkit.inventory.ItemStack;
public class ItemRefillListener extends ApplianceListener<HotbarRefill> {
private HotbarReplaceSetting.HotbarReplaceConfig getPlayerSetting(Player player) {
public class HotbarRefillListener extends ApplianceListener<HotbarRefill> {
private HotbarRefillSetting.HotbarReplaceConfig getPlayerSetting(Player player) {
return Settings.instance().getSetting(
player,
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.SettingCategory;
@ -6,7 +6,7 @@ import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.MultiBoolSetting;
import org.bukkit.Material;
public class HotbarReplaceSetting extends MultiBoolSetting<HotbarReplaceSetting.HotbarReplaceConfig> implements CategorizedSetting {
public class HotbarRefillSetting extends MultiBoolSetting<HotbarRefillSetting.HotbarReplaceConfig> implements CategorizedSetting {
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
@ -18,7 +18,7 @@ public class HotbarReplaceSetting extends MultiBoolSetting<HotbarReplaceSetting.
@DisplayName("Essen") boolean onConsumable
) {}
public HotbarReplaceSetting() {
public HotbarRefillSetting() {
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(status != Status.FORCED) return;
try {
Main.instance().getAppliance(Whitelist.class).integrityCheck(player);
queryAppliance(Whitelist.class).integrityCheck(player);
} catch(DisconnectInfo.Throwable e) {
Bukkit.getScheduler().runTask(Main.instance(), () -> e.getDisconnectScreen().applyKick(player));
}
@ -75,7 +75,7 @@ public class Outlawed extends Appliance {
private void setLawStatus(Player player, Status status) {
playerStatusMap.put(player, status);
Main.instance().getAppliance(DisplayName.class).update(player);
queryAppliance(DisplayName.class).update(player);
List<String> newList = localConfig().getStringList(voluntarilyEntry);
if(status.equals(Status.VOLUNTARILY)) {
@ -93,7 +93,7 @@ public class Outlawed extends Appliance {
}
private boolean isTimeout(Player player) {
return timeouts.get(player.getUniqueId()) < System.currentTimeMillis() - timeoutInMs;
return timeouts.getOrDefault(player.getUniqueId(), 0L) > System.currentTimeMillis() - timeoutInMs;
}
private void setTimeout(Player player) {
@ -127,7 +127,7 @@ public class Outlawed extends Appliance {
@Override
@NotNull
protected List<Listener> eventHandlers() {
protected List<Listener> listeners() {
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
@NotNull
protected List<Listener> eventHandlers() {
protected List<Listener> listeners() {
return List.of(new PanicBanJoinListener());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
@ -57,7 +58,7 @@ public abstract class SelectSetting extends Setting<SelectSetting.Options.Option
}
@Override
protected void change(ClickType clickType) {
protected void change(Player player, ClickType clickType) {
int optionModifier = clickType.equals(ClickType.LEFT) ? 1 : -1;
List<Options.Option> options = this.options.options;
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) {
this.change(clickType);
this.change(p, clickType);
toStorage(p.getPersistentDataContainer(), this.state());
}
@ -57,7 +57,7 @@ public abstract class Setting<TDataType> {
protected abstract String description();
protected abstract Material icon();
public abstract ItemMeta buildMeta(ItemMeta meta);
protected abstract void change(ClickType clickType);
protected abstract void change(Player player, ClickType clickType);
protected abstract TDataType defaultValue();
protected abstract void fromStorage(PersistentDataContainer container);
protected abstract void toStorage(PersistentDataContainer container, TDataType value);

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

View File

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

View File

@ -44,15 +44,13 @@ public class WorldMuseum extends Appliance {
public void handleVillagerInteraction(Player player) {
if(Floodgate.isBedrock(player)) {
Floodgate.runBedrockOnly(player, floodgatePlayer -> {
floodgatePlayer.sendForm(
SimpleForm.builder()
.title("Nicht unterstützt")
.content("Bedrock-Spieler werden derzeit für das Weltenmuseum aus Kompatiblitätsgründen nicht zugelassen! Tut uns Leid.")
.button("Ok")
.build()
);
});
Floodgate.runBedrockOnly(player, floodgatePlayer -> floodgatePlayer.sendForm(
SimpleForm.builder()
.title("Nicht unterstützt")
.content("Bedrock-Spieler werden derzeit für das Weltenmuseum aus Kompatiblitätsgründen nicht zugelassen! Tut uns Leid.")
.button("Ok")
.build()
));
return;
}
@ -68,7 +66,7 @@ public class WorldMuseum extends Appliance {
@Override
@NotNull
protected List<Listener> eventHandlers() {
protected List<Listener> listeners() {
return List.of(
new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> handleVillagerInteraction(playerInteractAtEntityEvent.getPlayer())),
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;
import eu.mhsl.craftattack.spawn.Main;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
@ -10,9 +11,11 @@ public class Configuration {
private static final String configName = "config.yml";
private static final File configFile = new File(Main.instance().getDataFolder().getAbsolutePath() + "/" + configName);
public static FileConfiguration cfg;
public static ConfigurationSection pluginConfig;
public static void readConfig() {
cfg = YamlConfiguration.loadConfiguration(configFile);
pluginConfig = cfg.getConfigurationSection("plugin");
}
public static void saveChanges() {

View File

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

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;
import org.bukkit.entity.Player;
import org.geysermc.cumulus.form.SimpleForm;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
@ -24,4 +25,22 @@ public class Floodgate {
public static void runJavaOnly(Player p, Consumer<Player> callback) {
if(!isBedrock(p)) callback.accept(p);
}
public static void throwWithMessageWhenBedrock(Player player) {
if(isBedrock(player)) {
SimpleForm.builder()
.title("Nicht unterstützt")
.content("Bedrock-Spieler werden derzeit für diese Aktion unterstützt! Tut uns Leid.")
.button("Ok")
.build();
throw new BedrockNotSupportedException(player);
}
}
public static class BedrockNotSupportedException extends RuntimeException {
public BedrockNotSupportedException(Player player) {
super(String.format("Bedrock player '%s' tried using an Operation which is unsupported.", player.getName()));
}
}
}

View File

@ -10,6 +10,7 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.awt.*;
import java.lang.management.OperatingSystemMXBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -21,6 +22,13 @@ public class ComponentUtil {
return Component.text().append(a.appendNewline().append(b)).build();
}
public static TextComponent appendWithSpace(Component a, Component b) {
return Component.text().append(a).append(Component.text(" ")).append(b).build();
}
public static Stream<String> lineBreak(String text) {
return lineBreak(text, 50);
}
public static Stream<String> lineBreak(String text, int charactersPerLine) {
List<String> lines = new ArrayList<>();
String[] words = text.split(" ");
@ -46,34 +54,6 @@ public class ComponentUtil {
return lines.collect(Collectors.joining("\n"));
}
public static Component getFormattedTPS() {
double[] tpsValues = Bukkit.getTPS();
double min1 = Math.min(1.0, Math.max(0.0, tpsValues[0] / 20.0));
double min2 = Math.min(1.0, Math.max(0.0, tpsValues[1] / 20.0));
double min3 = Math.min(1.0, Math.max(0.0, tpsValues[2] / 20.0));
int red1 = (int) (255 * (1.0 - min1));
int green1 = (int) (255 * min1);
int red2 = (int) (255 * (1.0 - min2));
int green2 = (int) (255 * min2);
int red3 = (int) (255 * (1.0 - min3));
int green3 = (int) (255 * min3);
TextColor tpsColor1 = TextColor.color(red1, green1, 0);
TextColor tpsColor2 = TextColor.color(red2, green2, 0);
TextColor tpsColor3 = TextColor.color(red3, green3, 0);
return Component.text()
.append(Component.text("TPS 1, 5, 15m: ", NamedTextColor.GRAY))
.append(Component.text(String.format("%.2f", tpsValues[0]), tpsColor1))
.append(Component.text(", "))
.append(Component.text(String.format("%.2f", tpsValues[1]), tpsColor2))
.append(Component.text(", "))
.append(Component.text(String.format("%.2f", tpsValues[2]), tpsColor3))
.build();
}
public static Component getFormattedTickTimes(boolean detailed) {
long[] times = Bukkit.getServer().getTickTimes();
float mspt = ((float) Arrays.stream(times).sum() / times.length) * 1.0E-6f;
@ -132,14 +112,72 @@ public class ComponentUtil {
public static Component getFormattedNetworkStats(NetworkMonitor.Traffic traffic, NetworkMonitor.Packets packets) {
return Component.text()
.append(Component.text(
DataSizeConverter.convertBytesToHumanReadable(traffic.rxBytes()) + "" + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps",
DataSizeConverter.convertBytesPerSecond(traffic.rxBytes()) + "" + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps",
NamedTextColor.GREEN
))
.append(Component.text(" | ", NamedTextColor.GRAY))
.append(Component.text(
DataSizeConverter.convertBytesToHumanReadable(traffic.txBytes()) + "" + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps",
DataSizeConverter.convertBytesPerSecond(traffic.txBytes()) + "" + NumberAbbreviation.abbreviateNumber(packets.rxCount()) + "pps",
NamedTextColor.RED
))
.build();
}
public static Component getFormattedSystemStats(OperatingSystemMXBean systemMonitor) {
if(!(systemMonitor instanceof com.sun.management.OperatingSystemMXBean monitor))
return Component.text("Could not get System information", NamedTextColor.DARK_GRAY);
return Component.text()
.append(Component.text("proc: ", NamedTextColor.GRAY))
.append(Component.text(
String.format("%.0f%%cpu", monitor.getProcessCpuLoad() * 100),
NamedTextColor.GOLD
))
.append(Component.text(" | ", NamedTextColor.GRAY))
.append(Component.text(
String.format("%s time", DataSizeConverter.formatCpuTimeToHumanReadable(monitor.getProcessCpuTime())),
NamedTextColor.LIGHT_PURPLE
))
.append(Component.text(" | ", NamedTextColor.GRAY))
.append(Component.text(
String.format(
"%s free, %s committed RAM",
DataSizeConverter.formatBytesToHumanReadable(monitor.getFreeMemorySize()),
DataSizeConverter.formatBytesToHumanReadable(monitor.getCommittedVirtualMemorySize())
),
NamedTextColor.DARK_AQUA
))
.appendNewline()
.append(Component.text("sys: ", NamedTextColor.GRAY))
.append(Component.text(
String.format("%.0f%%cpu", monitor.getCpuLoad() * 100),
NamedTextColor.GOLD
))
.append(Component.text(" | ", NamedTextColor.GRAY))
.append(Component.text(
String.format(
"1min %.2f load avg (%.0f%%)",
monitor.getSystemLoadAverage(),
(monitor.getSystemLoadAverage() / monitor.getAvailableProcessors()) * 100
),
NamedTextColor.LIGHT_PURPLE
))
.append(Component.text(" | ", NamedTextColor.GRAY))
.append(Component.text(
String.format("%s total RAM", DataSizeConverter.formatBytesToHumanReadable(monitor.getTotalMemorySize())),
NamedTextColor.DARK_AQUA
))
.appendNewline()
.append(Component.text(
String.format(
"%s(%s) \uD83D\uDE80 on %s with %s cpu(s)",
monitor.getName(),
monitor.getVersion(),
monitor.getArch(),
monitor.getAvailableProcessors()
),
NamedTextColor.GRAY
))
.build();
}
}

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.util.text;
public class DataSizeConverter {
public static String convertBytesToHumanReadable(long bytes) {
public static String convertBytesPerSecond(long bytes) {
double kbits = bytes * 8.0 / 1000.0;
double mbits = kbits / 1000.0;
@ -11,4 +11,31 @@ public class DataSizeConverter {
return String.format("%.2f Kbit", kbits);
}
}
public static String formatBytesToHumanReadable(long bytes) {
String[] units = {"B", "KB", "MB", "GB", "TB", "PB", "EB"};
int unitIndex = 0;
double readableSize = bytes;
while (readableSize >= 1024 && unitIndex < units.length - 1) {
readableSize /= 1024;
unitIndex++;
}
return String.format("%.2f%s", readableSize, units[unitIndex]);
}
public static String formatCpuTimeToHumanReadable(long nanoseconds) {
if (nanoseconds < 0) return "unsupported";
long seconds = nanoseconds / 1_000_000_000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
seconds %= 60;
minutes %= 60;
hours %= 60;
return String.format("%dd%dh%dm%ds", days, hours, minutes, seconds);
}
}

View File

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

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:
uuid:
connect-server-name: worldmuseum
@ -42,11 +46,24 @@ help:
spawn: "Der Weltspawn befindet sich bei x:0 y:0 z:0"
playerLimit:
maxPlayers: 100
maxPlayers: 10
whitelist:
overrideIntegrityCheck: false
api: https://mhsl.eu/craftattack/api/user
tablist:
interface: eth0
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:
vogelfrei:
settings:
texturepack:
maintanance:
yearRank:
msg:
r: